mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-29 08:13:12 +00:00
1666ec427e1ca8daaa9005f589a5235ed89bdcea
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f1f9199ff4 |
v0.37.7.0 fix wave: federated brains + autopilot safety + OAuth confidential clients (#1253)
* fix(reindex-frontmatter): connect engine before query (#1225) `createEngine()` from src/core/engine-factory.ts only constructs the engine; callers MUST call connect() before any executeRaw. The reindex-frontmatter CLI was constructing the engine and going straight to countAffected, which crashed on PGLite with "PGLite not connected. Call connect() first." even on --dry-run. Fix follows the existing-command pattern (src/commands/auth.ts, src/commands/backfill.ts, src/commands/integrity.ts all do the same): pass toEngineConfig(cfg) into both createEngine() AND engine.connect(), then engine.initSchema() (idempotent on a current schema, ~1ms cost). Pre-fix verification: codex outside-voice CF5 flagged the related "can't import connectEngine from cli.ts" misdirection in the original fix plan. This implementation uses the canonical sibling pattern instead. Regression test pinned at test/reindex-frontmatter-connect.test.ts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump VERSION to 0.37.7.0 + stub CHANGELOG v0.37.5.0 claimed by #1229 (warsaw-v4); v0.37.6.0 by #1246 (OpenRouter recipe). v0.37.7.0 is the next free slot for this fix wave. CHANGELOG entry stubbed in user-facing voice per CLAUDE.md "CHANGELOG voice + release-summary format" — ELI10 lead-first, real fix details below. The "## To take advantage of v0.37.7.0" block follows the v0.13+ self-repair pattern from CLAUDE.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(subagent): short-circuit terminal-on-resume (#1151) Bug: when the worker resumed a subagent job whose persisted last message was an assistant turn with text-only content (no tool_use blocks), the replay reconciler at subagent.ts:241-247 had no branch for that case. The main loop then called messages.create against a conversation ending in assistant role, which Sonnet 4.6+ rejects with HTTP 400 "This model does not support assistant message prefill." 3 retries later → dead-letter, despite all the job's work having committed in earlier turns. @zscgeek's bug report pinned this exactly: dream-cycle Otter corpus runs hit ~7% dead-letter rate, every dead job's last subagent_messages row was a text-only synthesis summary listing slugs that already existed in `pages`. Their proposed fix mirrors this implementation. Fix: add an else branch to the assistant-tail check that mirrors the live-loop terminal logic at subagent.ts:440-447 — reconstruct finalText from the persisted text blocks, return stop_reason='end_turn' immediately. No LLM call, no schema change. Two new regression cases: - text-only terminal on resume returns immediately with zero messages.create calls - tool-use replay path unchanged (existing behavior preserved) Codex outside-voice (CF13) initially flagged this fix as mis-targeted, claiming subagent.ts already handled the case. /investigate run revealed the live-loop terminal at :440-447 was covered but the REPLAY-path terminal at :241-247 was missing — both branches need symmetric handling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(autopilot): scope lockfile to GBRAIN_HOME (#1226) The autopilot lockfile was hardcoded at `~/.gbrain/autopilot.lock` (via `process.env.HOME`), bypassing GBRAIN_HOME. Two brains pointed at different GBRAIN_HOME directories still wrote to the same global lockfile; one would silently take over the other on each restart. Fix: route through `gbrainPath('autopilot.lock')` from src/core/config.ts (imported aliased as gbrainHomePath since the local `gbrainPath` var in installAutopilot references the CLI binary path). The mkdirSync(`~/.gbrain`) call also routes through the helper so the directory is created in the right place too. Co-authored with @rafaelreis-r — same fix shape as PR #1227, re-implemented against current master per the wave's "re-implement, credit, close" workflow. Tests cover: one GBRAIN_HOME → one canonical lock; two GBRAIN_HOME values → two distinct locks; default fall-through still works. Co-Authored-By: rafaelreis-r <noreply@github.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(graph-query): foreign-edge footer + --include-foreign (#1153) The graph-query CLI silently dropped edges to pages in other sources on federated brains. Users had no signal those edges existed unless they read the source code. Fix: - New --include-foreign flag (off by default, preserves the existing scoping contract; on = explicit cross-source traversal). - After every traversal, count edges from rootSlug whose target page lives in a different source. When count > 0 AND user didn't opt in, emit a stderr footer: `(N edge(s) to foreign-source pages hidden; pass --include-foreign to include them)` - The "no edges found" path also runs the count + footer so users discover foreign edges even when scoped traversal returned nothing. - Thin-client path skips the count (engine query not available); future T1 work threads source resolution through MCP for that path. - Single quotation correctness in count SQL: page_links table is `links` (not `page_links`); JOIN both endpoints to pages and compare source_id, NULL-safe via `IS NOT NULL` guards on both sides. - Fail-open on missing source_id column for pre-v0.18 brains: return 0 (no foreign edges to report) instead of throwing. 4 new test cases: footer fires on scoped query with foreign edge, --include-foreign suppresses footer, zero-foreign no-footer case, pluralization regression guard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(sources): `gbrain sources current` + tier attribution (#1222) Federated-brain users running destructive ops (extract, import, purge) need a way to verify which source they're targeting BEFORE the op runs. Pre-fix, the only way was to grep config files or run the op with --dry-run and inspect output. New command: gbrain sources current # human output gbrain sources current --json # machine-readable gbrain sources current --source X # show what an explicit --source # X would resolve to (validates # X exists in the sources table) Output names BOTH the resolved source id AND which tier of the 6-tier resolution chain won (flag / env / dotfile / local_path / brain_default / seed_default), plus a `detail` line naming the winning signal (e.g. "GBRAIN_SOURCE=dept-x" or ".gbrain-source" or "/work/gstack/src"). Implementation: - New `resolveSourceWithTier()` in source-resolver.ts as an additive variant of `resolveSourceId()`. Walks the same 6 steps in the same order; just returns `{ source_id, tier, detail? }` instead of bare string. Existing `resolveSourceId()` unchanged — all callers continue working. - New `SOURCE_TIER_NAMES` const + `SourceTier` type export so the CLI, doctor (Tier 5 follow-up), and future MCP consumers share one vocabulary instead of inlining strings. - Help text updated; `current` subcommand registered in dispatcher. 11 new tests pin the 6-tier ladder + priority semantics. Existing 19 source-resolver tests still pass (regression preserved). Per codex CF3 (the existing src/core/source-resolver.ts was missed in the original plan). Re-uses the existing helper instead of inventing a duplicate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(extract): --source-id scopes extraction to one brain source (#1204) Federated brain users running `gbrain extract` had no way to scope extraction to one source. The DB path walks all sources together via listAllPageRefs(), which is correct for cross-source resolution but sometimes the user wants to extract per-source explicitly (e.g. re-running extract on a specific source after a manual import). The pre-existing `--source` flag is the data-source axis (fs|db) and can't be repurposed. New flag `--source-id <id>` joins it on the brain-source-id axis: gbrain extract all --source db --source-id alpha -> walks only alpha-source pages; extracts links + timeline from those, into the alpha source Important: the resolver maps (allSlugs + slugToSources) stay built from the FULL listAllPageRefs result, not the scoped subset. This ensures qualified cross-source wikilinks like `[[other-src:slug]]` still resolve correctly even when the extract walk is scoped — the filter is on which pages we extract FROM, not what we can resolve TO. Threaded through both `extractLinksFromDB` and `extractTimelineFromDB` with backward-compat: callers passing no opts get the old behavior. 4 new test cases pin: walks-all-without-flag baseline, alpha-only-when-scoped-to-alpha, beta-only-when-scoped-to-beta, empty-set-on-unknown-source. Note: #1204's wider "silent 0 links" report on federated brains has additional facets beyond this flag (resolver path edge cases on overlapping slugs). The scoped-walk fix gives users an explicit workaround AND closes the per-source extraction gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(todos): file v0.37.7.0 follow-ups (#1173, #1204, T5N) Three items deferred from v0.37.7.0: 1. #1173 .sql indexing — verify-first gate found tree-sitter-sql.wasm missing from src/assets/wasm/grammars/. Dedicated wave needed: vendor the wasm, add .sql to walker filter, address slug-shape collision with #1172. 2. #1204 deeper investigation — wave added --source-id flag as workaround. Underlying silent-zero-links bug on unscoped federated extracts needs its own /investigate pass against a cross-source-duplicate-slug fixture. 3. Tier 5N doctor sweep for dead-lettered subagent jobs matching the #1151 fingerprint. Deferred to v0.37.8+ behind the islamabad doctor.ts conflict resolution. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sync): walker skips git submodule directories (#1169) Sync walker descended into git submodules and indexed their markdown content as if it belonged to the parent brain. Users with submodules in their brain repo saw foreign content in their pages table. Fix: pruneDir gains an optional `parentDir` arg. When set, the helper stats `<parentDir>/<name>/.git` and skips the directory if `.git` exists as a FILE (gitfile pointer — the canonical submodule shape). Directories containing `.git` as a DIRECTORY (a real nested repo, not a submodule) are descended into; the inner `.git` dir itself is then dot-prefix-excluded. Callers updated to pass parentDir: - src/commands/extract.ts walkMarkdownFiles - src/core/cycle/transcript-discovery.ts walker Back-compat preserved: existing pruneDir(name) callers without parentDir get the pre-v0.37.7.0 behavior unchanged. Companion `.gitignore`-respect feature from PR #1159 (@jetsetterfl) NOT in this wave — it would require adding the `ignore` npm package as a dep, which the plan's "no new deps in this PR" gate excludes. Filed as follow-up TODO for a dedicated wave. 5 new test cases pin the submodule shape + back-compat + nested-repo ambiguity. Existing extract-fs / extract-db tests unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(brain-routing): document 6-tier source resolution chain (#1222) The convention skill didn't have a tier-by-tier reference for how gbrain resolves the active source. Users running federated brains had to read the source code to know which signal wins. Added: - Canonical 6-tier table (flag → env → dotfile → local_path → brain_default → seed_default) matching src/core/source-resolver.ts. - Pointer to `gbrain sources current` (new in v0.37.7.0) as the verification command. - The CLI-layer trust boundary note: operations.ts handlers don't read env/dotfile (preserves v0.34.1.0 source-isolation work for MCP callers). - Per-command flag map: --source, --source-id (extract), and --include-foreign (graph-query). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(import): --source-id flag routes pages to a brain source (#1167) `gbrain import --source dept-x ./pages` silently fell back to the default source because the CLI parser never consumed --source. PR #707's design intent excluded the flag explicitly; users had no signal their pages were going to the wrong place. #1167 + #1222 filed the regression. Fix: parse `--source-id <id>` (matching v0.37.7.0 extract.ts T2's naming convention — --source-id stays out of conflict with future axes that may want --source). When set, the flag value wins over any programmatic opts.sourceId; back-compat preserved for callers that pass sourceId via opts only. Also threaded into the positional-dir arg parser's flagValues set so `--source-id <value> <dir>` doesn't treat <value> as the dir. Note on related surfaces: - `gbrain query "X" --source_id dept-x` already routed correctly via the operations.ts query op (added in v0.34) — no fix needed. - `gbrain extract --source-id <id>` shipped in T2. - `gbrain sync --source <id>` already worked (pre-existing). - `gbrain sources current` (shipped in T4) is the verification tool — run it before destructive ops to confirm routing. Closes the silent-fallback for the import path. Co-authored with @tyad67-netizen (#1168), @hnshah (#1124, #1120), whose patches informed the shape; re-implemented against current master per the wave's "re-implement, credit, close" workflow. 3 new test cases pin: default-without-flag, --source-id-routes-correctly, flag-value-not-treated-as-dirArg. Co-Authored-By: tyad67-netizen <noreply@github.com> Co-Authored-By: hnshah <noreply@github.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(autopilot): reconnect classifier + launchd ThrottleInterval (#1162) Pre-fix: when database_url was unset/malformed, the DB-health-check reconnect loop logged `config.database_url undefined` forever because the catch swallowed every error type uniformly. launchd's KeepAlive=true respawned immediately on any exit, so even when the process did exit, it came right back into the same bad state. @colin477 reported the daemon-thrash pattern. Two-part fix: 1. In-process error classifier — `classifyReconnectError(err)`: - `unrecoverable` (database_url missing/empty/malformed, auth failure, no-brain-configured): exit immediately with a clear stderr line. Pattern-matched against postgres / config-loader error shapes. Tests pin the matcher against the #1162 fingerprint exactly. - `recoverable` (network blip, pool saturated, connection refused on a port coming up, Supabase 503): retry. Up to GBRAIN_AUTOPILOT_MAX_RECONNECT_FAILS (default 30 = ~5min) before finally giving up with `max_reconnect_fails_exceeded`. - Counter resets on every successful health probe or reconnect. 2. launchd plist gains `ThrottleInterval=60`. Combined with the in-process exit, launchd waits 60s before relaunching instead of immediate respawn. Pure-function `generateLaunchdPlist()` exported for tests. 16 new test cases: - 11 classifier cases (database_url shapes, malformed URL, auth, role-does-not-exist with quoted name, network blip, pool saturated, 503, non-Error inputs, case-insensitivity) - 5 plist generator cases (ThrottleInterval=60, KeepAlive preserved, wrapper path, XML escaping, StandardErrorPath). Pre-existing autopilot-lock-path tests unchanged — both fixes land cleanly side-by-side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(oauth): confidential clients via custom /token middleware (#1166) v0.34.1.0 (#909) fixed PUBLIC PKCE clients (client_secret=undefined) by normalizing NULL → undefined in getClient. Confidential clients regressed: the MCP SDK's clientAuth middleware does plaintext `client.client_secret !== presented_secret` compare, but gbrain stores SHA-256 hashes, so the SDK's compare always failed for authorization_code and refresh_token grants on confidential clients. Result: /token returned `invalid_client` for every confidential exchange. Fix shape per locked-decision-5: custom /token middleware BEFORE the SDK's authRouter, similar to the pre-existing client_credentials handler. The middleware: 1. Detects confidential auth via `client_secret` in body (client_secret_post) OR `Authorization: Basic` header (client_secret_basic per RFC 6749 §2.3.1). 2. Falls through to the SDK when neither is present (public PKCE path stays canonical, preserves v0.34.1.0 behavior). 3. Calls new `verifyConfidentialClientSecret(clientId, presented)` on the provider which does SHA-256 hash compare ourselves (same shape as exchangeClientCredentials' existing hash check). 4. On verification success, calls existing `exchangeAuthorizationCode` / `exchangeRefreshToken` directly with the validated client. 5. RFC 6749 §5.2 error semantics: 401 invalid_client for auth failures, 400 invalid_grant for code/token problems. Per CLAUDE.md "GBRAIN:RLS_EXEMPT" annotation contract: this surface sits in front of the SDK's clientAuth and doesn't depend on the SDK's plaintext compare working — the SDK's middleware never fires for confidential paths the new middleware claims. 7 new test cases pin: correct-secret-returns-client, wrong-secret opaque rejection, non-existent client, public-client refuses the confidential path, case-sensitivity, soft-deleted revocation, verify-then-exchange-refresh round-trip with second-use rejection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(doctor): 3 new checks — source routing + oauth + autopilot lock (T12/T13/T14) Three v0.37.7.0 doctor checks landing in one atomic commit (single file, shared merge-conflict surface with garrytan/islamabad-v3 per locked decision 1): 1. source_routing_health (T12 / #1167): Sample non-default sources for pages; warn when a registered source has zero pages (silent-collapse-to-default fingerprint). D5 lock: total-sample cap of 200 pages across all sources, with per-source cap = min(50, ceil(200/N)) so a 20-source CEO brain pays 200 selects, not 1000. Fix hint paste-ready to `gbrain sources current --json` for verification. 2. oauth_confidential_client_health (T13 / #1166): Probe every oauth_clients row. Confidential clients (auth_method != 'none') must have a non-NULL client_secret_hash; if any row claims confidential auth but stores NULL hash, that's the pre-v0.37.7.0 regression. Public clients (auth_method='none') correctly keep NULL hash per v0.34.1.0 #909. Fix hint: `gbrain auth revoke-client + register-client` OR `gbrain upgrade`. Pre-OAuth schemas (missing oauth_clients table) skip gracefully. 3. autopilot_lock_scope (T14 / #1226): Detect stale ~/.gbrain/autopilot.lock outside the current GBRAIN_HOME. Codex CF11: dangerous to paste-ready `rm` without verifying the owning PID isn't a live process. Hint reads the PID file and gives the user a `ps -p <pid>` check before any delete — matches sshd-style stale-lock recovery hints. 9 new test cases pin the canonical paths. Pre-existing 80+ doctor checks unchanged. Expected to conflict with garrytan/islamabad-v3 at merge time. The 3 new check functions live in their own block far from the islamabad skill_brain_first check; the conflict surface should be limited to the `checks.push(...)` call site near the end of runDoctor's DB-checks phase (~10 lines). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(test): withEnv wrapper in source-resolver-with-tier (test-isolation lint) The new source-resolver-with-tier.test.ts from T4 mutated process.env.GBRAIN_SOURCE directly in two cases, which violates scripts/check-test-isolation.sh R1 (env mutations leak across parallel-loaded test files in the same shard process). Fix: wrap both mutation sites in withEnv() from test/helpers/with-env.ts, which saves+restores via try/finally per the canonical pattern in CLAUDE.md. Pure refactor — all 11 cases still green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: update project documentation for v0.37.7.0 CHANGELOG.md — populated the "What landed" stub with the 18-commit brisbane wave (source-id flag threading, sources current subcommand, graph-query foreign-edge footer, autopilot lockfile scope + reconnect classifier + launchd ThrottleInterval, OAuth confidential client middleware, reindex-frontmatter connect fix, subagent terminal-on-resume fix, sync walker submodule skip, 3 new doctor checks, brain-routing.md convention skill). Voice: ELI10 lead, capability table, paste-ready verification, "what's safe to know" + "what we caught" sections. CLAUDE.md — extended Key Files annotations for the v0.37.7.0 changes: import/extract --source-id flags, sources current subcommand, graph-query --include-foreign, resolveSourceWithTier() additive helper, autopilot classifyReconnectError + generateLaunchdPlist exports, OAuth confidential client middleware, pruneDir submodule detection, subagent terminal short-circuit, 3 new doctor checks. Pinned by their test files. llms-full.txt — regenerated via `bun run build:llms` (CI guard at test/build-llms.test.ts will fail otherwise). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: rafaelreis-r <noreply@github.com> |
||
|
|
96178d726e |
fix(subagent): v0.16.3 — bind Anthropic SDK correctly + enable tsc in CI (#318)
* fix(subagent): bind Anthropic SDK messages.create() correctly The makeSubagentHandler was casting `new Anthropic()` directly to MessagesClient, but MessagesClient.create() maps to sdk.messages.create(), not sdk.create(). Every subagent job immediately died with: client.create is not a function Fix: wrap the SDK instance so .create() delegates to .messages.create() with proper `this` binding via .bind(sdk.messages). Discovered on first production run of gbrain agent against Supabase. Co-Authored-By: Wintermute <wintermute@openclaw.ai> * chore(ci): add typescript typecheck to test pipeline + clean up baseline errors Root cause infra gap that let the v0.16.0 subagent bug ship: CI ran only `bun test`, which transpiles types without checking them. Type errors only surfaced at runtime, in production. Changes: - Add `typescript` devDep and a `typecheck` npm script (`tsc --noEmit`). - Chain `bun run typecheck` into `bun run test` so developers get the same pipeline locally that CI runs. - Flip `.github/workflows/test.yml` to invoke `bun run test` (the npm script, including typecheck) instead of `bun test` (runner only). - Clean up 100+ pre-existing type errors across 30+ files so the first run of `tsc --noEmit` is green. Root causes were: - `databaseUrl` → `database_url` rename drift in test fixtures (9 files) - `PageType` union missing `'meeting'` / `'note'` entries that are already used in both src and tests (link-extraction.ts comments acknowledged the gap) - `GBrainConfig.storage` field never declared despite being read in files.ts and operations.ts - `ErrorCode` union missing `'permission_denied'` - `OrchestratorOpts` shape changed; test callers not updated - Dead-code comparisons in migration orchestrators against narrowed status types - postgres.js `Row`-callback type drift on several `.map()` calls - Buffer-as-BodyInit assignment in supabase.ts (real but non-fatal runtime bug; Uint8Array slice works and is type-correct) - Various `as X` single-step casts that now need `as unknown as X` per TS's stricter structural-conversion rules - Bump `beforeAll` hook timeout to 30s on four PGLite-heavy tests that were flaky under parallel test execution: wait-for-completion, extract-fs, e2e/search-quality, e2e/graph-quality. All pass in isolation; timeouts only happened when dozens of PGLite instances init'd simultaneously. The new CI pipeline now fails on any type error across src/ or test/, giving us the compile-time regression guard the subagent fix depends on. * fix(subagent): bind Anthropic SDK messages.create() correctly Shipped bug: v0.16.0 cast `new Anthropic()` to `MessagesClient`, but `.create()` lives at `sdk.messages.create`, not on the top-level client. Every subagent job in production died on first LLM call with `client.create is not a function`. Discovered on the first `gbrain agent run` against Supabase. Fix: assign `sdk.messages` directly to the `MessagesClient` slot. `sdk.messages` IS the object with a callable `.create()`; the original bug was picking the wrong entry point on the SDK. No helper, no wrapper, no `.bind()` — JS method-call semantics preserve `this` at the call site because `subagent.ts:336` invokes `client.create(...)` with `client === sdk.messages`. The one-line assignment also typechecks cleanly against the existing `MessagesClient` interface (SDK's first `create` overload: `(MessageCreateParamsNonStreaming, Core.RequestOptions?) => APIPromise<Message>` is assignable structurally). This gives us compile-time regression protection: anyone reverting to `new Anthropic()` would fail tsc because `Anthropic` has no top-level `.create`. (The companion chore commit puts `tsc --noEmit` in CI so this guard is enforced.) Also adds a `makeAnthropic?: () => Anthropic` dep-injection seam so the factory default construction branch is testable without real API calls. Regression test drives one handler turn through a fake SDK, asserting `sdk.messages.create` is actually called. If someone later reverts to `new Anthropic()`, both guards fire: tsc fails AND the test fails. Co-Authored-By: Wintermute <wintermute@garrytan.com> * chore(tests): add bunfig.toml + 60s hook timeouts to stabilize PGLite-heavy suites After turning on tsc in CI (previous commit), running the full `bun run test` suite in one shot triggered flaky `beforeEach/afterEach hook timed out` failures on 8+ test files. Every failure traced to PGLite WASM init contention when many test files spin up fresh PGLite instances in parallel; each one alone passes in isolation. - `bunfig.toml` sets the global test hook timeout to 60s (default is 5s), covering every test file without per-file edits. - Individual `beforeAll(fn, 60_000)` / `beforeEach(fn, 15_000)` calls on the 8 tests that flaked most stay in place as explicit safety nets so a future bunfig config change doesn't silently re-introduce the flake. Result: 1997 pass, 0 fail on `bun run test` (117 tests added since the prior baseline by picking up typecheck-gated passes). No infrastructure flake tolerated in CI. * chore: bump version and changelog (v0.16.3) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Wintermute <wintermute@garrytan.com> Co-authored-by: Wintermute <wintermute@openclaw.ai> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
0e9f8814a5 |
feat: v0.16.0 — durable agent runtime (gbrain agent + subagent handler + plugin loader) (#258)
* refactor(mcp): extract buildToolDefs helper for subagent tool registry reuse
The inline operations.map(...) block in src/mcp/server.ts became the only
source of truth for agent-facing tool definitions. Extract into a reusable
exported helper so the v0.15 subagent tool registry can call it with a
filtered OPERATIONS subset instead of duplicating the shape.
Byte-for-byte equivalence regression pinned in test/mcp-tool-defs.test.ts —
legacy inline mapping kept verbatim inside the test so any future drift
between the new helper and the pre-extraction MCP schema fails loudly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(operations): subagent-aware OperationContext + put_page namespace
Adds three optional fields to OperationContext:
- jobId?: number — the currently running Minion job id
- subagentId?: number — the owning subagent job id for tool-dispatched calls
- viaSubagent?: boolean — FAIL-CLOSED flag for agent-path gating
put_page now enforces a namespace rule when invoked on the subagent tool
dispatch path (viaSubagent=true): writes MUST target
`wiki/agents/<subagentId>/...`. Anchored, slash-boundary enforced so a
collision like `wiki/agents/12evil/...` can't impersonate subagent 12.
The check runs BEFORE the dry-run short-circuit so preview calls surface
the same rejection. Fail-closed: a missing subagentId with viaSubagent=true
rejects every slug rather than letting a dispatcher bug open a hole.
Existing callers unaffected — all three fields are optional and the legacy
put_page behavior is unchanged when viaSubagent is undefined/false.
12 regression + namespace tests pin:
- local CLI writes (viaSubagent unset) accept arbitrary slugs
- MCP writes (remote=true, viaSubagent unset) accept arbitrary slugs
- subagent-path: anchored prefix accepted, wrong id rejected, prefix-
collision defeated, leading-slash rejected, bare-prefix rejected,
fail-closed on missing/NaN subagentId, permission_denied code emitted
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(schema): v0.15.0 subagent runtime tables + migration orchestrator
Adds three new tables for the durable LLM agent runtime:
subagent_messages — Anthropic message-block persistence.
Parallel tool_use blocks in one assistant
message live in content_blocks JSONB, not
across rows (fixes the (job_id, turn_idx, role)
misdesign codex caught in v0.13 drafting).
subagent_tool_executions — Two-phase tool ledger. INSERT pending before
execute, UPDATE complete/failed after. Replay
re-runs pending rows only if the tool is
idempotent (v1 ships only idempotent tools so
this is preventive).
subagent_rate_leases — Lease-based concurrency cap for outbound
providers (e.g. anthropic:messages). Stale
leases auto-prune on next acquire so crashed
workers can't strand capacity.
All DDL uses CREATE TABLE/INDEX IF NOT EXISTS — order-independent vs
PR #244's initSchema() reorder, and idempotent across fresh-install +
upgrade paths. Shipped in both src/schema.sql (Postgres) and
src/core/pglite-schema.ts (PGLite); schema-embedded.ts regenerated.
Migration orchestrator v0_15_0.ts (phases: schema → verify → record).
v0_14_0.ts is a no-op stub so the registry's version sequence stays
gapless (v0.14.0 shipped shell-jobs — code change, no DB migration).
10 unit tests for registry wiring, ordering, dry-run phase behavior, and
schema-embedded table presence. test/apply-migrations.test.ts updated for
the two new registry entries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): emit child_done on every terminal + max_stalled per-job + terminal set fix
Three correctness fixes the v0.15 subagent aggregator spine depends on:
1. child_done emission on ALL terminal transitions, not just success.
- completeJob already emitted on success — now also tags outcome='complete'.
- failJob newly emits on terminal 'failed' or 'dead' (outcome='failed'|'dead',
error=<text>), BEFORE the parent-terminal UPDATE so the EXISTS guard on
the inbox INSERT doesn't skip it on fail_parent paths (codex catch).
- cancelJob now emits outcome='cancelled' per descendant with a parent.
- handleTimeouts now emits outcome='timeout' per timed-out child.
ChildDoneMessage gains optional { outcome, error } — backwards compatible
(legacy writers omitted them; consumers treat absent outcome as 'complete').
2. Parent-resolution terminal set now includes 'failed'.
Pre-v0.15 the `NOT EXISTS (... status NOT IN ('completed','dead','cancelled'))`
guard treated a failed child as still-pending, stranding aggregator parents
that chose on_child_fail='continue' or 'ignore' in waiting-children forever.
Expanded to {completed, failed, dead, cancelled} everywhere parent resolution
reads child status (completeJob inline, failJob remove_dep + continue,
cancelJob sweep, handleTimeouts sweep, and the resolveParent method itself).
3. MinionJobInput.max_stalled threads through MinionQueue.add() on INSERT.
Column exists with default 1 — that is "first stall → dead", which defeats
crash recovery for long-running handlers. Subagent children will set
max_stalled: 3 to survive mid-run worker kills. Second-submitter under an
idempotency-key hit does NOT mutate the existing row (codex-flagged
footgun — first-submit options are load-bearing state).
13 unit tests pin: emission on each of completeJob/failJob/cancelJob/
handleTimeouts, insertion order on fail_parent, terminal-set expansion with
continue policy, max_stalled default + override + idempotency behavior.
E2E tier 1 (Postgres) passes 141 tests unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): rate-leases + waitForCompletion infra for v0.15 subagent
Two infrastructure modules the subagent handler spine depends on:
rate-leases.ts — lease-based concurrency cap for outbound providers
(anthropic:messages, openai:*, etc.). Counter-based limiters leak capacity
on worker crash; leases are owner-tagged rows with expires_at that
auto-prune on the next acquire. Two-phase: txn-scoped pg_advisory_xact_lock
guards the check-then-insert so concurrent acquires can't both win the
"last slot". renewLeaseWithBackoff retries 3x (250/500/1000ms) for mid-
call DB blips — on persistent failure the LLM-loop caller aborts with a
renewable error so the worker re-claims and the rate invariant is
preserved. Owner FK cascades clean up leases on job deletion.
wait-for-completion.ts — poll-until-terminal helper for CLI callers.
Minions' NOTIFY is worker-side only; `gbrain agent run --follow` polls
getJob() until status is {completed, failed, dead, cancelled}. TimeoutError
carries jobId + elapsedMs and does NOT cancel the job — the user can
inspect via `gbrain jobs get <id>` later. Supports AbortSignal for Ctrl-C
without throwing. Default pollMs is 1000 on Postgres, 250 on PGLite (inline
CLI has no network RTT).
21 unit tests cover: single/multi acquire under cap, rejection past cap,
release frees slot, different keys are independent, stale prune, cascade
on owner delete, renew bumps expires_at, renew on missing is false,
backoff path success + pruned short-circuit. waitForCompletion: fast-path
terminal, transitions mid-wait (completed/failed/cancelled), TimeoutError
shape, abort-signal early exit, non-existent job error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): subagent ToolDef types + brain-tool registry (v0.15)
Types first so the handler has a stable contract:
- SubagentHandlerData / AggregatorHandlerData — the two job.data shapes
- ToolCtx (engine, jobId, remote, signal) + ToolDef (name, description,
input_schema, idempotent, execute) — Anthropic-envelope, distinct from
the MCP McpToolDef extraction landed earlier
- ContentBlock discriminated union for subagent_messages.content_blocks
- SubagentStopReason + SubagentResult emitted on terminal completion
brain-allowlist.ts derives one ToolDef per allow-listed OPERATION. Reuses
the ParamDef → JSONSchema shape from the MCP extraction in a local helper
(Anthropic's input_schema field diverges from MCP's inputSchema by a
character). The 11-name allow-list is read-safe + put_page — every
destructive / filesystem / identity-mutating op stays off by default.
put_page gets a namespace-wrapped tool schema: `slug` pattern = anchored
`^wiki/agents/<subagentId>/.+`. The server-side check in put_page op
(shipped in prior commit) is still the authoritative gate — the schema
just helps the model write correct slugs first-try. `subagentId` is
plumbed into the ToolCtx so the viaSubagent=true fail-closed path lights
up on every tool-dispatched put_page.
filterAllowedTools narrows a registry by subagent_def's allowed_tools
frontmatter field. Rejects unknown names at load time (no silent drop —
typos in a skills/subagents/*.md would otherwise ship to prod with a
tool silently missing).
18 tests pin: every allowlist name exists in OPERATIONS (catches upstream
rename), Anthropic name regex, put_page namespace pattern per-subagent,
execute() routes through the op handler with viaSubagent=true, out-of-
namespace put_page throws permission_denied, filter passes prefixed +
unprefixed names, rejects unknowns, deduplicates.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): subagent-audit JSONL + transcript renderer
Two small plumbing pieces the v0.15 subagent handler + `gbrain agent logs`
depend on:
subagent-audit.ts — JSONL-rotated audit log mirroring the shell-audit
pattern. Two event flavors: submission (one line per job submit) and
heartbeat (one line per turn boundary — llm_call_started / completed /
tool_called / tool_result / tool_failed). Heartbeats fix the "--follow on
a long Anthropic call shows nothing for 30 seconds" problem codex flagged.
Never logs prompts or tool inputs (PII risk — subagent input_vars may
carry user-supplied free text); DOES log tokens, ms_elapsed, tool_name,
first 200 chars of error text. Rotates weekly via ISO week. `readSubagent
AuditForJob` is the readback path for `gbrain agent logs` — scans the
current + prior week file so job boundaries across weeks still resolve.
`GBRAIN_AUDIT_DIR` overrides the default ~/.gbrain/audit/ for container
deploys.
transcript.ts — renders subagent_messages + subagent_tool_executions to
markdown. Message order is authoritative; tool rows splice under their
owning assistant tool_use by tool_use_id. Handles text, tool_use (with
pending / complete / failed execution rows), tool_result (skipped if
we already rendered the owning tool_use — avoids double-printing), and
unknown block types (fenced JSON dump for diagnostics). Output is
UTF-8-safe truncated at maxOutputBytes.
21 unit tests: ISO week filename rotation (incl. 2027-01-01 → W53-2026
boundary), submission + heartbeat write shapes, 200-char error cap, best-
effort write failure doesn't throw, readback filters by job_id and
sinceIso. Transcript: empty input, ordering, token line, tool_use +
complete/failed/pending execution rendering, truncation, unknown-block
diagnostic dump.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): subagent LLM-loop handler with crash-resumable replay
The main event: runs one Anthropic Messages API conversation with tool
use, persists every turn + tool execution, and resumes cleanly after a
worker kill anywhere in the loop.
Design points that carry the v0.15 guarantees:
1. Two-phase tool persistence. INSERT status='pending' before dispatch,
UPDATE to 'complete' or 'failed' after. subagent_messages rows are
the canonical conversation; subagent_tool_executions rows are the
canonical "did this tool run + what did it return". Either DB commit
is atomic, so replay has a single source of truth.
2. Replay reconciliation. If the last persisted message is an assistant
with tool_use blocks AND no following synthesized user message, we
crashed mid-dispatch. On resume, finish those tools first (respecting
idempotent flag for 'pending' rows), synthesize the user turn, and
THEN call the LLM again. Non-idempotent pending rows abort the job
with a clear error — v0.15 ships only idempotent tools so this is
preventive.
3. Rate lease around every LLM call. acquireLease before, releaseLease
after (both success and error paths). acquired=false throws
RateLeaseUnavailableError — the worker treats it as a renewable
error and re-claims later, so a temporary capacity cap doesn't fail
the job terminally.
4. Anthropic prompt caching. system block gets cache_control=ephemeral;
the LAST tool def gets it too (Anthropic caches everything up to and
including the marked block). ~10x cost reduction on multi-turn
agents per the plan.
5. Dual-signal abort. AbortSignal.any merges ctx.signal (timeout / lock
loss / cancel) with ctx.shutdownSignal (worker SIGTERM). Both feed
the Anthropic call's AbortSignal; mid-turn abort bails before the
next LLM call with whatever turns are already persisted. Node ≥ 20
has AbortSignal.any; older runtimes get a manual-merge polyfill.
6. Injectable Anthropic client. The real SDK implements MessagesClient
structurally; tests inject a FakeMessagesClient that scripts
responses.
12 unit tests pin: no-tool happy path, single tool_use complete, tool
throws → failed row + loop continues, unknown tool name rejection,
max_turns cap, crash-then-resume with partial state, replay skips already-
complete tool execs without re-invoking execute, non-idempotent pending
rejects on resume, lease acquire + release roundtrip, RateLeaseUnavailable
under cap-full, missing prompt validation, allowed_tools unknown-name.
NOT in v0.15: refusal detection (stop_reason + content shape), stop_reason
=max_tokens partial recovery, mid-call lease renewal with backoff loop.
All three are documented as P2 items in the plan file.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): subagent_aggregator handler with mixed-outcome rendering
Claims AFTER all subagent children resolve — by then Lane 1B's queue
changes have posted one child_done message per terminal transition into
this job's inbox (complete / failed / dead / cancelled / timeout). The
aggregator reads those, builds a deterministic markdown summary, and
returns it as the handler result.
Not an LLM call in v0.15 — output is reproducible concatenation so
fan-out runs stay comparable. v0.16+ can add an LLM synthesis pass
behind an opt-in flag.
Contract:
- empty children_ids → `(no children)` marker
- missing child_done (shouldn't happen under v0.15 invariants but
possible if a terminal-state path slipped past Lane 1B) → counted as
failed with "no child_done message observed" error
- non-complete outcomes: result is null in the output so no payload
leaks alongside a failure label
- children appear in the order children_ids was supplied
- custom aggregate_prompt_template replaces the markdown header
13 unit tests cover: empty input, all-success, mixed outcomes, result
suppression on failure, missing child_done handling, order preservation,
custom template, progress + log emission, stringified JSONB payload
parsing, non-child_done inbox filtering, legacy-writer outcome fallback,
and internal helper edges.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): GBRAIN_PLUGIN_PATH loader + plugin-authors guide (v0.15)
Plumbing that makes Wintermute (and future downstream agents) day-1
usable on v0.15. Host repos drop a `gbrain.plugin.json` + `subagents/`
directory somewhere, set GBRAIN_PLUGIN_PATH (colon-separated like \$PATH),
and their custom subagent defs load at worker startup.
Path policy is strict: absolute paths only. Relative, ~-prefixed, and
URL-style (https://, file://) all rejected with warnings — the user
controls where plugins live. Non-existent paths and files (not dirs) are
warned and skipped so a typo doesn't crash worker startup.
Collision policy: left-wins. If two plugins ship a subagent with the same
name, the first one in GBRAIN_PLUGIN_PATH keeps it and the other gets a
warning naming both sources. Deterministic + debuggable.
Trust policy: plugins ship subagent defs ONLY. Cannot declare new tools,
cannot extend the brain allow-list, cannot override safety flags. The
subagent def's `allowed_tools:` frontmatter MUST subset the derived
registry — validation happens at load time (worker startup), not at
dispatch time, so a typo in a skill gives a loud startup error instead
of silently "tool never fires at 3am."
Manifest `plugin_version: "gbrain-plugin-v1"` locks the contract. Unknown
versions rejected. `subagents` field escape attempts (`../../../etc` etc)
rejected. gray-matter handles the markdown frontmatter parse — subagent
defs don't conform to the page schema, so we don't use parseMarkdown.
docs/guides/plugin-authors.md is the Wintermute-facing walkthrough.
Covers the minimum viable plugin shape, the three policies, the
frontmatter fields, known caveats (audit JSONL is local-only, tool calls
always run remote=true, put_page is namespace-scoped).
22 unit tests pin path rejection, missing/invalid manifest, unsupported
version, escape-attempt, basename fallback for missing frontmatter.name,
allowed_tools round-trip, unknown-tool rejection with validAgentToolNames,
empty env, multi-path, collision warning with left-wins, trimmed paths,
manifest-rejection as warning.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): gbrain agent run + logs + worker registration (v0.15 Lane 4H)
Three integration seams wired:
src/commands/agent.ts — \`gbrain agent run\`. Submits subagent jobs (or a
fan-out of N + aggregator) under the trusted-submit flag so the
PROTECTED_JOB_NAMES guard doesn't reject. Fan-out path creates the
aggregator first (so children can reference its id as parent), submits
each child with on_child_fail='continue' (required by Lane 1B's terminal-
set + child_done machinery), then jsonb_set's the aggregator's
children_ids. Short-circuits a 1-entry manifest to a single subagent
with no aggregator. Follow mode runs agent-logs streaming + waitFor
Completion in parallel and exits on terminal status; detach prints the
job id and exits. Ctrl-C is handled as detach, not cancel — the job
keeps running, consistent with durability invariants.
src/commands/agent-logs.ts — \`gbrain agent logs\`. Merges ~/.gbrain/audit/
subagent-jobs-*.jsonl (heartbeats + submissions) with subagent_messages
(persisted conversation) in one chronological stream. --follow polls at
1s and exits when the job hits terminal. --since accepts ISO-8601 OR
relative shorthand (5m / 1h / 2d). Writes transcript tail (full message
+ tool tree) only for terminal jobs, so mid-run --follow doesn't spam a
half-rendered transcript.
src/commands/jobs.ts registerBuiltinHandlers — matches the shell-handler
opt-in shape. GBRAIN_ALLOW_LLM_JOBS=1 registers the subagent +
subagent_aggregator handlers, then loads plugins from GBRAIN_PLUGIN_PATH
with validAgentToolNames pulled from BRAIN_TOOL_ALLOWLIST. Every plugin
warning + loaded-plugin line prints to stderr, mirroring the openclaw-
seam startup convention.
src/core/minions/protected-names.ts — subagent + subagent_aggregator
join the protected set. MCP submit_job returns permission_denied; only
trusted-CLI callers (with allowProtectedSubmit) can insert these rows.
src/cli.ts — adds 'agent' to CLI_ONLY + dispatches it like 'jobs'.
Test fallout: subagent-handler.test.ts + subagent-transcript.test.ts
helpers now submit under allowProtectedSubmit (they insert rows named
'subagent' directly against the queue). 23 new tests in agent-cli.test.ts
cover: flag parsing (including --detach implies !follow, --tools comma
split, -- terminator, unknown flag throw), --since parse (ISO, relative
5m/2h/1d, unparseable error), protected-name guard for all three names,
trusted-submit gate, and a fan-out integration check that verifies the
aggregator + children shape after --fanout-manifest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(e2e): rename max_children test's spawned jobs off the protected 'subagent' name
The spawn-storm test submitted 50 literal-string 'subagent' children to
exercise the max_children row-lock serialization. In v0.15 'subagent' is
a PROTECTED_JOB_NAME (CLI-only; trusted submit required), so the old
literal submission now throws before reaching the row-lock check.
The test is about max_children semantics, not the v0.15 subagent runtime
specifically — rename the child name to 'child_worker' so the test
exercises the exact same queue.add path without tripping the new guard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(ship): v0.15.0 — VERSION, CHANGELOG, README, upgrading-agents, CLAUDE.md
Bumps VERSION → 0.15.0 and package.json → 0.15.0 (resolves the pre-existing
drift — on master, VERSION=0.14.0 but package.json=0.13.1; src/version.ts
reads package.json, so this is what the binary prints now).
CHANGELOG lands the release-summary entry in the GStack voice + the full
itemized change list (11 new modules, 3 new tables, queue correctness
fixes, trust-model additions, 159 new unit tests). Voice rules respected
— no em dashes, no AI vocabulary, real file names + real numbers.
README gets a "Durable agents: `gbrain agent` (v0.15)" section next to
the Minions block, with the three canonical CLI shapes (single run,
fanout-manifest, logs --follow) and a pointer to plugin-authors.md.
docs/UPGRADING_DOWNSTREAM_AGENTS.md gets a full v0.15.0 section covering
the four adoption steps downstream agents (Wintermute and similar) need:
(1) worker opt-in via GBRAIN_ALLOW_LLM_JOBS, (2) moving custom subagent
defs to a plugin repo, (3) replacing ephemeral subagent runs with durable
`gbrain agent run`, (4) the put_page namespace rule for agent-driven writes.
CLAUDE.md updated with concise per-file descriptions for every new module:
the handler, aggregator, audit, rate-leases, wait-for-completion,
transcript, plugin-loader, brain-allowlist, tool-defs extraction, agent
CLI + logs CLI, and the registerBuiltinHandlers wiring for subagent
handlers + plugin-loader.
Verified: binary builds (940 modules, 89ms compile), prints `gbrain 0.15.0`,
`gbrain agent --help` shows the new subcommand shape. 170 new tests pass
(full v0.15 surface). Full unit suite passes bar one parallel-load
flake on a pre-existing E2E (graph-quality, passes in isolation).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): drop GBRAIN_ALLOW_LLM_JOBS flag — subagent handlers always-on
The env flag was ceremony. Shell jobs need the flag because they execute
arbitrary CLI commands (RCE surface). Subagent jobs don't — they call the
Anthropic API with whatever ANTHROPIC_API_KEY is in env, so the key is
already the cost gate (no key → SDK fails on the first turn). And
who-can-submit is already protected by PROTECTED_JOB_NAMES +
TrustedSubmitOpts: MCP callers get permission_denied; only `gbrain agent
run` with allowProtectedSubmit can insert subagent / subagent_aggregator
rows. The flag added nothing the existing guards didn't already give us.
registerBuiltinHandlers now always registers subagent + subagent_aggregator
and loads GBRAIN_PLUGIN_PATH plugins. Worker startup prints:
[minion worker] subagent handlers enabled
instead of the conditional enabled/disabled pair. Plugin discovery runs
unconditionally — empty PATH is a no-op.
README, CHANGELOG, docs/UPGRADING_DOWNSTREAM_AGENTS, CLAUDE.md, agent CLI
help text, and subagent handler docstring all updated to drop the flag
reference. Shell handler's GBRAIN_ALLOW_SHELL_JOBS gate is untouched —
separate concern (RCE, not billing).
Full suite: 1859 pass, 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: scrub private agent-fork name from all public artifacts
Enforces the rule added to CLAUDE.md (privacy section): never say
`Wintermute` in any CHANGELOG, README, doc, PR, or commit message.
Reader-facing copy says `your OpenClaw` (the term covers every
downstream OpenClaw deployment — Wintermute, Hermes, AlphaClaw — in
one umbrella the reader already recognizes). First-person /
origin-story copy says `Garry's OpenClaw` (honest that this is the
production deployment driving the feature, without exposing the
private agent's name).
Swept across:
CHANGELOG.md (v0.15 entry + 4 historical mentions)
README.md
TODOS.md
docs/UPGRADING_DOWNSTREAM_AGENTS.md
docs/guides/plugin-authors.md (including example plugin names)
docs/guides/plugin-handlers.md
docs/guides/minions-fix.md
docs/designs/KNOWLEDGE_RUNTIME.md (27 refs, mostly analytical)
docs/benchmarks/2026-04-18-minions-vs-openclaw-production.md
skills/migrations/v0.11.0.md
skills/skillpack-check/SKILL.md
scripts/skillify-check.ts
src/commands/doctor.ts
src/commands/migrations/v0_15_0.ts
src/commands/skillpack-check.ts
src/core/enrichment/completeness.ts
src/core/minions/plugin-loader.ts
src/core/operations.ts
src/core/output/scaffold.ts
Intentionally kept (these mentions define/test the rule itself):
CLAUDE.md — the privacy rule section necessarily uses the literal
name to define the restriction and examples
test/plugin-loader.test.ts — fixture name in a plugin-loading test;
renaming risks breaking assertion logic
test/integrations.test.ts — the word appears in a privacy-regex
test that explicitly enforces name redaction
test/doctor-minions-check.test.ts — a comment referencing the rule
CEO plan artifact at ~/.gstack/projects/… — private, not distributed
Binary builds (941 modules), 198/198 relevant tests pass, `gbrain --version`
prints `0.15.0`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: gitignore bun --compile artifacts with a glob, not specific hashes
Each `bun build --compile` emits a fresh hash-named `.*-*.bun-build` file
in cwd. The prior entries listed two specific hashes that were already
stale, so every build after those created a new untracked file requiring
manual cleanup.
Replace the two stale entries with `*.bun-build` so any current or future
compile artifact is ignored automatically.
Verified: ran `bun build --compile`, got two new `.*-*.bun-build` files,
`git status` stays clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(ship): rename v0.15.0 → v0.16.0
gbrain master is at 0.14.2. Other 0.15.x PRs may land before/after
this one — we bump the minor (new capability) and lock to 0.16.0 so
ordering with concurrent work doesn't matter.
Touches:
- VERSION: 0.15.0 → 0.16.0
- package.json: 0.15.0 → 0.16.0
- Rename src/commands/migrations/v0_15_0.ts → v0_16_0.ts (+ all
version strings inside + import in index.ts registry)
- Rename test/migrations-v0_15_0.test.ts → migrations-v0_16_0.test.ts
- test/apply-migrations.test.ts: skippedFuture lists now reference
'0.16.0'
- test/put-page-namespace.test.ts + test/mcp-tool-defs.test.ts: Lane
comment refs updated
- src/schema.sql + src/core/pglite-schema.ts: "v0.15.0" section
comment updated; src/core/schema-embedded.ts regenerated
- CHANGELOG.md: top entry renamed to [0.16.0]; inline v0_15_0 /
v0.15.0 refs swept
- docs/UPGRADING_DOWNSTREAM_AGENTS.md: section heading v0.15.0 → v0.16.0
Verified: `gbrain --version` prints 0.16.0, migration registry /
buildPlan / put_page / mcp-tool-defs / handlers tests all green
(49/49).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: reframe v0.16 durability headline around OpenClaw crashes
"Laptop closed mid-run" framing implied a consumer workflow. Real pain is
OpenClaw subagents dying daily on worker kill, memory blip, or timeout.
Headline + README copy match the body now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: regenerate llms-full.txt after README copy change
Regen drift guard caught the README edit from
|