mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-31 04:07:52 +00:00
c023a6041de307ee34bbbcade09e08621ef603e9
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ecd6ae8772 |
v0.42.40.0 fix(extract,ingest): well-form lone UTF-16 surrogates before jsonb (#2011) (#2031)
* fix(extract,ingest): well-form lone UTF-16 surrogates before jsonb (#2011) excerpt() in link-extraction.ts sliced the link-context window by raw UTF-16 index, so a boundary landing inside a non-BMP char (emoji, math, CJK) left an unpaired surrogate half in `context`. Serialized to JSONB for the jsonb_to_recordset batch insert, Postgres rejects it at the ::jsonb cast and aborts the whole batch — wedging `extract --stale` because the staleness bookmark only advances on a clean finish. - text-safe.ts: new ensureWellFormed() (Bun isWellFormed/toWellFormed) — one shared surrogate-cleaning primitive. - link-extraction.ts: excerpt() well-forms the slice (root-cause fix). - batch-rows.ts: new sanitizeForJsonb() = ensureWellFormed(stripNul(s)) applied to every free-text body field (link context; timeline summary/detail/source; take claim/source). Identity/security fields stay un-sanitized and fail closed. - postgres-engine.ts + pglite-engine.ts: scalar addLink + addTimelineEntry use sanitizeForJsonb too, matching the batch path on both engines. - brainstorm/orchestrator.ts: consolidate hand-rolled sanitizeUnicode onto ensureWellFormed (also fixes consecutive-lone-surrogate mishandling). Tests: ensureWellFormed unit cases (incl. consecutive lone surrogates), an excerpt window-split regression, PGLite + Postgres-e2e surrogate cases across all free-text fields and scalar paths, and fail-closed identity-field tests proving sanitization was NOT extended to slugs/holders. * v0.42.39.0 chore: bump version and changelog (#2011) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: update project documentation for v0.42.39.0 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * v0.42.40.0 chore: re-slot release version (was 0.42.39.0) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: fix env-mutation isolation violations in retrieval-reflex tests check:test-isolation (rule R1) flags direct process.env mutation in non-serial test files — bun's parallel runner loads multiple files into one process, so a leaked GBRAIN_RETRIEVAL_REFLEX flips reflex behavior in unrelated tests. Both files landed via the #2019 merge; convert the beforeEach/afterEach env juggling to the canonical withEnv() wrapper, which restores the prior value via try/finally even on throw. Fixes the failing `verify` CI check on #2031 (and the `test-status` aggregate that inherits it). All 30 verify checks green locally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9a0bae8d62 |
v0.42.25.0 fix(pricing): unify chat-model pricing into one canonical source; add Opus 4.8 (#1819) (#1827)
* fix(pricing): unify chat-model pricing into one canonical source; add Opus 4.8 (#1819) Single canonical CANONICAL_PRICING table (src/core/model-pricing.ts) with canonicalLookup; ANTHROPIC_PRICING and takes-quality MODEL_PRICING become derived views. cost-tracker, cross-modal runner, skillopt preflight, brainstorm orchestrator, and brain-score all source from it. Adds Opus 4.8 ($5/$25) so --max-cost-usd and the dream-cycle budget meter enforce on 4.8 runs; fixes a stale Opus 4.7 $15/$75 in the takes-quality gate and reconciles Gemini 2.0 Flash to $0.10/$0.40. Because every table derives from canonical, cross-table price drift is structurally impossible. * chore: bump version and changelog (v0.42.25.0) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: document canonical chat-pricing table for v0.42.25.0 Add KEY_FILES.md entries for src/core/model-pricing.ts (canonical CANONICAL_PRICING + canonicalLookup) and refresh the now-derived anthropic-pricing.ts + takes-quality-eval/pricing.ts entries to current-state. Add the "one canonical chat-pricing table" cross-cutting invariant to CLAUDE.md. Fix the stale model-price snapshot pointer in SEARCH_MODE_METHODOLOGY.md (anthropic-pricing.ts -> model-pricing.ts). Regenerate llms-full.txt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
63977054af |
v0.41.30.0 fix(brainstorm/lsd): --save writes the advertised .md file via canonical ingestion path (#1655)
* refactor: extract shared atomic writePageThrough helper Lift the v0.38 put_page disk write-through (operations.ts) into a shared src/core/write-through.ts helper and upgrade it to write atomically (unique temp file + rename) so a crash or a concurrent gbrain sync can never read a half-written .md. put_page now calls the helper; behavior is preserved (repo guards, source-awareness, provenance overrides) and its write is now atomic. brainstorm/lsd --save will call the same helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(brainstorm/lsd): --save writes the advertised file via canonical ingestion --save printed 'Saved to <slug>' unconditionally but only did a raw DB putPage: the promised wiki/ideas/<slug>.md file was never written, the page had no chunks (unsearchable, and churned by the next sync), and a failed DB write under PgBouncer still claimed success. Route save through importFromContent({noEmbed:true}) for a canonical row, then the shared writePageThrough helper renders the file from that row. persistSavedIdea + formatSaveOutcome report honestly which sinks landed and exit nonzero when nothing persisted. buildIdeaSlug gets a random nonce so same-day ideas don't clobber. New buildBrainstormFrontmatterObject feeds serializeMarkdown. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v0.41.30.0) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: document write-through.ts + brainstorm --save canonical path (v0.41.30.0) Add Key Files entry for the new shared atomic src/core/write-through.ts helper, note the brainstorm/lsd --save canonical-ingestion rewrite on the brainstorm entry, and note put_page now calls the shared atomic helper on the operations.ts entry. Regenerate llms-full.txt to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
127842e9ef |
v0.41.22.1 feat: brainstorm/lsd judge fixes (closes #1540 end-to-end) (#1562)
* feat(core): add splitProviderModelId centralizer for pricing-side parsing
New pure helper in src/core/model-id.ts that splits provider:model,
provider/model, and bare model strings into a {provider, model} pair.
Defensive contract: null/undefined/empty/whitespace returns
{provider: null, model: ''}.
Will be wired into the 5 pricing/budget sites in the next commit.
Named splitProviderModelId (not parseModelId) to avoid the in-project
collision with the gateway-side src/core/ai/model-resolver.ts:parseModelId
which has a different bare-name contract.
Pinned by 16 cases in test/model-id.test.ts covering all separator
forms plus defensive + edge inputs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(gateway): accept slash-form provider id in model-resolver
src/core/ai/model-resolver.ts:parseModelId now accepts both
provider:model (colon) and provider/model (slash) forms. Colon wins
when both separators present so OpenRouter nested ids like
openrouter:anthropic/claude-sonnet-4.6 route as
{providerId: 'openrouter', modelId: 'anthropic/claude-sonnet-4.6'}.
Pre-fix: every gateway entry point (chat / embed / rerank) threw
AIConfigError 'missing a provider prefix' on slash form ids. That
meant CLI users running
gbrain brainstorm --judge-model anthropic/claude-sonnet-4-6
would still fail mid-judge with AIConfigError even after pricing
was relaxed to accept slash form. Closes the end-to-end bug class.
Bare names without ANY separator still throw — gateway routing
always needs an explicit provider. Existing tests pinning that
throw (test/ai/capabilities.test.ts:43) stay green.
Pinned by 10 cases in test/ai/model-resolver-slash.test.ts
including a resolveRecipe round-trip that slash and colon forms
land on the same recipe.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor: route 5 pricing/config sites through splitProviderModelId
Five sites had inline ':'-only provider-prefix splits that silently
missed slash-form ids. Centralizing through splitProviderModelId
closes the bug class:
- src/core/anthropic-pricing.ts:estimateMaxCostUsd
- src/core/budget/budget-tracker.ts:lookupPricing (closes the
headline BudgetExhausted no_pricing failure on --max-cost +
slash-form --judge-model)
- src/core/eval-contradictions/cost-tracker.ts:pricingFor
(legacy silent-Haiku fallback preserved per plan D9)
- src/core/minions/batch-projection.ts (deleted bareModel inline
helper; inlined splitProviderModelId at 2 call sites)
- src/core/model-config.ts:isAnthropicProvider (silently fixed
v0.31.12 subagent-guard bypass for slash-form Anthropic ids)
Test gates land together so any bisect step is green:
- NEW test/anthropic-pricing.test.ts (7 cases including structural
regression guard: every ANTHROPIC_PRICING key reachable via all
three forms)
- NEW test/eval-contradictions/cost-tracker-slash.test.ts (6 cases
including legacy-Haiku-fallback pin)
- EXTENDED test/batch-projection.test.ts (slash + double-separator
cases)
- EXTENDED test/model-config.serial.test.ts (2 slash-form
isAnthropicProvider cases)
- EXTENDED test/core/budget/budget-tracker.test.ts (2 slash + colon
reserve() cases)
Behavior changes for slash-prefix ids only; bare and colon ids
unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(brainstorm): scale judge maxTokens with per-model output cap
Replace the hard-coded maxTokens: 4000 with computeJudgeMaxTokens
that scales with idea count and respects each model's actual output
cap.
Pre-fix: any judge call with 36+ ideas produced ~100 tokens/idea of
JSON that got truncated mid-output. parseJudgeJSON threw, orchestrator
surfaced judge_failed: true, all ideas saved unscored. Verified
failure mode on 72-idea fixture: 0/72 passing before, 39/72 after.
Formula: min(modelCap, max(LEGACY_MIN_MAX_TOKENS, ideaCount*150+500))
Named constants extracted at top of judges.ts:
- TOKEN_BUDGET_PER_IDEA = 150 (1.5x headroom over observed ~100/idea)
- TOKEN_BUDGET_ENVELOPE = 500 (JSON wrapper)
- LEGACY_MIN_MAX_TOKENS = 4000 (pre-fix floor preserved for 1-idea)
- MAX_OUTPUT_TOKENS_CEIL = 32_000 (fallback when model unknown)
- ANTHROPIC_OUTPUT_CAPS (per-model: Opus 4.7 = 32K, Sonnet 4.6 /
Haiku 4.5 = 64K, legacy 3.5 = 8K)
When the caller passes no modelOverride, the cap routes through the
gateway's actual configured chat model via getChatModel() so the
formula matches what chat() will use, not whatever the override
hints at. Pre-fix the undefined-override case fell back to 32K even
if the configured default was a legacy 8K model.
Pinned by 16 cases in test/brainstorm/judges-maxtokens.test.ts:
formula at 1/10/36/96/200/300 ideas, per-model cap binding (Haiku 3.5
8K, Opus 4.7 32K, Sonnet 4.6 64K), and integration via runJudge with
a stubbed chatFn that captures ChatOpts.maxTokens.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore: bump version and changelog (v0.41.21.0)
Brainstorm judge fix-wave: closes #1540 end-to-end. parseModelId
centralizer + gateway resolver slash-form acceptance + per-model
maxTokens cap.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: update project documentation for v0.41.21.0
CLAUDE.md: add v0.41.21.0 annotations to brainstorm/judges + model-config
entries; add new key-files entry for src/core/model-id.ts (the shared
splitProviderModelId centralizer) and src/core/ai/model-resolver.ts
slash-form extension.
README.md: add user-facing callout for the brainstorm judge_failed +
slash-form pricing fix, mirroring the v0.41.19.0 callout shape.
llms-full.txt: regenerated to absorb the CLAUDE.md + README changes
(passes test/build-llms.test.ts drift guard).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
f702ec053b |
v0.41.16.0 feat: conversation parser cathedral + progressive-batch primitive (closes #1461) (#1510)
* v0.41.15.0 feat: conversation parser cathedral + progressive-batch primitive (closes #1461) Replaces PR #1461's single-format Telegram regex with a 12-pattern built-in registry covering iMessage/Slack, Telegram (×2), Discord (×2), WhatsApp (×2 locales), Signal, Matrix/Element, IRC (×2), Teams. Each pattern is hand-vetted from public format docs (signal-cli, DiscordChatExporter, Telegram Desktop, WhatsApp export docs, Element matrix-archive, irssi/weechat defaults); module-load validation runs test_positive[] + test_negative[] for every pattern at startup so a typo makes gbrain refuse to start. PR #1461 contributor's BRACKET_TIME_RX + cleanSpeaker survive verbatim as the `telegram-bracket` built-in pattern + DEFAULT_SPEAKER_CLEAN export. All 33 of their test cases pass against the new orchestrator. Three layers per page (orchestrator chooses): 1. Built-in pattern registry (zero-cost, deterministic) 2. User-declared simple_pattern via config (deferred to v0.42+) 3. Opt-IN LLM polish + fallback (privacy-first; chat content goes to Anthropic only when user explicitly enables) D18 priority scoring picks the highest-match-rate pattern across the first 10 lines (not first-wins) so overlapping formats don't silently mis-route. D5 multi_line per-pattern + D11 quick_reject prefix screen + D19 timezone_policy per-pattern complete the registry shape. Companion: src/core/progressive-batch/ primitive (rule of three satisfied across 12+ ad-hoc cost-prompt sites). Wintermute-inspired ramp shape (trial 10 → 100 → 500 → full with verification at each stage), productionized with verifier+policy injection (callers describe HOW TO MEASURE SUCCESS, not WHEN TO WAIT FOR CTRL-C). D3 fail-closed budget gate: null tracker + null Policy.maxCostUsd → abort_cost_cap reason='no_budget_safety_net'. D20 discriminated Verifier union (output_count | idempotent_mutation | noop). extract-conversation-facts is the one proven consumer in v0.41.15.0; 9-site retrofit deferred to v0.41.16.0+ per TODOS.md. Codex outside-voice review absorbed 8 substantive findings: - Privacy posture (LLM polish/fallback flipped to opt-IN) - ReDoS theater (dropped arbitrary user regex; v0.42+ uses RE2) - LLM-inferred-regex persistence as silent-corruption machine - Pattern priority scoring across first 10 lines - Timezone policy on every PatternEntry - Verifier shape discriminated union - Behavior parity for sites that "jumped straight to full" - Real-corpus-redacted fixture gap (v0.42+ TODO) CI gates: - bun run check:conversation-parser (13 fixtures, --no-llm, deterministic) - bun run check:fixture-privacy (banned-token grep) Doctor surfaces 3 new checks: conversation_format_coverage, progressive_batch_audit_health, conversation_parser_probe_health. Tests: 198/198 across primitive + parser + LLM + nightly probe + eval CLI + debug CLI + doctor checks + migration v97 round-trip + E2E parser ↔ engine integration. Real bug caught + fixed during gap audit: IdempotentMutationVerifier was comparing absolute mutated-count vs per-stage expected (failed silently on stage 2+); now uses per-stage delta semantics matching OutputCountVerifier. Schema migration v97: conversation_parser_llm_cache table with (content_sha256, model_id, call_shape) composite key. NO inferred_patterns table (D17: silent-corruption machine). Plan + 23 decisions + codex outside-voice absorption at ~/.claude/plans/system-instruction-you-are-working-cuddly-hollerith.md. Co-Authored-By: garrytan-agents (PR #1461) <noreply@github.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(check-privacy): allowlist scripts/check-fixture-privacy.sh The new sibling privacy guard literally names the banned tokens in its BANNED_TOKENS array — same meta-exception that check-privacy.sh itself gets. Without this allowlist entry, bun run verify rejects the file post-merge because the banned name appears in the rule-definition script. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: renumber v0.41.15.0 → v0.41.16.0 (queue drift) Mechanical rename across all surfaces: VERSION, package.json, CHANGELOG (header + body refs), CLAUDE.md, TODOS.md, src/core/ migrate.ts (migration v98 comment), all src/core/conversation-parser/* and src/core/progressive-batch/* file headers, all test/ headers, scripts/check-privacy.sh allowlist comment, llms-full.txt regenerated. Audit clean: VERSION + package.json + CHANGELOG header all show 0.41.16.0. verify 24/24, touched tests 179/179. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: garrytan-agents (PR #1461) <noreply@github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6987934ebb |
v0.39.3.0: productionize the v0.38 ingestion cathedral (smoke-test fix wave from PR #1299) (#1308)
* docs: land v0.38.0.0 production smoke-test report (PR #1299 + editor's note) PR #1299 from garrytan-agents shipped a 308-line production smoke-test report against v0.38.0.0 on Supabase+PgBouncer. Bringing the report verbatim into this worktree alongside the actual fixes (v0.38.3.0 wave). Privacy scrub passed per CLAUDE.md placeholder rule — no real people, companies, funds, or deals named. Editor's Note prepended to flag two re-diagnosed findings: - BUG-2 actual crash line is :1594-1597 (req.body === undefined → JSON.stringify returns literal undefined → Buffer.from throws), not the originally reported :1508. - WARN-5 root cause is `capture` missing from CLI_ONLY_SELF_HELP set; the detailed HELP constant exists but is unreachable. Co-Authored-By: garrytan-agents <garrytan-agents@users.noreply.github.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(capture): BUG-1 — merge frontmatter instead of double-wrap on --file Pre-fix: `gbrain capture --file foo.md` on a file that already has YAML frontmatter stamped a second outer `---` block whose `title` field was the file's own opening `---` delimiter. Users got pages with two frontmatter blocks: outer `title: '---'`, inner the real metadata. The parser then treated the second block as a body-side horizontal rule. Root cause: capture.ts:136-152 `buildContent` always prepended its own frontmatter without inspecting whether the input already had one. The title-from-first-line heuristic at :141 picked up the `---` delimiter when present. Fix: new pure helper `mergeCaptureFrontmatter(rawBody, opts)` parses existing frontmatter via gray-matter (the lib markdown.ts already uses) and merges capture's auto-fields with user-wins precedence on user- declared keys. Single output frontmatter block in all cases. Precedence: - `type`: opts.type (CLI flag) > userFm.type > 'note' - `title`: userFm.title > derived-from-body - `captured_via`: userFm.captured_via > opts.source > 'capture-cli' - `captured_at`: userFm.captured_at > now() (user can pre-stamp) - All other user keys (description, tags, slug, ...) pass through verbatim Files without frontmatter keep the original behavior (stamp fresh, wrap under derived `# heading` if body lacks markdown structure). CQ2 boil-the-lake test coverage in test/capture-build-content.test.ts (19 cases, 47 assertions): 13 specified cases including CJK title, CRLF line endings, UTF-8 BOM, empty frontmatter `---\n---\n`, malformed YAML, no-trailing-newline-before-body, user description/tags/slug passthrough; plus 3 deriveTitle helper cases, 2 --type CLI flag precedence cases, and the BUG-1 regression guard with the exact reported input shape. Decisions deferred to later wave phases: - CV3 source_kind taxonomy + CV15 canonical source resolver: Phase 3c - CV8 dedup hash from rawBody: Phase 3c (receipt) + Phase 3d (DB) - CV9 trim boundary split: Phase 3c - CV10 binary-byte guard: Phase 3c Plan: ~/.claude/plans/system-instruction-you-are-working-async-popcorn.md (Phase 2a) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(serve-http): BUG-2 — /ingest 500-HTML on missing body becomes 400 JSON Pre-fix: a POST /ingest with NO body (no Content-Length, no body bytes — common shape for misconfigured webhooks and the smoke-test repro) leaves `req.body === undefined`. The body-coercion block's `else` branch then called `Buffer.from(JSON.stringify(undefined), 'utf8')` — and `JSON.stringify(undefined) === undefined` (the literal, not the string), so `Buffer.from(undefined, 'utf8')` threw TypeError. The unhandled throw reached express's default error handler, which served an HTML 500 page. Clients expecting JSON envelopes broke. Two changes: 1. Explicit `req.body == null` null-guard at the top of the handler (BEFORE the body coercion). Catches both `null` and `undefined` request bodies and returns the same 400 `empty_body` envelope as the empty-Buffer guard at :1600. Closes the TypeError class structurally. 2. Outer try/catch wrapping the entire handler body with a `!res.headersSent` guard (codex F#16) so any unexpected throw — downstream of the inner queue.add try/catch, in a logging side-effect, or in the SSE broadcast — returns a JSON 500 envelope instead of leaking the HTML error page. Mirrors the F14 pattern around the MCP handler's transport.handleRequest at serve-http.ts:1508-1520. E2E regression test in test/e2e/serve-http-ingest-webhook.test.ts: 'BUG-2: POST with no body (undefined req.body) → 400 JSON envelope (not 500 HTML)' uses `fetch(URL, {method:'POST'})` with no body field to provoke the exact pre-fix shape. Asserts status !== 500, status === 400, content-type is application/json, body.error === 'empty_body'. The existing 'empty body → 400' test at line 200 already covered the empty-string-body case (Express raw() produces an empty Buffer; the :1600 guard fires correctly). Both cases now reach the same envelope via two structurally distinct paths. Codex F#15 correction absorbed: the misleading 'body \"\"' → empty_body test case was dropped from the plan. An empty JSON string body has bytes and is not the same as a missing body. Codex F#14 correction absorbed: header in the smoke-test verification command is `Authorization:` not `Auth:` (updated in the plan; tests already use the correct shape). Plan: ~/.claude/plans/system-instruction-you-are-working-async-popcorn.md (Phase 2b) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(put_page): WARN-8 — provenance write-through with CV6 trust gate + CV12 COALESCE Migration v81 added 4 nullable provenance columns to `pages` (source_kind, source_uri, ingested_via, ingested_at). Pre-fix, put_page wrote them into the FILE's frontmatter (operations.ts:637-644 write- through path) but never to the DB columns. `gbrain call get_page | jq .source_kind` returned null even when the JSON receipt claimed `capture-cli`. This commit closes the loop on the write side. Surface changes: - src/core/types.ts: PageInput gains 4 optional provenance fields (source_kind, source_uri, ingested_via, ingested_at) with docstring explaining the trust model. - src/core/import-file.ts: importFromContent opts accepts 3 (source_kind, source_uri, ingested_via); threads them to tx.putPage. ingested_at is NOT a caller-controllable param — engine stamps it. - src/core/operations.ts put_page op: accepts 3 client params on the wire schema (uniform across transports). CV6 trust gate: when ctx.remote !== false, IGNORES client params and server-stamps source_kind='mcp:put_page'/source_uri=null/ingested_via='mcp:put_page'. Only ctx.remote === false (capture CLI, autopilot, dream cycle) honors client values. - src/core/postgres-engine.ts + pglite-engine.ts putPage: INSERT/UPDATE SQL extended with 4 columns. ingested_at stamped to now() only when ANY provenance field is being written this call (null otherwise). ON CONFLICT clause uses COALESCE-preserve UPDATE (CV12) so a later put_page without provenance does NOT erase the original first-write audit trail — first-write-wins for routine edits. CV6 closes the spoofing surface codex caught: a write-scope OAuth token can no longer poison the audit trail with arbitrary labels ('source_kind: capture-cli' from an MCP agent). Anything that isn't strictly `ctx.remote === false` falls through to server-stamped 'mcp:put_page', matching the v0.26.9 F7b fail-closed discipline. CV12 closes the audit-trail-erasure trap: routine put_page edits (no provenance args) preserve the original ingestion's source_kind / ingested_at via `COALESCE(EXCLUDED.x, pages.x)`. Explicit re-ingestion that passes new provenance overwrites (latest-ingestion-wins). Tests in test/put-page-provenance.test.ts (11 cases, 29 assertions): - Trusted local caller: client params populate DB columns; partial provenance still triggers ingested_at stamp; omitting all leaves all 4 null. - CV6 spoofing guard: remote caller's source_kind='capture-cli' claim becomes 'mcp:put_page'; ctx.remote === undefined treated as remote (v0.26.9 F7b: fail-closed on anything not strictly false). - CV12 COALESCE-preserve: second write without provenance preserves first-write audit AND timestamp; explicit re-ingestion with new provenance overwrites; remote second write after local first records the most-recent honest source (mcp:put_page). - T2 subagent regression: namespace check fires when provenance params are present; subagent within wiki/agents/<id>/ succeeds with server- stamped provenance per CV6; missing subagentId still fail-closes. Plan: ~/.claude/plans/system-instruction-you-are-working-async-popcorn.md (Phase 3a) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(read-path): CV5 — expose put_page provenance via getPage / listPages Phase 3a wrote the 4 provenance columns into the DB via put_page. This phase makes them visible to the read side so the smoke-test verification command `gbrain call get_page <slug> | jq .source_kind` actually returns the value the write side just stored. Surface changes: - src/core/types.ts Page: 4 optional fields (source_kind, source_uri, ingested_via, ingested_at). Three-state read pattern matching v0.26.5's deleted_at convention — undefined when the SELECT projection didn't include the column (older callers), null when historical pre-v0.38 row, populated when the v0.38+ ingestion stamped it. - src/core/utils.ts rowToPage: maps the 4 columns to Page fields via the same conditional-spread pattern as deleted_at / effective_date. Older SELECTs without the projection continue to compile. - src/core/postgres-engine.ts + pglite-engine.ts getPage: projection extended to include the 4 columns. listPages uses `SELECT p.*` so the columns flow through automatically — no listPages SQL change. Both engines' putPage RETURNING clause already projects the 4 columns from Phase 3a, so the round-trip (write→get) is symmetric. get_page op + JSON renderer require no changes: `gbrain call get_page` goes through `runCall` (src/commands/call.ts:52) which is `console.log(JSON.stringify(result, null, 2))`. Since `result` is the Page object from get_page (which is the engine's getPage return, which is rowToPage), the new fields surface automatically. The markdown renderer (`gbrain get`) goes through serializeMarkdown which strips structured fields; that's the right behavior for the markdown view. Structured access stays on `gbrain call get_page` and `list_pages` (JSON output). Engine-parity test (T3) extended in test/e2e/engine-parity.test.ts with 2 new cases: - 'provenance columns: putPage writes + getPage returns identical shape on both engines' — seeds capture-cli provenance, asserts source_kind/source_uri/ingested_via match across engines and ingested_at is a Date on both. - 'provenance COALESCE-preserve UPDATE: parity on both engines (CV12)' — first write stamps capture-cli, second write WITHOUT provenance preserves the first-write source_kind / ingested_via on BOTH engines (CV12 first-write-wins is engine-uniform). Gated on DATABASE_URL — runs PGLite half always; Postgres half skips without it (existing engine-parity pattern). When the test fires, a drift between the two engines now fails loudly instead of waiting for a user's `gbrain migrate --to supabase` to surface the bug. Phase 3a test suite (test/put-page-provenance.test.ts) re-verified green (11 pass) after the read-path additions — no regression. Plan: ~/.claude/plans/system-instruction-you-are-working-async-popcorn.md (Phase 3b) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(capture): WARN-1/3/7 + CV3/CV7/CV8/CV9/CV10/CV15/A2 — capture write-side overhaul Lands the seven decisions resolved in the plan-eng-review for the capture CLI's write-side. Each addresses a specific smoke-test finding or codex outside-voice concern. Atomic single commit because all changes are in capture.ts and tightly coupled (CV9's trim split feeds CV8's hash input which the tests depend on). Decisions resolved: CV3 — source_kind taxonomy: capture ALWAYS stamps source_kind='capture-cli' in the receipt JSON AND threads it through to put_page's provenance params. Pre-fix `parsed.source ?? 'capture-cli'` conflated the DB source FK (where to write) with the ingestion-channel taxonomy (what kind of ingestion this was). --source now ONLY maps to source_id; source_kind is closed taxonomy per migration v81's documented set. CV7 — thin-client --source rejection: when isThinClient(cfg) AND parsed.source is set, exit 1 BEFORE any network call with a clear error pointing at the right fix: `gbrain auth register-client <name> --source <id>` on the server. Mirrors CV6 trust posture (server-side OAuth client registration owns source scope; per-call override would reopen the spoofing surface CV6 just closed). CV8 (CLI side) — receipt content_hash now comes from normalized rawBody, NOT the assembled fullContent (which contained a timestamp-bearing frontmatter). Two captures of identical text now produce identical content_hash, restoring the daemon's 24h LRU dedup at the CLI receipt layer. The DB hash (importFromContent) gets the same treatment in Phase 3d. CV9 — trim boundary split: introduced `normalizeForHash(s)` pure helper (trim + BOM strip + LF + NFKC). Hash input gets aggressive normalization for dedup correctness; the STORED body (passed to buildContent → mergeCaptureFrontmatter) preserves user bytes (CRLF, BOM, whitespace) for round-trip fidelity. CQ2's CRLF/BOM tests continue to pass. CV10 — binary file guard: read --file via `readFileSync(path)` with NO encoding (Buffer-side), then sniff first 8KB for NUL bytes via `detectBinaryNullByte(buf)`. Mirror the same sniff for --stdin (which also now reads Buffer-side via `readStdinBuffer()`). Real UTF-8 text (including CJK, emoji, BOM) never contains NUL bytes; binary formats (executables, archives, most image formats) do. Reject with friendly error before UTF-8 decode mangles the bytes. Deterministic test fixtures use `Buffer.from([...])` with explicit byte arrays instead of /dev/urandom (which a 256-byte sample often had no NUL in). CV15 — canonical source resolver: route through `resolveSourceWithTier(engine, parsed.source, cwd)` from src/core/source-resolver.ts (the v0.37.7.0 6-tier chain every other CLI op uses). Honors flag → env (GBRAIN_SOURCE) → dotfile (.gbrain-source) → local_path → brain_default → seed_default. Closes the WARN-3-adjacent UX divergence where capture silently used `parsed.source ?? 'default'`, ignoring env / dotfile / local_path / brain_default tiers. Bonus: resolveSourceWithTier's assertSourceExists throws a friendly error if the source is missing, BEFORE put_page is called — making capture-level pre-flight redundant for the common case (A2 fallback below handles TOCTOU + thin-client edge cases). A2 — friendly FK error rewrite: new `maybeRewriteSourceFkError(err, sourceId)` helper detects the Postgres FK-violation patterns ('pages_source_id_fk' OR 'foreign key constraint ... source') and returns a paste-ready hint: `source '<id>' is not registered. Register it first: gbrain sources add <id> --path <path>`. Applied in BOTH the local-engine catch block AND the thin-client (callRemoteTool) catch block per T1 — so the same friendly error surfaces regardless of install type. Defense-in-depth alongside CV15's upstream check (covers TOCTOU race + thin-client implicit source from dotfile pointing at a server-deleted source). WARN-1 receipt-side dedup, WARN-3 friendly source error, WARN-7 binary guard, and the source_kind taxonomy fix all become user-visible via this commit. The DB-side dedup (WARN-1's daemon side) lands in Phase 3d. HELP text updated: - Documents the 6-tier source resolution chain - Notes thin-client --source restriction - Notes binary content rejection - Notes dedup behavior (whitespace + line endings normalized) - Notes source_kind != source_id distinction Tests in test/capture-runcapture.test.ts (26 cases, 30 assertions): - CV10 detectBinaryNullByte: 10 cases incl. ASCII, CJK, emoji, BOM, start/mid NUL, PNG magic, 8KB cap boundary, empty - CV9 normalizeForHash: 6 cases incl. whitespace, BOM, CRLF, NFKC, no-op on clean, whitespace-only → empty - CV8 hash stability: 4 cases proving identical input → identical hash regardless of whitespace / line endings / timing - A2 maybeRewriteSourceFkError: 6 cases incl. raw PG message, wrapped OperationError, postgres.js-wrapped, unrelated errors, missing sourceId, non-Error throws Phase 3a + Phase 2a tests re-verified green (30 pass across both files) — no regression in the surfaces this commit didn't directly touch. Plan: ~/.claude/plans/system-instruction-you-are-working-async-popcorn.md (Phase 3c) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(import-file): CV8 — exclude timestamp frontmatter from DB content_hash Pre-fix: every gbrain capture invocation produced a fresh DB content_hash because parsed.frontmatter included captured_at (and now ingested_at) which change per call. Two consequences: - existing.content_hash === hash short-circuit never fired for identical body captures → every capture re-chunked and re-embedded unchanged content, burning embedding API spend - daemon-side 24h LRU dedup (separate consumer keyed on the same hash) silently never matched, defeating its design intent Phase 3c shipped the CLI-side fix (receipt hash from normalized rawBody). This commit shipps the DB-side fix. Approach: strip timestamp-bearing frontmatter keys before hashing, NOT strip all frontmatter. Whitelist: ['captured_at', 'ingested_at']. Future timestamp keys add to the list — small, stable surface. Why not strip ALL frontmatter? Sync would regress: a user editing a markdown file to add a tag changes the frontmatter without changing the body. The pre-fix hash captures that change; tag reconciliation fires. If we stripped all frontmatter, the hash wouldn't change, the short-circuit would fire, and the tag-add would silently no-op. The narrow whitelist preserves frontmatter-change-detection for real edits (tags, type, slug, description, ...) while ignoring the ephemeral timestamp keys that capture-cli and provenance-write-through stamp per-call. Tests in test/import-file.test.ts (4 new cases in 'CV8 DB content_hash stability' describe block): - captured_at differences → IDENTICAL hash → second capture status 'skipped' (short-circuit fires); putPage NOT called the second time - body change → DIFFERENT hash → second capture status 'imported' (real edits still flow through) - tag change → DIFFERENT hash → re-import fires (REGRESSION GUARD: proves frontmatter-change detection survives the strip) - ingested_at differences → IDENTICAL hash (provenance-only refresh doesn't invalidate the chunk cache) Combined with Phase 3c's CLI-side hash fix, WARN-1 (dedup not actually deduplicating) is now fully resolved: both the user-visible receipt hash AND the DB / daemon hash stabilize across identical-content captures. Plan: ~/.claude/plans/system-instruction-you-are-working-async-popcorn.md (Phase 3d) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(cli): WARN-5 + WARN-6 — capture help discoverable; main --help lists BRAIN WARN-5: `gbrain capture --help` printed only the generic short-circuit fallback ('Usage: gbrain capture\\n\\ngbrain capture - run gbrain --help for the full command list.'). Root cause: `capture` was missing from CLI_ONLY_SELF_HELP at src/cli.ts:34-53. The detailed HELP constant at src/commands/capture.ts:90+ existed but was unreachable because the dispatcher's printCliOnlyHelp short-circuit at :101 fired first. WARN-6: `gbrain --help` did not mention capture / brainstorm / lsd anywhere. New v0.37/v0.38 commands were implemented and dispatched but absent from the hardcoded printHelp text. Two fixes in cli.ts: 1. Add 'capture' to CLI_ONLY_SELF_HELP. This skips the generic short-circuit, allowing the dispatch flow to reach runCapture which has its own --help branch printing the detailed HELP constant. 2. Add a pre-engine-bind '--help' short-circuit for capture in handleCliOnly (mirrors the existing sync + reinit-pglite pattern). Without this, `gbrain capture --help` on a fresh tmpdir with no config would hit the engine bind at :1077 and exit with 'Cannot connect to database' before runCapture's --help branch fires. 3. Add BRAIN section to printHelp text between TOOLS and SOURCES. Documents capture / brainstorm / lsd with their key flags, matching the tone of the existing grouped sections. Tests in test/cli-help-discoverability.test.ts (6 cases, 31 assertions): - WARN-5: capture --help contains every documented flag (--slug, --type, --file, --stdin, --source, --quiet, --json) - WARN-5: output is NOT the generic short-circuit fallback (presence of 'Examples:' + length > 10 lines + does not match the bare- short-circuit regex) - WARN-5: -h short flag works too - WARN-6: main --help mentions all 3 commands as command-line entries - WARN-6: BRAIN section heading is present and the 3 commands appear textually after it - regression: existing top-level commands (init, doctor, get, search, query, import, export, files, embed) still listed (snapshot guard against accidental deletion of other groups during the BRAIN insertion) Tests use spawnSync subprocess execution so the real dispatcher flow is exercised end-to-end (no mocking of cli.ts internals). Plan: ~/.claude/plans/system-instruction-you-are-working-async-popcorn.md (Phase 4a) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(serve-http): WARN-9 — admin register-client scopes normalization Pre-fix at serve-http.ts:1091: the /admin/api/register-client route destructured `scopes` from req.body and passed `scopes || 'read'` to `oauthProvider.registerClientManual(name, grants, scopes, uris)`. Three bugs in that single line: 1. Field-name mismatch: OAuth wire format uses `scope` (singular). The destructure looked for `scopes` (plural). A request body sending `{"scope": "read write"}` had `scopes === undefined`, fell through to the `'read'` default, and silently created a read-only client. This is the exact behavior the smoke test reported. 2. Array shapes crashed: registerClientManual's parseScopeString calls `.split(' ')` on its argument. Arrays don't have `.split`, so `{"scopes": ["read", "write"]}` threw TypeError mid-request, surfacing as a 500. 3. Empty-array truthy: `[]` is truthy in JS so `scopes || 'read'` returned the empty array, also crashing on split. Codex outside-voice (CV12) also flagged: validation depth is insufficient. `["read write"]` (a single-element array where the element contains a space) silently passes the type check but produces an unknown scope `"read write"` that registers as garbage. Same for non-string elements (null, numbers), empty strings, and duplicates. Fix: new `normalizeScopesInput(raw: unknown)` helper in src/core/scope.ts handles all four valid input shapes and rejects everything else with a typed error. The admin route accepts BOTH `scopes` (admin SPA) AND `scope` (OAuth wire), normalizes, and surfaces a 400 invalid_scopes on validation failure. Validation matrix: - undefined / null / missing → 'read' default - string → split on /\s+/, dedupe, validate each element against ALLOWED_SCOPES allowlist, re-join sorted - string[] → reject non-string elements, reject empty strings, reject internal-whitespace (catches ['read write'] bug shape), dedupe, validate each element, re-join sorted - everything else (number, boolean, plain object) → Error - empty array / whitespace-only string after split → Error - unknown scope name → InvalidScopeError ('Unknown scope "X". Allowed: ...') Output is sorted for determinism so two registrations with the same scope set produce identical DB rows. Tests in test/scope-normalize.test.ts (28 cases, 30 assertions): - Happy paths: 12 cases incl. all 4 shapes, dedupe in both directions, whitespace tolerance (tab/newline), every hierarchy scope - Rejection paths: 14 cases incl. number/object/boolean inputs, empty array, empty string, non-string array elements, empty-string element, whitespace-in-element (the codex bug), unknown scope name in both string and array forms, mix of known+unknown - Determinism: 2 cases proving sorted output is order-independent Plan: ~/.claude/plans/system-instruction-you-are-working-async-popcorn.md (Phase 4b) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(facts-absorb): WARN-4 — suppress 'No database connection' noise with diagnostic Pre-fix: every gbrain capture invocation logged '[facts:absorb] failed to log gateway_error for inbox/...: No database connection: connect() has not been called. Fix: Run gbrain init...'. Non-fatal — the page write itself succeeded — but loud per-capture noise that the smoke test rightly flagged as a user-visible bug. Root cause: the facts subsystem grabs a separate engine handle that isn't connected on the CLI capture path. The actual write succeeds via the connected engine that capture uses; the facts:absorb log is a courtesy that the doctor health check reads. Codex flagged this as symptom-treatment vs root-cause-fix (CV13). Plan picked the middle path: suppress the per-capture noise NOW, instrument first-occurrence with a stack trace so the v0.38.4 fix knows where to look. Two changes: CQ1 — typed access via instanceof + .problem field on GBrainError (NOT string-match on .message). The class GBrainError already has structured (problem, cause_description, fix) fields per src/core/types.ts:1104. Matching the structured field is impossible to silently break: if someone edits the error wording in db.ts thinking it's cosmetic, the typed access still routes correctly. String-match on .message would be the same fragility class we just closed elsewhere in this wave (capture's FK error rewrite uses both patterns deliberately because raw PG messages don't go through GBrainError). CV13 — first-occurrence diagnostic: module-scoped _hasLoggedDisconnectedFactsAbsorb flag fires ONE stderr warn with the full stack trace the first time this class occurs in a process. Subsequent occurrences are silent. The next user reporting the warning gives us the call site without an extra round-trip. Other failure classes (GBrainError with a different .problem field, plain Error, anything else) keep the loud per-call warn so real subsystem errors (PgBouncer crash, schema drift, etc.) still surface. The suppression is narrowly scoped to the known-broken-but-non-fatal WARN-4 class only. Test seam: `_resetFactsAbsorbDisconnectedFlagForTests()` exported so each test can assert from a clean slate. Tests in test/facts-absorb-log.test.ts (4 new cases in 'WARN-4 disconnected-engine suppression' describe block): - First occurrence prints ONE warn with 'WARN-4' + 'First-occurrence trace' substring; subsequent 3 calls are silent - GBrainError with a DIFFERENT problem field still warns loudly (the suppression is narrow, not class-wide) - Plain Error (non-GBrainError) still warns loudly - The engine.logIngest call STILL fires for the suppressed case (the suppression is on the WARN output, not the attempt — preserves the doctor health check's read path if/when the wiring is fixed in v0.38.4) v0.38.4 follow-up TODO filed per the plan: trace why the facts pipeline opens a separate engine handle on the CLI capture path, and either share the connected engine OR no-op the absorb-log when called from a CLI context. The diagnostic stack trace this commit prints is the input that fix needs. Plan: ~/.claude/plans/system-instruction-you-are-working-async-popcorn.md (Phase 4c) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(brainstorm): WARN-10 + CV11 — surface SQLSTATE 57014 as typed StructuredAgentError Pre-fix: brainstorm + lsd silently produced no output on PgBouncer transaction-mode environments. Postgres statement_timeout fired canceling listPrefixSampledPages or hybrid search; the unhandled error reached main()'s catch-all and surfaced as a generic 'gbrain: unknown error' line — after the user had already waited through the 10-second cost-preview window. Zero ideas, no diagnostic, no hint about what to do. Three changes per the resolved decisions: CV11 + T4 — orchestrator-level entry-point wrap (NOT per-call whack-a-mole). The public runBrainstorm becomes a thin wrapper that delegates to runBrainstormImpl inside try/catch; the catch runs classifyBrainstormError on the thrown value. Adding a new internal SQL call to runBrainstormImpl is automatically covered — codex F#20's 'scope too narrow' concern resolves structurally. A3 — reuse StructuredAgentError (the v0.19.0 envelope every new agent-facing surface uses) with code='brainstorm_timeout'. No new BrainstormError class; matches CLAUDE.md's stated convention. Future typed errors in brainstorm follow the same pattern. T4 + codex F#19 — classifier matches SQLSTATE 57014 specifically (the spec-defined query_canceled code) via postgres.js .code, alternate .sqlState, or message-substring fallback. Hint wording reads 'query canceled' (generic) covering all three PG cancel sub-causes: statement_timeout (often PgBouncer transaction-mode), lock_timeout, user-cancel. Honest under each. CV11 CLI formatter — runBrainstormCli (used by both gbrain brainstorm AND gbrain lsd) catches StructuredAgentError before main()'s catch-all sees it. Prints in the cli.ts:188-191 OperationError-block shape: 'Error [<code>]: <message>' then ' Hint: <hint>'. JSON mode emits the structured envelope (matches serializeError shape). Non-typed errors fall through to the dispatcher's existing catch — natural shape preserved (codex F#20 — no broad swallowing). Files: - src/core/brainstorm/error-classify.ts (new): isQueryCanceledError + classifyBrainstormError pure helpers. Module isolated from the orchestrator so the classifier can be unit-tested without spinning up the full brainstorm pipeline. - src/core/brainstorm/orchestrator.ts: imports the helpers; public runBrainstorm becomes a try/catch wrapper around the unchanged runBrainstormImpl. ~30 LOC change with zero body edits below. - src/commands/brainstorm.ts: catches StructuredAgentError before the generic main() handler. Imports StructuredAgentError from '../core/errors.ts'. Tests in test/brainstorm-timeout.test.ts (14 cases, 43 assertions): - isQueryCanceledError: 8 cases covering postgres.js {code}, alternate {sqlState}, message-substring fallbacks for all 3 cancel sub-causes, case-insensitive SQLSTATE match, negative cases (different codes, non-DB errors, null/undefined/non-object inputs) - classifyBrainstormError: 5 cases pinning the StructuredAgentError envelope (class, code, message), hint covers all 3 PG sub-causes (codex F#19 honesty contract), non-57014 errors pass through with SAME REFERENCE (codex F#20 — no clone, no swallow), null/undefined pass through, classified Error.message channel is descriptive - Source-shape regression guards: orchestrator.ts imports the helpers AND wraps runBrainstormImpl in try/catch at the public entry point (NOT per-call); commands/brainstorm.ts has the CLI formatter recognizing StructuredAgentError with 'Error [' + 'Hint:' shape WARN-10 root cause (PgBouncer-friendly SQL shape for listPrefixSampledPages) deferred per the plan's out-of-scope rationale — this commit adds the diagnostic surfacing so users know what hit them instead of silent no-output. TODOS.md follow-up filed in Phase 6. Plan: ~/.claude/plans/system-instruction-you-are-working-async-popcorn.md (Phase 5) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * v0.38.3.0: capture smoke-test wave — VERSION + CHANGELOG + docs + llms regen VERSION 0.38.2.0 → 0.38.3.0; package.json mirror. CHANGELOG: ELI10-lead-first entry per CLAUDE.md voice rules. "Things you can now do" section covers all 12 user-visible fixes (BUG-1 frontmatter merge, BUG-2 /ingest empty body, WARN-1 dedup, WARN-3 friendly source error, WARN-5 capture --help, WARN-6 main --help, WARN-7 binary guard, WARN-8 provenance write+read, WARN-9 admin scopes, WARN-10 brainstorm timeout surfacing, WARN-2 type-overwrite documentation, WARN-4 facts:absorb suppression). "What you'd see in a concrete example" block shows real terminal output. "Things to watch" covers CV7 thin-client --source rejection, CV12 COALESCE- preserve UPDATE semantics, CV3 closed source_kind taxonomy, brainstorm diagnostic-only deferral, facts:absorb first-occurrence diagnostic. "To take advantage" verification block with paste-ready commands. "For contributors" credits @garrytan-agents for PR #1299 and links the plan + decision trace. TODOS.md: 7 new v0.39 follow-ups filed at the top (SQL-shape rewrite of listPrefixSampledPages for PgBouncer, magic-byte allowlist, facts:absorb root-cause trace, --source-kind override flag, ingest_capture Minion handler architecture migration, provenance-history table, ingest webhook provenance pass-through). CLAUDE.md: single consolidated v0.38.3.0 entry under "Key files" naming every touched file + every decision (D1, A1-A3, CQ1-CQ2, T1-T4, CV3, CV5-CV15) with their resolution. Future maintainers see the full surface from one paragraph instead of grepping the diff. llms.txt + llms-full.txt: regenerated via `bun run build:llms` per CLAUDE.md's mandatory chaser ('every CLAUDE.md edit needs a build:llms chaser or test/build-llms.test.ts fails in CI'). 7 cases pass post-regen. Verify gate passes: typecheck clean + all 8 shell pre-checks (privacy, jsonb, progress, wasm, admin-scope-drift, cli-executable, system-of- record, eval-glossary-fresh, synthetic-corpus-privacy, skill-brain- first, fuzz-purity). Trio audit: VERSION: 0.38.3.0 package.json: 0.38.3.0 CHANGELOG: ## [0.38.3.0] - 2026-05-22 Plan: ~/.claude/plans/system-instruction-you-are-working-async-popcorn.md (Phase 6) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(tests): update legacy tests for v0.39.3.0 implementation shape changes Two CI failures from the post-merge state, both pre-existing tests pinning implementation detail that v0.39.3.0's fixes legitimately changed. Repair the tests, not the production behavior. 1) test/commands/capture.test.ts — 2 cases ('uses first non-empty line as the title' + 'caps title at 80 chars') were checking the YAML literal `title: "..."` (double-quoted). v0.39.3.0 Phase 2a (BUG-1 frontmatter merge) replaced the hand-rolled `JSON.stringify`- quoting with `matter.stringify()`, which follows YAML defaults: simple strings emit unquoted (`title: Real first line`), special- char strings get single-quoted. The semantic ("title equals X") is correct; the literal-quoting check was incidental. Parse the YAML and assert on the value via gray-matter. 2) test/fix-wave-structural.test.ts — the v0.36.1.x #1077 PKCE- public-clients regex pinned the exact destructure `const { name, scopes, tokenTtl, ... } = req.body`. v0.39.3.0 Phase 4b (WARN-9 admin scopes normalization) moved `scopes` to a separate read line so the route can accept BOTH `scopes` (admin SPA) AND `scope` (OAuth wire singular) via `?? `. Relax the destructure regex to accept either layout AND add a NEW regex pinning the `scopes ?? scope` fallback so the actual v0.39.3.0 contract is load-bearing. PKCE-fix assertions (tokenEndpointAuthMethod === 'none' + client_secret_hash = NULL + token_endpoint_auth_method = 'none') unchanged. Both fixes are tests-only — no production code change. Verify gate clean post-fix; 30/30 cases pass in the two affected test files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: rename v0.38.3.0 → v0.39.3.0 inline references across the wave The merge resolution bumped VERSION + package.json + CHANGELOG header to v0.39.3.0 (per user direction; higher than master's existing v0.39.0.0 + v0.39.1.0 commit subjects). But the wave's source code comments + test file headers + the smoke-test report's Editor's Note + the CLAUDE.md extension entry all still carried the v0.38.3.0 internal label. Sed-pass across 25 files (24 in-tree + the smoke-test report Editor's Note): - 13 src/ files: capture.ts, cli.ts, serve-http.ts, operations.ts, import-file.ts, types.ts, utils.ts, scope.ts, postgres-engine.ts, pglite-engine.ts, facts/absorb-log.ts, brainstorm/orchestrator.ts, brainstorm/error-classify.ts - 10 test files: capture-build-content, capture-runcapture, put-page-provenance, scope-normalize, cli-help-discoverability, brainstorm-timeout, facts-absorb-log, import-file, e2e/engine-parity, e2e/serve-http-ingest-webhook - docs/v0.38-smoke-test-report.md (Editor's Note only — the filename + the report body's references to v0.38.0.0 stay since they identify the historical subject) - CLAUDE.md (extension entry tag) Comments-only change; no production behavior shift. Existing tests continue to pass (175 cases across 10 wave-specific test files). llms.txt + llms-full.txt regenerated to keep the CLAUDE.md update in sync (per CLAUDE.md's mandatory build:llms chaser). Trio audit re-confirmed: VERSION: 0.39.3.0 package.json: 0.39.3.0 CHANGELOG: ## [0.39.3.0] - 2026-05-22 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: garrytan-agents <garrytan-agents@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
83c4ca0564 |
v0.39.0.0 feat: brainstorm cost cathedral (P1-P7) + page_links schema fix (#1283)
* feat(brainstorm): T1 cost guardrails + judge chunking + far-set cap Ports PR #1234 with a typed-error swap (Q2). Brings: - `--max-cost`, `--max-far-set`, `--strict-budget`, `--judge-model`, `--max-ideas-per-judge-call` CLI flags on `gbrain brainstorm` / `lsd` - Domain-bank prefix-cap + shuffle + final-trim to `m` by distance score - Judge auto-chunks idea sets > 100 across multiple LLM calls - UTF-16 surrogate sanitization on cross prompts - Phase-0.5 hard cost ceiling + mid-run cost guard Phase-1 diff from PR #1234: per-cross error-rethrow uses inline typed `BudgetExhausted` instead of string-match on the error message. Phase 2 of the wave will move the class to `src/core/budget/budget-tracker.ts` and the orchestrator will import it. Postmortem doc + 12-case regression test included verbatim from #1234. T1 of the brainstorm cost cathedral plan (~/.claude/plans/system-instruction-you-are-working-rippling-moth.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(budget): T2 BudgetTracker + BudgetExhausted + audit-week helper The keystone primitive for the v0.37.x budget cathedral. One class, one typed error, one schema-stable audit JSONL. Replaces three parallel copies (brainstorm orchestrator inline class, cycle/budget-meter, eval-contradictions cost-prompt/tracker) — those adapt to this one in T5/T6. Contracts pinned by 26 unit tests: - TX1: record() throws BudgetExhausted(reason:'cost') when cumulative spend > cap. A single underestimated call cannot leak past the cap. - TX2: reserve() hard-fails with BudgetExhausted(reason:'no_pricing') when cap is set + model is missing from pricing maps. When cap is unset, legacy warn-once behavior is preserved. - A3 amended: extractUsageFromError(err, fallback) returns err.usage when SDK provides it, else the pessimistic fallback (caller passes maxOutputTokens, not the optimistic pre-call estimate). - onExhausted callback fires once, synchronously, before the throw propagates. Callbacks do sync I/O (writeFileSync) for checkpoint persistence. - Audit JSONL is schema-stable: every line carries schema_version=1. Reorderings tolerated, field renames are breaking. Also ships src/core/audit-week-file.ts — the shared ISO-week filename helper consumed by every audit writer in T4. Year-boundary correctness pinned by 5 cases including 2020-W53 (the 53-week year), 2025-W01 rolling in from 2024-12-30 (Monday), and the GBRAIN_AUDIT_DIR override. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(gateway): T3 withBudgetTracker + AsyncLocalStorage composition TX5: every gateway.chat / embed / rerank call now auto-composes the active BudgetTracker via a module-internal AsyncLocalStorage. No per-call injection seam, no flag plumbing — callers wrap their entrypoint in `withBudgetTracker(tracker, async () => { ... })` and every downstream LLM call honors the cap. Outside any scope, the gateway is a budget no-op (back-compat with the pre-v0.37 contract). Wiring: - chat(): reserves on entry using prompt-char heuristic + opts.maxTokens. Records actual usage from result.usage on success; on failure, charges the pessimistic A3-amended fallback so the cap is real. - embed(): reserves total estimated input tokens (chars / chars-per-token). Records the same total in try/finally; SDK doesn't surface per-batch embed token counts. - rerank(): reserves and records query + docs char count. Reranker pricing isn't in the canonical map yet, so reserve() takes the warn-once path under no-cap and the TX2 hard-fail under cap. 6 unit cases pin the contract: chat auto-composes, outside-scope is no-op, nested scope restores outer, over-cap reserve throws BEFORE provider call (proves circuit breaker), TX1 mid-run cumulative cap fires via record(), parallel Promise.all scopes do not bleed trackers. All 255 existing gateway tests and 50 brainstorm tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(audit): T4 migrate 4 audit writers to shared isoWeekFilename helper Q1: extract the ISO-week filename math into one canonical helper (src/core/audit-week-file.ts, landed in T2) and migrate every audit JSONL writer in the codebase to consume it. Sites migrated: - src/core/minions/handlers/shell-audit.ts (shell-jobs-YYYY-Www.jsonl) - src/core/facts/phantom-audit.ts (phantoms-YYYY-Www.jsonl) - src/core/audit-slug-fallback.ts (slug-fallback-YYYY-Www.jsonl) - src/core/cycle/budget-meter.ts (dream-budget-YYYY-Www.jsonl) Each call site had its own copy of the ISO-week-from-Date algorithm. They mostly agreed but subtle drift was already accumulating (one used local time, one approximated the Thursday-anchor formula, etc.). One helper, one set of regression tests, no drift. Compute helpers (computeAuditFilename, computePhantomAuditFilename, computeSlugFallbackAuditFilename) are preserved as thin wrappers so existing import sites and tests don't break. All audit + slug-fallback + phantom + budget-meter tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(cycle): T5 BudgetMeter schema_version=1 + golden fixture (A2 amended) Adapter pass: the existing BudgetMeter keeps its public shape (`BudgetMeter`, `SubmitEstimate`, `BudgetCheckResult`) verbatim so every dream-cycle call site keeps working without rewires. The audit JSONL grew one new field on every line: `schema_version: 1`. A2 amended: the codex outside-voice review relaxed the byte-stable contract to schema-stable. Field reorderings are tolerated; the documented set (schema_version, ts, phase, event, model, label, plus per-event cost or token fields) is what every consumer can rely on. Renames or removals are breaking. test/fixtures/dream-budget-schema-v1.jsonl carries one canonical row per event variant (submit / submit_denied / submit_unpriced) as documentation of the schema. The new in-suite case in test/budget-meter.test.ts walks every emitted line and asserts the fields are present + the right type. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(eval): T6 wrap eval-contradictions runner in withBudgetTracker The runner now installs a BudgetTracker scope around its body so every gateway-layer chat / embed / rerank call (the judge model + per-query embedding) auto-records via the AsyncLocalStorage from T3. Currently telemetry-only — the existing CostTracker remains the primary soft- ceiling enforcement, so the public --budget-usd surface and PreFlightBudgetError shape are byte-identical. The wiring is the seam: future waves can promote the cap to BudgetTracker semantics (TX1 + TX2 semantics on cumulative + no_pricing) by passing maxCostUsd through to BudgetTracker without touching the CLI. All 79 eval-contradictions tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(doctor): T7 --remediate budget tracker + checkpoint + --resume (A4) A4 amended: doctor --remediate gains a resumable cost ceiling. The runRemediate loop now runs inside `withBudgetTracker(tracker, ...)` so every gateway-routed LLM call inside a Minion handler (synthesize, patterns, consolidate, embed) honors the cap. When BudgetExhausted fires mid-run, the onExhausted callback persists a checkpoint of completed step ids + idempotency_keys to ~/.gbrain/remediation/<plan_hash>.json BEFORE the throw propagates, and the catch surfaces a paste-ready --resume hint. Wire-up: - New --resume <plan_hash> flag (with implicit "most recent matching" when no hash given) loads the checkpoint and skips already- completed steps. Mismatched plan_hash refuses with an explicit message. - --max-cost is now an alias for --max-usd. Both spellings honored and threaded through to BudgetTracker.maxCostUsd so the cap is a real ceiling, not just pre-flight advice. - On BudgetExhausted, exit 1 with the resume hint; on clean completion, clear the checkpoint. New file: src/core/remediation-checkpoint.ts with computePlanHash / save / load / list / clear helpers. Atomic write via .tmp + rename. Pinned by 13 unit cases including determinism + sort-order invariance + schema-mismatch return-null + atomic-rename. All 48 doctor.test.ts cases still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(subagent): T8 A1 ordering ASCII diagram before acquireLease Documents the load-bearing ordering invariant: the gateway's BudgetTracker reserve() runs (implicitly, via AsyncLocalStorage) BEFORE acquireLease() inside the subagent loop. A BudgetExhausted throw must NOT consume a rate-lease slot, because the lease is the rate-limit pacer for the entire fleet. The handler body intentionally does NOT explicitly thread BudgetTracker; TX5 (gateway-layer composition) handles that. The comment is the reader's signpost. No behavioral change. All 58 subagent tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(diarize): T9 payload-fitter (P6) with batch + summarize + gate Generic utility for fitting arbitrarily-large item lists into a downstream caller's per-call token budget. Two strategies: - 'batch': deterministic token-budgeted chunking. No LLM calls. The fitted list shape matches the input; the caller decides how to consume it (e.g. brainstorm judge concatenates per-chunk results). Surfaces a `dropped` count for items that exceed the per-call cap. - 'summarize': embed-cluster into ceil(items/4) groups via cheap deterministic nearest-neighbor on cosine; Haiku-summarize each cluster via Promise.allSettled at parallelism=4 (Perf1). Each Haiku call composes the active BudgetTracker via the gateway's AsyncLocalStorage scope (T3) — no per-call injection. Quality gate (codex outside-voice finding #4): when summarize's success_ratio < min_success_ratio (default 0.75), the result is flagged `degraded: true` so the caller (brainstorm) can decide to surface a partial result or abort. The fitter itself preserves the successful subset either way. Tested via 4 cases across two files (T3 contract): - happy path (all clusters succeed → degraded=false) - partial failure tolerated (1/5 fails, success_ratio=0.8 > 0.75 → degraded=false) - high-failure rate flips the gate (3/5 fails → degraded=true) - budget-respecting (BudgetExhausted thrown mid-cluster propagates via Promise.allSettled) 11 unit cases across batch + summarize. Brainstorm + cost-guardrails tests still green; judges.ts internal chunking deferred to a follow-up wave (TODOS) so the existing chunked-batch contract stays byte-stable during this drop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(brainstorm): T10 checkpoint + --resume with full idea bodies (P7) The brainstorm cathedral capstone. Crashed runs can resume cleanly via `gbrain brainstorm --resume <run_id>` (and `gbrain lsd --resume` etc). TX3 load-bearing contract: completed_crosses on disk carries FULL idea bodies (~50KB per run), not just counts. The resumed BrainstormResult contains the pre-crash ideas (loaded from disk) merged with the post- resume ideas — codex's outside-voice finding was that a resume that produces only "what we generated this run" is silent partial output. TX4 single rule: --resume continues any cross not in completed_crosses. The proposed --retry-failed was dropped per codex review; failed AND never-attempted crosses both go through --resume. A5 amended: run_id = sha256(question + profile + sort(close_slugs) + sort(far_slugs)).slice(0,16). NO embedding bits — stable across embedding-model swaps. 7-day mtime-based GC. Q2 fold: orchestrator.ts drops its inline BudgetExhausted class and re-exports the canonical one from src/core/budget/budget-tracker.ts (Phase 2). runBrainstorm now wraps the body in withBudgetTracker so every gateway-layer chat call auto-records cost. The cap remains opts.maxCostUsd (default $5). New CLI flags: --resume <run_id> Continue any cross not in completed_crosses. Refuses to start when run_id doesn't match the active inputs (paste-ready hint). --force-resume Bypass the 7-day staleness gate. --list-runs Print saved run_ids and exit. Cycle purge phase (the 9th cycle phase) now also GCs stale brainstorm checkpoints alongside op_checkpoints (~7d window). Tests: - 20 unit cases in test/brainstorm/checkpoint.test.ts: computeRunId is deterministic + slug-array-order invariant + stable across embedding-model swaps; round-trip preserves ideas verbatim; saveCheckpoint atomic via .tmp+rename; loadCheckpoint returns null on missing/schema-mismatch/corrupt-JSON; gcStaleCheckpoints unlinks >N days; listRuns mtime-ordered. - 3 E2E cases in test/e2e/brainstorm-resume.test.ts: crash on cross 4 → first run aborts with checkpoint of crosses 1..N with full idea bodies; second run with resumeRunId merges pre-crash + post-resume ideas (TX3 contract); mismatched run_id refuses with paste-ready hint. The PGLite schema-gap workaround in the E2E (CREATE VIEW page_links AS SELECT * FROM links) is filed as a follow-up in TODOS T12 — the real-engine brainstorm path needs that view to materialize as a canonical schema fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: T11 + T12 wave release docs + deferred follow-ups CHANGELOG entry for the brainstorm cost cathedral (Unreleased slot; /ship will assign the next version): - ELI10 lead per CLAUDE.md voice rules - "How to turn it on" with paste-ready commands - "Things to watch" calls out the A4 semantic shift for `doctor --remediate --max-usd` (pre-flight → mid-run abort with resumable checkpoint) - Itemized changes by file/area - "For contributors" section noting the 73 new tests + the PGLite schema-gap workaround for the E2E CLAUDE.md Key Files: 6 new entries for budget-tracker, audit-week-file, gateway withBudgetTracker, payload-fitter, brainstorm/checkpoint, remediation-checkpoint. Regenerated llms-full.txt + llms.txt (passes test/build-llms.test.ts). docs/incidents/2026-05-20-lsd-cost-explosion.md gains a closing "Shipped in v0.37.x (the budget cathedral wave)" section listing P1-P7 completion status + the deferred follow-ups so the incident's audit trail closes the loop. TODOS.md gets a new top section for the wave's deferred items: - PGLite `page_links` schema gap fix - Explicit --max-cost on extract / enrich / integrity auto - P5 config-schema budgets: block in ~/.gbrain/config.json - Multi-day brainstorm resume (>7d) - Async-batched audit writes (profiling trigger criterion) - BudgetLedger unification with BudgetTracker - judges.ts internal chunking → payload-fitter delegation Also: fixed a payload-fitter typecheck error (ChatFn import). Final typecheck is clean on every file the wave touched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(schema): F1 page_links view alias for both engines Brainstorm's domain-bank queries reference `page_links` (pglite-engine.ts:896, postgres-engine.ts:959) but the canonical table is `links`. Without the alias view, `gbrain brainstorm` against PGLite fails with `relation "page_links" does not exist`; the same was a latent bug on Postgres. This commit lands the fix at three sites: 1. `src/core/pglite-schema.ts` — embedded schema bundle gets the view at table-bundle time, so fresh PGLite installs are correct from boot. 2. `src/core/migrate.ts` v81 (`page_links_view_alias`) — existing brains on either engine pick up the view via `gbrain apply-migrations`. CREATE OR REPLACE VIEW is idempotent; re-running is safe. 3. `test/e2e/brainstorm-resume.test.ts` — removed the ad-hoc workaround view from the test setup. The E2E now exercises the same schema path real users will see. `TODOS.md` entry for the gap closed out. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(brainstorm): F2 pre-flight --max-cost refusal smoke E2E Pins the user-facing path that closed the original \$50 incident: when the pre-run estimate exceeds the configured cap, runBrainstorm throws BudgetExhausted with reason='cost' and a paste-ready hint pointing at --limit / --max-cost / --max-far-set before any chat call happens. The four assertions are the four things a real user can verify after the throw lands: 1. Typed BudgetExhausted (not a generic Error) 2. reason === 'cost' (not runtime or no_pricing) 3. Message names the remediation flags 4. No provider HTTP would have happened (chat.crossCalls === 0) Uses the same PGLite engine + tinyProfile + stub chatFn as the existing --resume tests. Hermetic; ~5s wallclock. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(reindex-code): F3 --max-cost flag via withBudgetTracker Wires gbrain reindex --code into the v0.38 budget cathedral. When the caller passes --max-cost N (or --max-cost-usd N), runReindexCode wraps its per-page import loop in withBudgetTracker so every gateway.embed() call inside importCodeFile auto-composes the cap. On BudgetExhausted, the partial-progress result reports what got reindexed before the cap fired plus a synthetic failure row naming the cap throw. reindex-code is idempotent (content_hash short-circuit in importCodeFile), so a re-run after a budget abort picks up where the cap fired — no manual checkpoint state needed. Both --max-cost and --max-cost-usd are accepted (symmetry with brainstorm which uses --max-cost, and a precedent for the spelling we want long-term). When --max-cost is unset, the body runs outside any tracker scope — byte- stable pre-F3 behavior for legacy callers. Files: src/commands/reindex-code.ts: - ReindexCodeOpts.maxCostUsd?: number - runReindexCode wraps body in withBudgetTracker when set - runReindexCodeCli parses --max-cost / --max-cost-usd - BudgetExhausted caught + returned as partial-progress result test/reindex-code-max-cost.serial.test.ts (NEW): - dry-run + maxCostUsd happy path - empty-brain + maxCostUsd hits early-return cleanly - no tracker installed when cap is unset (regression guard for the conditional wrap) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(schema): narrow page_links view projection to bootstrap-safe columns The v0.38 page_links view alias initially used SELECT * FROM links, which broke the pre-v0.13 bootstrap test: applyForwardReferenceBootstrap drops link_source + origin_page_id to simulate the pre-v0.13 schema shape, but the SELECT * view created a dependency that blocked the column DROP. Engine queries only reference pl.id (via COUNT(*)) and pl.to_page_id, so the view's projection is now SELECT id, from_page_id, to_page_id FROM links — what callers actually use, no more. This unblocks legacy-brain upgrade paths AND keeps the bootstrap forward-reference probes safe. Bootstrap suite: 15/15 pass after the change. Also files a P0 TODO for a pre-existing test failure (test/doctor-report-remote.test.ts "full report on healthy brain") that fails on master too — out of scope for this wave but noticed during /ship triage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump version to v0.39.0.0 Brainstorm cost cathedral wave (P1-P7). MINOR bump per user direction: new architectural seam (gateway-layer BudgetTracker via AsyncLocalStorage), 5 new modules, new CLI flags (--max-cost / --resume / --list-runs / --force-resume), new migration v81 (page_links view alias). No breaking changes — BudgetExhausted re-exported from orchestrator for back-compat; --max-usd preserved as alias for --max-cost; eval-contradictions --budget-usd surface byte-identical. CHANGELOG entry renamed from [Unreleased] to [0.39.0.0] and adds the mandatory "To take advantage of v0.39.0.0" block per CLAUDE.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(isolation): rename 3 env-mutating tests to .serial.test.ts (CI fix) CI's `check:test-isolation` flagged three tests added in the v0.39.0.0 cathedral that directly mutate `process.env` across test boundaries: - test/brainstorm/checkpoint.test.ts (mutates GBRAIN_HOME) - test/core/audit-week-file.test.ts (mutates GBRAIN_AUDIT_DIR) - test/core/remediation-checkpoint.test.ts (mutates GBRAIN_HOME) Per CLAUDE.md rule R1: env-mutating tests either use withEnv() OR rename to *.serial.test.ts (the quarantine escape hatch). The mutation lives in beforeEach/afterEach which spans the whole describe block, so .serial rename is the cleaner fix — withEnv() would require restructuring every test. The serial-test runner gives them their own bun process; no cross- file env races. Verified: check:test-isolation passes (527 non-serial unit files clean), `bun run verify` passes, all 41 tests in the three renamed files pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
39e14cd50e |
v0.37.1.0 feat: brainstorm + lsd — bisociation idea generator grounded in your own brain (#1214)
* feat: brainstorm + lsd (v0.37 wave, pre-merge snapshot) Brainstorm + LSD bisociation idea generator. Will rebase + bump after master merge. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs: CHANGELOG voice tightening — strip meta-content from v0.37.1.0 entry CLAUDE.md gains an IRON RULE for CHANGELOG entries: the changelog describes what the user gets, not how the work happened. No mentions of review processes, plan files, decision tags, migration version drama, or 'what we caught and fixed before merging.' If a fact only exists because of the development workflow, it does not belong in release notes. Rewrite v0.37.1.0 entry to comply: cut the 'what we caught' section (architectural review drama), the 'plan + reviews' bullet, and the migration-renumbering aside. Entry shrinks 67 → 56 lines, every sentence now answers 'what can I do / how do I use it / what should I watch for.' Regenerated llms-full.txt to absorb the CLAUDE.md voice update. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |