mirror of
https://github.com/garrytan/gstack.git
synced 2026-07-01 14:05:43 +02:00
feat(gbrain): orchestrator SKIP when local engine not ok + remote-http transcripts via artifacts pipeline
Two changes in the sync orchestrator, both per plan D11/D12: 1. bin/gstack-gbrain-sync.ts: runCodeImport + runMemoryIngest call localEngineStatus() (shared classifier from lib/gbrain-local-status.ts). When status is not 'ok', return a SKIP stage result with a clear reason instead of crashing with "source registration failed: gbrain not configured". Brain-sync stage runs regardless — it doesn't depend on local engine. dry-run preview path is gated above the check so it continues to show would-do steps even when the engine is broken. 2. bin/gstack-memory-ingest.ts: when gbrain MCP is registered as remote-http (Path 4), persist staged transcripts to ~/.gstack/transcripts/run-<pid>-<ts>/ instead of the ephemeral ~/.gstack/.staging-ingest-<pid>-<ts>/ tmp dir, and SKIP the local `gbrain import` call entirely. The artifacts pipeline (gstack-brain-sync push to git, brain admin pulls and indexes) handles routing to the remote brain. Local PGLite (when present via Step 4.5) stays code-only. State recording still happens — prepared pages get their mtime+sha256 stamped under remote-http mode so the next /sync-gbrain doesn't re-stage them. Cleanup is skipped intentionally so the persisted dir survives until gstack-brain-sync moves it. Adds test/gbrain-sync-skip.test.ts covering 5 SKIP scenarios (broken-db, broken-config, no-cli, missing-config, ok pass-through). All 25 sync-related unit tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ import { createHash } from "crypto";
|
||||
|
||||
import { detectEngineTier, withErrorContext, canonicalizeRemote } from "../lib/gstack-memory-helpers";
|
||||
import { ensureSourceRegistered, sourcePageCount } from "../lib/gbrain-sources";
|
||||
import { localEngineStatus, type LocalEngineStatus } from "../lib/gbrain-local-status";
|
||||
|
||||
// ── Types ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -290,6 +291,42 @@ function releaseLock(): void {
|
||||
|
||||
// ── Stage runners ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Build a SKIP result for the code/memory stage when the local engine is
|
||||
* not in 'ok' state (per plan D12). Surface the status verbatim so the
|
||||
* verdict block tells the user exactly what's wrong without re-probing.
|
||||
*
|
||||
* Reasons mapped to user-actionable summaries:
|
||||
* no-cli → "gbrain CLI not on PATH; install via /setup-gbrain"
|
||||
* missing-config → "no local engine; run /setup-gbrain to add local PGLite"
|
||||
* broken-config → "config file at ~/.gbrain/config.json is malformed; see /setup-gbrain Step 1.5"
|
||||
* broken-db → "config points at unreachable DB; see /setup-gbrain Step 1.5"
|
||||
*/
|
||||
function skipStageForLocalStatus(
|
||||
stage: "code" | "memory",
|
||||
status: LocalEngineStatus,
|
||||
t0: number,
|
||||
): StageResult {
|
||||
const reasons: Record<Exclude<LocalEngineStatus, "ok">, string> = {
|
||||
"no-cli": "gbrain CLI not on PATH; install via /setup-gbrain",
|
||||
"missing-config":
|
||||
"no local engine; run /setup-gbrain to add local PGLite for code search",
|
||||
"broken-config":
|
||||
"config at ~/.gbrain/config.json is malformed; see /setup-gbrain Step 1.5",
|
||||
"broken-db":
|
||||
"config points at unreachable DB; see /setup-gbrain Step 1.5",
|
||||
};
|
||||
const reason = reasons[status as Exclude<LocalEngineStatus, "ok">];
|
||||
return {
|
||||
name: stage,
|
||||
ran: false,
|
||||
ok: true, // SKIP (per D12) — not a stage failure, just an unsatisfied prerequisite
|
||||
duration_ms: Date.now() - t0,
|
||||
summary: `skipped — local engine ${status} — ${reason}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async function runCodeImport(args: CliArgs): Promise<StageResult> {
|
||||
const t0 = Date.now();
|
||||
const root = repoRoot();
|
||||
@@ -302,6 +339,9 @@ async function runCodeImport(args: CliArgs): Promise<StageResult> {
|
||||
|
||||
const sourceId = deriveCodeSourceId(root);
|
||||
|
||||
// dry-run preview always shows the would-do steps, regardless of local
|
||||
// engine state. Useful for "what would /sync-gbrain do" without probing
|
||||
// the engine.
|
||||
if (args.mode === "dry-run") {
|
||||
return {
|
||||
name: "code",
|
||||
@@ -313,6 +353,17 @@ async function runCodeImport(args: CliArgs): Promise<StageResult> {
|
||||
};
|
||||
}
|
||||
|
||||
// Split-engine pre-flight (per plan D12): when local engine is not ok, SKIP
|
||||
// code stage cleanly. Brain-sync stage still runs because it doesn't depend
|
||||
// on local engine. The /sync-gbrain Step 1.5 pre-flight surfaces the user
|
||||
// remediation message; this skip just keeps the orchestrator from crashing
|
||||
// when the local DB is dead. Skipped on --dry-run (above) since dry-run
|
||||
// never actually probes anything.
|
||||
const localStatus = localEngineStatus({ noCache: false });
|
||||
if (localStatus !== "ok") {
|
||||
return skipStageForLocalStatus("code", localStatus, t0);
|
||||
}
|
||||
|
||||
// Step 0: Best-effort cleanup of pre-pathhash legacy source.
|
||||
// Earlier /sync-gbrain versions registered `gstack-code-<slug>` (no path
|
||||
// suffix). On a multi-worktree repo, those collapsed onto a single id
|
||||
@@ -431,6 +482,15 @@ function runMemoryIngest(args: CliArgs): StageResult {
|
||||
return { name: "memory", ran: false, ok: true, duration_ms: 0, summary: "would: gstack-memory-ingest --probe" };
|
||||
}
|
||||
|
||||
// Split-engine pre-flight (per plan D12). gstack-memory-ingest shells out
|
||||
// to `gbrain import` which targets the LOCAL engine. When that engine is
|
||||
// not ok, SKIP cleanly so brain-sync (the only stage that doesn't depend
|
||||
// on local engine) still runs.
|
||||
const localStatus = localEngineStatus({ noCache: false });
|
||||
if (localStatus !== "ok") {
|
||||
return skipStageForLocalStatus("memory", localStatus, t0);
|
||||
}
|
||||
|
||||
const ingestPath = join(import.meta.dir, "gstack-memory-ingest.ts");
|
||||
const ingestArgs = ["run", ingestPath];
|
||||
if (args.mode === "full") ingestArgs.push("--bulk");
|
||||
|
||||
Reference in New Issue
Block a user