/** * OpenSpec tool module — Recipe-based API for the openspec CLI. * * Replaces the ad-hoc spawnSync/execFile calls in `openspec-poller.ts` * with typed Recipes executed through the runner. The higher-level * `pollOpenSpec` / `pollOpenSpecAsync` functions remain in * `openspec-poller.ts` (they aggregate list + per-change status into * the dashboard's OpenSpecData shape) and now use these primitives. * * See change: platform-command-executor. */ import { run, unwrap, type Recipe, type Result } from "./runner.js"; const OPENSPEC_TIMEOUT = 10_000; interface WithCwd { cwd: string; } /** Parse JSON from stdout; returns null on parse failure. */ function parseJsonOrNull(out: string): unknown | null { const trimmed = out.trim(); if (!trimmed) return null; try { return JSON.parse(trimmed); } catch { return null; } } // ── Recipes ───────────────────────────────────────────────────────────────── export const OPENSPEC_LIST: Recipe = { argv: () => ["openspec", "list", "--json"], parse: parseJsonOrNull, timeout: OPENSPEC_TIMEOUT, }; export const OPENSPEC_STATUS: Recipe = { argv: ({ change }) => ["openspec", "status", "--change", change, "--json"], parse: parseJsonOrNull, timeout: OPENSPEC_TIMEOUT, }; /** * `openspec archive --completed` — bulk-archives all completed changes. * Stdout is human-readable (not JSON); callers typically don't parse it, * they just await success/failure. */ export const OPENSPEC_ARCHIVE_COMPLETED: Recipe = { argv: () => ["openspec", "archive", "--completed"], parse: (out) => out, // Archive operations can be slow when many changes are processed. timeout: 30_000, }; export const OPENSPEC_RECIPES = { OPENSPEC_LIST, OPENSPEC_STATUS, OPENSPEC_ARCHIVE_COMPLETED, } as const; // ── Public API ────────────────────────────────────────────────────────────── /** Run `openspec list --json` and return the parsed JSON, or null on failure. */ export function list(input: WithCwd): Result { return run(OPENSPEC_LIST, input, { cwd: input.cwd }); } /** Run `openspec status --change --json` and return parsed JSON or null. */ export function status(input: WithCwd & { change: string }): Result { return run(OPENSPEC_STATUS, input, { cwd: input.cwd }); } /** Run `openspec archive --completed`. Returns raw stdout on success. */ export function archiveCompleted(input: WithCwd): Result { return run(OPENSPEC_ARCHIVE_COMPLETED, input, { cwd: input.cwd }); } // ── Best-effort variants (mirror the pattern established in git.ts) ───────── export function listOr(input: WithCwd, fallback: unknown | null = null): unknown | null { return unwrap(list(input), fallback); } export function statusOr( input: WithCwd & { change: string }, fallback: unknown | null = null, ): unknown | null { return unwrap(status(input), fallback); }