akm-cli
Version:
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 Cur
64 lines (63 loc) • 2.99 kB
JavaScript
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
import { parse as parseYaml } from "yaml";
import { localDateStamp } from "../common.js";
import { UsageError } from "../errors.js";
import { serializeFrontmatter } from "./asset-serialize.js";
import { parseFrontmatterBlock, spliceFrontmatterLine } from "./frontmatter.js";
/**
* Ensure an AKM-authored Markdown concept is also a conformant OKF concept.
*
* Stamps BOTH `type` and `updated`, because both are required of a conformant
* document and this is the one chokepoint every `.md` write passes through
* (`core/write-source.ts`). Without the `updated` stamp, every asset akm
* created for you — `akm remember`, `akm import`, accepted proposals,
* authored workflows — was immediately flagged `missing-updated` by akm's own
* `akm lint`, so the tool disagreed with itself about its own output.
*
* An existing `updated` is left alone: this fills a gap, it does not
* re-stamp on every write (which would churn timestamps and manufacture
* needless diffs in git-backed bundles).
*
* Source preservation: when the type already matches and the ONLY change is
* adding `updated`, the line is spliced into the original block textually —
* round-tripping through the YAML serializer would drop user-authored
* comments and normalize formatting just to contribute one field. Only a
* document whose `type` must actually be corrected takes the re-serialize
* path (as it always has).
*/
export function ensureAkmMarkdownType(content, type, now = new Date()) {
const block = parseFrontmatterBlock(content);
if (!block) {
return `---\n${serializeFrontmatter({ type, updated: localDateStamp(now) })}\n---\n${content}`;
}
let parsed;
try {
parsed = block.frontmatter.trim() ? parseYaml(block.frontmatter) : {};
}
catch {
throw new UsageError("AKM Markdown has malformed YAML frontmatter.", "INVALID_FLAG_VALUE");
}
if (parsed === null)
parsed = {};
if (typeof parsed !== "object" || Array.isArray(parsed)) {
throw new UsageError("AKM Markdown frontmatter must be a YAML mapping.", "INVALID_FLAG_VALUE");
}
const data = parsed;
const needsUpdated = !("updated" in data);
if (data.type === type) {
if (!needsUpdated)
return content;
const spliced = spliceFrontmatterLine(content, `updated: ${localDateStamp(now)}`);
if (spliced !== null)
return spliced;
// Unreachable in practice (parseFrontmatterBlock succeeded above), but a
// re-serialized document beats a non-conformant one.
}
const { type: _priorType, ...rest } = data;
const next = { type, ...rest };
if (needsUpdated)
next.updated = localDateStamp(now);
return `---\n${serializeFrontmatter(next)}\n---\n${block.content}`;
}