import { readdir } from "node:fs/promises"; import { join } from "node:path"; import { clearActiveId, loadActiveId, saveActiveId } from "./active-state.js"; import { isNotFound, readJsonIfExists } from "./fs-errors.js"; import { writeJsonFile } from "./json-files.js"; import type { PlaybookRunState } from "./types.js"; export const RUNS_DIR = ".pi/playbook-runs"; const ACTIVE_FILE = "active.json"; export function runsDir(cwd: string): string { return join(cwd, RUNS_DIR); } export function runFile(cwd: string, runId: string): string { return join(runsDir(cwd), `${runId}.json`); } export function activeFile(cwd: string): string { return join(runsDir(cwd), ACTIVE_FILE); } export async function saveRun(cwd: string, run: PlaybookRunState): Promise { await writeJsonFile(runFile(cwd, run.runId), run); } export async function loadRun(cwd: string, runId: string): Promise { return readJsonIfExists(runFile(cwd, runId)); } export async function listRunIds(cwd: string): Promise { let files: string[]; try { files = await readdir(runsDir(cwd)); } catch (error) { if (isNotFound(error)) return []; throw error; } return files .filter((file) => file.endsWith(".json") && file !== ACTIVE_FILE) .map((file) => file.replace(/\.json$/, "")) .sort(); } export async function setActiveRun(cwd: string, runId: string): Promise { await saveActiveId(activeFile(cwd), "runId", runId); } export async function loadActiveRunId(cwd: string): Promise { return loadActiveId(activeFile(cwd), "runId"); } export async function clearActiveRun(cwd: string): Promise { await clearActiveId(activeFile(cwd)); } export function createRunId(playbookId: string, runName?: string): string { const base = slugify(runName ?? playbookId); const stamp = new Date().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14); return `${base}-${stamp}`; } export function slugify(value: string): string { const slug = value .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, ""); return slug || "playbook-run"; }