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

119 lines (118 loc) • 4.23 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/. /** * Flexible asset resolution system. * * Provides a rich FileContext built once per file during walking, plus a * matcher/renderer registry that decouples asset classification from rendering. */ import fs from "node:fs"; import path from "node:path"; import { parseFrontmatter } from "../../core/asset/frontmatter.js"; import { toPosix } from "../../core/common.js"; /** * Build a FileContext from a stash root and an absolute file path. * * Path-derived fields are computed eagerly. The content, frontmatter, and * stat getters use lazy caching so the file is only read from disk when * (and if) a matcher or renderer actually needs it. */ export function buildFileContext(stashRoot, absPath) { const relPath = toPosix(path.relative(stashRoot, absPath)); const ext = path.extname(absPath).toLowerCase(); const fileName = path.basename(absPath); const parentDirAbs = path.dirname(absPath); const parentDir = path.basename(parentDirAbs); // Compute ancestor directory segments from the POSIX relPath's directory portion. // For "scripts/azure/deploy/run.sh" the dir portion is "scripts/azure/deploy" // which splits into ["scripts", "azure", "deploy"]. const relDir = toPosix(path.dirname(relPath)); const ancestorDirs = relDir === "." ? [] : relDir.split("/").filter((seg) => seg.length > 0); // Lazy caches let cachedContent; let cachedFrontmatter; let frontmatterComputed = false; let cachedStat; return { absPath, relPath, ext, fileName, parentDir, parentDirAbs, ancestorDirs, stashRoot, content() { if (cachedContent === undefined) { cachedContent = fs.readFileSync(absPath, "utf8"); } return cachedContent; }, frontmatter() { if (!frontmatterComputed) { const raw = this.content(); const parsed = parseFrontmatter(raw); cachedFrontmatter = Object.keys(parsed.data).length > 0 ? parsed.data : null; frontmatterComputed = true; } return cachedFrontmatter ?? null; }, stat() { if (cachedStat === undefined) { cachedStat = fs.statSync(absPath); } return cachedStat; }, }; } // ── Registry ───────────────────────────────────────────────────────────────── /** Renderer lookup by name. */ const renderers = new Map(); /** * Ensure that all built-in indexer contributors are registered. * * Delegates to the single `initIndexer()` composition root (see * `src/indexer/init.ts`). Imported dynamically to keep this a lazy gate and to * avoid a static import cycle (init -> renderers -> file-context). Called on * first use of getRenderer/getAllRenderers; idempotent. */ async function ensureBuiltinsRegistered() { const { initIndexer } = await import("../init.js"); await initIndexer(); } /** * Register an AssetRenderer. * * If a renderer with the same name already exists it is silently replaced. */ export function registerRenderer(renderer) { renderers.set(renderer.name, renderer); } /** * Look up a renderer by name. */ export async function getRenderer(name) { await ensureBuiltinsRegistered(); return renderers.get(name); } /** * Return all registered renderers (snapshot, safe to iterate). */ export async function getAllRenderers() { await ensureBuiltinsRegistered(); return Array.from(renderers.values()); } /** * Build a RenderContext by merging a FileContext with its winning MatchResult * and the list of stash search paths. */ export function buildRenderContext(ctx, match, stashDirs, origin, defaultBundle) { return { ...ctx, matchResult: match, stashDirs, origin, defaultBundle, }; }