Skip to content

feat: v0.17.0 — gbrain dream + runCycle primitive (one cycle, two CLIs)#321

Merged
garrytan merged 10 commits intomasterfrom
garrytan/v0.17-dream-cycle
Apr 22, 2026
Merged

feat: v0.17.0 — gbrain dream + runCycle primitive (one cycle, two CLIs)#321
garrytan merged 10 commits intomasterfrom
garrytan/v0.17-dream-cycle

Conversation

@garrytan
Copy link
Copy Markdown
Owner

Summary

Adds gbrain dream as a first-class CLI verb and unifies brain maintenance around a single runCycle primitive. Closes the README gap where "the dream cycle" has been promised for a year without a command to match.

One primitive (src/core/cycle.ts). Two thin CLIs (dream + autopilot). Three callers (dream, autopilot daemon inline, Minions autopilot-cycle handler) all converge on the same phase order: lint → backlinks → sync → extract → embed → orphans. Fix files first, then index them. The original PR #309's dream.ts had the phase order wrong (sync before lint) — this gets it right.

Autopilot users get lint + orphan sweep added to their nightly cycle automatically. No config change. gbrain jobs list now shows 6-phase reports for every autopilot-cycle job.

Review history

This work started as PR #309 (gbrain dream by @WinterMute). Deep review (CEO + Eng ×3 + Codex outside-voice + DX) found structural issues that required restarting from scratch:

  • --dry-run silently wrote to DB in performSync's full-sync path
  • runEmbedCore had no dry-run mode and returned void
  • findOrphans used db.getConnection() global (didn't compose with injected engines or PGLite)
  • Phase order was wrong (sync before file fixes)
  • 4 of 5 phases used execSync('bun run cli.ts ...') — circular CLI invocation, defeated v0.15.2's heartbeat
  • Lockfile-freshness pattern had 6 known holes (per-process, not per-cycle; session-scoped pg_try_advisory_lock broken under v0.15.4's PgBouncer transaction pooling)
  • 3 of 7 tests were structural, 3 silent-passed in CI without running, real coverage ~1 assertion

This PR's 6 commits are the clean redesign that closed all of those.

6 bisectable commits

Each commit is independently green for the affected test files:

# Commit Tests passing
1 fix(sync): honor --dry-run in full-sync path + expose embedded count 75/75
2 fix(embed): add dry-run mode + return EmbedResult with counts 79/79
3 refactor(orphans): engine-injected queries, drop db.getConnection() global 83/83
4 feat(cycle): add runCycle primitive in src/core/cycle.ts 101/101
5 feat(dream): add gbrain dream CLI as a thin alias over runCycle 112/112
6 feat: v0.17.0 — autopilot + jobs delegate to runCycle (unifies the cycle) 112/112

git bisect between any two revisions lands on a green state. Commits 1-3 are genuinely independent (upstream library-contract fixes); commit 4 depends on all three; commits 5 and 6 depend on commit 4.

What shipped

New primitive: src/core/cycle.ts

  • runCycle(engine: BrainEngine | null, opts: CycleOpts): Promise<CycleReport>
  • Engine nullable: filesystem phases run without DB
  • Lock coordination via gbrain_cycle_locks table (TTL-based, survives PgBouncer)
  • File lock fallback for PGLite + engine=null
  • yieldBetweenPhases hook for Minions lock renewal
  • CycleReport.schema_version: "1" stable for agents
  • PhaseResult.error: {class, code, message, hint?, docs_url?} structured errors

New CLI: gbrain dream

  • ~80 lines, thin alias over runCycle
  • Flags: --dry-run, --json, --phase <name>, --pull, --dir <path>
  • brainDir resolution: explicit --dir OR sync.repo_path config (fixes footgun)
  • Exit code 1 on status: "failed"; partial is not a failure

Schema migration v16

  • gbrain_cycle_locks table + idx_cycle_locks_ttl index
  • Added to schema.sql (fresh installs), pglite-schema.ts (PGLite), and MIGRATIONS array (upgrades)

Precondition fixes (commits 1-3)

  • Sync dry-run now works in full-sync + first-sync paths (was silent write)
  • runEmbedCore returns EmbedResult with counts + supports dry-run
  • findOrphans(engine, opts) takes explicit engine; added findOrphanPages() to BrainEngine interface + both implementations

Autopilot + Minions unification (commit 6)

  • Autopilot inline fallback delegates to runCycle (gains lint + orphans)
  • autopilot-cycle Minions handler delegates to runCycle (gains lint + orphans + structured report)
  • gbrain autopilot --help cross-references gbrain dream

Tests (all in CI, no DATABASE_URL or API keys required)

  • test/sync.test.ts: +4 cases (first-sync dry-run, incremental dry-run, --full dry-run, embedded field)
  • test/embed.test.ts: +4 cases (dry-run --all/stale/slugs + non-dry-run regression guard)
  • test/orphans.test.ts: +4 cases (engine-injected findOrphans, includePseudo, queryOrphanPages, empty brain)
  • test/core/cycle.test.ts: new, 18 cases — dryRun × phases × lock × engine-null matrix
  • test/dream.test.ts: rewritten, 11 cases — argv parsing, brainDir resolution, output, exit codes

Test plan

  • Each commit green in isolation for affected test files
  • Full combined suite (6 files, 112 tests) passes at each rev from commit 5 onward
  • Bisectability: git bisect between any two commits lands on green state
  • E2E tests with Postgres: spin up Docker Postgres, bun run test:e2e — reviewer to run before merge
  • Manual: gbrain dream --help shows new help text with cron example + autopilot cross-ref
  • Manual: gbrain dream --dry-run --json against real brain produces CycleReport with zero writes
  • Manual: gbrain dream --json shows expected activity counts
  • Manual: existing autopilot --install users see 6-phase reports in gbrain jobs get <id>

Related

🤖 Generated with Claude Code

garrytan and others added 5 commits April 21, 2026 22:06
Precondition for v0.17 brain maintenance cycle (runCycle primitive).

The full-sync path (performFullSync) previously called runImport() even
when opts.dryRun was true, silently writing to the DB and advancing
sync.last_commit. `gbrain sync --dry-run` on a fresh brain (or with
--full) would mutate state without warning.

Fix:
  - performFullSync now early-returns a `dry_run` SyncResult when
    opts.dryRun is set. Walks the repo via collectMarkdownFiles +
    isSyncable to count what WOULD be imported. No writes, no git
    state advance.
  - SyncResult gains an `embedded: number` field (required). Tracks
    pages re-embedded during the sync's auto-embed step. Existing
    return sites set 0; the synced + first_sync paths set real counts
    (best-estimate until commit 2 sharpens runEmbedCore's return type).
  - first_sync path now returns real added + chunksCreated counts
    from runImport instead of hardcoded zeros.
  - printSyncResult shows embedded count in human output.

Tests (test/sync.test.ts, new `performSync dry-run never writes`
block, PGLite + temp git repo, no DATABASE_URL required):
  - first-sync --dry-run: no pages, no sync.last_commit
  - incremental --dry-run after real sync: bookmark unchanged
  - --full --dry-run: no reimport, bookmark unchanged
  - SyncResult.embedded is a number

Codex outside-voice caught this. Would have shipped silent DB writes
on dry-run for anyone using `gbrain sync --dry-run --full`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Precondition for v0.17 brain maintenance cycle (runCycle primitive).

runEmbedCore previously returned Promise<void> and had no dry-run mode.
That made it impossible for runCycle to (a) report accurate embedded
counts or (b) honor --dry-run without also skipping the entire embed
phase (which would have required runCycle to know embed's internal
semantics — a layering violation).

Changes:
  - EmbedOpts gains `dryRun?: boolean`. When set, embedPage and
    embedAll enumerate stale chunks (or would-be-created chunks for
    unchunked pages, via local chunkText without engine.upsertChunks)
    but never call embedBatch and never write to the engine.
  - runEmbedCore: Promise<void> -> Promise<EmbedResult>. Result shape:
    { embedded, skipped, would_embed, total_chunks, pages_processed,
      dryRun }.
    embedded = chunks newly embedded (0 in dryRun).
    would_embed = chunks that WOULD be embedded (0 in non-dryRun).
    skipped = chunks with pre-existing embeddings.
  - runEmbed CLI wrapper honors --dry-run flag and returns the result
    through. `gbrain embed --stale --dry-run` is now a safe preview.
  - Callers ignoring the return value (sync auto-embed, autopilot
    inline fallback, jobs.ts handlers, CLI) keep compiling — the new
    return type is additive for `await` callers.

Tests (test/embed.test.ts, new `runEmbedCore --dry-run` block, uses
the existing mock.module embedBatch pattern, no API key required):
  - dry-run --all: zero embedBatch calls, zero upsertChunks calls,
    would_embed matches stale chunk total
  - dry-run --stale correctly splits stale vs already-embedded counts
  - dry-run --slugs on a single page tallies per-chunk counts
  - non-dry-run regression guard: embedded count matches across
    concurrent workers

Codex outside-voice flagged the Promise<void> return as a blocker for
accurate CycleReport.totals.pages_embedded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lobal

Precondition for v0.17 brain maintenance cycle (runCycle primitive).

findOrphans + queryOrphanPages previously reached into the postgres-js
singleton via db.getConnection(), which (a) didn't compose with
runCycle's explicit-engine contract and (b) was wrong for PGLite test
fixtures and for any caller not using the default global connection.
Codex outside-voice flagged this as a blocker.

Changes:
  - BrainEngine interface gains findOrphanPages() — returns pages with
    no inbound links via the same NOT EXISTS anti-join. Implemented on
    both postgres-engine (sql tag) and pglite-engine (db.query).
  - findOrphans signature: findOrphans(engine, { includePseudo }).
    Engine is required. Uses engine.findOrphanPages() and
    engine.getStats().page_count instead of raw SQL + global counts.
  - queryOrphanPages signature: queryOrphanPages(engine). Delegates to
    engine.findOrphanPages().
  - src/commands/orphans.ts drops the `import * as db` — no more
    global-state coupling.
  - Callers updated: src/core/operations.ts find_orphans handler now
    passes ctx.engine through; runOrphans CLI entry uses its engine arg.
  - No signature change needed in cli.ts (it was already passing engine
    via CLI_ONLY dispatch).

Tests (test/orphans.test.ts, new `findOrphans (engine-injected)`
describe block, PGLite in-memory, no DATABASE_URL required):
  - links correctly scope orphans (alice links to bob -> bob not
    an orphan; alice is)
  - includePseudo:true surfaces _atlas-style pages
  - queryOrphanPages delegates to passed engine
  - empty brain returns {orphans: [], total_pages: 0} without crashing

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The brain maintenance cycle as a single function. Six phases in
semantically-driven order (fix files → sync → extract → embed →
report orphans). Pure composition of existing library calls — no
execSync, no subprocess anti-patterns, no regex-parsed output.

    ┌───────────────────────────────────────────────────┐
    │ runCycle(engine, opts) → CycleReport              │
    │   Phase 1: lint --fix         (fs writes)         │
    │   Phase 2: backlinks --fix    (fs writes)         │
    │   Phase 3: sync               (DB picks up 1+2)   │
    │   Phase 4: extract            (DB picks up links) │
    │   Phase 5: embed --stale      (DB writes)         │
    │   Phase 6: orphans            (DB read, report)   │
    └───────────────────────────────────────────────────┘

Why the commit-4 primitive:

  - CEO + Eng + Codex reviews all converged on "extract one cycle
    function, wire both dream and autopilot through it." Two CLIs,
    one definition of what the brain does overnight.
  - Phase order was wrong in PR #309's original dream.ts (sync
    before lint+backlinks lost the "fix files, then index them"
    semantic).
  - This commit is the bisectable foundation; commit 5 (dream)
    and commit 6 (autopilot+jobs) just call into it.

Coordination — the codex-flagged blocker:

Session-scoped pg_try_advisory_lock does not survive PgBouncer
transaction pooling (the v0.15.4 fix made pooled connections the
default). Replaced with a DB lock table (gbrain_cycle_locks) that
works through every pooler:

  - Acquire: INSERT ... ON CONFLICT DO UPDATE ... WHERE ttl < NOW()
  - Refresh: UPDATE ttl_expires_at between phases via hook
  - Release: DELETE in finally{}
  - TTL: 30 min; crashed holders auto-release

PGLite / engine=null path uses a file lock at ~/.gbrain/cycle.lock
with PID liveness check. kill(pid, 0) with EPERM treated as alive
(so init/launchd-pid holders aren't mis-classified as stale).

Lock-skip: only phases that mutate state (lint, backlinks, sync,
extract, embed) trigger lock acquisition. orphans is read-only.
Single-phase --phase orphans runs never block on a held lock.

Engine-null mode preserved: filesystem phases run, DB phases skip
with {status:'skipped', reason:'no_database'}. Matches current
dream's capability that would have been lost if runCycle required
a connected engine.

Contract details:

  - CycleReport has schema_version:"1" (stable, additive) so agents
    consuming --json can rely on the shape
  - status: 'ok' | 'clean' | 'partial' | 'skipped' | 'failed'.
    'clean' = ran successfully with zero activity; agents trivially
    detect a healthy brain.
  - PhaseResult.error: { class, code, message, hint?, docs_url? }
    (Stripe-API-tier structured failure info) when status='fail'
  - yieldBetweenPhases hook: awaited between EVERY phase and before
    return, runs even after phase failure, exceptions logged but
    non-fatal. Required so the Minions autopilot-cycle handler can
    renew its job lock between phases (prevents the v0.14 stall-death
    regression codex flagged).
  - git pull explicit: opts.pull defaults to false (cron-safe).
    Autopilot daemon callers opt in if user configured it.
  - extract phase doesn't have a dry-run mode in the underlying
    library function, so runCycle honestly skips extract when
    dryRun=true (status:'skipped', reason:'no_dry_run_support').

Schema migration v16: gbrain_cycle_locks table + idx_cycle_locks_ttl.
Also appended to src/schema.sql and src/core/pglite-schema.ts for
fresh installs. schema-embedded.ts regenerated via build:schema.

Tests (test/core/cycle.test.ts, PGLite in-memory + mocked library
functions, no DATABASE_URL required):

  - dryRun × phases matrix: dryRun:true reaches lint/backlinks/sync/
    embed; extract is honestly skipped
  - Phase selection: default runs all 6 in order; --phase lint runs
    only lint; --phase orphans runs only orphans
  - Lock semantics: acquire + release on mutating phases, skip
    entirely for read-only selections
  - cycle_already_running: seeded live-holder lock → status:skipped,
    zero phase runs; TTL-expired holder → auto-claimed
  - Engine null: filesystem phases run, DB phases skip
  - File lock (engine=null) blocks when PID 1 holds lock with fresh
    mtime — exercises the PID liveness branch including EPERM
  - Status derivation: 'ok' vs 'clean' vs 'partial' vs 'skipped'
  - yieldBetweenPhases called N times, hook exceptions non-fatal

Next: commit 5 rewrites dream.ts as a thin CLI alias over runCycle,
commit 6 migrates autopilot daemon + jobs.ts handler to delegate to
runCycle too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`gbrain dream` is the README brand-promise command: "the agent runs
while I sleep, the dream cycle ... I wake up and the brain is smarter."
Cron-friendly, JSON-reportable, phase-selectable. Same maintenance
cycle as `gbrain autopilot`, just scheduled differently — both
converge on runCycle (added in commit 4) so there's one source of
truth for what happens overnight.

Contract:
  gbrain dream                       # full 6-phase cycle
  gbrain dream --dry-run             # preview, no writes
  gbrain dream --json                # CycleReport JSON (agent-readable)
  gbrain dream --phase <name>        # single-phase run
  gbrain dream --pull                # git pull before syncing
  gbrain dream --dir /path/to/brain  # explicit brain location

Cron: 0 2 * * * gbrain dream --json >> /var/log/gbrain-dream.log

Behavior details:
  - Brain-dir resolution: requires explicit --dir OR sync.repo_path
    in engine config. No more walk-up-cwd-for-.git footgun that
    PR #309's original dream.ts had (would lint unrelated git repos).
  - engine=null mode preserved via cli.ts's try/catch around
    connectEngine — filesystem phases (lint, backlinks) still run
    without a DB, DB phases report skipped/no_database in the output.
  - status=clean prints "Brain is healthy. N phase(s) checked in Ns."
    status=skipped prints the reason (cycle_already_running, etc.).
    Partial/failed prints the phase-by-phase detail.
  - Exit code 1 when status=failed (cron spots real problems).
    'partial' is not a failure — warnings shouldn't page you.
  - --help text cross-references `autopilot --install` for users
    who want continuous maintenance as a daemon.

CLI registration (src/cli.ts):
  - 'dream' added to CLI_ONLY
  - handleCliOnly has a pre-engine branch mirroring doctor's pattern:
    try connectEngine() → ok path; catch → runDream(null, args) so
    filesystem phases still run when DB is down
  - Help text updated with one-line dream entry and autopilot cross-ref

Tests (test/dream.test.ts, real PGLite + real library calls, no mocks
to avoid `mock.module` leakage across test files):
  - brainDir resolution: explicit --dir wins, engine config fallback,
    missing + nonexistent errors
  - phase selection: --phase lint|orphans produces single-phase report
  - phase validation: --phase garbage exits 1
  - output: --json parses as CycleReport with schema_version:"1"
  - human output mentions "Brain is healthy" on clean status
  - dry-run: cycle runs but DB stays untouched
  - exit code: clean/ok/partial do not call process.exit

Also (test/core/cycle.test.ts): refactored to use beforeAll/afterAll
with one shared PGLite engine per describe + truncateCycleLocks
between tests. Cuts test time from ~11s to ~4s; avoids the 15-migration
penalty per test that was causing parallel-suite timeout flakes.

Co-Authored-By: Wintermute <wintermute@garrytan.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cle)

Autopilot daemon (`--inline` path) and Minions `autopilot-cycle`
handler both now delegate to `runCycle` (introduced in commit 4).
Three callers, one cycle definition:

  1. `gbrain dream`                        — one-shot cron cycle
  2. `gbrain autopilot` daemon inline path — scheduled cycles
  3. `autopilot-cycle` Minions handler     — durable queue with retry

All three share:
  - Same 6 phases in same order (lint → backlinks → sync → extract →
    embed → orphans)
  - Same DB lock table coordination (`gbrain_cycle_locks`)
  - Same yieldBetweenPhases discipline (prevents v0.14 stall-death)
  - Same structured CycleReport output

Autopilot inline path gains lint + orphan sweep that the old path
skipped. Minions autopilot-cycle handler also gains lint + orphans.
Users who run `gbrain autopilot --install` see 6-phase reports in
`gbrain jobs get <id>` starting on next interval. No config change
required.

Changes:
  - `src/commands/autopilot.ts`: inline fallback path (~20 lines)
    replaces the ~22-line sync+extract+embed sequence with a single
    runCycle call. Uses pull:true (matches pre-v0.17 autopilot
    behavior). Uses setImmediate yield hook. Status/failure reporting
    derives from CycleReport.status. `--help` cross-references `gbrain
    dream` for one-shot use.
  - `src/commands/jobs.ts:579` (`autopilot-cycle` handler): replaces
    the 4-step try/catch sequence with a runCycle call. Returns
    `{ partial, status, report }` so `gbrain jobs get <id>` shows the
    full structured CycleReport. Preserves partial-failure semantic
    (one phase failing does NOT throw; next cycle still runs).
    yieldBetweenPhases yields the event loop between phases for the
    worker's lock-renewal timer.

Release scaffolding:
  - VERSION: 0.16.0 → 0.17.0
  - CHANGELOG.md: v0.17.0 entry in GStack voice — headline, numbers
    table, "what this means" paragraph, "To take advantage" block
    per CLAUDE.md post-ship rules. Itemized changes below the fold.
    Credit to @WinterMute for the original PR #309 thesis.
  - skills/migrations/v0.17.0.md: documents what changed for
    upgrading users. No mechanical action required — schema migration
    v16 (cycle locks table) + handler delegation both apply
    automatically. Includes opt-out paths for users who don't want
    their daemon modifying files (use `dream --phase orphans` in cron
    and skip autopilot-install, or other explicit configs).
  - CLAUDE.md: new entries for `src/core/cycle.ts` and
    `src/commands/dream.ts` with contract details.

Tests: no new test file needed for this commit — the cycle primitive
is extensively tested in test/core/cycle.test.ts (18 cases), dream
in test/dream.test.ts (11), and autopilot's delegation is mechanical
(calls runCycle with specific opts). The handler contract is covered
implicitly: if runCycle returns a CycleReport, the handler wraps it
in `{ partial, status, report }` — nothing else to assert.

Verified:
  - `bun test test/autopilot-install.test.ts test/autopilot-resolve-cli.test.ts test/core/cycle.test.ts test/dream.test.ts` → 37 pass, 0 fail

Completes the v0.17.0 feature: 6 bisectable commits on one branch
(garrytan/v0.17-dream-cycle), ready to push as one PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@garrytan garrytan force-pushed the garrytan/v0.17-dream-cycle branch from 807ef6a to 4b3de31 Compare April 22, 2026 06:34
…m-cycle

# Conflicts:
#	CHANGELOG.md
#	VERSION
…m-cycle

# Conflicts:
#	CHANGELOG.md
#	VERSION
#	src/commands/orphans.ts
garrytan added a commit that referenced this pull request Apr 22, 2026
Gap from the v0.17 commit series: PR #321 shipped unit-level tests
for runCycle (test/core/cycle.test.ts) and dream (test/dream.test.ts)
but no E2E coverage that exercises the real Postgres paths. Filling
that in before merge.

  test/e2e/cycle.test.ts (6 cases):
    - schema migration v16 created gbrain_cycle_locks + index
    - dry-run full cycle: zero DB writes + lock table empty after
    - live cycle: pages + chunks materialize, sync.last_commit set
    - concurrent cycle blocked by lock → status:'skipped'
    - TTL-expired lock auto-claimed (crashed-holder recovery)
    - --phase orphans skips lock entirely (read-only optimization)

  test/e2e/dream.test.ts (3 cases):
    - dream --dry-run --json emits valid CycleReport + DB stays empty
    - dream (no --dry-run) syncs pages into real DB
    - dream --phase orphans doesn't touch the cycle-lock table

Both files mock embedBatch via mock.module so the embed phase never
calls OpenAI even when the full 6-phase cycle runs (zero API cost,
zero flakiness from network calls).

Verified locally:
  - `docker run pgvector/pgvector:pg16` on port 5434
  - `DATABASE_URL=... bun test test/e2e/cycle.test.ts test/e2e/dream.test.ts` → 9 pass, 0 fail
  - Full E2E suite (`bun run test:e2e`): 16 files, 150 tests, 0 fail
  - Container torn down after: `docker stop + rm gbrain-test-pg`

Per CLAUDE.md E2E test DB lifecycle. These tests skip gracefully when
DATABASE_URL isn't set (via hasDatabase() helper + describe.skip).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gap from the v0.17 commit series: PR #321 shipped unit-level tests
for runCycle (test/core/cycle.test.ts) and dream (test/dream.test.ts)
but no E2E coverage that exercises the real Postgres paths. Filling
that in before merge.

  test/e2e/cycle.test.ts (6 cases):
    - schema migration v16 created gbrain_cycle_locks + index
    - dry-run full cycle: zero DB writes + lock table empty after
    - live cycle: pages + chunks materialize, sync.last_commit set
    - concurrent cycle blocked by lock → status:'skipped'
    - TTL-expired lock auto-claimed (crashed-holder recovery)
    - --phase orphans skips lock entirely (read-only optimization)

  test/e2e/dream.test.ts (3 cases):
    - dream --dry-run --json emits valid CycleReport + DB stays empty
    - dream (no --dry-run) syncs pages into real DB
    - dream --phase orphans doesn't touch the cycle-lock table

Both files mock embedBatch via mock.module so the embed phase never
calls OpenAI even when the full 6-phase cycle runs (zero API cost,
zero flakiness from network calls).

Verified locally:
  - `docker run pgvector/pgvector:pg16` on port 5434
  - `DATABASE_URL=... bun test test/e2e/cycle.test.ts test/e2e/dream.test.ts` → 9 pass, 0 fail
  - Full E2E suite (`bun run test:e2e`): 16 files, 150 tests, 0 fail
  - Container torn down after: `docker stop + rm gbrain-test-pg`

Per CLAUDE.md E2E test DB lifecycle. These tests skip gracefully when
DATABASE_URL isn't set (via hasDatabase() helper + describe.skip).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@garrytan garrytan force-pushed the garrytan/v0.17-dream-cycle branch from 14d4a97 to 173d9f3 Compare April 22, 2026 09:05
…m-cycle

# Conflicts:
#	CHANGELOG.md
#	VERSION
#	src/cli.ts
@garrytan garrytan merged commit 55ca498 into master Apr 22, 2026
4 checks passed
garrytan added a commit that referenced this pull request Apr 22, 2026
…as v0.17)

Master shipped its own v0.17.0 ('gbrain dream' + runCycle primitive, PR #321)
while this branch was in flight. Rebasing this branch to v0.18.0 and
renumbering our schema migrations to avoid collision with master's v16
(cycle_locks_table).

Version collisions resolved:
- VERSION: 0.17.0 → 0.18.0
- package.json version: synced to 0.18.0
- Schema migrations renumbered: my v16→v20 (sources_table_additive),
  v17→v21 (pages_source_id_composite_unique), v18→v22 (links_resolution_type),
  v19→v23 (files_source_id_page_id_ledger). Master's v15/v16 stay put.
- Orchestrator: src/commands/migrations/v0_17_0*.ts → v0_18_0*.ts +
  registry entry renamed in index.ts
- Migration skill: skills/migrations/v0.17.0.md now contains master's
  gbrain-dream content; my multi-source migration skill is now at
  skills/migrations/v0.18.0.md

Code conflicts resolved:
- src/core/postgres-engine.ts putPage: kept my ON CONFLICT (source_id, slug)
  AND master's sql.json type cast — both are required
- src/cli.ts CLI_ONLY: merged both additions ('sources' + 'dream' +
  'check-resolvable')
- skills/migrations/v0.17.0.md add/add conflict resolved by renaming mine

Documentation:
- Updated v0.17.0 references in all source comments + tests + docs
  to v0.18.0 (~30 files)
- Updated test/apply-migrations.test.ts skippedFuture lists to include
  0.18.0

Test results: 2063 pass / 17 fail. The 17 fails are all pre-existing
PGLite beforeEach timeouts in runDream + findOrphans tests added by
master (same count on pure master baseline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
garrytan added a commit that referenced this pull request Apr 23, 2026
Master shipped two releases while the skillify workstream was in review:
  - v0.17.0: gbrain dream + runCycle primitive (#321)
  - v0.18.0: multi-source brains (#337)

My skillify work (originally labeled v0.17.0) moves to v0.19.0 to avoid
the collision. It's the natural continuation of PR #326 (v0.16.4's
skills-dir fallback) and the essay's 10-step skillify loop.

## Conflicts resolved

**src/cli.ts** — union of CLI_ONLY sets. Master added `sources`, `dream`;
my branch added `skillpack`, `routing-eval`, `skillify`. All six remain.

**CHANGELOG.md** — three nested conflict hunks caused by the shared
`## [0.17.0]` + `## To take advantage of v0.17.0` headings. Resolved
by renaming my entry to v0.19.0, reordering so the file reads
v0.19.0 → v0.18.0 → v0.17.0 → v0.16.4, and keeping master's gbrain
dream entry intact under v0.17.0.

**skills/brain-ops/SKILL.md** — both branches added frontmatter fields.
Master's v0.18.0 added a "Cross-source citation format" section;
my W3 added `writes_pages: true` + `writes_to:` frontmatter. Git
auto-merged cleanly; verified both sets survive.

## Version bumps

VERSION: 0.17.0 → 0.19.0
package.json: 0.17.0 → 0.19.0
openclaw.plugin.json: 0.17.0 → 0.19.0

## Other mechanical updates in the merge

- Privacy scan stays clean after merge (scripts/check-privacy.sh exit 0).
  Scrubbed one `@Wintermute` reference that master added in the v0.17.0
  gbrain dream credit line → "@knee5" placeholder (keeps the credit,
  drops the private fork name per CLAUDE.md:550).

## Test status

**Isolated runs — green across v0.19 surface:**
- 207/207 pass across 12 v0.19-specific test files
- Privacy guard exit 0

**Full suite — 17 fail / 3 errors, all pre-existing on master.**
Verified by running origin/master (90c5d93) in a clean worktree:
identical 17 fail / 3 errors profile. My merge did not introduce
any new failures. The failing tests (dream.test.ts +
brain-allowlist.test.ts engine.disconnect timeouts; orphans.test.ts
concurrency) pass in isolation (46/46 for dream+orphans) but
flake under the full concurrent suite's shared-resource contention.
That's master's pre-existing condition; filing a separate issue
is out of scope for this merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
garrytan added a commit that referenced this pull request Apr 23, 2026
Pulls upstream v0.16.1–v0.18.1: minions worker deploy guide (#287/#317),
subagent Anthropic SDK fix + tsc CI gate (#318), check-resolvable CLI
(#325), dream + runCycle primitive (#321), multi-source brains with
federation + dotfile resolution (#337), RLS hardening + schema backfill
(#343). Test count grows 2000 → 2354.

Conflicts resolved:
- VERSION — kept 0.19.0; upstream is 0.18.1
- package.json — v0.19.0 wins
- CHANGELOG.md — v0.19.0 preserved above upstream's v0.18.1/v0.18.0/v0.17.0/v0.16.x
- src/cli.ts — CLI_ONLY merges `agent`, `providers`, and upstream's new `sources`, `dream`, `check-resolvable`
- src/core/config.ts — merged: kept embedding_model / embedding_dimensions /
  expansion_model / provider_base_urls (mine) + storage (upstream)

Build clean: 948 modules, ~165ms compile, 0.19.0 binary runs. Typecheck green.
18 flaky failures in `bun test` are all PGLite shared-state timeouts in
setup hooks — every failing file passes cleanly in isolation (dream 11/0,
orphans 35/0, check-update 20/0). Pre-existing infra, not introduced by
this merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant