#!/usr/bin/env bun /** * gstack-decision-log — append a durable decision (or supersede/redact/compact it). * * Usage: * gstack-decision-log '{"decision":"...","rationale":"...","scope":"repo","source":"user"}' * gstack-decision-log --supersede * gstack-decision-log --redact * gstack-decision-log --compact * * Event-sourced (lib/gstack-decision): every call appends an event and refreshes the * bounded active snapshot. NON-INTERACTIVE — never prompts (agents/skills call this; * a prompt would hang them). Validation + injection + HIGH-secret rejection happen in * validateDecide; a rejected decision exits 1 with a message, nothing persisted. */ import { mkdirSync } from "fs"; import { dirname } from "path"; import { spawnSync } from "child_process"; import { decisionPaths, validateDecide, makeRefEvent, appendEvent, rebuildSnapshot, compact, type DecisionEvent, } from "../lib/gstack-decision"; import { resolveSlug, gitBranch, flagValue } from "../lib/bin-context"; const HERE = import.meta.dir; const args = process.argv.slice(2); const slug = resolveSlug(`${HERE}/gstack-slug`); const paths = decisionPaths(slug); mkdirSync(dirname(paths.log), { recursive: true }); function enqueue(): void { // Fire-and-forget cross-machine sync (no-op when artifacts_sync is off). spawnSync(`${HERE}/gstack-brain-enqueue`, [`projects/${slug}/decisions.jsonl`], { stdio: "ignore" }); } if (args.includes("--compact")) { const r = compact(paths); if (r.skipped) { console.log("compact skipped: a concurrent write/compact is in progress; log left intact — re-run"); process.exit(0); } console.log(`compacted: ${r.activeCount} active, ${r.archivedCount} archived, ${r.expungedCount} expunged`); enqueue(); process.exit(0); } const supersedeId = flagValue(args, "--supersede"); const redactId = flagValue(args, "--redact"); if (supersedeId || redactId) { const kind = supersedeId ? "supersede" : "redact"; const targetId = (supersedeId || redactId) as string; appendEvent(paths, makeRefEvent(kind, targetId, { source: "agent" })); rebuildSnapshot(paths); enqueue(); console.log(`${kind}: ${targetId}`); process.exit(0); } const jsonArg = args.find((a) => !a.startsWith("--")); if (!jsonArg) { process.stderr.write( "gstack-decision-log: provide a JSON decision, or --supersede/--redact , or --compact\n", ); process.exit(1); } let obj: Partial; try { obj = JSON.parse(jsonArg); } catch { process.stderr.write("gstack-decision-log: invalid JSON\n"); process.exit(1); } if (obj.scope === "branch" && !obj.branch) obj.branch = gitBranch(); const res = validateDecide(obj); if (!res.ok) { process.stderr.write(`gstack-decision-log: ${res.error}\n`); process.exit(1); } appendEvent(paths, res.event); rebuildSnapshot(paths); enqueue(); console.log(res.event.id);