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

95 lines (94 loc) • 4.02 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 { parseFrontmatter } from "./frontmatter.js"; /** Stable GitHub-style selector for a Markdown heading. */ export function markdownHeadingSlug(heading) { return heading .trim() .toLowerCase() .replace(/<[^>]*>/g, "") .replace(/[^\p{L}\p{N}\s_-]+/gu, "-") .replace(/[\s_]+/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, ""); } export function markdownFragmentSlugs(content) { return uniqueHeadingSlugs(parseMarkdownToc(content).headings).filter(Boolean); } function uniqueHeadingSlugs(headings) { const used = new Set(); return headings.map((heading) => { const base = markdownHeadingSlug(heading.text); if (!base) return ""; let slug = base; let suffix = 0; while (used.has(slug)) slug = `${base}-${++suffix}`; used.add(slug); return slug; }); } // ── Parsing ───────────────────────────────────────────────────────────────── export function parseMarkdownToc(content) { const lines = content.split(/\r?\n/); const headings = []; const parsed = parseFrontmatter(content); const start = parsed.frontmatter ? parsed.bodyStartLine - 1 : 0; let inFence = false; for (let i = start; i < lines.length; i++) { // Track fenced code blocks (``` or ~~~) so headings inside them are skipped. if (/^\s*(`{3,}|~{3,})/.test(lines[i])) { inFence = !inFence; continue; } if (inFence) continue; const match = lines[i].match(/^(#{1,6})\s+(.+)$/); if (match) { headings.push({ level: match[1].length, text: match[2].replace(/\s+#+\s*$/, "").trim(), line: i + 1, }); } } return { headings, totalLines: lines.length }; } // ── Extraction ────────────────────────────────────────────────────────────── export function extractSection(content, heading) { const lines = content.split(/\r?\n/); const headings = parseMarkdownToc(content).headings; const fragment = heading.trim(); const slugIndex = uniqueHeadingSlugs(headings).indexOf(fragment); const exact = slugIndex < 0 ? headings.find((candidate) => candidate.text.toLowerCase() === fragment.toLowerCase()) : undefined; const selected = slugIndex >= 0 ? headings[slugIndex] : exact; if (!selected) return null; const next = headings.find((candidate) => candidate.line > selected.line && candidate.level <= selected.level); const startIdx = selected.line - 1; const endIdx = next ? next.line - 1 : lines.length; return { content: lines.slice(startIdx, endIdx).join("\n"), startLine: selected.line, endLine: endIdx, }; } // ── Fence stripping ────────────────────────────────────────────────────────── /** * Best-effort fence stripping. Strips `<think>` reasoning blocks emitted by * local LLMs (e.g. Qwen3) before the content, which otherwise breaks YAML * frontmatter detection. Only strips outer triple-fence pairs — leaves inner * code blocks intact. */ export function stripMarkdownFences(raw) { const stripped = raw .trim() .replace(/<think>[\s\S]*?<\/think>/gi, "") .trim(); const fence = stripped.match(/^```(?:markdown|md)?\s*\n([\s\S]*?)\n```\s*$/i); if (fence) return fence[1].trim(); return stripped; }