akm-cli 0.9.17-alpha.4 → 0.9.17-alpha.5
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 +32 -3
- package/dist/commands/lint/base-linter.js +19 -5
- package/dist/commands/sources/bundle-config-ops.js +3 -6
- package/dist/commands/sources/source-manage.js +9 -2
- package/dist/core/asset/asset-placement.js +4 -13
- package/dist/scripts/akm-migrate-node.js +0 -4
- package/dist/scripts/akm-migrate.js +0 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,36 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.17-alpha.5] - 2026-09-27
|
|
10
|
+
|
|
11
|
+
`akm show` works again for a memory that has a `.derived.md` child (835 of them
|
|
12
|
+
in one real bundle), and `akm bundle add --provider … --name` holds to the same
|
|
13
|
+
`--name` contract as every other add.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **`akm show` works for a memory that has a `.derived.md` child.** When
|
|
18
|
+
`memories/X.md` and `memories/X.derived.md` both existed, `akm show
|
|
19
|
+
memories/X`, with or without a `#fragment`, failed with
|
|
20
|
+
`RESOURCE_ALREADY_EXISTS` ("multiple physical owners"); `akm curate`
|
|
21
|
+
previewed such a memory from its description alone, and `akm curate --pack`
|
|
22
|
+
left it out. The index gives the derived child its own ref,
|
|
23
|
+
`memories/X.derived`, but the ref lookup also counted `X.derived.md` as a
|
|
24
|
+
file for `memories/X`. The lookup now follows the index: `memories/X` is
|
|
25
|
+
`X.md` and `memories/X.derived` is `X.derived.md`. A derived child whose
|
|
26
|
+
parent file is gone no longer answers for the parent's ref either, so it
|
|
27
|
+
cannot hide a real `X.md` in a lower-priority bundle. `akm lint` and
|
|
28
|
+
`--xref` / `--supersedes` validation still accept a ref to `memories/X`
|
|
29
|
+
when only `X.derived.md` remains. Broken since 0.9.7.
|
|
30
|
+
(`src/core/asset/asset-placement.ts`, `src/commands/lint/base-linter.ts`)
|
|
31
|
+
- **`akm bundle add --provider … --name` keeps the `--name` contract too.**
|
|
32
|
+
Since 0.9.17-alpha.4 an explicit `--name` that is not a legal bundle slug,
|
|
33
|
+
or is taken by another bundle, fails with exit 2, and re-adding a source
|
|
34
|
+
under a different name points at `akm bundle rename`. A declarative add
|
|
35
|
+
(`akm bundle add <target> --provider npm|git|website`) still replaced such
|
|
36
|
+
a name with a derived one and exited 0. It now fails the same way, before
|
|
37
|
+
any write. (`src/commands/sources/source-manage.ts`)
|
|
38
|
+
|
|
9
39
|
## [0.9.17-alpha.4] - 2026-09-27
|
|
10
40
|
|
|
11
41
|
Search and curate are rebuilt on measured evidence. On a 221-query suite of real
|
|
@@ -640,9 +670,8 @@ config migration, and it lands with fewer lines in `src/` than 0.9.17-alpha.3.
|
|
|
640
670
|
documented for `registryId`. The key used to come from the basename of the
|
|
641
671
|
cache directory the package was unpacked into, which is always `extracted`,
|
|
642
672
|
so every registry bundle after the first was `extracted-<hash>`. A dotted
|
|
643
|
-
or mixed-case name is slugged like a directory name (`Foo.js` → `foo-js`)
|
|
644
|
-
|
|
645
|
-
too. Bundles that are already installed keep their current key, including
|
|
673
|
+
or mixed-case name is slugged like a directory name (`Foo.js` → `foo-js`).
|
|
674
|
+
Bundles that are already installed keep their current key, including
|
|
646
675
|
`extracted`, because every recorded `extracted//…` ref depends on it.
|
|
647
676
|
- **A one-file change in a large directory no longer costs `akm index` half
|
|
648
677
|
an hour.** Both full-text tables keyed their per-entry deletes on
|
|
@@ -42,6 +42,7 @@ import { isArchivedRelPath } from "../../core/asset/memory-archive.js";
|
|
|
42
42
|
import { conceptIdFromTypeName, typeNameFromConceptId } from "../../core/asset/resolve-ref.js";
|
|
43
43
|
import { localDateStamp } from "../../core/common.js";
|
|
44
44
|
import { containsRedactedContent, REDACTED_CONTENT_MARKER } from "../../core/content-safety.js";
|
|
45
|
+
import { DERIVED_SUFFIX } from "../../core/recognition-util.js";
|
|
45
46
|
import { findFenceRegions } from "./markdown-insertion.js";
|
|
46
47
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
47
48
|
/** Fold physically wrapped prose the same way a YAML plain scalar does. */
|
|
@@ -216,10 +217,23 @@ export function refExistsInAnyStash(relPath, refType, refName, stashRoots) {
|
|
|
216
217
|
// record while leaving the live stash untouched.
|
|
217
218
|
return memoryArchiveHasRef(refType, refName, stashRoots);
|
|
218
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* The stash-relative files that satisfy a ref, in preference order: its own
|
|
222
|
+
* placement spellings, then, for a memory, the `<name>.derived.md` child (#882),
|
|
223
|
+
* so an edge to a parent whose plain `.md` is gone still reaches the child it was
|
|
224
|
+
* distilled into. This is lint's reachability rule only; the child owns
|
|
225
|
+
* `memories/<name>.derived`, never `memories/<name>`.
|
|
226
|
+
*/
|
|
227
|
+
function refPathCandidates(refType, typeDir, refName) {
|
|
228
|
+
const candidates = assetPathCandidatesForName(refType, typeDir, refName);
|
|
229
|
+
if (refType !== "memory" || refName.endsWith(DERIVED_SUFFIX))
|
|
230
|
+
return candidates;
|
|
231
|
+
return [...candidates, assetPathForName(refType, typeDir, `${refName}${DERIVED_SUFFIX}`)];
|
|
232
|
+
}
|
|
219
233
|
/**
|
|
220
234
|
* True when `(refType, refName)` names a memory that prune archived in any
|
|
221
235
|
* root. Mirrors `resolveRefPathInStash`'s candidate set so a ref that resolved
|
|
222
|
-
* through the `.derived.md`
|
|
236
|
+
* through the `.derived.md` child (#882) still resolves once archived.
|
|
223
237
|
*/
|
|
224
238
|
function memoryArchiveHasRef(refType, refName, stashRoots) {
|
|
225
239
|
if (refType !== "memory")
|
|
@@ -227,7 +241,7 @@ function memoryArchiveHasRef(refType, refName, stashRoots) {
|
|
|
227
241
|
const typeDir = stashDirFor(refType);
|
|
228
242
|
if (typeDir === undefined)
|
|
229
243
|
return false;
|
|
230
|
-
const candidates =
|
|
244
|
+
const candidates = refPathCandidates(refType, typeDir, refName);
|
|
231
245
|
for (const root of stashRoots) {
|
|
232
246
|
for (const candidate of candidates) {
|
|
233
247
|
if (isArchivedRelPath(candidate, root))
|
|
@@ -241,8 +255,8 @@ function memoryArchiveHasRef(refType, refName, stashRoots) {
|
|
|
241
255
|
* the same reachability rules (in the same order) as
|
|
242
256
|
* {@link refExistsInAnyStash}, which delegates here. Returns the absolute path
|
|
243
257
|
* of the file that makes the ref "exist" — for a multi-file skill directory
|
|
244
|
-
* that is its `SKILL.md` primary, for a `memory` ref its `.derived.md`
|
|
245
|
-
* when the plain `.md` is absent (#882, see
|
|
258
|
+
* that is its `SKILL.md` primary, for a `memory` ref its `.derived.md` child
|
|
259
|
+
* when the plain `.md` is absent (#882, see {@link refPathCandidates}) —
|
|
246
260
|
* or `null` when the ref does not resolve in this root.
|
|
247
261
|
*
|
|
248
262
|
* Extracted for SPEC-5 (`--supersedes` demotion): write commands need the
|
|
@@ -253,7 +267,7 @@ function memoryArchiveHasRef(refType, refName, stashRoots) {
|
|
|
253
267
|
*/
|
|
254
268
|
export function resolveRefPathInStash(relPath, refType, refName, root) {
|
|
255
269
|
const typeDir = stashDirFor(refType);
|
|
256
|
-
const candidates = typeDir === undefined ? [relPath] :
|
|
270
|
+
const candidates = typeDir === undefined ? [relPath] : refPathCandidates(refType, typeDir, refName);
|
|
257
271
|
for (const candidate of candidates) {
|
|
258
272
|
const absPath = path.join(root, candidate);
|
|
259
273
|
if (fs.existsSync(absPath))
|
|
@@ -93,12 +93,9 @@ export function bundleKeyForUrl(config, url) {
|
|
|
93
93
|
* (path/url) — the shared {@link deriveBundleId} rule (D-R5), made unique against
|
|
94
94
|
* the currently-configured bundle keys.
|
|
95
95
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* the D6 contract (an illegal or already-taken explicit name failing loudly)
|
|
100
|
-
* validates with {@link validateExplicitBundleName} itself before calling in,
|
|
101
|
-
* as `source-add.ts`'s local/website/registry add paths do.
|
|
96
|
+
* Every add path validates an explicit `--name` with
|
|
97
|
+
* {@link validateExplicitBundleName} before calling in (an illegal or taken
|
|
98
|
+
* name fails loudly), so a `preferredName` that reaches here is returned as is.
|
|
102
99
|
*/
|
|
103
100
|
export function nextBundleKey(bundles, preferredName, seedLocator) {
|
|
104
101
|
return deriveBundleId(preferredName, seedLocator, new Set(Object.keys(bundles)));
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { detectAdapterId } from "../../core/adapter/detect-adapter.js";
|
|
6
|
+
import { validateExplicitBundleName } from "../../core/bundle-id.js";
|
|
6
7
|
import { isRemoteUrl } from "../../core/common.js";
|
|
7
8
|
import { bundleEntryToSourceEntry, bundlesToSourceEntries, getSources, mutateConfig } from "../../core/config/config.js";
|
|
8
9
|
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
@@ -51,7 +52,10 @@ export function addStash(opts) {
|
|
|
51
52
|
const bundles = { ...(config.bundles ?? {}) };
|
|
52
53
|
let key;
|
|
53
54
|
if (useDescriptorPath) {
|
|
54
|
-
|
|
55
|
+
const existingKey = bundleKeyForUrl(config, target);
|
|
56
|
+
if (name !== undefined)
|
|
57
|
+
validateExplicitBundleName(bundles, name, existingKey);
|
|
58
|
+
if (existingKey) {
|
|
55
59
|
const already = targetIsUrl ? "Source URL already configured" : "Source already configured";
|
|
56
60
|
result = { sources: getSources(config), added: false, message: already };
|
|
57
61
|
return config;
|
|
@@ -69,7 +73,10 @@ export function addStash(opts) {
|
|
|
69
73
|
}
|
|
70
74
|
else {
|
|
71
75
|
const resolvedPath = path.resolve(target);
|
|
72
|
-
|
|
76
|
+
const existingKey = bundleKeyForPath(config, resolvedPath);
|
|
77
|
+
if (name !== undefined)
|
|
78
|
+
validateExplicitBundleName(bundles, name, existingKey);
|
|
79
|
+
if (existingKey) {
|
|
73
80
|
result = { sources: getSources(config), added: false, message: "Source path already configured" };
|
|
74
81
|
return config;
|
|
75
82
|
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
import fs from "node:fs";
|
|
27
27
|
import path from "node:path";
|
|
28
28
|
import { toPosix } from "../common.js";
|
|
29
|
-
import {
|
|
29
|
+
import { SCRIPT_EXTENSIONS, WORKFLOW_EXTENSIONS } from "../recognition-util.js";
|
|
30
30
|
const workflowSpec = {
|
|
31
31
|
isRelevantFile: (fileName) => WORKFLOW_EXTENSIONS.includes(path.extname(fileName).toLowerCase()),
|
|
32
32
|
toCanonicalName: (typeRoot, filePath) => {
|
|
@@ -245,21 +245,12 @@ export function assetPathForName(assetType, typeRoot, name) {
|
|
|
245
245
|
* "default" alias is genuinely dual-owned: both `<dir>/.env` and
|
|
246
246
|
* `<dir>/default.env` derive the same canonical name (`toCanonicalName`
|
|
247
247
|
* above), so a physical-owner lookup must consider both without reading
|
|
248
|
-
* either file.
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* the name (see `resolveParentRef`/`isDerivedMemory` in
|
|
252
|
-
* `commands/improve/memory/derived-ref.ts`, and the belief-edge identity
|
|
253
|
-
* channel's own `memory:<name>.derived` refs). The plain `.md` file wins when
|
|
254
|
-
* both exist, so it stays `primary` — first in the returned list — and every
|
|
255
|
-
* caller here already prefers the first candidate that exists on disk. Every
|
|
256
|
-
* other placement type has exactly one inverse spelling.
|
|
248
|
+
* either file. Every other placement type has exactly one inverse spelling —
|
|
249
|
+
* including `memory`: `<name>.derived.md` is a separate item that owns
|
|
250
|
+
* `<name>.derived`, never a second spelling of `<name>`.
|
|
257
251
|
*/
|
|
258
252
|
export function assetPathCandidatesForName(assetType, typeRoot, name) {
|
|
259
253
|
const primary = assetPathForName(assetType, typeRoot, name);
|
|
260
|
-
if (assetType === "memory" && !name.endsWith(DERIVED_SUFFIX)) {
|
|
261
|
-
return [primary, assetPathForName(assetType, typeRoot, `${name}${DERIVED_SUFFIX}`)];
|
|
262
|
-
}
|
|
263
254
|
if (assetType !== "env")
|
|
264
255
|
return [primary];
|
|
265
256
|
const base = name === "default" ? "" : name.endsWith("/default") ? name.slice(0, -"default".length) : undefined;
|
|
@@ -30545,7 +30545,6 @@ var SCRIPT_EXTENSIONS = new Set([
|
|
|
30545
30545
|
".kts"
|
|
30546
30546
|
]);
|
|
30547
30547
|
var WORKFLOW_EXTENSIONS = [".md", ".yml"];
|
|
30548
|
-
var DERIVED_SUFFIX = ".derived";
|
|
30549
30548
|
var KNOWN_TYPES = [
|
|
30550
30549
|
"skill",
|
|
30551
30550
|
"command",
|
|
@@ -30703,9 +30702,6 @@ function assetPathForName(assetType, typeRoot, name) {
|
|
|
30703
30702
|
}
|
|
30704
30703
|
function assetPathCandidatesForName(assetType, typeRoot, name) {
|
|
30705
30704
|
const primary = assetPathForName(assetType, typeRoot, name);
|
|
30706
|
-
if (assetType === "memory" && !name.endsWith(DERIVED_SUFFIX)) {
|
|
30707
|
-
return [primary, assetPathForName(assetType, typeRoot, `${name}${DERIVED_SUFFIX}`)];
|
|
30708
|
-
}
|
|
30709
30705
|
if (assetType !== "env")
|
|
30710
30706
|
return [primary];
|
|
30711
30707
|
const base = name === "default" ? "" : name.endsWith("/default") ? name.slice(0, -"default".length) : undefined;
|
|
@@ -29873,7 +29873,6 @@ var SCRIPT_EXTENSIONS = new Set([
|
|
|
29873
29873
|
".kts"
|
|
29874
29874
|
]);
|
|
29875
29875
|
var WORKFLOW_EXTENSIONS = [".md", ".yml"];
|
|
29876
|
-
var DERIVED_SUFFIX = ".derived";
|
|
29877
29876
|
var KNOWN_TYPES = [
|
|
29878
29877
|
"skill",
|
|
29879
29878
|
"command",
|
|
@@ -30031,9 +30030,6 @@ function assetPathForName(assetType, typeRoot, name) {
|
|
|
30031
30030
|
}
|
|
30032
30031
|
function assetPathCandidatesForName(assetType, typeRoot, name) {
|
|
30033
30032
|
const primary = assetPathForName(assetType, typeRoot, name);
|
|
30034
|
-
if (assetType === "memory" && !name.endsWith(DERIVED_SUFFIX)) {
|
|
30035
|
-
return [primary, assetPathForName(assetType, typeRoot, `${name}${DERIVED_SUFFIX}`)];
|
|
30036
|
-
}
|
|
30037
30033
|
if (assetType !== "env")
|
|
30038
30034
|
return [primary];
|
|
30039
30035
|
const base = name === "default" ? "" : name.endsWith("/default") ? name.slice(0, -"default".length) : undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.17-alpha.
|
|
3
|
+
"version": "0.9.17-alpha.5",
|
|
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": [
|