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

168 lines (167 loc) • 7.28 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/. /** * `type-directory-disagreement` advisory for `akm health` (#831). * * The invariant "a file's directory declares its type" is load-bearing for * refs, namespace listings, and `akm show` paths (see #824: three files * written to `memories/` were indexed as `type: command`, moved their refs * under `commands/memories/<slug>`, and silently vanished from the * `memories/` namespace — nothing on any normal surface said so). This * advisory re-checks that invariant against every currently indexed entry. * * Legitimate disagreements exist by design — a `knowledge/` file containing * `$ARGUMENTS` is deliberately a `command`, and an `agents/` file with an * `agent:` frontmatter key is deliberately a `command` (both asserted in * `tests/integration/commands/show.test.ts`). So this is never a hard * failure: every disagreement is reported with a `winner` naming which * classifier signal produced the resolved type, so a deliberate override * reads differently from an unexplained one. */ import fs from "node:fs"; import path from "node:path"; import { parseFrontmatter } from "../../core/asset/frontmatter.js"; /** * Directory → declared-type map, mirroring `DIR_TYPE_MAP` in * `src/indexer/walk/matchers.ts` minus its per-directory extension test — * this check only needs "which type does this directory declare", not which * extensions it accepts. Keep in sync if `DIR_TYPE_MAP` gains, renames, or * removes a directory. */ const DECLARED_DIR_TYPES = { memories: "memory", knowledge: "knowledge", commands: "command", agents: "agent", workflows: "workflow", facts: "fact", lessons: "lesson", sessions: "session", instructions: "instruction", scripts: "script", env: "env", secrets: "secret", tasks: "task", }; const realReadFile = (absPath) => { try { return fs.readFileSync(absPath, "utf8"); } catch { return undefined; } }; /** * The type a file's directory declares, mirroring the `classifyByDirectory` / * `classifyByParentDirHint` precedence in matchers.ts: the immediate parent * directory wins when it is itself typed (parentDirHint, specificity 15); * otherwise the outermost typed ancestor wins (directoryMatcher, specificity * 10, which walks root-to-leaf and returns on the first hit). */ function declaredTypeForPath(absPath) { const segments = path .dirname(absPath) .split(path.sep) .filter((seg) => seg.length > 0); const immediateParent = segments.at(-1); if (immediateParent) { const parentType = DECLARED_DIR_TYPES[immediateParent]; if (parentType) return { dir: immediateParent, type: parentType }; } for (const seg of segments) { const type = DECLARED_DIR_TYPES[seg]; if (type) return { dir: seg, type }; } return undefined; } /** * Best-effort explanation for why `classifyBySmartMd` (matchers.ts) would * have produced `resolvedType` for this content, in the SAME precedence * order the real function checks them. Returns `undefined` when no known * override signal is found — that absence is itself the accident signal: * nothing in the file explains why its type disagrees with its directory. * * The numeric-placeholder branch is flagged `knownGoodOverride: false` on * purpose: since #826, that heuristic is guarded to never fire when the file * sits under a declared-type directory, so seeing it win here would mean the * guard regressed, not that this is a sanctioned override. */ function explainOverride(resolvedType, content) { const fm = parseFrontmatter(content).data; if (fm.type === "workflow" && resolvedType === "workflow") { return { winner: "smart-md:workflow-frontmatter", knownGoodOverride: true }; } if ("tools" in fm && resolvedType === "agent") { return { winner: "smart-md:tools-frontmatter", knownGoodOverride: true }; } if ("agent" in fm && resolvedType === "command") { return { winner: "smart-md:agent-frontmatter", knownGoodOverride: true }; } if (resolvedType === "command" && content.includes("$ARGUMENTS")) { return { winner: "smart-md:$ARGUMENTS", knownGoodOverride: true }; } if (resolvedType === "command" && /\$[123](?!\d|[.,]\d)/.test(content)) { return { winner: "smart-md:numeric-placeholder", knownGoodOverride: false }; } if ("model" in fm && resolvedType === "agent") { return { winner: "smart-md:model-frontmatter", knownGoodOverride: true }; } return undefined; } /** * Compare every indexed entry's resolved type against the type its * directory declares (see {@link DECLARED_DIR_TYPES}), and return one * {@link TypeDirectoryDisagreement} per mismatch, sorted by path. Entries * outside any declared-type directory are not checked — this is only the * "directory declares type" invariant. */ export function collectTypeDirectoryDisagreements(entries, readFile = realReadFile) { const disagreements = []; for (const entry of entries) { const declared = declaredTypeForPath(entry.filePath); if (!declared || declared.type === entry.type) continue; const content = readFile(entry.filePath); const explanation = content === undefined ? undefined : explainOverride(entry.type, content); disagreements.push({ path: entry.filePath, resolved: entry.type, expected: declared.type, winner: explanation?.winner ?? "unknown", knownGoodOverride: explanation?.knownGoodOverride ?? false, }); } return disagreements.sort((a, b) => a.path.localeCompare(b.path)); } const MAX_DETAIL_LINES = 10; /** * Build the `type-directory-disagreement` advisory, or `undefined` when * every indexed entry agrees with its directory. Always `status: "warn"` * (never `"fail"`) — a deliberate override is still a disagreement worth * seeing, just not a gate. */ export function buildTypeDirectoryAdvisory(entries, readFile = realReadFile, displayPath = (p) => p) { const disagreements = collectTypeDirectoryDisagreements(entries, readFile); if (disagreements.length === 0) return undefined; const lines = disagreements.slice(0, MAX_DETAIL_LINES).map((d) => { const note = d.knownGoodOverride ? " (known-good override)" : ""; return `${displayPath(d.path)} resolved=${d.resolved} expected=${d.expected} winner=${d.winner}${note}`; }); if (disagreements.length > MAX_DETAIL_LINES) { lines.push(`+${disagreements.length - MAX_DETAIL_LINES} more`); } return { name: "type-directory-disagreement", kind: "deterministic", status: "warn", confidence: "high", message: `${disagreements.length} indexed asset(s) have a resolved type that disagrees with the type their directory declares: ${lines.join("; ")}`, evidence: { disagreements: disagreements.map((d) => ({ ...d, path: displayPath(d.path) })), }, }; }