UNPKG

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
// 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}`; }