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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.10.261020] - 2026-10-29
10
+
11
+ ### Added
12
+
13
+ - `akm upgrade --channel latest|next` switches the CLI and installed Claude,
14
+ Codex and OpenCode plugins together, preserving unrelated native settings and
15
+ writing backups. `--check` shows channel mismatches; an older target requires
16
+ `--allow-downgrade` (#1285).
17
+
18
+ ### Fixed
19
+
20
+ - Claude plugin health uses native installation, scope and enabled metadata
21
+ instead of choosing the highest cached version. It reads `AKM_MIN_VERSION`
22
+ as well as the legacy range, and reports uncertain selections as unknown (#1286).
23
+ - Repeated fenced identifiers contribute once per document to search content,
24
+ reducing repetition bias while retaining distinct code names. Existing
25
+ content projections and embedding inputs refresh on the next index pass (#1284).
26
+
27
+ ## [0.10.261019] - 2026-10-29
28
+
29
+ The first stable release of the 0.10 line, published to npm `latest`. Its
30
+ changes are in the 0.10 sections below; the upgrade from 0.9 is in
31
+ [the 0.9 to 0.10 migration guide](docs/migration/v0.9-to-v0.10.md) and
32
+ `akm help migrate 0.10.261019`.
33
+
9
34
  ## [0.10.261018] - 2026-10-29
10
35
 
11
36
  ### Fixed
@@ -2,51 +2,11 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  /**
5
- * `plugin-version` advisory for `akm health` (itlackey/akm#832).
6
- *
7
- * #828 was filed as a CLI bug — session extraction failing on 232/234 runs —
8
- * and took a full investigation to resolve. The actual cause: the harness
9
- * plugin (`akm@akm-plugins`, installed in Claude Code's plugin cache) was
10
- * three days stale relative to the fix, and its own `AKM_VERSION_RANGE` gate
11
- * had nothing to do with it — the plugin was simply running old code. Every
12
- * fact needed to reach that conclusion in one step was already on disk:
13
- *
14
- * - installed plugin version: `~/.claude/plugins/cache/<marketplace>/akm/<version>/.claude-plugin/plugin.json`
15
- * - the plugin's own akm-cli compatibility contract: `<pluginDir>/shared/akm-version.ts`'s `AKM_VERSION_RANGE`
16
- * - the running CLI's version: `../../version.ts`'s `pkgVersion`
17
- *
18
- * Nothing correlated them, so a stale plugin was indistinguishable from a
19
- * broken CLI. This module closes that gap with three checks, one per
20
- * detected plugin:
21
- *
22
- * 1. report the installed version;
23
- * 2. compare it against the newest tag published to the plugin's git
24
- * remote, and warn (naming the update command) when behind;
25
- * 3. the sharp one — check whether the *installed plugin's* declared
26
- * `AKM_VERSION_RANGE` admits the *running* CLI version. When it does
27
- * not, the plugin has silently disabled itself (both surfaces log
28
- * `version_out_of_range` / `akm_version_mismatch` and degrade quietly)
29
- * and there was previously no way to know that from the CLI side.
30
- *
31
- * Read-only: this never fetches, writes, or mutates the plugin cache or
32
- * marketplace clone. Check 2 is one of two deliberate exceptions to "`akm
33
- * health` makes no network call" (see `./health-advisories.md`; the other is
34
- * the `cli-version` advisory in `./version-drift.ts`, #950): a plugin's
35
- * local marketplace clone is not proof of what is newest upstream — the
36
- * incident above involved a clone that hadn't seen the fix's tag at all — so
37
- * the only way to ever detect drift is to ask the remote what tags exist.
38
- * That query is a `git ls-remote --symref` (lists refs; fetches nothing,
39
- * writes nothing) with a short timeout, and any failure (offline, no
40
- * remote, timeout) degrades to "installed version reported, no staleness
41
- * claim" rather than a false positive or a hang. Like `cli-version`, this
42
- * check is gated behind `--probe`/`--no-probe`: with probing off the remote is
43
- * not asked, the row reports `unknown` ("not probed") and only the local range
44
- * check (3) runs.
45
- *
46
- * Every collector here is best-effort and silent on missing/unreadable
47
- * input: no Claude plugin installed, no marketplace clone, an unreadable
48
- * manifest, or a malformed version range must never crash `akm health` and
49
- * must never produce a false "stale" or "inactive" warning.
5
+ * Report the installations Claude's native listing selects, not the versions
6
+ * left in its cache. The listing owns scope and merged enabled settings; it
7
+ * cannot prove what an already-running session has loaded. Ambiguous scoped
8
+ * installations and unavailable compatibility contracts are reported as unknown.
9
+ * Remote release checks remain optional under --no-probe.
50
10
  */
51
11
  import { spawnSync } from "node:child_process";
52
12
  import fs from "node:fs";
@@ -64,7 +24,9 @@ import { openCodeConfigSpec } from "../sources/plugin-upgrade.js";
64
24
  * `AKM_CLAUDE_PROJECTS_DIR` in `../../integrations/harnesses/claude/session-log.ts`.
65
25
  */
66
26
  function claudePluginsDir() {
67
- return process.env.AKM_CLAUDE_PLUGINS_DIR ?? path.join(os.homedir(), ".claude", "plugins");
27
+ return (process.env.AKM_CLAUDE_PLUGINS_DIR ??
28
+ process.env.CLAUDE_CODE_PLUGIN_CACHE_DIR ??
29
+ path.join(process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude"), "plugins"));
68
30
  }
69
31
  /**
70
32
  * Root of OpenCode's package cache, same override pattern as
@@ -159,74 +121,90 @@ function buildOpencodeBundledAdvisory(plugin, bundled, cliVersion) {
159
121
  evidence: bundled.spec ? { bundledVersion, cliVersion, spec: bundled.spec } : { bundledVersion, cliVersion },
160
122
  };
161
123
  }
162
- /**
163
- * Scan `<pluginsRoot>/cache/<marketplace>/akm/<version>/` for every
164
- * `akm` plugin cache entry, picking the highest cached version per
165
- * marketplace when more than one is present. Returns `[]` (never throws)
166
- * when the cache directory is absent, empty, or unreadable — that is the
167
- * ordinary "no Claude plugin installed" case, not an error.
168
- */
169
124
  function detectInstalledPlugins(pluginsRoot) {
170
- const cacheDir = path.join(pluginsRoot, "cache");
171
- let marketplaces;
125
+ if (!fs.existsSync(path.join(pluginsRoot, "installed_plugins.json")) &&
126
+ !fs.existsSync(path.join(pluginsRoot, "cache")))
127
+ return [];
172
128
  try {
173
- marketplaces = fs.readdirSync(cacheDir);
129
+ const listed = spawnSync("claude", ["plugin", "list", "--json"], {
130
+ encoding: "utf8",
131
+ timeout: 30_000,
132
+ env: {
133
+ ...process.env,
134
+ ...(process.env.AKM_CLAUDE_PLUGINS_DIR
135
+ ? { CLAUDE_CONFIG_DIR: process.env.CLAUDE_CONFIG_DIR ?? path.dirname(pluginsRoot) }
136
+ : {}),
137
+ CLAUDE_CODE_PLUGIN_CACHE_DIR: pluginsRoot,
138
+ },
139
+ });
140
+ if (listed.status !== 0)
141
+ return undefined;
142
+ const rows = JSON.parse(listed.stdout);
143
+ if (!Array.isArray(rows))
144
+ return undefined;
145
+ const detected = rows.flatMap((row) => {
146
+ if (typeof row?.id !== "string" || !row.id.startsWith("akm@"))
147
+ return [];
148
+ const applicable = typeof row.projectPath !== "string" ||
149
+ path.resolve(process.cwd()) === path.resolve(row.projectPath) ||
150
+ path.resolve(process.cwd()).startsWith(`${path.resolve(row.projectPath)}${path.sep}`);
151
+ return [
152
+ {
153
+ harness: "claude",
154
+ marketplace: row.id.slice(4),
155
+ pluginName: "akm",
156
+ version: typeof row.folderVersion === "string"
157
+ ? row.folderVersion
158
+ : typeof row.version === "string"
159
+ ? row.version
160
+ : "unknown",
161
+ pluginDir: typeof row.readFromFolder === "string"
162
+ ? row.readFromFolder
163
+ : typeof row.installPath === "string"
164
+ ? row.installPath
165
+ : "",
166
+ applicable,
167
+ selectionKnown: false,
168
+ scope: typeof row.scope === "string" ? row.scope : "unknown",
169
+ enabled: typeof row.enabled === "boolean" ? row.enabled : undefined,
170
+ ...(typeof row.projectPath === "string" ? { projectPath: row.projectPath } : {}),
171
+ },
172
+ ];
173
+ });
174
+ for (const plugin of detected) {
175
+ const applicable = detected.filter((p) => p.marketplace === plugin.marketplace && p.applicable);
176
+ plugin.selectionKnown =
177
+ plugin.applicable &&
178
+ isExactSemver(plugin.version) &&
179
+ plugin.scope !== "unknown" &&
180
+ plugin.pluginDir !== "" &&
181
+ new Set(applicable.map((p) => `${p.version}:${p.pluginDir}`)).size === 1;
182
+ }
183
+ return detected;
174
184
  }
175
185
  catch {
176
- return [];
177
- }
178
- const detected = [];
179
- for (const marketplace of marketplaces) {
180
- const pluginDir = path.join(cacheDir, marketplace, "akm");
181
- let versions;
182
- try {
183
- versions = fs.readdirSync(pluginDir).filter(isExactSemver);
184
- }
185
- catch {
186
- continue;
187
- }
188
- if (versions.length === 0)
189
- continue;
190
- const latest = highestVersion(versions);
191
- if (!latest)
192
- continue;
193
- const versionDir = path.join(pluginDir, latest);
194
- const manifestPath = path.join(versionDir, ".claude-plugin", "plugin.json");
195
- try {
196
- const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
197
- if (typeof manifest.version !== "string")
198
- continue;
199
- detected.push({
200
- harness: "claude",
201
- marketplace,
202
- pluginName: "akm",
203
- version: manifest.version,
204
- pluginDir: versionDir,
205
- });
206
- }
207
- catch {
208
- // Unreadable/malformed manifest — skip this entry rather than crash.
209
- }
186
+ return undefined;
210
187
  }
211
- return detected;
212
188
  }
213
- /**
214
- * Extract `AKM_VERSION_RANGE` from the installed plugin's vendored
215
- * `shared/akm-version.ts`. Returns `undefined` when the file is missing,
216
- * unreadable, or does not contain the expected declaration — callers must
217
- * treat that as "compatibility unknown", never as a mismatch.
218
- */
219
- function readVersionRange(pluginDir) {
220
- const versionFilePath = path.join(pluginDir, "shared", "akm-version.ts");
189
+ /** Read the contract, never execute plugin source. A minimum admits later prereleases too. */
190
+ function readCompatibility(pluginDir, cliVersion) {
221
191
  let text;
222
192
  try {
223
- text = fs.readFileSync(versionFilePath, "utf8");
193
+ text = fs.readFileSync(path.join(pluginDir, "shared", "akm-version.ts"), "utf8");
224
194
  }
225
195
  catch {
226
- return undefined;
196
+ return {};
197
+ }
198
+ const minimum = text.match(/export\s+const\s+AKM_MIN_VERSION\s*=\s*["']([^"']+)["']/)?.[1];
199
+ if (minimum !== undefined) {
200
+ if (!isExactSemver(minimum) || !isExactSemver(cliVersion))
201
+ return {};
202
+ return { versionRange: `>=${minimum}`, admitted: highestVersion([minimum, cliVersion]) === cliVersion };
227
203
  }
228
- const match = text.match(/export\s+const\s+AKM_VERSION_RANGE\s*=\s*["']([^"']+)["']/);
229
- return match?.[1];
204
+ const range = text.match(/export\s+const\s+AKM_VERSION_RANGE\s*=\s*["']([^"']+)["']/)?.[1];
205
+ if (!range || !isSemverRange(range) || !isExactSemver(cliVersion))
206
+ return {};
207
+ return { versionRange: range, admitted: satisfiesRange(cliVersion, range) };
230
208
  }
231
209
  const LS_REMOTE_TIMEOUT_MS = 5_000;
232
210
  /**
@@ -311,7 +289,17 @@ export function collectPluginStalenessAdvisories(options) {
311
289
  const listRemoteTags = options.listRemoteTags ?? realListRemoteTags;
312
290
  const plugins = detectInstalledPlugins(pluginsRoot);
313
291
  const probe = options.probe ?? true;
314
- const results = plugins.map((plugin) => buildAdvisory(plugin, options.cliVersion, pluginsRoot, listRemoteTags, probe));
292
+ const results = (plugins ?? []).map((plugin) => buildAdvisory(plugin, options.cliVersion, pluginsRoot, listRemoteTags, probe));
293
+ if (plugins === undefined && fs.existsSync(pluginsRoot)) {
294
+ results.push({
295
+ name: "plugin-version",
296
+ kind: "deterministic",
297
+ status: "unknown",
298
+ confidence: "high",
299
+ message: "Claude plugin installation and enabled state could not be resolved with `claude plugin list --json`; cached versions do not establish the selected installation.",
300
+ evidence: { harness: "claude", installedVersion: null, enabled: null, admitted: null },
301
+ });
302
+ }
315
303
  const opencodeCacheRoot = options.opencodeCacheRoot ?? defaultOpencodeCacheRoot();
316
304
  for (const plugin of OPENCODE_PLUGIN_LOCATIONS) {
317
305
  for (const bundled of findBundledAkm(plugin, opencodeCacheRoot)) {
@@ -326,7 +314,9 @@ function buildAdvisory(plugin, cliVersion, pluginsRoot, listRemoteTags, probe) {
326
314
  const marketplaceDir = path.join(pluginsRoot, "marketplaces", plugin.marketplace);
327
315
  let availableVersion;
328
316
  try {
329
- const tags = probe && fs.existsSync(marketplaceDir) ? listRemoteTags(marketplaceDir) : undefined;
317
+ const tags = probe && plugin.selectionKnown && plugin.enabled !== false && fs.existsSync(marketplaceDir)
318
+ ? listRemoteTags(marketplaceDir)
319
+ : undefined;
330
320
  availableVersion = tags && tags.length > 0 ? highestVersion(tags) : undefined;
331
321
  }
332
322
  catch {
@@ -336,10 +326,20 @@ function buildAdvisory(plugin, cliVersion, pluginsRoot, listRemoteTags, probe) {
336
326
  plugin.version !== availableVersion &&
337
327
  highestVersion([plugin.version, availableVersion]) === availableVersion;
338
328
  // Point 3: does the plugin's own declared range admit the running CLI?
339
- const versionRange = readVersionRange(plugin.pluginDir);
340
- const rangeKnown = versionRange !== undefined && isSemverRange(versionRange);
341
- const admitted = rangeKnown ? satisfiesRange(cliVersion, versionRange) : undefined;
342
- const messageParts = [`${pluginRef}: installed ${plugin.version}`];
329
+ const { versionRange, admitted } = readCompatibility(plugin.pluginDir, cliVersion);
330
+ const messageParts = [
331
+ `${pluginRef}: installed ${plugin.version} (${plugin.scope}${plugin.projectPath ? `: ${plugin.projectPath}` : ""})`,
332
+ ];
333
+ if (!plugin.applicable)
334
+ messageParts.push("installed for another project; not selected in this directory");
335
+ else if (!plugin.selectionKnown)
336
+ messageParts.push("effective selection unknown across native installations");
337
+ if (plugin.enabled === false)
338
+ messageParts.push("disabled in Claude settings");
339
+ else if (plugin.enabled === undefined)
340
+ messageParts.push("enabled state unknown");
341
+ if (admitted === undefined)
342
+ messageParts.push("CLI compatibility unknown");
343
343
  if (!probe)
344
344
  messageParts.push("newest tag was not probed");
345
345
  if (availableVersion !== undefined) {
@@ -347,15 +347,23 @@ function buildAdvisory(plugin, cliVersion, pluginsRoot, listRemoteTags, probe) {
347
347
  }
348
348
  if (stale)
349
349
  messageParts.push(`-> claude plugin update ${pluginRef}`);
350
- if (rangeKnown && admitted === false) {
351
- messageParts.push(`installed plugin requires akm-cli ${versionRange}; running ${cliVersion} -> NOT ADMITTED (plugin is inactive)`);
350
+ if (plugin.selectionKnown && plugin.enabled === true && admitted === false) {
351
+ messageParts.push(`installed plugin requires akm-cli ${versionRange}; running ${cliVersion} -> NOT ADMITTED by the plugin compatibility contract`);
352
352
  }
353
353
  return {
354
354
  name: "plugin-version",
355
355
  kind: "deterministic",
356
356
  // Not probed => staleness is unknown, not "up to date"; only the local
357
357
  // range check can still warn.
358
- status: stale || admitted === false ? "warn" : probe ? "pass" : "unknown",
358
+ status: plugin.enabled === false || !plugin.applicable
359
+ ? "pass"
360
+ : !plugin.selectionKnown || plugin.enabled === undefined
361
+ ? "unknown"
362
+ : stale || admitted === false
363
+ ? "warn"
364
+ : probe && availableVersion !== undefined && admitted !== undefined && plugin.enabled === true
365
+ ? "pass"
366
+ : "unknown",
359
367
  confidence: "high",
360
368
  message: messageParts.join(" — "),
361
369
  evidence: {
@@ -363,6 +371,12 @@ function buildAdvisory(plugin, cliVersion, pluginsRoot, listRemoteTags, probe) {
363
371
  marketplace: plugin.marketplace,
364
372
  plugin: plugin.pluginName,
365
373
  installedVersion: plugin.version,
374
+ scope: plugin.scope,
375
+ applicable: plugin.applicable,
376
+ selectionKnown: plugin.selectionKnown,
377
+ projectPath: plugin.projectPath ?? null,
378
+ enabled: plugin.enabled ?? null,
379
+ installPath: plugin.pluginDir,
366
380
  availableVersion: availableVersion ?? null,
367
381
  ...(probe ? {} : { probed: false }),
368
382
  stale,
@@ -0,0 +1,147 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import { isDeepStrictEqual } from "node:util";
7
+ import { applyEdits, modify } from "jsonc-parser";
8
+ import { parse as parseToml } from "smol-toml";
9
+ import { existingFileMode, parseJsonc, timestampForFilename, writeFileAtomic } from "../../core/common.js";
10
+ /** Preserve unrelated settings and comments; only the named native key changes. */
11
+ export function editChannelJson(file, keys, value, content) {
12
+ const before = content ?? fs.readFileSync(file, "utf8");
13
+ parseJsonc(before);
14
+ const after = applyEdits(before, modify(before, keys, value, { formattingOptions: { insertSpaces: true, tabSize: 2 } }));
15
+ parseJsonc(after);
16
+ return { file, content: after };
17
+ }
18
+ export function backupChannelFile(file) {
19
+ const backup = `${file}.akm-channel-${timestampForFilename()}-${process.pid}.bak`;
20
+ fs.copyFileSync(file, backup, fs.constants.COPYFILE_EXCL);
21
+ fs.chmodSync(backup, existingFileMode(file) ?? 0o600);
22
+ return backup;
23
+ }
24
+ export class ChannelWriteError extends Error {
25
+ backups;
26
+ writtenFiles;
27
+ constructor(cause, backups, writtenFiles) {
28
+ super(`Channel settings could not all be written: ${String(cause)}. Backups: ${backups.join(", ") || "none"}`);
29
+ this.backups = backups;
30
+ this.writtenFiles = writtenFiles;
31
+ }
32
+ }
33
+ export function writeChannelEdits(edits) {
34
+ const backups = [];
35
+ const writtenFiles = [];
36
+ try {
37
+ const changed = edits.filter(({ file, content }) => fs.readFileSync(file, "utf8") !== content);
38
+ for (const { file } of changed)
39
+ backups.push(backupChannelFile(file));
40
+ for (const { file, content } of changed) {
41
+ writeFileAtomic(file, content, existingFileMode(file));
42
+ writtenFiles.push(file);
43
+ }
44
+ return backups;
45
+ }
46
+ catch (error) {
47
+ throw new ChannelWriteError(error, backups, writtenFiles);
48
+ }
49
+ }
50
+ /** Claude has no command to change a registered marketplace's ref in place. Never remove the marketplace. */
51
+ export function claudeChannelEdits(configDir, pluginsRoot, channel, projectPaths = []) {
52
+ const ref = channel === "next" ? "next" : undefined;
53
+ const knownFile = path.join(pluginsRoot, "known_marketplaces.json");
54
+ const known = parseJsonc(fs.readFileSync(knownFile, "utf8"));
55
+ const source = known["akm-plugins"]?.source;
56
+ if (!source || !["github", "git"].includes(source.source ?? "")) {
57
+ throw new Error("the akm-plugins marketplace has no Git source whose channel can be switched");
58
+ }
59
+ const edits = [editChannelJson(knownFile, ["akm-plugins", "source", "ref"], ref)];
60
+ const settingsFiles = [
61
+ path.join(configDir, "settings.json"),
62
+ ...[process.cwd(), ...projectPaths].flatMap((project) => [
63
+ path.join(project, ".claude", "settings.json"),
64
+ path.join(project, ".claude", "settings.local.json"),
65
+ ]),
66
+ ];
67
+ for (const file of new Set(settingsFiles)) {
68
+ if (!fs.existsSync(file))
69
+ continue;
70
+ const settings = parseJsonc(fs.readFileSync(file, "utf8"));
71
+ if (!settings.extraKnownMarketplaces?.["akm-plugins"])
72
+ continue;
73
+ edits.push(editChannelJson(file, ["extraKnownMarketplaces", "akm-plugins", "source", "ref"], ref));
74
+ }
75
+ return edits;
76
+ }
77
+ /** Read native TOML, including quoted keys and inline tables. */
78
+ export function codexMarketplace(file) {
79
+ const config = parseToml(fs.readFileSync(file, "utf8"));
80
+ const marketplaces = config.marketplaces;
81
+ const marketplace = marketplaces?.["akm-plugins"];
82
+ return marketplace && typeof marketplace === "object" ? marketplace : undefined;
83
+ }
84
+ /** Codex's add refuses a changed ref on an existing source; preserve the installed plugin and edit only its ref. */
85
+ export function codexChannelEdit(file, channel) {
86
+ const before = fs.readFileSync(file, "utf8");
87
+ const expected = parseToml(before);
88
+ const entry = expected.marketplaces?.["akm-plugins"];
89
+ if (!entry || entry.source_type !== "git")
90
+ throw new Error("the akm-plugins marketplace has no Git source whose channel can be switched");
91
+ if (channel === "next")
92
+ entry.ref = "next";
93
+ else
94
+ delete entry.ref;
95
+ const headers = [...before.matchAll(/^[ \t]*\[(?!\[)([^\]\r\n]+)\][ \t]*(?:#[^\r\n]*)?\r?$/gm)];
96
+ let after = before;
97
+ let edited = false;
98
+ for (const [index, header] of headers.entries()) {
99
+ let parsed;
100
+ try {
101
+ parsed = parseToml(`[${header[1]}]`);
102
+ }
103
+ catch {
104
+ continue;
105
+ }
106
+ const target = parsed.marketplaces?.["akm-plugins"];
107
+ if (!target || Object.keys(target).length !== 0)
108
+ continue;
109
+ const start = header.index + header[0].length;
110
+ const end = headers[index + 1]?.index ?? before.length;
111
+ const section = before.slice(start, end);
112
+ const refLine = /^[ \t]*(?:ref|"ref"|'ref')[ \t]*=[ \t]*(?:"(?:\\.|[^"\\])*"|'[^']*')([ \t]*(?:#[^\r\n]*)?)(\r?\n|$)/m;
113
+ const newline = before.includes("\r\n") ? "\r\n" : "\n";
114
+ const nextSection = refLine.test(section)
115
+ ? section.replace(refLine, (_line, comment, end) => channel === "next" ? `ref = "next"${comment}${end}` : comment.trim() ? `${comment.trimStart()}${end}` : "")
116
+ : channel === "next"
117
+ ? `${newline}ref = "next"${section}`
118
+ : section;
119
+ after = before.slice(0, start) + nextSection + before.slice(end);
120
+ edited = true;
121
+ break;
122
+ }
123
+ if (!edited) {
124
+ // Inline tables have no section to insert into. Change just this table's ref and retain every other byte.
125
+ const inline = /((?:"akm-plugins"|'akm-plugins'|akm-plugins)\s*=\s*\{)([^\n]*?)(\})/g;
126
+ for (const match of before.matchAll(inline)) {
127
+ const [, open, body, close] = match;
128
+ const ref = /(?:^|,)\s*(?:ref|"ref"|'ref')\s*=\s*(?:"(?:\\.|[^"\\])*"|'[^']*')\s*/;
129
+ const stripped = body.replace(ref, "").replace(/^\s*,/, "");
130
+ const next = channel === "next" ? `${stripped.trimEnd()}${stripped.trim() ? ", " : ""}ref = "next" ` : stripped;
131
+ const candidate = before.slice(0, match.index) + `${open}${next}${close}` + before.slice(match.index + match[0].length);
132
+ try {
133
+ if (isDeepStrictEqual(parseToml(candidate), expected)) {
134
+ after = candidate;
135
+ break;
136
+ }
137
+ }
138
+ catch {
139
+ /* Try the next table with this name. */
140
+ }
141
+ }
142
+ }
143
+ // Validate the whole semantic document: an unusual layout is never partially rewritten.
144
+ if (!isDeepStrictEqual(parseToml(after), expected))
145
+ throw new Error("could not locate only the akm-plugins ref in Codex config.toml; no settings were changed");
146
+ return { file, content: after };
147
+ }