mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
* fix(code-callers/callees): honor .gbrain-source pin via full source-resolution chain code-callers and code-callees called resolveDefaultSource directly, which only knew "1 source -> use it, else multiple_sources_ambiguous" and ignored the .gbrain-source pin (and env / local_path / brain_default / sole_non_default tiers). On a multi-source brain they errored even when a pin clearly selected one source, while code-def/code-refs "worked" only because they never scope by source at all. New shared helper resolveScopedSourceOrThrow(engine, cwd) in sources-ops.ts runs the full resolveSourceWithTier chain and applies the ambiguity guard ONLY when nothing matched (tier seed_default). Both commands route through it; an explicit --source/--all-sources still overrides. Adds source_id + scope to the JSON envelope, a stderr nudge on the sole_non_default tier (matches sync/import), a zero-result "try --all-sources" hint, and clean exit-2 handling for a bad pin. Tests: test/code-scoped-source-resolve.test.ts (8 helper cases incl. dotfile pin, env/brain_default/sole_non_default tiers, ambiguity, bad pin) and test/code-callers-pin.serial.test.ts (9 CLI-wiring cases via process.chdir). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dream): run against postgres engines (skip filesystem-only phases when no checkout) gbrain dream hard-failed with "No brain directory found" on a postgres/Supabase brain with no local checkout, so the DB-only maintenance phases (notably resolve_symbol_edges, the call-graph builder) could never run. doctor even recommended `gbrain dream --source <id>`, a command that couldn't run. - dream.ts: resolveBrainDir returns string|null (order: --dir -> resolved source's local_path -> sync.repo_path -> null); runDream owns the both-null (no checkout AND no engine) exit 1. - cycle.ts: CycleOpts.brainDir is string|null; resolveSourceForDir null-tolerant; the 6 filesystem phases (lint/backlinks/sync/synthesize/extract/patterns) skip with reason 'no_brain_dir' when there's no checkout; DB phases run. cycleSourceId = opts.sourceId ?? resolveSourceForDir(...) scopes the per-source DB phases (extract_facts/extract_atoms/calibration) correctly even on a checkout-less brain (previously they scoped to 'default' while the cycle stamped the requested source fresh — a freshness stamp that lied). deriveStatus counts resolved/ambiguous edges as work so an edges-only cycle reports 'ok'. - jobs.ts: the autopilot-cycle handler passes null (not cwd '.') when no repo is configured, so the queued cycle follows the same no_brain_dir contract. Tests: test/dream-postgres.test.ts (8: null-brainDir path, --source scope regression, edges->ok, both-null exit) + test/jobs-autopilot-cycle-braindir.test.ts (1: handler passes null -> FS phases skip). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v0.41.38.0) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(review): scope dream brainDir to --source; null fallback for phase handlers; quarantine heavy PGLite tests Addresses pre-landing review findings (codex P1/P2): - dream.ts resolveBrainDir: when --source resolves but that source has no on-disk checkout, return null (DB-only) instead of falling through to the global sync.repo_path. That global path belongs to the default/unscoped brain; running FS phases against it while DB phases + the last_full_cycle_at stamp target the requested source mixed scopes (codex P1). Adds a regression test (--source repo-a + a configured global sync.repo_path → brain_dir null). - jobs.ts makePhaseHandler: fall back to null (not cwd '.') when no repo is configured, matching the autopilot-cycle handler + gbrain dream. A direct phase job (synthesize/patterns) on a checkout-less brain now skips FS phases as no_brain_dir instead of running against the worker cwd (codex P2). - Move dream-postgres + jobs-autopilot-cycle-braindir tests to *.serial.test.ts (they run full runCycle passes; the serial pass avoids parallel-shard PGLite cold-start contention). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(todos): record v0.41.38.0 dream-postgres / source-pin follow-ups Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: update CLAUDE.md Key Files for v0.41.38.0 (dream-postgres + source pin) - dream.ts entry: resolveBrainDir returns string|null; checkout-less postgres runs DB phases + skips FS phases (no_brain_dir); --source-with-no-checkout doesn't borrow a different source's global repo. - cycle.ts entry: CycleOpts.brainDir nullable; cycleSourceId per-source scope; deriveStatus counts edges; jobs.ts handlers pass null not '.'. - Regenerated llms-full.txt (bun run build:llms). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
69 lines
2.6 KiB
TypeScript
69 lines
2.6 KiB
TypeScript
/**
|
|
* v0.41.30.0 (T2) — the Minions `autopilot-cycle` handler runs on a
|
|
* checkout-less brain.
|
|
*
|
|
* Pre-fix jobs.ts defaulted repoPath to cwd `'.'` when no repo was configured,
|
|
* then fed that into runCycle — so a queued cycle (what `gbrain remote ping`
|
|
* triggers) on a checkout-less postgres brain ran filesystem phases against the
|
|
* worker's cwd instead of skipping them. Now it passes `null`, so the handler
|
|
* follows the same no_brain_dir contract as `gbrain dream`.
|
|
*
|
|
* Drives the REAL handler (captured from registerBuiltinHandlers) — not a
|
|
* source-grep — so a future refactor that reintroduces the '.' fallback fails
|
|
* here. PGLite in-memory.
|
|
*/
|
|
|
|
import { describe, test, expect, beforeAll, afterAll, beforeEach } from 'bun:test';
|
|
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
|
|
import { registerBuiltinHandlers } from '../src/commands/jobs.ts';
|
|
import { resetPgliteState } from './helpers/reset-pglite.ts';
|
|
|
|
let engine: PGLiteEngine;
|
|
|
|
beforeAll(async () => {
|
|
engine = new PGLiteEngine();
|
|
await engine.connect({});
|
|
await engine.initSchema();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await engine.disconnect();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await resetPgliteState(engine);
|
|
});
|
|
|
|
/** Capture registered handlers via a minimal fake worker. */
|
|
async function captureHandlers(): Promise<Map<string, (job: any) => Promise<any>>> {
|
|
const handlers = new Map<string, (job: any) => Promise<any>>();
|
|
const fakeWorker = { register(name: string, fn: (job: any) => Promise<any>) { handlers.set(name, fn); } };
|
|
await registerBuiltinHandlers(fakeWorker as never, engine);
|
|
return handlers;
|
|
}
|
|
|
|
describe('jobs autopilot-cycle handler — no repo configured', () => {
|
|
test('feeds null brainDir → filesystem phases skip (no_brain_dir), DB phases run', async () => {
|
|
const handlers = await captureHandlers();
|
|
const handler = handlers.get('autopilot-cycle');
|
|
expect(handler).toBeTruthy();
|
|
|
|
// No sync.repo_path config on a fresh brain, no repoPath in job data →
|
|
// the handler must pass null (not cwd '.') to runCycle.
|
|
const result = await handler!({
|
|
data: { phases: ['lint', 'resolve_symbol_edges'] },
|
|
signal: undefined,
|
|
});
|
|
|
|
const report = result.report;
|
|
expect(report.brain_dir).toBeNull();
|
|
const lint = report.phases.find((p: any) => p.phase === 'lint');
|
|
expect(lint?.status).toBe('skipped');
|
|
expect(lint?.details?.reason).toBe('no_brain_dir');
|
|
const rse = report.phases.find((p: any) => p.phase === 'resolve_symbol_edges');
|
|
expect(rse).toBeTruthy();
|
|
expect(rse?.details?.reason).not.toBe('no_brain_dir');
|
|
expect(rse?.status).not.toBe('fail');
|
|
});
|
|
});
|