mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 14:06:50 +01:00
A7: run only the resolve_symbol_edges dream phase, detected first
/sync-gbrain --dream and the --full auto-build ran gbrain's full dream cycle (~35 minutes, LLM phases) when the call graph needs only the resolve_symbol_edges phase. The stage now runs gbrain dream [--source <id>] --phase resolve_symbol_edges when the installed gbrain accepts that phase (detected by behavior: gbrain validates --phase before printing --help). Otherwise it skips the forced dream and says the full cycle costs about 35 minutes and how to run it, instead of starting it. Co-authored-by: liutiming <timingliu@outlook.com>
This commit is contained in:
1 parent
ce651c2089
commit
76bf72d89f
4 files changed
+106
-8
No files matched your search
@@ -1690,6 +1690,16 @@ export function shouldRunDream(args: CliArgs, cycle: CycleStatus | null): boolea
|
||||
* binary / malformed env) — a broken install must be visible, not disguised as
|
||||
* optional maintenance.
|
||||
*/
|
||||
/**
|
||||
* A7: detected, not assumed. gbrain validates --phase values while parsing,
|
||||
* before --help prints, so a gbrain without the phase exits non-zero and one
|
||||
* without --phase prints help that never mentions it.
|
||||
*/
|
||||
export function gbrainSupportsSymbolEdgePhase(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
const r = spawnGbrain(["dream", "--phase", "resolve_symbol_edges", "--help"], { baseEnv: env, timeout: 15_000 });
|
||||
return r.status === 0 && /--phase\b/.test(`${r.stdout || ""}${r.stderr || ""}`);
|
||||
}
|
||||
|
||||
export async function runDream(args: CliArgs): Promise<StageResult> {
|
||||
const t0 = Date.now();
|
||||
|
||||
@@ -1702,8 +1712,8 @@ export async function runDream(args: CliArgs): Promise<StageResult> {
|
||||
ok: true,
|
||||
duration_ms: 0,
|
||||
summary: sourceId
|
||||
? `would: gbrain dream --source ${sourceId} (build this source's call graph)`
|
||||
: "would: gbrain dream (call-graph build)",
|
||||
? `would: gbrain dream --source ${sourceId} --phase resolve_symbol_edges (build this source's call graph; skipped if gbrain cannot scope the phase)`
|
||||
: "would: gbrain dream --phase resolve_symbol_edges (call-graph build; skipped if gbrain cannot scope the phase)",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1744,7 +1754,24 @@ export async function runDream(args: CliArgs): Promise<StageResult> {
|
||||
// only when we can't derive the source id (not in a git repo).
|
||||
const root = repoRoot();
|
||||
const sourceId = root ? resolveCodeSourceId(root, gbrainEnv) : null;
|
||||
const dreamArgs = sourceId ? ["dream", "--source", sourceId] : ["dream"];
|
||||
// A7 (#2783): the call graph needs only the resolve_symbol_edges phase. A
|
||||
// full `gbrain dream` runs every maintenance phase, LLM ones included
|
||||
// (~35 min). Scope it when the installed gbrain can; never fall back to
|
||||
// the full cycle on its own.
|
||||
if (!gbrainSupportsSymbolEdgePhase()) {
|
||||
return {
|
||||
name: "dream",
|
||||
ran: false,
|
||||
ok: true,
|
||||
duration_ms: Date.now() - t0,
|
||||
summary:
|
||||
"skipped — the installed gbrain cannot run only the resolve_symbol_edges phase, and the full dream cycle " +
|
||||
`costs about 35 minutes (LLM phases). Upgrade with gstack-gbrain-install, or run it yourself: gbrain dream${sourceId ? ` --source ${sourceId}` : ""}`,
|
||||
};
|
||||
}
|
||||
const dreamArgs = sourceId
|
||||
? ["dream", "--source", sourceId, "--phase", "resolve_symbol_edges"]
|
||||
: ["dream", "--phase", "resolve_symbol_edges"];
|
||||
|
||||
// spawnGbrain seeds DATABASE_URL from gbrain's config via buildGbrainEnv.
|
||||
//
|
||||
|
||||
@@ -453,6 +453,25 @@ with `gbrain sources remove <id>` only if you are sure.
|
||||
|
||||
---
|
||||
|
||||
## `dream: skipped — the installed gbrain cannot run only the resolve_symbol_edges phase, and the full dream cycle costs about 35 minutes (LLM phases).`
|
||||
|
||||
**Problem.** `/sync-gbrain --dream` (or `--full`) did not build the call
|
||||
graph.
|
||||
|
||||
**Cause.** The call graph needs only gbrain's `resolve_symbol_edges` phase.
|
||||
The installed gbrain cannot run that phase alone (no `dream --phase`, or no
|
||||
such phase), and gstack no longer starts gbrain's full maintenance cycle on
|
||||
its own: it takes about 35 minutes and runs LLM phases.
|
||||
|
||||
**Fix.**
|
||||
```bash
|
||||
gstack-gbrain-install # upgrade gbrain, then:
|
||||
/sync-gbrain --dream
|
||||
```
|
||||
Or run the full cycle yourself when you have the time: `gbrain dream --source <id>`.
|
||||
|
||||
---
|
||||
|
||||
## Nothing is syncing but I expect it to
|
||||
|
||||
**Not an error, but a common gotcha.** Check in order:
|
||||
|
||||
@@ -47,8 +47,8 @@ When the user types `/sync-gbrain`, run this skill. Argument modes (parsed by
|
||||
the skill itself, not a dispatcher binary):
|
||||
|
||||
- `/sync-gbrain` — incremental sync (default; mtime fast-path; ~50ms steady-state)
|
||||
- `/sync-gbrain --full` — full code reindex via `gbrain reindex-code` (~25-35 min on a big repo). Auto-builds the call graph (`gbrain dream`) **only when it was never built**.
|
||||
- `/sync-gbrain --dream` — build this source's call graph (`gbrain code-callers`/`code-callees`) via a source-scoped `gbrain dream --source <id>` cycle; ~minutes; runs lock-free after the sync stages. Always forces, even if already built. Only produces a graph on a code-aware schema pack; otherwise the run reports a WARN explaining why the graph is still empty.
|
||||
- `/sync-gbrain --full` — full code reindex via `gbrain reindex-code` (~25-35 min on a big repo). Auto-builds the call graph (`gbrain dream --phase resolve_symbol_edges`) **only when it was never built**.
|
||||
- `/sync-gbrain --dream` — build this source's call graph (`gbrain code-callers`/`code-callees`) via `gbrain dream --source <id> --phase resolve_symbol_edges`; ~minutes; runs lock-free after the sync stages. Always forces, even if already built. Runs only that phase, never gbrain's full maintenance cycle (about 35 minutes with LLM phases); if the installed gbrain cannot scope the phase, the dream row says so and nothing runs. Only produces a graph on a code-aware schema pack; otherwise the run reports a WARN explaining why the graph is still empty.
|
||||
- `/sync-gbrain --no-dream` — skip the dream cycle that `--full` would otherwise auto-run.
|
||||
- `/sync-gbrain --code-only` — only run the code stage; skip memory + brain-sync
|
||||
- `/sync-gbrain --dry-run` — preview what would sync; no writes anywhere
|
||||
@@ -346,8 +346,9 @@ If `CYCLE == never` AND the user did NOT pass `--dream`/`--full` AND Step 3
|
||||
>
|
||||
> ELI10: `gbrain code-callers`/`code-callees` (who calls this function / what it
|
||||
> calls) return nothing until the `resolve_symbol_edges` phase runs for this
|
||||
> source. `gbrain dream --source <this source>` runs it (scoped to this
|
||||
> worktree's code, takes a few minutes). It only produces a graph if this
|
||||
> source. `gbrain dream --source <this source> --phase resolve_symbol_edges`
|
||||
> runs only that phase (scoped to this worktree's code, takes a few minutes,
|
||||
> not the full ~35-minute maintenance cycle). It only produces a graph if this
|
||||
> source's schema pack extracts code symbols; if it doesn't, the run completes
|
||||
> but the graph stays empty and the dream row will say so.
|
||||
>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect, afterEach } from "bun:test";
|
||||
import { mkdtempSync, existsSync, writeFileSync, utimesSync, rmSync } from "fs";
|
||||
import { mkdtempSync, existsSync, writeFileSync, utimesSync, rmSync, mkdirSync, readFileSync } from "fs";
|
||||
import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
import { usePrivateStateRoot } from "./helpers/private-state-root";
|
||||
@@ -279,3 +279,54 @@ describe("formatStage — WARN render", () => {
|
||||
expect(formatStage({ ...base, ran: false, ok: true })).toContain("SKIP");
|
||||
});
|
||||
});
|
||||
|
||||
// A7 (#2783): the forced dream runs only the resolve_symbol_edges phase, and
|
||||
// only when the installed gbrain can scope it (detected, not assumed).
|
||||
describe("dream stage scopes gbrain dream to resolve_symbol_edges (A7)", () => {
|
||||
function sandbox(phaseSupport: "phase" | "no-phase-flag" | "unknown-phase") {
|
||||
const home = mkdtempSync(join(tmpdir(), "gstack-dream-phase-"));
|
||||
const bin = join(home, "bin");
|
||||
const cwd = join(home, "not-a-repo");
|
||||
mkdirSync(join(home, ".gbrain"), { recursive: true });
|
||||
mkdirSync(bin, { recursive: true });
|
||||
mkdirSync(cwd, { recursive: true });
|
||||
writeFileSync(join(home, ".gbrain", "config.json"), JSON.stringify({ engine: "pglite", database_path: join(home, ".gbrain", "brain.pglite") }));
|
||||
const help = phaseSupport === "no-phase-flag" ? "Usage: gbrain dream [--source <id>]" : " --phase <name> Run only the named phase(s).";
|
||||
writeFileSync(join(bin, "gbrain"), `#!/bin/sh
|
||||
echo "$*" >> "${home}/calls.log"
|
||||
case "$1" in
|
||||
--version) echo "gbrain 0.60.37.0" ;;
|
||||
sources) echo '{"sources":[]}' ;;
|
||||
dream)
|
||||
if [ "${phaseSupport}" = "unknown-phase" ]; then echo 'Unknown phase "resolve_symbol_edges".' >&2; exit 1; fi
|
||||
case " $* " in *" --help "*) echo "${help}"; exit 0 ;; esac
|
||||
echo "[cycle] resolve_symbol_edges: resolved 4 edges" ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
`, { mode: 0o755 });
|
||||
const r = spawnSync(process.execPath, [SCRIPT, "--dream", "--no-code", "--no-memory", "--no-brain-sync"], {
|
||||
cwd,
|
||||
encoding: "utf-8",
|
||||
timeout: 60_000,
|
||||
env: { ...process.env, HOME: home, GSTACK_HOME: join(home, ".gstack"), GBRAIN_HOME: "", PATH: `${bin}:/usr/bin:/bin` },
|
||||
});
|
||||
const calls = readFileSync(join(home, "calls.log"), "utf-8").trim().split("\n").filter((l) => l.startsWith("dream"));
|
||||
rmSync(home, { recursive: true, force: true });
|
||||
return { out: (r.stdout || "") + (r.stderr || ""), calls };
|
||||
}
|
||||
|
||||
it("runs `gbrain dream --phase resolve_symbol_edges` when gbrain supports it, never the full cycle", () => {
|
||||
const { calls } = sandbox("phase");
|
||||
const cycles = calls.filter((c) => !c.includes("--help"));
|
||||
expect(cycles).toEqual(["dream --phase resolve_symbol_edges"]);
|
||||
});
|
||||
|
||||
for (const kind of ["no-phase-flag", "unknown-phase"] as const) {
|
||||
it(`skips the forced dream and names the 35-minute full cycle when gbrain cannot scope it (${kind})`, () => {
|
||||
const { out, calls } = sandbox(kind);
|
||||
expect(calls.every((c) => c.includes("--help"))).toBe(true);
|
||||
expect(out).toContain("full dream cycle costs about 35 minutes");
|
||||
expect(out).toContain("gstack-gbrain-install");
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user