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

149 lines (148 loc) • 7.17 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/. /** * Show renderer for peer workflow sources. `.md` and `.yml` both compile * through `compileWorkflowSource` into a plan, which is projected down to the * public `ShowResponse` shape, including a compact per-step orchestration * summary (engine/model or an exec unit's argv, `map.over` reference, route * table) when the step declares one. */ import { displayRef } from "../core/asset/resolve-ref.js"; import { UsageError } from "../core/errors.js"; import { compileWorkflowSource, routeDescription, workflowStepInstructions } from "./compile.js"; function shellQuote(value) { return `'${value.replace(/'/g, `'\\''`)}'`; } export function buildWorkflowAction(ref) { return `Start or resume execution with \`akm workflow run ${shellQuote(ref)}\`.`; } function deriveName(ctx) { const metaName = ctx.matchResult.meta?.name; if (typeof metaName === "string" && metaName) return metaName; const ext = ctx.relPath.lastIndexOf("."); return ext > 0 ? ctx.relPath.slice(0, ext) : ctx.relPath; } function loadPlan(ctx) { const result = compileWorkflowSource(ctx.content(), { path: ctx.relPath, workspaceRoot: ctx.stashRoot }); if (result.ok) return result.plan; const summary = result.errors.map((e) => `${e.path}:${e.line} — ${e.message}`).join("\n"); throw new UsageError(`Workflow has errors:\n${summary}`); } /** Project the `params` block into the flat `WorkflowParameter` list. */ function projectParameters(plan) { if (!plan.paramSchemas) return undefined; const parameters = Object.entries(plan.paramSchemas).map(([name, schema]) => { const description = schema.description; return { name, ...(typeof description === "string" && description !== "" ? { description } : {}) }; }); return parameters.length > 0 ? parameters : undefined; } /** * Compact, show-facing orchestration summary for one step. Field mapping: * `engine`/`model`/`timeoutMs` merge the run-level `defaults` exactly like the * compiler does (per-unit override wins), `fanOut.over` carries the raw * reference string, and `route` carries the explicit input + branch table. * Returns undefined when the step declares nothing worth summarizing. * * ## exec units * * An `exec` unit runs a shell command and names NO engine — the parser rejects * `engine`/`model`/`llm` alongside `exec:`. Merging `defaults.engine` into its * summary would make `show` state something untrue about what will run, so the * two fields are suppressed and the argv is projected instead, under `exec` * (field presence carries the dispatch kind, exactly like `fanOut`/`route` * carry the step kind). * * `timeoutMs` still merges the defaults: an exec unit really does inherit * `defaults.timeout`, so that number stays true for it. * * The argv is shown IN FULL, never clipped. It is authored literally in the * asset — the `${{ … }}` interpolation language is gone, so nothing in it is * resolved from the environment, from a secret ref, or from a prior step's * output — which makes it (a) safe to display, since every byte is already * visible in the workflow file `show` is rendering, and (b) pointless to clip: * the whole finding this projection answers is `show` describing something * other than what runs, and a truncated argv is that same bug in miniature. * It is far smaller than the step `instructions` this same projection * already carries whole. */ function summarizeStepOrchestration(step, defaults) { const spec = step.spec; const unit = spec?.unit; const exec = spec?.exec; const engine = exec ? undefined : (unit?.engine ?? defaults?.engine); const model = exec ? undefined : (unit?.model ?? defaults?.model); const timeoutMs = unit?.timeoutMs !== undefined ? unit.timeoutMs : defaults?.timeoutMs; const summary = { ...(engine !== undefined ? { engine } : {}), ...(model !== undefined ? { model } : {}), ...(timeoutMs !== undefined ? { timeoutMs } : {}), // Same projection the frozen plan uses, so what `show` prints cannot drift from what runs. ...(exec ? { exec: { ...exec } } : {}), ...(spec?.map ? { fanOut: { over: spec.map.over, ...(spec.map.concurrency !== undefined ? { concurrency: spec.map.concurrency } : {}), reducer: spec.map.reducer ?? "collect", }, } : {}), ...(unit?.output !== undefined || step.outputSchema !== undefined ? { hasSchema: true } : {}), ...(unit?.env !== undefined ? { env: [...unit.env] } : {}), ...(step.route ? { route: { input: step.route.input, branches: Object.entries(step.route.when).map(([match, stepId]) => ({ match, stepId })), ...(step.route.defaultStepId !== undefined ? { defaultStepId: step.route.defaultStepId } : {}), }, } : {}), }; return Object.keys(summary).length > 0 ? summary : undefined; } function projectStepDefinitions(plan) { return plan.steps.map((step, sequenceIndex) => { const orchestration = summarizeStepOrchestration(step, plan.defaults); const instructions = workflowStepInstructions(step); return { id: step.stepId, title: step.stepId, instructions: instructions || (step.route ? routeDescription(step.route) : ""), ...(step.gate.criteria.length > 0 ? { completionCriteria: [...step.gate.criteria] } : {}), sequenceIndex, ...(orchestration ? { orchestration } : {}), }; }); } export const workflowMdRenderer = { name: "workflow-md", buildShowResponse(ctx) { const name = deriveName(ctx); const plan = loadPlan(ctx); // WI-8.5b (display flip): the `akm workflow run <ref>` action is DISPLAY // output — its spelling follows the D-R5 display rule (`displayRef`). A // primary/default-bundle workflow renders the SHORT conceptId // (`workflows/<name>`); a named source qualifies it as // (`<bundle>//workflows/<name>`). const ref = displayRef({ type: "workflow", name, bundleId: ctx.origin }, ctx.defaultBundle); const parameters = projectParameters(plan); return { type: "workflow", name, path: ctx.absPath, action: buildWorkflowAction(ref), ...(plan.preamble ? { content: plan.preamble } : {}), description: plan.description, // No authored title — the asset's human name is its `description`/H1; this is its canonical name. workflowTitle: name, ...(parameters ? { parameters: parameters.map((p) => p.name), workflowParameters: parameters } : {}), steps: projectStepDefinitions(plan), }; }, };