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

93 lines (92 loc) • 5.68 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/. /** * Canonical HARD authoring rules — the single source of truth shared by the * proposal validators (which REJECT violations) and the improve/authoring * prompts (which must TELL the agent the same rules, in the same words). * * Why this module exists: authoring rules were duplicated and drifted across * prompt templates. `distill-lesson-system.md` told the model "80–200 chars" * while the validator enforced 20–400; reflect's prompt omitted the * no-pseudo-frontmatter / single-fence rules entirely, so reflect generated * proposals that the gate then rejected and that got stuck in the queue. * * The fix: the numeric bounds live HERE and are imported by both the validators * (`isValidDescription` / `isValidWhenToUse` in proposal-quality-validators.ts) * and the prompt text (`authoringRulesForType`). The agent-facing rule prose * sits next to the bounds it describes, so a developer changing a validator * sees the prompt copy that must change with it. `tests/authoring-rules-*` * asserts the two representations stay consistent. * * SCOPE: only HARD rules (a validator rejects the proposal if violated) belong * here. Soft/style conventions (voice, paragraph count, "include a # Title") * are user-editable and flow through the separate `standardsContext` seam * (stash `category: convention` facts) — NOT this module. */ // ── Canonical numeric bounds (imported by the validators — do not duplicate) ── /** `description` length bounds (chars). Enforced by `isValidDescription`. */ export const DESCRIPTION_MIN_CHARS = 20; export const DESCRIPTION_MAX_CHARS = 400; /** `when_to_use` length bounds (chars). Enforced by `isValidWhenToUse`. */ export const WHEN_TO_USE_MIN_CHARS = 15; export const WHEN_TO_USE_MAX_CHARS = 400; // ── Agent-facing rule prose (mirrors the validator checks one-for-one) ──────── /** * Rules that apply to any markdown asset authored with YAML frontmatter + a * body. Enforced by `detectDoubleFrontmatter` (currently fires for lesson * proposals, but the rules are universally correct, so we state them for every * type to prevent the same defect class elsewhere). */ const FRONTMATTER_BODY_RULES = [ "Emit EXACTLY TWO `---` fence lines — the opening and closing of the YAML frontmatter. Do NOT use `---` as a horizontal rule anywhere in the body.", "Do NOT restate `description:` or `when_to_use:` inside the body (no `**description:** …` or `**when_to_use:** …` lines). Those keys belong in the frontmatter ONLY.", ]; /** Rules for the `description` frontmatter field. Enforced by `isValidDescription`. */ const DESCRIPTION_RULES = [ `\`description\` must be ${DESCRIPTION_MIN_CHARS}–${DESCRIPTION_MAX_CHARS} characters of plain-prose sentence — no leading digit or markdown marker, balanced backticks, and it must NOT end with \`:\`, \`;\`, or \`,\` (those read as truncation).`, '`description` must NOT be a section-heading fragment (e.g. "Overview", "Key points", "Summary"), a code fragment (must not start with `def`/`function`/`class`/`const`/…), or end on a hanging connector word ("a", "the", "and", "to", …).', "`description` must NOT merely restate the asset's ref/name; write what the asset actually does.", '`description` should NOT start with "When" — that phrasing belongs in `when_to_use`.', ]; /** Rules for the `when_to_use` frontmatter field. Enforced by `isValidWhenToUse`. */ const WHEN_TO_USE_RULES = [ `\`when_to_use\` is REQUIRED and must be ${WHEN_TO_USE_MIN_CHARS}–${WHEN_TO_USE_MAX_CHARS} characters describing a concrete trigger. Never write the circular fallback "When working with <name>".`, "`description` and `when_to_use` must be different from each other.", ]; /** * Asset types that carry a `description` and a body where the * frontmatter/body rules apply. (Types without those — if any are added later — * simply fall through to the cross-cutting block.) */ const DESCRIPTION_TYPES = new Set(["lesson", "knowledge", "memory", "skill", "command", "agent", "workflow", "fact"]); /** Types where `when_to_use` is a HARD requirement (validator rejects if absent). */ const WHEN_TO_USE_TYPES = new Set(["lesson"]); /** * Build the hard-rules block for a given asset type, ready to inject as a prompt * section. Returns `""` for an unknown type (no over-claiming). The block is * deterministic so prompt snapshots stay stable. * * Inject this VERBATIM into every improve/authoring prompt that creates or edits * an asset of `type`, so the agent is told exactly what the gate will reject. */ /** * Whether an asset of `type` carries a `description` that the validator * (`isValidDescription` / `validateProposalFrontmatter`) treats as required. * Reflect uses this to decide when a description-synthesis instruction (and the * deterministic reflect-side fallback) must fire for a description-less source. */ export function requiresDescription(type) { return DESCRIPTION_TYPES.has(type); } export function authoringRulesForType(type) { const rules = [...FRONTMATTER_BODY_RULES]; if (DESCRIPTION_TYPES.has(type)) rules.push(...DESCRIPTION_RULES); if (WHEN_TO_USE_TYPES.has(type)) rules.push(...WHEN_TO_USE_RULES); if (rules.length === 0) return ""; const heading = `Hard authoring rules for ${type} assets (the validator REJECTS proposals that violate these):`; return [heading, ...rules.map((r) => `- ${r}`)].join("\n"); }