mirror of
https://github.com/garrytan/gstack.git
synced 2026-06-17 23:30:09 +02:00
a52e033576
Two bins mirroring gstack-learnings-* (D3A). log writes decide/--supersede/--redact/ --compact events + refreshes the bounded snapshot + enqueues for cross-machine sync; search reads the O(active) snapshot, scope-filtered to current branch, newest-first, --all to include superseded, --json for machines. Empty store returns silently (no snapshot write on an empty read). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
3.1 KiB
TypeScript
Executable File
100 lines
3.1 KiB
TypeScript
Executable File
#!/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 <decision-id>
|
|
* gstack-decision-log --redact <decision-id>
|
|
* 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";
|
|
|
|
const HERE = import.meta.dir;
|
|
|
|
function resolveSlug(): string {
|
|
const r = spawnSync(`${HERE}/gstack-slug`, { encoding: "utf-8" });
|
|
const m = (r.stdout || "").match(/^SLUG=(.+)$/m);
|
|
return m ? m[1].trim() : "unknown";
|
|
}
|
|
function gitBranch(): string | undefined {
|
|
const r = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf-8" });
|
|
const b = (r.stdout || "").trim();
|
|
return b && b !== "HEAD" ? b : undefined;
|
|
}
|
|
|
|
const args = process.argv.slice(2);
|
|
const slug = resolveSlug();
|
|
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" });
|
|
}
|
|
function flagValue(name: string): string | undefined {
|
|
const i = args.indexOf(name);
|
|
return i >= 0 ? args[i + 1] : undefined;
|
|
}
|
|
|
|
if (args.includes("--compact")) {
|
|
const r = compact(paths);
|
|
console.log(`compacted: ${r.activeCount} active, ${r.archivedCount} archived, ${r.expungedCount} expunged`);
|
|
enqueue();
|
|
process.exit(0);
|
|
}
|
|
|
|
const supersedeId = flagValue("--supersede");
|
|
const redactId = flagValue("--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 <id>, or --compact\n",
|
|
);
|
|
process.exit(1);
|
|
}
|
|
let obj: Partial<DecisionEvent>;
|
|
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);
|