diff --git a/llms-full.txt b/llms-full.txt index 183d24777..c160a8d11 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -3486,11 +3486,16 @@ for remote clients over OAuth 2.1. ### Local stdio (zero setup) ```bash -gbrain serve +gbrain serve # full operation catalog (default) +gbrain serve --surface verbs # just the 5 memory verbs (quickstart surface) ``` Works with Claude Code, Cursor, Windsurf, and any MCP client that supports stdio. No server, no tunnel, no token needed. Works on both PGLite and Postgres engines. +`--surface verbs` exposes exactly the five-verb memory protocol (`recall`, +`remember`, `entity`, `synthesize`, `forget` — +[MEMORY_VERBS v1](../protocol/MEMORY_VERBS_v1.md)) instead of the full catalog; +omit the flag (default `full`) for every operation. ### Remote over OAuth 2.1 (recommended, v0.26.0+) diff --git a/src/commands/migrations/v0_43_0.ts b/src/commands/migrations/v0_43_0.ts index a73e4c0d8..e8c337091 100644 --- a/src/commands/migrations/v0_43_0.ts +++ b/src/commands/migrations/v0_43_0.ts @@ -18,11 +18,11 @@ import type { Migration, OrchestratorOpts, OrchestratorResult } from './types.ts async function orchestrator(_opts: OrchestratorOpts): Promise { // No schema/data work — MEMORY_VERBS v1 is a façade over existing tables. - return { version: '0.43.0.0', status: 'complete', phases: [] }; + return { version: '0.43.0', status: 'complete', phases: [] }; } export const v0_43_0: Migration = { - version: '0.43.0.0', + version: '0.43.0', featurePitch: { headline: 'Five memory verbs — recall, remember, entity, synthesize, forget — are now the agent-facing memory protocol (MEMORY_VERBS v1).', diff --git a/test/apply-migrations.test.ts b/test/apply-migrations.test.ts index 455f772a5..7e50fc86e 100644 --- a/test/apply-migrations.test.ts +++ b/test/apply-migrations.test.ts @@ -108,7 +108,7 @@ describe('buildPlan — diff against completed + installed VERSION', () => { // autopilot cooperative, v0.16.0 = subagent runtime, v0.18.0 = multi- // source brains, v0.18.1 = RLS hardening, v0.21.0 = Cathedral II // (renumbered from v0.20.0 after master shipped v0.20.x in parallel). - expect(plan.skippedFuture.map(m => m.version)).toEqual(['0.12.0', '0.12.2', '0.13.0', '0.13.1', '0.14.0', '0.16.0', '0.18.0', '0.18.1', '0.21.0', '0.22.4', '0.28.0', '0.29.1', '0.31.0', '0.32.2', '0.43.0.0']); + expect(plan.skippedFuture.map(m => m.version)).toEqual(['0.12.0', '0.12.2', '0.13.0', '0.13.1', '0.14.0', '0.16.0', '0.18.0', '0.18.1', '0.21.0', '0.22.4', '0.28.0', '0.29.1', '0.31.0', '0.32.2', '0.43.0']); }); test('already applied → v0.11.0 lands in `applied` bucket, not pending', () => { @@ -148,7 +148,7 @@ describe('buildPlan — diff against completed + installed VERSION', () => { // v0.22.4, v0.28.0, v0.29.1, v0.31.0 were added later; installed=0.12.0 // means they belong in skippedFuture, not pending. v0.11.0 and v0.12.0 // stay pending despite being ≤ installed — that is the H9 invariant. - expect(plan.skippedFuture.map(m => m.version)).toEqual(['0.12.2', '0.13.0', '0.13.1', '0.14.0', '0.16.0', '0.18.0', '0.18.1', '0.21.0', '0.22.4', '0.28.0', '0.29.1', '0.31.0', '0.32.2', '0.43.0.0']); + expect(plan.skippedFuture.map(m => m.version)).toEqual(['0.12.2', '0.13.0', '0.13.1', '0.14.0', '0.16.0', '0.18.0', '0.18.1', '0.21.0', '0.22.4', '0.28.0', '0.29.1', '0.31.0', '0.32.2', '0.43.0']); }); test('--migration filter narrows to one version', () => { diff --git a/test/memory-verbs-conformance.test.ts b/test/memory-verbs-conformance.test.ts index e086c2ac6..8b1e9b501 100644 --- a/test/memory-verbs-conformance.test.ts +++ b/test/memory-verbs-conformance.test.ts @@ -44,6 +44,7 @@ import { __setEmbedTransportForTests, } from '../src/core/ai/gateway.ts'; import { __setUsageLogPathForTests } from '../src/core/verbs/usage-log.ts'; +import { withEnv } from './helpers/with-env.ts'; let engine: PGLiteEngine; let home: string; @@ -295,11 +296,12 @@ describe('synthesize — marked expensive + unavailable conversion [c10]', () => }); it('delegates to runThink and returns the frozen envelope with a priced cost block (chat seam — no real LLM)', async () => { - // Hermetic: the chat transport seam answers deterministically. (The - // keyless `unavailable` conversion [c10] is covered end-to-end by the CI - // conformance step, which runs `protocol conformance --synthesize` in a - // credential-free environment — a unit-level trigger here would depend on - // the developer machine NOT having keys, the opposite of hermetic.) + // Fully hermetic regardless of the dev/CI machine's ambient credentials: + // runThink builds its client via a real-key check (hasAnthropicKey reads + // process.env), NOT the gateway chat seam — so we must BOTH provide a fake + // key via withEnv (so the client builds) AND install the chat seam (so no + // real API call fires). Without the env key, CI (credential-free) takes the + // NO_ANTHROPIC_API_KEY path → the verb's `unavailable` conversion → isError. __setChatTransportForTests(async () => ({ text: JSON.stringify({ answer: 'Synthesized test answer.', citations: [], gaps: ['none'] }), blocks: [], @@ -308,7 +310,9 @@ describe('synthesize — marked expensive + unavailable conversion [c10]', () => model: 'anthropic:claude-haiku-4-5-20251001', providerId: 'anthropic', })); - const { isError, body } = await callRemote('synthesize', { question: 'what do we know?' }); + const { isError, body } = await withEnv({ ANTHROPIC_API_KEY: 'sk-test-hermetic' }, async () => + callRemote('synthesize', { question: 'what do we know?' }), + ); expect(isError).toBe(false); expect(body.answer).toBe('Synthesized test answer.'); expect(body.protocol_version).toBe(1);