akm-cli 0.9.0-beta.37 → 0.9.0-beta.38
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,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Per-type SOFT authoring conventions are now user-editable stash facts.** A
|
|
12
|
+
third authoring-guidance layer joins the hard rules (#645) and general stash
|
|
13
|
+
standards (#642): a stash owner can author
|
|
14
|
+
`facts/conventions/assets/<type>.md` (e.g. `…/skill.md`, `…/command.md`) to
|
|
15
|
+
capture soft, type-specific guidance — voice, structure, length *preference*,
|
|
16
|
+
naming style. When an agent authors a `skill:x`, the body of
|
|
17
|
+
`fact:conventions/assets/skill` is injected (type-scoped — authoring
|
|
18
|
+
`command:y` pulls the `command` convention, never the `skill` one), labeled
|
|
19
|
+
as soft guidance and kept separate from the validator-enforced hard rules.
|
|
20
|
+
The basename must be a `getAssetTypes()`-validated asset type; facts are read
|
|
21
|
+
straight from disk (no index rebuild) and degrade to empty safely. When no
|
|
22
|
+
per-type fact exists, the built-in `TYPE_HINTS` fallback is unchanged (no
|
|
23
|
+
regression). These facts carry soft conventions only and can never weaken the
|
|
24
|
+
authoring contract the gate enforces (`authoringRulesForType` remains the sole
|
|
25
|
+
source of validator-rejecting rules). The general convention/meta resolver now
|
|
26
|
+
excludes `facts/conventions/assets/*` so per-type guidance never leaks
|
|
27
|
+
un-type-scoped into other authoring flows. (#646)
|
|
28
|
+
|
|
9
29
|
## [0.9.0-beta.36] — 2026-06-22
|
|
10
30
|
|
|
11
31
|
### Added
|
|
@@ -17,8 +17,24 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { extractWikiNameFromRef, INDEX_MD, LOG_MD, loadWikiSchema, SCHEMA_MD } from "../../wiki/wiki.js";
|
|
19
19
|
import { resolveStashStandards } from "./resolve-stash-standards.js";
|
|
20
|
+
import { resolveTypeConventions, typeConventionRef } from "./resolve-type-conventions.js";
|
|
20
21
|
/** Wiki infra files that are not authored pages (relative to the wiki root). */
|
|
21
22
|
const WIKI_INFRA_BASENAMES = new Set([SCHEMA_MD, INDEX_MD, LOG_MD]);
|
|
23
|
+
/**
|
|
24
|
+
* Extract the asset type from a canonical ref (`[origin//]type:name`) without
|
|
25
|
+
* throwing. Returns `undefined` for refs that have no `type:` prefix. Kept local
|
|
26
|
+
* and lenient — the per-type resolver validates the result against
|
|
27
|
+
* `getAssetTypes()`, so a bogus prefix here simply yields no convention.
|
|
28
|
+
*/
|
|
29
|
+
function refType(ref) {
|
|
30
|
+
if (!ref)
|
|
31
|
+
return undefined;
|
|
32
|
+
const body = ref.includes("//") ? ref.slice(ref.indexOf("//") + 2) : ref;
|
|
33
|
+
const colon = body.indexOf(":");
|
|
34
|
+
if (colon <= 0)
|
|
35
|
+
return undefined;
|
|
36
|
+
return body.slice(0, colon).trim() || undefined;
|
|
37
|
+
}
|
|
22
38
|
/**
|
|
23
39
|
* Resolve the standards context for a write target identified by its asset ref.
|
|
24
40
|
*
|
|
@@ -30,8 +46,23 @@ const WIKI_INFRA_BASENAMES = new Set([SCHEMA_MD, INDEX_MD, LOG_MD]);
|
|
|
30
46
|
export function resolveStandardsContext(ref, stashRoot) {
|
|
31
47
|
const wikiName = ref ? extractWikiNameFromRef(ref) : undefined;
|
|
32
48
|
if (!wikiName) {
|
|
33
|
-
// Non-wiki asset target → Feature B (stash
|
|
34
|
-
|
|
49
|
+
// Non-wiki asset target → Feature B (general stash standards) plus the
|
|
50
|
+
// per-type SOFT conventions layer (#646), type-scoped to the write target.
|
|
51
|
+
const general = resolveStashStandards(stashRoot);
|
|
52
|
+
const type = refType(ref);
|
|
53
|
+
// A non-empty body here guarantees `type` is a `getAssetTypes()`-validated
|
|
54
|
+
// string (the resolver returns "" otherwise).
|
|
55
|
+
const typeConventions = type ? resolveTypeConventions(stashRoot, type) : "";
|
|
56
|
+
if (!typeConventions || !type)
|
|
57
|
+
return general;
|
|
58
|
+
// Soft, type-scoped guidance — clearly labeled and kept separate from the
|
|
59
|
+
// HARD (validator-enforced) rules that `authoringRulesForType` injects
|
|
60
|
+
// downstream. These facts are advice only; they never weaken the gate.
|
|
61
|
+
const softSection = [
|
|
62
|
+
`# ${typeConventionRef(type)} (soft per-type conventions — guidance, not enforced)`,
|
|
63
|
+
typeConventions,
|
|
64
|
+
].join("\n");
|
|
65
|
+
return general ? `${general}\n\n${softSection}` : softSection;
|
|
35
66
|
}
|
|
36
67
|
// Wiki target. Extract the page path after `wiki:<name>/`.
|
|
37
68
|
const prefix = `wiki:${wikiName}/`;
|
|
@@ -21,6 +21,13 @@ import { parseFrontmatter } from "../asset/frontmatter.js";
|
|
|
21
21
|
const STANDARD_CATEGORIES = new Set(["convention", "meta"]);
|
|
22
22
|
/** Directory (under the stash root) where `fact` assets live. */
|
|
23
23
|
const FACTS_SUBDIR = "facts";
|
|
24
|
+
/**
|
|
25
|
+
* Per-type SOFT convention facts (`facts/conventions/assets/<type>.md`, #646)
|
|
26
|
+
* are surfaced **type-scoped** through `resolveTypeConventions`, so they must
|
|
27
|
+
* NOT leak into this un-type-scoped general layer (authoring a `command` must
|
|
28
|
+
* not pull the `skill` convention). Excluded by relative path (POSIX form).
|
|
29
|
+
*/
|
|
30
|
+
const TYPE_CONVENTIONS_REL = "conventions/assets/";
|
|
24
31
|
/**
|
|
25
32
|
* Recursively collect `.md` files under `dir` in stable (sorted) enumeration
|
|
26
33
|
* order. Returns absolute paths. Missing dir → `[]`.
|
|
@@ -59,6 +66,11 @@ export function resolveStashStandards(stashRoot) {
|
|
|
59
66
|
const factsRoot = path.join(stashRoot, FACTS_SUBDIR);
|
|
60
67
|
const sections = [];
|
|
61
68
|
for (const absPath of collectMarkdownFiles(factsRoot)) {
|
|
69
|
+
// Per-type SOFT conventions are delivered type-scoped (#646); skip them
|
|
70
|
+
// here so they never leak un-type-scoped into every authoring flow.
|
|
71
|
+
const relPosix = path.relative(factsRoot, absPath).split(path.sep).join("/");
|
|
72
|
+
if (relPosix.startsWith(TYPE_CONVENTIONS_REL))
|
|
73
|
+
continue;
|
|
62
74
|
let raw;
|
|
63
75
|
try {
|
|
64
76
|
raw = fs.readFileSync(absPath, "utf8");
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* Resolve **per-type SOFT authoring conventions** (#646) — the third and final
|
|
6
|
+
* authoring-guidance layer:
|
|
7
|
+
*
|
|
8
|
+
* 1. HARD rules (validator-rejecting, code-sourced) → `authoringRulesForType()`
|
|
9
|
+
* (`src/core/authoring-rules.ts`, #645). Never editable; the gate enforces them.
|
|
10
|
+
* 2. General stash standards (cross-type naming/tag conventions) →
|
|
11
|
+
* `resolveStashStandards()` `category: convention|meta` facts (#642).
|
|
12
|
+
* 3. **Per-type SOFT conventions** (voice, structure, length *preference* for
|
|
13
|
+
* *this* asset type) → user-editable `facts/conventions/assets/<type>.md`
|
|
14
|
+
* (THIS module). Augments the built-in `TYPE_HINTS` fallback for display.
|
|
15
|
+
*
|
|
16
|
+
* These facts are **soft only** — advice, not contract. They MUST NOT carry
|
|
17
|
+
* hard, validator-rejecting rules: a user editing or deleting one must never be
|
|
18
|
+
* able to weaken the authoring contract the gate enforces (#645 boundary).
|
|
19
|
+
*
|
|
20
|
+
* Selection is by a `getAssetTypes()`-validated basename: only
|
|
21
|
+
* `facts/conventions/assets/<known-type>.md` resolves. Read directly from disk
|
|
22
|
+
* (no index rebuild); any missing dir/file, unknown type, or read error degrades
|
|
23
|
+
* to `""` and never throws.
|
|
24
|
+
*/
|
|
25
|
+
import fs from "node:fs";
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
import { getAssetTypes } from "../asset/asset-spec.js";
|
|
28
|
+
import { parseFrontmatter } from "../asset/frontmatter.js";
|
|
29
|
+
/** Sub-path (under the stash root) for per-type SOFT convention facts. */
|
|
30
|
+
export const TYPE_CONVENTIONS_SUBDIR = path.join("facts", "conventions", "assets");
|
|
31
|
+
/** The `fact:` ref prefix for a per-type convention, e.g. `fact:conventions/assets/skill`. */
|
|
32
|
+
export function typeConventionRef(type) {
|
|
33
|
+
return `fact:conventions/assets/${type}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Read the SOFT authoring-convention body for asset type `type`, if a stash
|
|
37
|
+
* owner has authored `facts/conventions/assets/<type>.md`.
|
|
38
|
+
*
|
|
39
|
+
* @returns the trimmed markdown body (frontmatter stripped), or `""` when the
|
|
40
|
+
* type is unknown, the file is absent, or anything goes wrong.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveTypeConventions(stashRoot, type) {
|
|
43
|
+
if (!stashRoot || !type)
|
|
44
|
+
return "";
|
|
45
|
+
// Basename MUST be a known asset type — never resolve an arbitrary file.
|
|
46
|
+
if (!getAssetTypes().includes(type))
|
|
47
|
+
return "";
|
|
48
|
+
const abs = path.join(stashRoot, TYPE_CONVENTIONS_SUBDIR, `${type}.md`);
|
|
49
|
+
let raw;
|
|
50
|
+
try {
|
|
51
|
+
raw = fs.readFileSync(abs, "utf8");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return ""; // missing dir/file or read error → degrade to empty
|
|
55
|
+
}
|
|
56
|
+
let body = "";
|
|
57
|
+
try {
|
|
58
|
+
body = parseFrontmatter(raw).content;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Malformed frontmatter: fall back to the whole file (parseFrontmatter
|
|
62
|
+
// normally returns whole content as body, but guard defensively).
|
|
63
|
+
body = raw;
|
|
64
|
+
}
|
|
65
|
+
return body.trim();
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.0-beta.
|
|
3
|
+
"version": "0.9.0-beta.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Knowledge Management) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
|
|
6
6
|
"keywords": [
|