import { existsSync } from "node:fs"; import { dirname, isAbsolute, relative, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; import { truncateToWidth } from "@mariozechner/pi-tui"; type StoryRunnerModule = typeof import("../../src/workflows/story-runner.js"); const STATUS_KEY = "abulafia-live-status"; const READY_WORKING_MESSAGE = "\u0412\u043d\u0443\u0442\u0440\u044f\u043d\u043a\u0430: \u0433\u043e\u0442\u043e\u0432\u043e, story complete"; function resolveAppRoot(): string { return resolve(dirname(fileURLToPath(import.meta.url)), "..", ".."); } async function loadStoryRunner(appRoot: string): Promise { const sourcePath = resolve(appRoot, "src", "workflows", "story-runner.ts"); const distPath = resolve(appRoot, "dist", "workflows", "story-runner.js"); const modulePath = existsSync(sourcePath) ? sourcePath : distPath; return (await import(pathToFileURL(modulePath).href)) as StoryRunnerModule; } function relativeForUi(cwd: string, path: string): string { const rel = relative(resolve(cwd), resolve(path)); if (rel && !rel.startsWith("..") && !isAbsolute(rel)) { return rel.replace(/\\/g, "/"); } return path.replace(/\\/g, "/"); } function formatStoryResult(cwd: string, result: ReturnType): string { const primary = result.primaryOutputs[0] ? relativeForUi(cwd, result.primaryOutputs[0]) : "not produced"; const report = relativeForUi(cwd, result.reportPath); return [ "Story complete", `Primary artifact: ${primary}`, `Run report: ${report}`, result.validationCommand ? `Validate: ${result.validationCommand}` : undefined, ].filter(Boolean).join("\n"); } function truncateForTui(text: string, maxVisible: number): string { const width = Math.max(1, maxVisible); return truncateToWidth(text, width, width <= 3 ? "" : "..."); } function widgetLineWidth(width: number): number { return Math.max(1, Math.min(width - 2, 100)); } function modelLabel(ctx: ExtensionCommandContext): string { return ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "model: unknown"; } function applyStoryCompletionStatus( ctx: ExtensionCommandContext, result: ReturnType, ): void { if (!ctx.hasUI) return; const primary = result.primaryOutputs[0] ? relativeForUi(ctx.cwd, result.primaryOutputs[0]) : "not produced"; const report = relativeForUi(ctx.cwd, result.reportPath); const lines = [ READY_WORKING_MESSAGE, `\u041c\u043e\u0434\u0435\u043b\u044c: ${modelLabel(ctx)}`, "\u042d\u0442\u0430\u043f: story complete", "Live status:", "Story complete", `Primary artifact: ${primary}`, `Run report: ${report}`, result.validationCommand ? `Validate: ${result.validationCommand}` : undefined, ].filter((line): line is string => Boolean(line)); ctx.ui.setWorkingMessage(READY_WORKING_MESSAGE); ctx.ui.setStatus(STATUS_KEY, ctx.ui.theme.fg("dim", READY_WORKING_MESSAGE)); ctx.ui.setWidget( STATUS_KEY, () => ({ render(width: number): string[] { const maxWidth = widgetLineWidth(width); return lines.map((line) => truncateForTui(line, maxWidth)); }, invalidate() {}, }), { placement: "aboveEditor" }, ); } export function splitStoryCommandArgs(input: string): string[] { const args: string[] = []; const pattern = /"([^"]*)"|'([^']*)'|(\S+)/g; let match: RegExpExecArray | null; while ((match = pattern.exec(input)) !== null) { args.push(match[1] ?? match[2] ?? match[3] ?? ""); } return args; } export function registerStoryWorkflowCommands(pi: ExtensionAPI): void { pi.registerCommand("story-local", { description: "Run A/S story contracts with local deterministic artifacts without model execution.", handler: async (rawArgs, ctx) => { try { const appRoot = resolveAppRoot(); const { runStoryWorkflowCommand } = await loadStoryRunner(appRoot); const result = runStoryWorkflowCommand("story", { appRoot, workingDir: ctx.cwd, args: splitStoryCommandArgs(rawArgs), }); applyStoryCompletionStatus(ctx, result); ctx.ui.notify(formatStoryResult(ctx.cwd, result), "info"); } catch (error) { ctx.ui.notify(error instanceof Error ? error.message : String(error), "error"); } }, }); pi.registerCommand("evaluate-stories", { description: "Evaluate story artifacts with the local deterministic evaluator.", handler: async (rawArgs, ctx) => { try { const appRoot = resolveAppRoot(); const { runStoryWorkflowCommand } = await loadStoryRunner(appRoot); const result = runStoryWorkflowCommand("evaluate-stories", { appRoot, workingDir: ctx.cwd, args: splitStoryCommandArgs(rawArgs), }); ctx.ui.notify(`Evaluation complete: ${relativeForUi(ctx.cwd, result.reportPath)}`, "info"); } catch (error) { ctx.ui.notify(error instanceof Error ? error.message : String(error), "error"); } }, }); }