mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
82cc7fff90429ef05585705b3d91a74afeffb4ce
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d4211f4176 |
v0.42.11.0 feat(skillopt): held-out eval gate, honest receipts, ENFORCE + ablation opts (#1759)
* feat(skillopt): wire held-out gate, honest receipts, ENFORCE + ablation opts Wire the F11 held-out gate into the orchestrator at checkpoint acceptance (runHeldOutGate was dead code); parse + thread --held-out through CLI, batch, fleet, background job, and the run_skillopt MCP op. Populate the real receipt.baseline_sel_score (was hardcoded 0) and add a final-test eval (test_score + baseline_test_score) via a shared scoreSkillOnTasks primitive. Fix the --no-mutate proposed.md write (was a stub) and enforce maxRuntimeMin. D16 ENFORCE in core mutation policy (assertBundledMutationHeldOut): mutating a bundled skill in place requires a non-empty (>=5), benchmark-disjoint held-out set or hard-refuses. Add three eval-internal ablation opts (reflectMode, disableValidationGate, optimizerMode='one-shot-rewrite') recorded in the receipt + audit; ROLLOUT_SUCCESS_THRESHOLD named constant. Security: run_skillopt MCP op validates skill_name (kebab-only) and confines caller-supplied benchmark/held-out paths to the skills dir for remote callers. * test(skillopt): held-out gate, ENFORCE, one-shot rewrite, runtime + receipt honesty New test/skillopt/rollout.test.ts (rollout had zero coverage). Held-out ENFORCE unit cases + one-shot-rewrite fence handling (whole-response unwrap, embedded-fence preserved, error path). E2E: F11 held-out BLOCKS/ALLOWS, bundled no-mutate write, reflectMode/disableValidationGate/optimizerMode, maxRuntimeMin abort, receipt baseline/test-score honesty, held-out/benchmark disjointness, D2 no-DB-pollution. * chore: bump version and changelog (v0.42.9.0) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: document skillopt held-out gate + bundled mutation requirement for v0.42.9.0 Wire --held-out into the skill-optimizer SKILL.md, guide flags/safety tables, and the tutorial's bundled-skill step: mutating a bundled skill in place now requires --allow-mutate-bundled AND --held-out (>=5 benchmark-disjoint tasks) or it hard-refuses. Add the --held-out flag row + F11 held-out gate to the guide; update the receipt contract to the honest baseline/test-score fields. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(gateway): AI SDK v6 toolLoop compat — multi-turn tool calls work again The ai@6.x bump tightened ModelMessage + tool-schema validation, which silently broke every multi-turn tool loop. Both `gbrain skillopt` rollouts and production background `subagent` jobs route through `chat()`/`toolLoop` and crashed the moment the model called a tool ("messages do not match the ModelMessage[] schema" / "schema is not a function"). Surfaced end-to-end by the SkillOpt real-LLM eval. Three fixes: - chat(): wrap tool defs with the SDK's `jsonSchema()` helper instead of a bare `{jsonSchema}` object (v6 asSchema() treated the bare object as a thunk and threw). - chat(): new exported pure `toModelMessages()` converts gbrain's provider-neutral ChatMessage[] into v6 ModelMessage[] — tool results ride a dedicated `role:'tool'` message with structured `{type,value}` output; null output preserved as json null. Load-bearing for the production subagent path, not just skillopt. - rollout.ts: replace the inline params→schema mapper (dropped `items` on array params) with the shared `paramDefToSchema` single source of truth. Pinned by test/gateway-model-messages.test.ts (8 cases). Folds into the open v0.42.9.0 PR (#1759) — these complete the eval-readiness wave by making skillopt actually run against a live model. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(skillopt): budget no-pricing for Haiku silently scored every rollout 0 Surfaced by the SkillOpt real-LLM eval (Track B). Two coupled bugs that made a budget-capped Haiku run report a vacuous "0/N" measurement in ~2ms with zero LLM calls — indistinguishable from a real deficient-skill score: 1. Claude Haiku 4.5's canonical dateless id (`claude-haiku-4-5`) was missing from anthropic-pricing.ts (only the dated `-20251001` was present). With `--max-cost` set, BudgetTracker.reserve() threw no_pricing on the FIRST chat() of every rollout. Added the dateless entry (sonnet already had its dateless form). 2. runValidationGate swallowed that BUDGET_EXHAUSTED error — runWithLimit settled it as {ok:false}, which the gate turned into median:0. A pricing/cap crash became a fake score. The gate now scans settled results for isMustAbortError() and re-throws so the caller aborts loudly; ordinary (non-abort) rollout errors still fail-open to 0 (judge-hiccup posture kept). Pinned by test/skillopt/validate-gate-abort.test.ts (3 cases). Folds into the open v0.42.9.0 PR (#1759). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): llms-full.txt over size budget — drop what-schemas-unlock from full bundle The toolLoop + budget bug-fix annotations grew CLAUDE.md, pushing llms-full.txt to 756KB over the 750KB FULL_SIZE_BUDGET (the `build-llms > size budget` test failed, failing the `test` CI job). CLAUDE.md stays inlined by design (it's the point of the one-fetch bundle), so per the budget comment's own guidance ("ship with includeInFull=false exclusions") this excludes docs/what-schemas-unlock.md (15.4KB value-explainer, not load-bearing operational reference) from llms-full.txt; it stays linked in llms.txt. Bundle now 740KB with ~9KB headroom. No budget bump — 750KB is near the ~190k-token-context fit ceiling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(ci): re-admit policy docs into ci-cache-hash before doc relocation docs/**/*.md is deny-listed from the CI cache hash (test-irrelevant). The CLAUDE.md restructure moves test/release POLICY into docs/TESTING.md + docs/RELEASING.md, which DO carry contracts the test suite reads. Without re-admitting them, a policy-only edit would produce the same cache hash and skip the test shard that runs the build-llms + doc-history guards (false-pass). Adds an ALLOW_PATTERNS re-admit step after the deny, scoped to the named policy docs (not a blanket docs un-deny). Lands FIRST, before any doc moves. Pinned by 3 new cases in test/scripts/ci-cache-hash.test.ts: TESTING.md + RELEASING.md edits MUST change the hash; docs/guide.md still must not. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(docs): relocate Key files / thin-client / Testing out of CLAUDE.md (verbatim) CLAUDE.md had grown to 592KB / ~147k tokens auto-loaded every session (~77% of the llms-full.txt single-fetch bundle). The per-file index was append-only by mandate. This is the exact thin-dispatcher-vs-fat-blob anti-pattern gbrain exists to fix, so CLAUDE.md becomes a thin orientation + resolver that points at on-demand docs. This commit is the VERBATIM move (content-preserving — the next commit compresses): - docs/architecture/KEY_FILES.md <- ## Key files + the calibration key-files cluster + Schema Cathedral v3 impl detail - docs/architecture/thin-client.md <- ## Thin-client routing - docs/TESTING.md <- ## Testing - ## Commands DROPPED (18 'added in vX.Y' history blocks; current surface is gbrain 0.41.38.0 -- personal knowledge brain USAGE gbrain <command> [options] SETUP init [--pglite|--supabase|--url] Create brain (PGLite default, no server) migrate --to <supabase|pglite> Transfer brain between engines upgrade Self-update check-update [--json] Check for new versions doctor [--json] [--fast] Health check (resolver, skills, pgvector, RLS, embeddings) integrations [subcommand] Manage integration recipes (senses + reflexes) PAGES get <slug> Read a page put <slug> [< file.md] Write/update a page delete <slug> Delete a page list [--type T] [--tag T] [-n N] List pages SEARCH search <query> Keyword search (tsvector) query <question> [--no-expand] Hybrid search (RRF + expansion) ask <question> [--no-expand] Alias for query IMPORT/EXPORT import <dir> [--no-embed] Import markdown directory sync [--repo <path>] [flags] Git-to-brain incremental sync sync --watch [--interval N] Continuous sync (loops until stopped) sync --install-cron Install persistent sync daemon export [--dir ./out/] Export to markdown export --restore-only [--repo <p>] Restore missing supabase-only files [--type T] [--slug-prefix S] With optional filters FILES files list [slug] List stored files files upload <file> --page <slug> Upload file to storage files upload-raw <file> --page <s> Smart upload (size routing + .redirect.yaml) files signed-url <path> Generate signed URL (1-hour) files sync <dir> Bulk upload directory files verify Verify all uploads EMBEDDINGS embed [<slug>|--all|--stale] Generate/refresh embeddings LINKS link <from> <to> [--type T] Create typed link unlink <from> <to> Remove link backlinks <slug> Incoming links graph <slug> [--depth N] Traverse link graph (returns nodes) graph-query <slug> [--type T] Edge-based traversal with type/direction filters [--depth N] [--direction in|out|both] TAGS tags <slug> List tags tag <slug> <tag> Add tag untag <slug> <tag> Remove tag TIMELINE timeline [<slug>] View timeline timeline-add <slug> <date> <text> Add timeline entry TOOLS extract <links|timeline|all> Extract links/timeline (idempotent) [--source fs|db] fs (default) walks .md files; db iterates engine pages [--dir <brain>] brain dir for fs source [--type T] [--since DATE] filters (db source) [--dry-run] [--json] publish <page.md> [--password] Shareable HTML (strips private data, optional AES-256) check-backlinks <check|fix> [dir] Find/fix missing back-links across brain lint <dir|file> [--fix] Catch LLM artifacts, placeholder dates, bad frontmatter orphans [--json] [--count] Find pages with no inbound wikilinks salience [--days N] [--kind P] v0.29: pages ranked by emotional + activity salience anomalies [--since D] [--sigma N] v0.29: cohort-based statistical anomalies (tag, type) transcripts recent [--days N] v0.29: recent raw .txt transcripts (local-only) dream [--dry-run] [--json] Run the overnight maintenance cycle once (cron-friendly). See also: autopilot --install (continuous daemon). check-resolvable [--json] [--fix] Validate skill tree (reachability/MECE/DRY) report --type <name> --content ... Save timestamped report to brain/reports/ BRAIN (capture / ideate / explore — v0.37/v0.38) capture [content] [--file PATH] Single entrypoint for getting content into the brain [--stdin] [--slug s] [--type t] Inline content / file / stdin; writes to inbox/ by default [--source ID] [--quiet|--json] Multi-source brains: route to a non-default source brainstorm <question> [--json] Bisociation idea generator (hybrid search + far-set + judge) [--save|--no-save] [--limit N] lsd <question> [--json] Lateral Synaptic Drift: inverted-judge brainstorm [--save|--no-save] [--limit N] rewarding far-from-obvious + axiomatic inversions SOURCES (multi-repo / multi-brain) sources list Show registered sources sources add <id> --path <p> Register a source (id = short name, e.g. 'wiki') sources remove <id> Remove a source + its pages sync --all Sync all sources with a local_path sync --source <id> Sync one specific source repos ... DEPRECATED alias for 'sources' (v0.19.0) CODE INDEXING (v0.19.0 / v0.20.0 Cathedral II) code-def <symbol> [--lang l] Find the definition of a symbol across code pages code-refs <symbol> [--lang l] Find all references to a symbol (JSON-first) code-callers <symbol> Who calls this symbol? (v0.20.0 A1) code-callees <symbol> What does this symbol call? (v0.20.0 A1) query <q> --lang <l> Filter hybrid search to one language (v0.20.0) query <q> --symbol-kind <k> Filter to symbol type (function|class|method|...) (v0.20.0) reconcile-links [--dry-run] Batch-recompute doc↔impl edges (v0.20.0) reindex-code [--source id] [--yes] Explicit code-page reindex (v0.20.0) sync --strategy code Sync code files into the brain JOBS (Minions) jobs submit <name> [--params JSON] Submit background job [--follow] [--dry-run] jobs list [--status S] [--limit N] List jobs jobs get <id> Job details + history jobs cancel <id> Cancel job jobs retry <id> Re-queue failed/dead job jobs prune [--older-than 30d] Clean old jobs jobs stats Job health dashboard jobs work [--queue Q] Start worker daemon (Postgres only) ADMIN stats Brain statistics health Brain health dashboard history <slug> Page version history revert <slug> <version-id> Revert to version features [--json] [--auto-fix] Scan usage + recommend unused features autopilot [--repo] [--interval N] Self-maintaining brain daemon config [show|get|set] <key> [val] Brain config storage status [--repo <path>] Storage tier status and health [--json] (git-tracked vs supabase-only) serve MCP server (stdio) serve --http [--port N] HTTP MCP server with OAuth 2.1 --token-ttl N Access token TTL in seconds (default: 3600) --enable-dcr Enable Dynamic Client Registration --public-url URL Public issuer URL (required behind proxy/tunnel) call <tool> '<json>' Raw tool invocation version Version info --tools-json Tool discovery (JSON) Run gbrain <command> --help for command-specific help. + the per-command KEY_FILES entries; content stays in git) CLAUDE.md gains: a Reference map (resolver), a Maintaining section (the anti-disease rule), and a Cross-cutting invariants subsection under Architecture so the must-never-violate rules (trust fail-closed, sourceScopeOpts isolation, JSONB trap, engine parity, contract-first, migrations, multi-source) still auto-load after the index moved out. Result: CLAUDE.md 592KB -> 61KB; llms-full.txt 740KB -> 210KB (new docs link-only until compressed). build-llms drift + budget test green; verify 29/29 green. The pre-move content is recoverable at git show <this^>:CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(docs): compress relocated docs to current-state + add recurrence guard Compresses the verbatim-relocated reference docs from append-only release-history to current-state-only (the disease cure), then makes recurrence structurally impossible via a CI guard. Compression (fan-out subagents + adversarial verify, audited mechanically): - KEY_FILES.md 453KB -> 356KB; TESTING.md 42KB -> 38KB; thin-client.md already clean. - 393/393 entries preserved; every src/test/scripts path from the verbatim original survives (mechanical comm-check); zero bolded **v0. markers remain. - Conservative ratio (~22%) because the content is invariant-dense — correctness over brevity. Dropped: **vX.Y.Z (#NNN):** clauses, codex/review tags, contributor credits, PR-numbers-as-ids, pre-fix/then/was-now history deltas. Kept: every exported symbol, invariant, and Pinned-by reference. Verbatim original recoverable at git show <relocation-commit>:docs/architecture/KEY_FILES.md. Recurrence guard (scripts/check-key-files-current-state.sh, wired into verify + check:all): - HARD: bans the bolded **v0.<digit> marker in the reference docs (scoped — plain 'as of pgvector 0.7' prose is fine, no false positives). - HARD: CLAUDE.md size cap (90KB; currently 61KB) — the structural backstop. - Pinned by test/scripts/check-key-files-current-state.test.ts (7 cases). Content contracts (test/build-llms.test.ts, +5 cases per codex outside-voice): CLAUDE.md keeps inline ship IRON RULES (version format, document-release, never-hand-roll); AGENTS.md keeps its boot order; llms indexes the new docs; KEY_FILES stays link-only (not inlined). Privacy: scrubbed the relocated 'wintermute/chat/' source-boost examples + the literal harvest-lint regex to generic placeholders (legitimate in allowlisted CLAUDE.md; genericized for the new public docs per the privacy rule). Reverts the |
||
|
|
6ae94301a6 |
v0.41.26.1 fix: lock-renewal cathedral — closes ~39 worker crashes/day (supersedes #1567) (#1572)
* v0.41.26.1 fix: lock-renewal cathedral — closes ~39 worker crashes/day (supersedes #1567)
Production worker daemons against Supabase / PgBouncer were crashing
~39 times/day with `unhandledRejection at renewLock`. PR #1567
proposed the right try/catch shape; this wave incorporates it and
closes the entire bug class (4 inside-review + 8 outside-voice
findings absorbed via 9 locked design decisions).
What's fixed:
- `setInterval(async () => await renewLock(...))` replaced with a
sync wrapper around the new pure `runLockRenewalTick` function.
No more unhandled rejections escaping the timer callback.
- Second crash vector closed: `.catch()` on the stored
`executeJob(...).finally(...)` promise so failJob/completeJob
throws during the same outage can't propagate to
`process.on('unhandledRejection')`.
- Per-call `Promise.race` timeout (default `lockDuration/3`) bounds
hung renewLock calls so the re-entrancy guard can't wedge
indefinitely.
- Time-based abort (NOT count-based) so the worker releases its
lock BEFORE another worker can reclaim. With the prior 3-strike
count + 30s lockDuration, a 15s window let other workers race.
- Infrastructure aborts (`lock-renewal-failed`, `lock-lost`) don't
burn job attempts — `executeJob`'s catch consults the exported
`INFRASTRUCTURE_ABORT_REASONS` set and skips `failJob` so the
stall detector reclaims cleanly.
- Universal grace-eviction: 30s force-evict safety net now fires
for ANY abort reason, not just `job.timeout_ms`.
What's added:
- `src/core/minions/lock-renewal-tick.ts` (NEW): pure extracted
state-machine function + env-knob resolver. Three operator-tunable
knobs via env (max-failures-for-audit, call-timeout-ms,
safety-margin-ms) with stderr-warn-once on bad input + default
fallback.
- `src/core/audit/lock-renewal-audit.ts` (NEW): sibling of
`batch-retry-audit.ts`. Four outcomes: failure /
success_after_failure / gave_up / executeJob_rejected. JSONL at
`~/.gbrain/audit/lock-renewal-YYYY-Www.jsonl`.
- `src/core/audit/redact-connection-info.ts` (NEW): shared privacy
helper. Strips Postgres URLs, host=, user=, password=, IPv4 from
error messages before they hit audit JSONL. Wired into BOTH the
new lock-renewal audit AND the existing batch-retry audit
(privacy backfill — same risk class).
- `scripts/check-worker-lock-renewal-shape.sh` (NEW): CI guard
wired into `bun run verify`. Asserts the v0.41.22.1 bug pattern
(`lockTimer = setInterval(async ...)`) stays absent AND the pure
function call site survives refactors. Bug-pattern-specific so it
doesn't fight legitimate refactors (codex C12).
Tests: 64 new cases across 5 new test files. 182 existing minion +
worker tests still pass. All hermetic — no PGLite, no real network,
no `mock.module`.
Plan + 9 decisions + codex outside-voice review at
~/.claude/plans/system-instruction-you-are-working-humming-nygaard.md
Closes #1567 (incorporates the contributor's try/catch shape; closes
the bug class structurally).
Co-Authored-By: @garrytan-agents <noreply@github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: fill A-H gaps for v0.41.26.1 lock-renewal cathedral
The original v0.41.26.1 wave shipped 64 hermetic unit tests on the
pure tick function, audit primitives, and redactor. Post-ship audit
flagged 8 wiring gaps the pure tests can't see — A (launchJob
wiring), B (executeJob skip-failJob), C (.catch on stored promise),
D (INFRASTRUCTURE_ABORT_REASONS export), E (universal grace-evict),
F (executeJob_rejected end-to-end), G (re-entrancy guard at worker
layer), H (gold-standard E2E regression).
Now closed:
- **test/worker-lock-renewal-e2e.serial.test.ts** (1 test, gap H):
the headline gold-standard regression. Real PGLite + real
MinionWorker + executeRaw wrap that injects renewLock failures on
demand. Pins that the worker process DOES NOT crash via
unhandledRejection under sustained renewLock throws, the handler
observes abort.signal.aborted = true with reason
'lock-renewal-failed', and the audit JSONL contains both `failure`
and `gave_up` events. The exact v0.41.22.1 production bug class.
Quarantined to its own file because bun:test serial + PGLite has an
unresolved interaction with multiple MinionWorker-driven tests in
the same file (second test's queue.add hangs indefinitely).
- **test/worker-lock-renewal-shape.test.ts** (18 tests, gaps A-G):
source-shape behavioral pins. Greps worker.ts function bodies for
the patterns the locked decisions promised: launchJob calls
runLockRenewalTick + resolveLockRenewalKnobs + uses
lockRenewalAudit; tickInFlight declared and gated correctly; stored
executeJob promise has .catch with logExecuteJobRejected + console
stderr; abort.signal.addEventListener fires for any abort (not just
timeout_ms); INFRASTRUCTURE_ABORT_REASONS used inside executeJob's
catch with return-early shape. Bug-pattern-specific so a refactor
that genuinely improves the shape passes; a refactor that
accidentally strips a guarantee fails loud.
All 83 lock-renewal wave tests pass in 5.2s. 205 existing minion +
worker tests still green. No production code changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: typecheck errors in worker-lock-renewal-e2e.serial.test.ts
CI verify failed on the new E2E gap-fill test (commit
|
||
|
|
a7b79b66d4 |
feat: v0.41.19.0 Supavisor Retry Cathedral (#1537)
Engine-level retry primitive that closes the v0.41.17 production incident
where ~3,000 wiki links + timeline entries were silently lost per dream
cycle on a 16K-page brain. Supavisor's circuit-breaker takes 5-10s to
recover; the prior single-500ms-retry shape couldn't survive it.
ARCHITECTURE
============
Retry becomes a data-primitive contract, not a caller responsibility.
postgres-engine.ts + pglite-engine.ts now self-retry inside addLinksBatch,
addTimelineEntriesBatch, and upsertChunks. Every caller — current AND
future — inherits retry-for-free. CI lint guard `scripts/check-no-double-retry.sh`
fails the build if anyone re-wraps an engine batch method (preventing
3×3=9 retry amplification on incomplete reverts).
CODEX-HARDENED DEFAULTS
=======================
BULK_RETRY_OPTS = {maxRetries:3, delayMs:1000, delayMaxMs:10000,
jitter:'decorrelated'}. Total worst-case wait ≈12s covers full Supavisor
recovery window. Decorrelated jitter (AWS-style uniform(base, prevDelay*3)
capped at maxDelay) replaces 'full' which allowed near-zero retries that
re-hit the still-recovering breaker.
AbortSignal threading from MinionWorker.shutdownAbort.signal through
engine method opts → withRetry → abortableSleep. SIGTERM aborts sleeping
retries instead of blocking deploys for up to delayMaxMs.
OBSERVABILITY
=============
`~/.gbrain/audit/batch-retry-YYYY-Www.jsonl` records every retry event
(success-after-blip AND exhausted-retries). Built on the v0.40.4.0
audit-writer cathedral. Privacy posture: never logs slugs / page IDs /
content (mirrors shell-audit.ts).
`gbrain doctor` learns `batch_retry_health` check. Reads last 24h
(not 7d — codex H-9: avoid permanent noise from one historical blip).
Thresholds: ok (zero or <3 same-site), warn (>=3 same-site OR >=5
cross-site), fail (>=20 sustained breaker). Surfaces bad GBRAIN_BULK_*
env at startup (codex M-10). Corrupt-JSONL tolerant.
30-day audit pruning hooked into the dream cycle's purge phase (codex H-8
— implements the 'pruning convention' for real).
OPERATOR TUNING
===============
GBRAIN_BULK_MAX_RETRIES (int >= 0; 0 disables retries for debugging)
GBRAIN_BULK_RETRY_BASE_MS (int > 0)
GBRAIN_BULK_RETRY_MAX_MS (int >= base)
Bad values throw GBrainError with paste-ready fix hints at doctor startup,
not at first-retry mid-cycle.
VERIFICATION
============
- bun run verify: 28/28 checks green (includes 2 new lint guards:
check-no-double-retry, check-batch-audit-site)
- bun run test: 11453 pass / 1 pre-existing flake (schema-cli.test.ts —
confirmed by running on clean master, NOT introduced by this wave)
- bun run test:slow: 40/40 including new test/core/retry-stress.slow.test.ts
(100 batches × 30% blip rate × decorrelated jitter, zero row loss)
- bunx tsc --noEmit: 0 errors
REVIEWS
=======
- CEO review (SELECTIVE EXPANSION): 4 cherry-picks proposed, 4 accepted
- Eng review (2 passes): 10 findings, 0 critical gaps, architectural
pivot from per-site to engine-level wrap
- Codex independent review: 23 findings; 10 critical/high absorbed
(decorrelated jitter, 12s backoff window, AbortSignal, idempotency
proof, backfill unification, typed audit-site enum, doctor expiry
thresholds, audit pruning, env validation at doctor startup)
PR #1523 closed and absorbed (@garrytan-agents original extract.ts fix
preserved via co-author trailer; 5 test cases moved to test/core/retry.test.ts
with assertions adjusted for the v0.41.19.0 BULK_RETRY_OPTS defaults).
Co-authored-by: garrytan-agents <noreply@anthropic.com>
|
||
|
|
10816cba38 |
v0.41.18.0: gbrain onboard — the activation surface gbrain didn't have before (#1521)
* feat(schema): migrations v98/v99/v100 for onboard wave (A6 A10 A11 A13 A25, codex #1 #9 #10 #11 #12) Three schema additions supporting the gbrain onboard wave: v98 — links.link_kind nullable column (A10, codex finding #12). The NER extraction was originally going to add a new link_source='ner' provenance, but that would have forced every existing link_source='mentions' query (backlink-count filter, orphan-ratio, doctor checks) to update or metrics would drift across the cutover. Instead: keep link_source='mentions' for the storage layer AND add a nullable link_kind column. Three kinds: 'plain', 'typed_ner', NULL (legacy/unknown — semantically 'plain'). NOT in the links UNIQUE constraint so the storage shape stays compatible. v99 — timeline_entries dedup widening (A11, codex finding #11). Pre-v99 dedup key was (page_id, date, summary). The new --from-meetings extraction writes timeline entries with source='extract-timeline-from- meetings:<meeting-slug>', and codex caught that two meetings with the same date+summary on the same entity page would silently DO NOTHING — the second meeting's provenance is lost. Widened to (page_id, date, summary, source). Legacy rows (source='') preserve current dedup behavior. v100 — migration_impact_log table + content_chunks_stale_idx partial (A6 + A25 + A13 + codex findings #10 + #9). Bundled because both are consumed by the onboard pipeline and ship together. Impact log captures before/after metric stats so gbrain onboard --history shows real deltas; attribution columns (job_id, source_id, brain_id, started_at, idempotency_key) prevent concurrent runs misattributing to wrong migrations. content_chunks_stale_idx partial WHERE embedding IS NULL supports gbrain embed --stale + --priority recent (outer ORDER BY p.updated_at DESC uses existing idx_pages_updated_at_desc via JOIN). Plain NUMERIC columns; delta computed at read time (NOT a stored GENERATED column per eng-review D2 — zero PGLite parity risk). Slot history note: plan originally proposed v97/v98/v99 but master had already used v95 (links 'mentions' CHECK widening), v96 (facts conversation session index), and v97 (pages_dedup_partial_index) by ship time. Codex caught the collision; renumbered to v98/v99/v100. Test pin: test/schema-bootstrap-coverage.test.ts (100/100 migrations apply clean on PGLite), test/migrate.test.ts (152 cases pass). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(remediation): extract doctor remediation library (A1, codex finding #2) Pre-fix: src/commands/doctor.ts contained two CLI-shaped functions (runRemediationPlan + runRemediate) with hardcoded argv parsing, process.exit calls, and console.log emission. Onboard CLI shell and the upcoming MCP run_onboard op couldn't compose against them — the plan file's "100-LOC thin wrapper" assumption didn't survive codex's review of the actual source. Post-fix: src/core/remediation/ exports a library shape that all three consumers (doctor CLI, onboard CLI, MCP run_onboard) wrap. src/core/remediation/types.ts RemediationPlanOpts, RemediationPlan, RemediationOpts, RemediationResult, StepResult, RemediationHooks (the observability seam — library never calls console.* itself). src/core/remediation/context.ts loadRecommendationContext moved verbatim from doctor.ts. Re-exports RecommendationContext from brain-score-recommendations.ts since that's still the canonical home for the type (consumed by computeRecommendations). src/core/remediation/plan.ts computeRemediationPlan(engine, opts): Promise<RemediationPlan>. Pure read; produces the stable JSON envelope downstream agents bind to. Pulls in computeRecommendations + classifyChecks + maxReachableScore behind one library entry point. src/core/remediation/run.ts runRemediation(engine, opts, hooks): Promise<RemediationResult>. Orchestrator with BudgetTracker, checkpoint resume, D5 dep cascade, D7 per-step recheck. Returns a result object instead of process.exit calls; the CLI shell maps result.budget_exhausted / .target_unreachable / .submitted to exit codes. src/core/remediation/index.ts Barrel for the three modules above. doctor.ts is now a thin wrapper: runRemediationPlan: parse argv → computeRemediationPlan → human/JSON render runRemediate: parse argv → TTY confirm gate → runRemediation(hooks: console.*) The TTY confirmation step deliberately stays in the CLI shell — the library never asks for confirmation; that's a CLI concern. Net: ~340 LOC removed from doctor.ts; ~470 LOC added across the library module (with full JSDoc + per-A-decision rationale comments). Functional behavior preserved bit-for-bit: 67 tests pass across doctor.test.ts + v0_37_gap_fill.serial.test.ts. The Lane E.4 source-text test (test/v0_37_gap_fill.serial.test.ts:329) followed loadRecommendationContext to its new home at src/core/remediation/context.ts — assertions otherwise unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(remediation): generalize computeRecommendations to accept extras (A2, codex finding #3) Pre-fix: computeRecommendations at brain-score-recommendations.ts:170 was a hardcoded planner for 5 synthetic check categories. Adding a Check.remediation field to a new doctor check would NOT auto-wire into --remediation-plan — the planner simply ignored it. Codex caught this when reviewing the plan's "checks ARE specs" framing. Post-fix: optional third arg `extraRemediations: RemediationStep[]` lets callers inject step entries discovered outside the hardcoded planner. The existing 5-category surface is preserved bit-for-bit; on id collision the hardcoded entry wins, so an extra accidentally duplicating a hardcoded id doesn't shadow legacy behavior. RemediationPlanOpts gains the matching field; computeRemediationPlan in src/core/remediation/plan.ts threads opts.extraRemediations through. The 4 new doctor checks (T4) will produce per-check helper functions that return RemediationStep[]; onboard's render layer (T12) aggregates them into the opts.extraRemediations slot. doctor's existing --remediation-plan call passes empty (no behavior change for legacy CLI). 84 tests pass across brain-score-recommendations + doctor suites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(doctor): 4 new onboard checks (embed_staleness, link_coverage, timeline_coverage, takes_count) (A16, T4) Adds src/core/onboard/checks.ts: 4 check helpers + a runAllOnboardChecks aggregator. Each helper returns {check, remediations}, so doctor pushes the Check entry (for human/JSON rendering) AND onboard's plan path collects the RemediationStep[] (via T3's new extraRemediations seam in computeRecommendations). embed_staleness: COUNT(*) on content_chunks WHERE embedding IS NULL. Cheap thanks to content_chunks_stale_idx partial (v100). warn at 1+ stale, fail at 1000+; remediation points at embed-catch-up handler (built in T6). entity_link_coverage: fraction of entity pages with inbound links. Per A21 + codex #15: TABLESAMPLE BERNOULLI on PG when total_pages > 50K with pinned sample formula (LEAST 100, GREATEST 2, target ~5000 rows) AND ±sqrt(p(1-p)/n) confidence interval embedded in message ("coverage: 31% ± 1.3%") so warn/fail decisions show their margin of error. PGLite path: full scan (rare >50K). warn <70%, fail <40%; remediation points at extract-ner handler. timeline_coverage: same TABLESAMPLE policy. warn <90%, fail <70%; remediation points at extract-timeline-from-meetings handler. takes_count: COUNT(*) on takes table. Per A12 two-gate consent: the remediation only emits when `takes.bootstrap_enabled` config is true. Otherwise the check shows "0 takes (takes.bootstrap_enabled is false; opt in to enable)" without an autopilot-eligible remediation. Prevents unattended LLM-bearing extractions on brains that haven't opted in. runDoctor wires runAllOnboardChecks at the end of the DB-checks block (after stale_locks); fast-mode skipped to preserve --fast UX. Thin-client parity (A16 spec) deferred to T16 — the MCP run_onboard op will run these helpers server-side where engine.executeRaw works, which is the real federated path. Adding them to doctor-remote.ts would duplicate the logic without functional benefit since the helpers are server-side queries. 55 doctor tests pass; typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(engine): listStaleChunks --priority recent + executeRaw AbortSignal (A13/A20, codex #7 #9) Two interface extensions on BrainEngine, with parity across postgres-engine and pglite-engine. Plus a follow-on fix for v99's timeline_entries dedup widening. listStaleChunks gains: - orderBy?: 'page_id' | 'updated_desc' (default 'page_id' = legacy) - afterUpdatedAt?: string | null (composite cursor for updated_desc) When orderBy === 'updated_desc' the query JOINs pages and orders by p.updated_at DESC NULLS LAST, p.id ASC, cc.chunk_index ASC backed by idx_pages_updated_at_desc + content_chunks_stale_idx partial (both indexes added in v100). The cursor "next row" semantic with DESC NULLS LAST + ASC tiebreakers is: (updated_at < prev) OR (updated_at = prev AND page_id > prev_page_id) OR (updated_at = prev AND page_id = prev_page_id AND chunk_index > prev_chunk_index) First page (afterUpdatedAt undefined AND afterPageId 0) bypasses the cursor predicate. Both engines parity-tested via 100/100 pglite-engine tests; Postgres path mirrors the same WHERE clause structure. executeRaw gains: - opts?: {signal?: AbortSignal} Postgres impl: real cancellation via postgres.js's .cancel() on the pending query. Pre-aborted signal short-circuits before the network round-trip; mid-flight abort fires .cancel(). The query throws on abort which the caller catches. PGLite impl: in-process WASM has no kernel-level cancellation. Best-effort: pre-check, then race the query against a signal-rejection promise. The query keeps running in WASM but the awaited result is discarded (DOMException AbortError thrown). Documented gap. ReservedConnection.executeRaw extends the signature for type compatibility but doesn't wire the signal (its only callers are migrations + cycle-lock writes that explicitly don't want cancellation). V99 timeline dedup follow-on: the dedup widening in migration v99 changed the unique index from (page_id, date, summary) to (page_id, date, summary, source). The ON CONFLICT clauses in both engines' addTimelineEntriesBatch + addTimelineEntry impls were still using the old 3-tuple, causing 12 PGLite tests to fail with SQLSTATE 42P10 "no unique constraint matching ON CONFLICT specification". Updated all 4 sites (2 per engine) to the 4-tuple. Typecheck clean, 100/100 PGLite engine tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(embed): --batch-size + --priority recent + --catch-up + embed-catch-up handler (A13) CLI surface on gbrain embed gains 3 flags: --batch-size N Override hardcoded PAGE_SIZE=2000 (clamped 1..10000) --priority recent Walk stale chunks newest-first (page.updated_at DESC) backed by content_chunks_stale_idx + idx_pages_updated_at_desc via T5's listStaleChunks(orderBy='updated_desc') extension. Composite cursor (updated_at, page_id, chunk_index). --catch-up Removes the GBRAIN_EMBED_TIME_BUDGET_MS wall-clock cap; loops until countStaleChunks() returns 0. EmbedOpts gains matching fields; embedAll + embedAllStale plumb them through. The cursor tracking in embedAllStale now advances (afterUpdatedAt, afterPageId, afterChunkIndex) instead of just (afterPageId, afterChunkIndex) when in 'updated_desc' mode. The engine returns p.updated_at as Date|string; the caller normalizes to ISO string for the next page's cursor. New Minion handler `embed-catch-up` registered in jobs.ts. Wraps runEmbedCore with stale=true + catchUp=true + the priority/batchSize the caller supplies. NOT in PROTECTED_JOB_NAMES (embedding spend only — same posture as the existing embed-backfill handler). Consumed by the gbrain onboard remediation pipeline (T11) when embed_staleness check fires. 63 embed tests pass; typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(extract): NER link extraction via schema-pack inference.regex (A10, T7, codex #12) NEW src/core/extract-ner.ts: extractNerLinks(engine, opts). Walks pages, reuses the by-mention gazetteer, applies the active schema-pack's link_types[].inference.regex patterns to assign a typed verb to each mention ("CEO of Acme" + Acme is a company → 'works_at' linking the source page to Acme). Codex finding #12 design: do NOT split link_source='ner' as a new provenance. NER is still mention-derived; splitting would break every existing link_source='mentions' query (backlink-count, orphan-ratio, doctor checks). Instead: keep link_source='mentions' AND set link_kind='typed_ner' (v98 column). LinkBatchInput type gains link_kind field. Both engines' addLinksBatch impls add the column to the INSERT projection + unnest() tuple (column #11). The links UNIQUE constraint excludes link_kind so an existing plain mention row + a typed_ner row for the same (from, to, type, source, origin) collide DO NOTHING; the typed link goes in as a separate row with a DIFFERENT link_type (the inferred verb), so they don't collide on the typical case. CLI: `gbrain extract links --ner` (DB source only). Combined `--by-mention --ner` walk shares ONE gazetteer build across both passes — saves a full walk on big brains. Either flag alone runs its pass solo. Each gets its own --source-id filter inheritance. Minion handler: `extract-ner` (NOT in PROTECTED_JOB_NAMES — regex-only, no LLM spend). Consumed by onboard's entity_link_coverage remediation when coverage <70%. Target-type lookup: one round-trip SELECT slug, source_id, type FROM pages WHERE type IN ('person', 'company', 'organization', 'entity') AND deleted_at IS NULL — built once at extraction start, consulted per-mention. Avoids the N+1 getPage cost. Pack best-effort: when no active pack OR no link_types declared OR no inference.regex on any link_type, returns pack_unavailable=true and 0 created. CLI prints a one-line note; handler returns silently. 122 tests pass (pglite-engine + by-mention); typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(extract): timeline from meetings — gbrain extract timeline --from-meetings (A11, T8, codex #11) NEW src/core/extract-timeline-from-meetings.ts: extractTimelineFromMeetings(engine, opts). Walks meeting pages, finds discussed entities via two sources, writes a timeline entry on each entity page. Discussed-entity sources merged: 1. Existing 'attended' links from the meeting (canonical attendees). One round-trip SELECT pulls all attended edges for the loaded meeting set; in-memory Map<meetingSlug → attendees[]> for O(1) lookup per meeting. 2. Body-text mentions via the existing by-mention gazetteer (findMentionedEntities + cross-source guard). Catches entities discussed in the meeting body even when no explicit 'attended' link exists. De-duped via Map<sourceId::slug → entity> within each meeting so a person who's both an attendee AND mentioned in the body gets exactly one timeline row per meeting, not two. Timeline write uses TimelineBatchInput with: source = 'extract-timeline-from-meetings:<meeting-slug>' summary = 'Discussed in <meeting-title>' date = meeting.effective_date Per v99 dedup widening (codex #11): the source field is now in the uniqueness key (page_id, date, summary, source). Two meetings on the same date with the same summary on the same entity page survive as distinct rows — the second meeting's provenance is no longer silently dropped. CLI: `gbrain extract timeline --from-meetings` (DB source only). Mode dispatch — runs SOLO (does not combine with --by-mention/--ner; those are links passes). Minion handler: `extract-timeline-from-meetings` (NOT in PROTECTED_JOB_NAMES — pure SQL + string scan). Consumed by onboard's timeline_coverage remediation when coverage <90%. Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(takes): takes-bootstrap from concept/atom/lore pages (A12, A24, T9) NEW src/core/extract-takes-from-pages.ts: Haiku classifier loop. Walks pages WHERE type IN ('concept','atom','lore','briefing','writing', 'originals') AND deleted_at IS NULL AND length(compiled_truth) > 200, ordered by updated_at DESC. Each page is truncated to 20K chars and sent to Haiku with a strict-JSON classifier prompt: {"claim", "kind": fact|take|bet|hunch, "weight": 0..1} Inserts via addTakesBatch with source='cli:takes-bootstrap-from-pages'. Two-gate consent per A12: 1. `takes.bootstrap_enabled` config (default false) — even the manual CLI refuses without it explicitly set. 2. --yes flag (CLI) — interactive confirmation that this sends content to Haiku. The handler-side gate also reads takes.bootstrap_enabled, so even a trusted local Minion submitter (allowProtectedSubmit=true) cannot fire takes-bootstrap on a brain that hasn't opted in. CLI: `gbrain takes extract --from-pages [--yes] [--dry-run] [--source-id X] [--max-pages N] [--holder name]`. Surfaces consent-gate-blocked vs llm-unavailable distinctly so users see the actual blocker. Minion handler `extract-takes-from-pages` added to PROTECTED_JOB_NAMES. Consumed by onboard's takes_count remediation when count=0 AND takes.bootstrap_enabled=true (handler-side double-check). Per A24: ships with classifier infrastructure ONLY. Per-prompt eval suite deferred to v0.42.1 follow-up; autopilot remediation tier for takes-bootstrap stays manual_only until eval coverage catches up. Manual `gbrain takes extract --from-pages --yes` is the only path that triggers it in v0.42.0. parseClaimsJson exported for unit testing — strict JSON parse + ```json fence strip + kind allowlist filter, returns [] on any parse failure. Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(minions): recordMinionJobSpend primitive for MCP client_id attribution (A7+A23, codex finding #4) NEW src/core/minion-spend.ts: small primitive that closes the per-OAuth- client spend chain gap codex flagged when MCP run_onboard submits child Minion jobs. Pre-fix: only subagent loops via budget-meter.ts recorded spend against the originating OAuth client. Generic Minion handlers (embed-catch-up, extract-ner, extract-timeline-from-meetings, extract-takes-from-pages) wrote to the gateway with no per-client attribution — admin-scope tokens would have unbounded indirect spend via the run_onboard fan-out. Convention for v0.42.0 (deferred schema column to v0.42.1): - run_onboard MCP op sets job.data.client_id when submitting each child handler. - Handlers that spend LLM/embedding budget call recordMinionJobSpend(engine, job, {operation, spendCents, ...}) which reads job.data.client_id and writes mcp_spend_log with the right attribution. - Local-submitted jobs (CLI, autopilot tick) pass no client_id; the row still lands with client_id=null for global accounting. Two exports: getJobClientId(job): undefined for local jobs; the OAuth client_id string for MCP-submitted ones. recordMinionJobSpend(engine, job, entry): wraps recordSpend with job-aware attribution. Best-effort throughout — spend telemetry failures MUST NOT fail the user's call. A23 full schema column (minion_jobs.client_id + index) deferred to v0.42.1; today's JSONB-pass-through is sufficient for the MCP run_onboard chain to land per-client attribution end-to-end. Handlers adopt the primitive over time; no behavior change for callers that haven't migrated. Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboard): impact capture module + writeImpactLogRow primitive (A6 + A25 + A17, T11) NEW src/core/onboard/impact-capture.ts. Three exports: captureMetric(engine, metric) Pure-ish: returns the current numeric value for one of 5 metrics (orphan_count, stale_count, entity_link_coverage, timeline_coverage, takes_count). Returns null on any throw per A17 best-effort posture — a stat-query failure MUST NOT block the extraction itself. writeImpactLogRow(engine, attribution, metric, before, after, details?) Best-effort INSERT into v100's migration_impact_log table. Attribution columns (job_id, source_id, brain_id, started_at, idempotency_key, applied_by) per A25 + codex finding #10 so concurrent runs can't misattribute deltas. withImpactCapture(engine, attribution, metric, runner, details?) Convenience: capture-before → run → capture-after → write log row. Per A17 the log row lands even when the runner throws (after-on-fail + error in details), so downstream consumers see a "ran but impact unknown" entry instead of silent loss. Designed to be picked up by the 4 new Minion handlers (embed-catch-up, extract-ner, extract-timeline-from-meetings, extract-takes-from-pages) when they wrap their main runner. Handlers stay decoupled from the log-write path — they just call withImpactCapture with the metric they move. Per-handler integration follows in T12/T13/T15 as those wrappers land. Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboard): types + render layer (A8, T12) NEW src/core/onboard/types.ts: OnboardRecommendation (extends RemediationStep with apply_policy + prompt_text + migration_id), OnboardReport (stable JSON envelope), OnboardOpts. NEW src/core/onboard/render.ts: toOnboardRecommendation(step): RemediationStep → OnboardRecommendation Sets apply_policy per A8 tiered rules: - protected + job === extract-takes-from-pages → 'manual_only' (A12/A24) - protected + other → 'prompt_required' - non-protected → 'auto_apply' buildOnboardReport(plan, opts?): assembles the stable JSON envelope. renderHuman(report): string. Echoes the "Recommendation + WHY" framing the CEO + Eng + Codex reviews settled on; CLI shell prints to stdout. Stable JSON envelope shape: schema_version: 1 brain_id?: string recommendations: OnboardRecommendation[] summary: { total, auto_eligible, prompt_required, manual_only, est_total_usd } history?: Array<{ remediation_id, metric_name, metric_before, metric_after, delta, applied_at }> Library-shaped — no console.* / process.exit. T13 (onboard CLI shell) calls these from the wrapping CLI. MCP run_onboard (T16) returns the JSON envelope unmodified. Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboard): gbrain onboard CLI shell (A1, T13) NEW src/commands/onboard.ts (~180 LOC). Thin wrapper that composes: - T2 library (computeRemediationPlan + runRemediation) - T4 onboard checks (runAllOnboardChecks → extraRemediations) - T12 render layer (buildOnboardReport + renderHuman) Three modes: --check (default): print plan, no submission. Computes plan via T2 library with T4 check-derived extraRemediations. Renders human (default) or JSON envelope (--json). --auto: submit auto_apply tier. Requires --max-usd N (cron-safety per A12 + A20 — refuses without explicit cap to avoid surprise spend). --auto --yes: also submit prompt_required tier. --history: dump last 50 migration_impact_log entries. Library hooks wired into stderr (per CLI/library separation): onStepStart, onStepEnd, onBudgetRefused, onBudgetExhausted, onNothingToDo, onTargetUnreachable. Final JSON envelope (--json) or human summary lands on stdout. CLI dispatch: registered in src/cli.ts CLI_ONLY set + case dispatch between 'takes' and 'founder'. Typecheck clean. Manual smoke-test pending T20 E2E (DATABASE_URL gated). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboard): init nudge + upgrade banner (A4, A18, A20, T14) NEW src/core/onboard/init-nudge.ts exports two fail-open hooks: runInitNudge(engine): Post-initSchema 5-query AbortSignal-bound parallel check against a 3-second wallclock budget. Per A20: uses REAL cancellation via the T5 executeRaw signal extension — Promise.race against a timer was codex's #7 wrong shape. Postgres queries actually .cancel(); PGLite documented gap. Partial-results path: if some checks complete and the budget fires on others, prints what landed + a fallthrough hint pointing at `gbrain onboard --check` for the full picture. Per A18: fail-open — ANY throw is caught, logged to stderr, and suppressed so init returns successfully. Bypass: GBRAIN_NO_ONBOARD_NUDGE=1 short-circuits. Non-TTY default short-circuits too (CI/scripted callers see nothing). Nudge format: one-line summary of opportunities ("Brain has opportunities: 23000 stale chunks, link coverage 32%, 0 takes") + a 'gbrain onboard --check' nudge. runUpgradeBanner(_engine): Lighter post-upgrade banner. Doesn't engine-query — just prints a one-line nudge that upgrades may surface new opportunities. Same fail-open posture. Wired into: src/commands/init.ts:initPGLite (end-of-function, after reportModStatus) src/commands/init.ts:initPostgres (same) src/commands/upgrade.ts:runPostUpgrade (end-of-function, after postUpgradeReferenceSweep) Each wire site uses dynamic import + try/catch so even an import failure can't crash init/upgrade. Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(autopilot): tick consults onboard recommendations (A5, A19, A22, T15) Pre-fix: autopilot tick's per-source recommendation walk called computeRecommendations(health, ctx) — doctor's hardcoded 5-category planner. The 4 new onboard checks (embed_staleness, entity_link_coverage, timeline_coverage, takes_count) had nowhere to hook in, so even with takes.bootstrap_enabled flipped on, autopilot never noticed 0 takes and never proposed bootstrap. Post-fix: tick body now ALSO calls runAllOnboardChecks(engine) and threads the result's RemediationStep[] into the T3-generalized third arg of computeRecommendations. The planner merges onboard's extras with the legacy hardcoded entries (hardcoded wins on id collision). Per A19 fail-open: any throw in the onboard-checks path is caught, logged to stderr, and suppressed. The legacy plan (without extras) runs as before — autopilot can't crash from an onboard-check failure. A22 (idempotency-key dedupe across concurrent manual + autopilot runs): inherits from the existing computeRecommendations → remediation.idempotency_key chain. T7-T9 handlers each get their content-hash key from the makeRemediationStep factory; an autopilot tick + a manual `gbrain onboard --auto` submitting the same step in the same brain produce the SAME key, so queue.add(...) dedupes. No behavior change for brains where all 4 onboard metrics already look healthy (extras=[]; legacy plan unchanged). Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mcp): run_onboard op with run_protected_onboard scope binding (A7, T16, codex finding #5) NEW MCP op `run_onboard`. Admin scope (NOT localOnly) so federated / thin-client brain installs can probe brain health + submit auto-eligible remediation handlers over OAuth-authenticated MCP. Two-tier authorization per A7 + codex #5: - Admin scope: sufficient for mode='check' (read-only OnboardReport JSON) AND for submitting non-protected handlers in mode='auto'/'auto-with-prompt'. - run_protected_onboard scope (NEW, additive): MUST be granted in addition to admin for any PROTECTED_JOB_NAMES handler to fire (synthesize, patterns, consolidate, extract-takes-from-pages, contextual_reindex_per_chunk). Without the new scope tier, an admin-scoped OAuth token would silently bypass the same protected-name gate `submit_job` enforces at operations.ts:2288. The codex finding #5 caught this: admin scope alone was insufficient guard. Now the run_onboard op explicitly FILTERS protected extras from the recommendation plan when the caller lacks run_protected_onboard; filtered items appear in the response as skipped_missing_scope[] so the caller knows what would have been available with the right grants. Modes: check — read-only OnboardReport JSON envelope. auto — submits auto_apply tier (plus prompt_required when --yes/auto-with-prompt). auto-with-prompt — adds prompt_required tier. Both auto modes REQUIRE max_usd per A12 + A20 cron-safety (rejects with invalid_params if missing). Per A26 source-scope: future extension will scope plans by ctx.sourceId / ctx.auth.allowedSources. Today the recommendation planner is brain-wide; the source-scope thread doesn't change correctness, just optimization. Per A19 fail-open: any error in runAllOnboardChecks during plan-build caught + suppressed; the plan still returns with extras=[] rather than crashing the op. Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(verify): add check-source-scope-onboard lint (A26, T17) NEW scripts/check-source-scope-onboard.sh. Grep guard for SQL sites in onboard surfaces (src/core/onboard/, src/commands/onboard.ts) that touch source_id-bearing tables (pages, content_chunks, takes, links, timeline_entries) WITHOUT either: (a) source_id / sourceIds in the WHERE clause, OR (b) the opt-out marker `sourcescope:brain-wide` within 4 lines above the SQL. File-level opt-out: `sourcescope:file-brain-wide` in the file header (first 30 lines) treats every SQL site in that file as intentionally brain-wide. Used by onboard/checks.ts, onboard/impact-capture.ts, and commands/onboard.ts because the onboard CHECKS are explicitly brain-wide aggregates (orphan_count, stale_count, link_coverage are reported across all sources by design). Wired into bun run verify (23 checks total now, all green). Without this gate, any future onboard SQL touching per-source data without source-scoping would silently leak rows across sources — exactly the class of bug v0.34.1's P0 seal closed at the engine layer. The lint adds an explicit forcing function for new code in the onboard surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(install): onboard surface agent prescription (D13, T18) Adds a v0.42.0+ section to INSTALL_FOR_AGENTS.md describing: - First-connect probe: gbrain onboard --check --json - Post-upgrade re-probe (after gbrain upgrade) - Unattended remediation: gbrain onboard --auto --max-usd 5 - MCP run_onboard op for federated/thin-client installs - run_protected_onboard scope requirement for LLM-bearing handlers - Two-gate consent for takes-bootstrap (takes.bootstrap_enabled + --yes) - GBRAIN_NO_ONBOARD_NUDGE=1 bypass for CI Per D13: agents should run --check on first connect AND after every upgrade as a hygiene step. The autopilot path makes this auto-improve on a 24h cycle; the explicit agent probe surfaces opportunities immediately on connect rather than waiting for the next autopilot tick. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(e2e): hermetic onboard surface contracts (T20) NEW test/e2e/onboard-full-flow.test.ts. 13 hermetic PGLite cases (no DATABASE_URL needed) covering the key onboard contracts: captureMetric — all 5 metrics return expected values on empty brain (0 for counts; 1 for coverage = vacuous truth). runAllOnboardChecks — returns exactly 4 results with correct names; empty brain shows stale/link/timeline ok BUT takes_count warns (0 takes); 0 remediations emitted because takes.bootstrap_enabled defaults to false per A12 two-gate consent. computeRemediationPlan — extras (T3 generalization) thread through to plan.plan output; stable schema_version: 2 envelope. buildOnboardReport — stable schema_version: 1 envelope with the right summary fields populated. toOnboardRecommendation tier policy (A8): - non-protected job → auto_apply - extract-takes-from-pages → manual_only (A12 + A24) - other protected jobs (synthesize, patterns, ...) → prompt_required Full DATABASE_URL-gated end-to-end (real Postgres, actual extractions through Minion handlers) deferred to v0.42.1 once the per-handler test seam lands; the hermetic suite covers the data-shape contracts that matter for downstream consumers binding to the JSON envelopes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * v0.42.0.0 gbrain onboard mega PR — activation surface (closes #1383, completes #1409) VERSION + package.json bumped to 0.42.0.0. CHANGELOG with full ELI10 lead + "What you can do that you couldn't before" itemized list + "To take advantage of v0.42.0.0" upgrade steps per CLAUDE.md voice rules. TODOS.md: 9 follow-up items filed (TODO-A through TODO-I) for the v0.42.1+ wave: pack-aware linkable types, LLM-disambiguation NER, onboard --explain, live-brain impact measurement, 100+-case takes classifier eval, admin SPA UI, full DATABASE_URL E2E, minion_jobs client_id schema column, thin-client doctor-remote parity. llms-full.txt regenerated per CLAUDE.md rule (every CHANGELOG edit followed by bun run build:llms in the same commit). 23/23 verify checks pass. Full implementation across 21 commits on this branch (T0-T21): T0 merge master T1 schema migrations v98/v99/v100 T2 extract doctor remediation library T3 generalize computeRecommendations T4 4 new doctor checks T5 engine API: listStaleChunks orderBy + executeRaw AbortSignal T6 embed --batch-size / --priority recent / --catch-up T7 NER extraction + extract-ner handler T8 timeline-from-meetings + extract-timeline-from-meetings handler T9 takes-bootstrap + extract-takes-from-pages handler T10 recordMinionJobSpend primitive T11 impact capture module + writeImpactLogRow T12 onboard render layer (types + render) T13 gbrain onboard CLI shell T14 init nudge + upgrade banner T15 autopilot tick consults onboard T16 MCP run_onboard + run_protected_onboard scope T17 check-source-scope-onboard lint T18 INSTALL_FOR_AGENTS.md agent prescription T20 hermetic PGLite E2E (13 cases) T21 ship (this commit) Reviews: CEO + Eng + Codex on plan ~/.claude/plans/system-instruction-you-are-working-lively-hollerith.md. 27 A-decisions locked; 18 codex findings absorbed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ci): connection-resilience regex + doctor warn-not-fail + v0.41.18.0 Two CI fixes from PR #1521 + version renumber per user request. Why fix #1 (connection-resilience.test.ts): T5/A20 extended PostgresEngine.executeRaw signature to accept an optional `opts?: { signal?: AbortSignal }` 3rd arg and rewrote the body as multi-line. The regression test's regex was anchored to the legacy single-line `(sql: string, params?: unknown[])` shape and the assertions banned `try {` / `catch` (which T5 legitimately added for AbortSignal cancellation swallow, NOT for retry). Updated regex to tolerate both shapes; replaced the wrong `not.toContain('conn.unsafe( sql, params')` assertion (which incorrectly flagged the legitimate single call) with a count assertion: `conn.unsafe(` must appear exactly ONCE in the body. Preserves the original D3 intent (no per-call retry — recovery is supervisor-driven via reconnect()) while accepting the new try/catch shape that swallows AbortSignal aborts. Why fix #2 (src/core/onboard/checks.ts): Three of the four new onboard doctor checks (entity_link_coverage, timeline_coverage, embed_staleness) emitted `status = 'fail'` on healthy DBs that simply hadn't run extractions yet. This flipped `gbrain doctor`'s exit code to non-zero on freshly initialized brains, breaking test/e2e/mechanical.test.ts:1280 ("gbrain doctor exits 0 on healthy DB"). Downgraded all three to `status = 'warn'` — these are remediation opportunities, not assertion failures. Doctor exit codes are reserved for actual failures; remediation surfaces use warn-level signaling so they can be picked up by `--remediate` without polluting the exit code. Why fix #3 (version renumber 0.42.0.0 → 0.41.18.0): Per user directive, this wave ships as v0.41.18.0 rather than v0.42.0.0. Master is at 0.41.16.0; 0.41.17.0 is reserved for an in-flight wave. Renamed every reference my branch added (54 files touched): VERSION, package.json, CHANGELOG.md header, TODOS.md, plus inline version-stamp comments across src/, test/, and scripts/. Preserved 13 files with PRE-EXISTING `v0.42.0.0` references on master (from earlier waves originally planned for v0.42 that landed at v0.41.x — those stay as historical record). Verified via per-file diff against origin/master: every renamed reference is one I added in this branch. Audit trio aligned: VERSION=0.41.18.0, package.json=0.41.18.0, CHANGELOG topmost entry=[0.41.18.0]. llms-full.txt regenerated to match CLAUDE.md updates. Bisect contract: this commit fixes CI test failures from PR #1521's landing. Typecheck clean; connection-resilience suite 26/26 pass. Refs A20 (executeRaw AbortSignal), A16 (4 new onboard checks), codex #1 (master collision avoidance via renumber). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8ab733471b |
v0.41.17.0 feat: --workers N on every bulk command + facts dim doctor parity (#1519)
* feat(worker-pool): shared sliding pool + bounded semaphore + PGLite-clamp wrapper T1 + T2 of the v0.41.16.0 workers cathedral. New src/core/worker-pool.ts is the canonical primitive every --workers N bulk command in this wave (and future bulk commands) builds on. Atomic-claim invariant enforced by scripts/check-worker-pool-atomicity.sh (wired into bun run verify). BudgetExhausted bypass + AbortSignal composition baked into the helper so budget caps are a structural ceiling under concurrency, not a per-caller convention. The new resolveWorkersWithClamp wrapper composes existing autoConcurrency with PGLite-clamp + per-(command, requested) stderr dedup. Deliberately NOT a modification to shared autoConcurrency (silent today, used by sync + import); embed.ts keeps GBRAIN_EMBED_CONCURRENCY || 20 default per codex #13. 23 + 12 + 9 = 44 hermetic tests pin every contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: structural + dim-check regression suites for v0.41.16.0 wave - test/embed-helper-migration.test.ts (T3): asserts embed.ts's two sliding-pool sites are migrated to runSlidingPool, pre-migration shapes (let nextIdx = 0, Promise.all(Array.from(...))) are gone, GBRAIN_EMBED_CONCURRENCY || 20 default preserved, failureLabel threads page.slug. Per codex #16/#17 these are invariant assertions, not byte-equality on progress event ORDERING. - test/embedding-dim-check-facts.test.ts (T6): readFactsEmbeddingDim covers vector(N) + halfvec(N), halfvec-before-vector regex ordering pinned (codex #19), buildFactsAlterRecipe emits DROP INDEX + ALTER USING + CREATE INDEX (codex #18, not bare REINDEX), FactsEmbeddingDimMismatchError tagged class shape, assertFactsEmbeddingDimMatchesConfig PGLite skip + Postgres absent- column skip, doctor check + insert-cast wiring assertions. - test/extract-conversation-facts-workers.test.ts (T5): helper exports (extractConversationFactsLockId, PER_PAGE_LOCK_TTL_MINUTES), structural wiring (runSlidingPool, resolveWorkersWithClamp, withRefreshingLock, LockUnavailableError, delete-orphans-first before segment loop, preflight before pool, exit 3 when lock_skipped > 0), Minion handler round-trip. - test/extract-workers.test.ts (T7): --workers wiring on all 3 inner fs-walk loops (extractForSlugs, extractLinksFromDir, extractTimelineFromDir) + CLI parse + opts threading through runExtractCore. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: rebump v0.41.16.0 → v0.41.17.0 (queue collision with PR #1510) PR #1510 (garrytan/dynamic-regex-conversation-formats) claimed v0.41.16.0 on master in parallel. Advancing this wave to v0.41.17.0 so both can land cleanly. Pure mechanical version bump: - VERSION + package.json → 0.41.17.0 - CHANGELOG.md header + "To take advantage of v0.41.17.0" block - TODOS.md section header + v0.41.18+ forward references - CLAUDE.md inline version tags - Regenerated llms-full.txt / llms.txt No code changes. The actual workers cathedral feature set is unchanged from the two prior commits in this branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(test): search-image-column probes column dim at runtime CI shard 5 failed on `searchVector column routing (v0.27.1)` with: error: expected 1280 dimensions, not 1536 The test had a hardcoded `fakeText1536` helper that seeded chunks at 1536-d vectors. Master's default embedding model switched from OpenAI text-embedding-3-large (1536) to ZeroEntropy zembed-1 (1280) so a fresh PGLite brain on CI now sizes content_chunks.embedding at 1280; the test's 1536-d INSERT trips pgvector's CheckExpectedDim. Fix: probe `content_chunks.embedding` width via `readContentChunksEmbeddingDim(engine)` in `beforeAll`, store in `TEXT_DIM`, and build `fakeTextDefault(seed)` at that width. The test now passes regardless of which default ships (the model has flipped twice and may flip again). Local dev (1536 from older config) and CI fresh-install (1280 from new default) both pass. Image-side vectors stay at 1024 (matches Voyage multimodal-3 + the column's fixed width on the image side). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(test): bump PGLite hook timeout for shard-4 deep-process files facts-anti-loop.test.ts and ingest-capture.test.ts were timing out in CI shard 4 with "beforeEach/afterEach hook timed out" after the v0.41.16.0 master merge brought migration count to 99. When these files run deep in a shard process that has already created ~20 PGLite engines, the WASM cold-start + 95-migration replay legitimately exceeds bun's 5s default hook timeout (observed 5.6s and 7.3s locally when reproducing). Bun's --timeout=60000 from scripts/test-shard.sh covers TEST timeouts but NOT hook timeouts; those default to 5s and must be set per-hook via the optional 2nd arg to beforeAll/afterAll. Reproduced locally by running the first 21 shard-4 files via head -21 /tmp/shard4-list.txt | xargs bun test → 179 pass, 2 fail (both with hook-timeout error) After fix: → 198 pass, 0 fail (the 4 anti-loop + 15 ingest-capture tests recover) Full shard 4 with fix: 955 pass, 0 fail. Full shard 5 with fix: 1261 pass, 0 fail. Also added a defensive diagnostic to the two put_page tests: if facts_backstop is missing in the response payload, throw with the full payload + isError so future failures surface the actual handler error instead of a bare "expected {...} got undefined" assertion. No-op when the test passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <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> |
||
|
|
32f8be96c2 |
v0.41.14.0 fix(#1451): close RESOLVER.md drift bug class structurally (#1458)
* feat(core): loadSkillTriggerIndex shared primitive (closes #1451 drift class) Single loader that unions per-skill SKILL.md frontmatter triggers: with curated RESOLVER.md / AGENTS.md rows. UNION semantics — explicit RESOLVER.md rows ADD to frontmatter triggers for the same skill (don't replace). Dedup keyed on (skillPath, normalized trigger string) so case or whitespace drift between the two surfaces collapses to one entry. This is the structural foundation for #1451: pre-fix, gbrain skills declared triggers in two places (per-skill frontmatter and a curated RESOLVER.md table) that could silently drift. Three consumers (checkResolvable, routing-eval CLI, mounts-cache.composeResolvers) each built their own resolver index from RESOLVER.md only, so fixing frontmatter would have closed doctor's warning without closing the other two surfaces. This primitive becomes the single join point for all three; consumers are wired in the next commit. Tests: 18 hermetic cases pinning frontmatter auto-registration, RESOLVER.md/AGENTS.md merge, case-insensitive dedupe, OpenClaw workspace-root layout (../AGENTS.md), graceful skip of conventions / deprecated skills / non-directory entries / missing skillsDir, plus synthesis round-trip and findPrimaryResolverPath. Plan: ~/.claude/plans/system-instruction-you-are-working-tidy-storm.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: wire 3 consumers through loadSkillTriggerIndex (#1451) Replace the three independent resolver-content loaders with calls to the v0.41.11 shared primitive so frontmatter triggers propagate to every dispatch surface, not just doctor. Before: checkResolvable, runRoutingEvalCli, and mounts-cache each walked RESOLVER.md / AGENTS.md files separately. Adding frontmatter triggers to one consumer (e.g. checkResolvable) wouldn't have reached the routing-eval CLI or cross-brain composed dispatchers — the same drift bug class as #1451 in cross-consumer form. Codex caught this in plan-eng-review. After: all three consumers fold through loadSkillTriggerIndex. UNION semantics across both surfaces means new skills with frontmatter triggers are reachable everywhere without editing RESOLVER.md. Also updates: - check-resolvable action text on routing_miss to point at the canonical surface (SKILL.md frontmatter triggers) first, with RESOLVER.md row as secondary. - test/resolver-merge.test.ts to test BOTH the legacy RESOLVER.md-only authority path (skills with no frontmatter triggers) AND the new auto-registration path (skills reachable via frontmatter alone, no RESOLVER.md needed). - 3 routing-eval.jsonl fixtures (voice-note-ingest, brain-taxonomist, strategic-reading) gain `ambiguous_with` declarations for skill overlaps that auto-registration newly exposes. These overlaps are legitimate (voice-note vs idea-ingest on audio notes, brain-taxonomist vs repo-architecture on filing, strategic-reading vs idea-ingest on reading-through-a-lens) — the agent picks based on context. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(#1451): broaden skillpack-harvest triggers + negative fixtures + tighten gate Closes the 7 residual routing_miss warnings on skillpack-harvest that gbrain doctor reported on every fresh install (resolver_health: WARN, ~5 health-score points). Three changes: 1. Broaden skills/skillpack-harvest/SKILL.md frontmatter triggers from 5 narrow to 10 realistic phrasings. Each new trigger is a contiguous substring of one of the 7 shipped routing-eval.jsonl intents (per kylma-code's design in PR #1331; moved from RESOLVER.md to frontmatter under the v0.41.11 frontmatter- authoritative contract). Existing RESOLVER.md row stays for human-readability of the dispatcher map. 2. Add 4 negative-fixture cases to skills/skillpack-harvest/ routing-eval.jsonl with expected_skill=null to defend against false positives the broader triggers might introduce ("publish this report to the team", "promote my role on LinkedIn", "bundle these screenshots into a deck", "lift weights at the gym"). Two candidate negatives ("save this report as PDF", "share this article with the channel") were excluded — they trip idea-ingest's existing "save this"/"share" triggers, a real overlap but a separate v0.42+ concern. 3. Tighten test/check-resolvable.test.ts's "repo skills/ pass cleanly" assertion: the v0.25.1 carve-out that allowed routing_miss as informational is removed. The contract is back to zero errors AND zero warnings — the CI gate (next commit) enforces this for PRs so future drift fails the build instead of degrading user-install resolver_health silently. Co-Authored-By: kylma-code <noreply@github.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(cli): register reindex in CLI_ONLY so --help works (closes part of #1354) Pre-fix: src/cli.ts had a `case 'reindex':` handler at line 1334 that dispatched to reindex-multimodal or reindex.ts based on flags, but 'reindex' was missing from the CLI_ONLY Set at line 38. The dispatcher rejected the command with "Unknown command: reindex" before the handler ever ran. Post-fix: 'reindex' is in CLI_ONLY (recognized as a registered command). NOT added to CLI_ONLY_SELF_HELP — the handler doesn't have its own --help branch, so the dispatcher's generic printCliOnlyHelp() shows "gbrain reindex - run gbrain --help for the full command list." Polishing this to per-flag help text (--multimodal, --markdown, --code) is a follow-up TODO. Regression test in test/cli.test.ts asserts `'reindex'` is in the CLI_ONLY Set source string. Mirrors the existing pattern for 'reinit-pglite' in test/v0_37_fix_wave.serial.test.ts:284 and 'book-mirror' in test/book-mirror.test.ts:73. Cherry-picked from lost9999's PR #1354 (which bundled this fix with their fixture-rewrite approach to #1451 — the routing-eval half of that PR was superseded by kylma-code's trigger-broadening direction in #1331, which we took structurally in the previous commits). Co-Authored-By: lost9999 <noreply@github.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(ci): wire check:resolver into bun run verify Adds `bun run check:resolver` (= `bun src/cli.ts check-resolvable --strict --skills-dir skills/`) to package.json scripts and registers it in scripts/run-verify-parallel.sh's CHECKS array. This gates PR CI on resolver health: any future drift between a skill's frontmatter triggers and its routing-eval.jsonl fixtures fails the build, instead of silently degrading the resolver_health score on user installs after merge. The --strict flag exits non-zero on warnings (not just errors), so routing_miss / routing_ambiguous / routing_false_positive all block. Closes the CI half of #1451's structural fix: doctor catches drift at runtime, this gate catches drift at PR time. Local pre-flight: `bun run check:resolver`. Codex finding #9 from plan review: scripts/run-verify-parallel.sh invokes entries as `bun run <script-name>`, not raw shell. The package.json script name + CHECKS-array entry is the correct shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(test): update CLI unreachable test for v0.41.11 contract change The prior fixture used `triggers: ['alpha']` + `inResolver: false` to simulate an unreachable skill. Under v0.41.11's structural fix, frontmatter triggers auto-register the skill independently of RESOLVER.md, so this skill is reachable now — the assertion `errors.length > 0` failed. Drop the `triggers:` array from the fixture so the skill is genuinely unreachable (neither frontmatter nor RESOLVER.md row), preserving the test's regression-guard intent: doctor/check-resolvable still exits 1 when a manifest skill is truly unreachable. Caught by the full unit test suite after the merge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: CLAUDE.md Key Files note for skill-trigger-index + regen llms.txt Document the v0.41.11 shared primitive (loadSkillTriggerIndex) in the Key Files section so future contributors find it before they reach for parseResolverEntries directly. Notes the 3 consumers (checkResolvable, runRoutingEvalCli, mounts-cache.composeResolvers), the UNION semantics, skip rules, parseSkillFrontmatter dependency, test coverage, and the CI gate wiring. Regenerated llms.txt + llms-full.txt per the CLAUDE.md edit rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * v0.41.14.0 fix(#1451): close RESOLVER.md drift bug class structurally Frontmatter triggers + RESOLVER.md / AGENTS.md rows now union into one unified index via the new loadSkillTriggerIndex primitive, consumed by all three dispatch surfaces (checkResolvable, routing-eval CLI, mounts-cache.composeResolvers). Closes the 7 residual routing_miss warnings #1451 reported on every fresh install, and the drift bug class that produced them. Highlights: - New shared primitive src/core/skill-trigger-index.ts (252 lines + 361 lines of tests across 18 cases). UNION semantics, case-insensitive dedupe keyed on (skillPath, normalized trigger). - Three consumers wired through the primitive — fixing frontmatter triggers for doctor now also fixes routing-eval CLI and cross-brain mounted dispatch (codex outside-voice catch). - skillpack-harvest frontmatter broadened from 5 to 10 triggers per kylma-code's design in #1331, plus 4 negative-fixture cases for false-positive defense. - reindex CLI added to CLI_ONLY set so `gbrain reindex --help` works instead of "Unknown command: reindex" (lost9999's #1354 hunk). - check:resolver wired into bun run verify CI gate so future drift fails PR CI instead of silently degrading user-install resolver_health. - check-resolvable's repo skills/ test tightened from "warn-tolerant" to "zero errors AND zero warnings" — the carve-out was a stop-gap pre-structural-fix. Plan + 5 decisions + codex outside-voice recalibration captured at ~/.claude/plans/system-instruction-you-are-working-tidy-storm.md. Co-Authored-By: kylma-code <noreply@github.com> Co-Authored-By: lost9999 <noreply@github.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: update project documentation for v0.41.14.0 - CLAUDE.md: tag skill-trigger-index entry with correct shipped version (v0.41.14.0, closes #1451) instead of the stale v0.41.11 draft tag. - CONTRIBUTING.md: list the new `check:resolver` gate in the `bun run verify` chain so contributors know to expect resolver-drift failures in PR CI. - llms-full.txt: regenerated from updated CLAUDE.md (mandatory per CLAUDE.md's auto-derived files rule; CI shard 1 fails the build 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: kylma-code <noreply@github.com> |
||
|
|
3a2605e9a0 |
v0.41.6.0 feat(ci): CI test speedup — 23min → ~9min via matrix 4→6 + weight-aware sharding + auto SHA cache + parallel verify (#1444)
* feat(ci): scripts/run-verify-parallel.sh — parallel verify dispatcher Fans out the 21 pre-test grep guards via & + wait, captures per-check exit codes in a tempdir, aggregates failures with named check + log tail to stderr on miss. Wallclock 27s sequential → 13s parallel locally (2x). Bigger CI win is shard 1 deload (workflow restructure in a later commit). Pinned by test/scripts/run-verify-parallel.test.ts (6 cases: CLI contract + synthetic dispatcher failure-surfacing). * feat(ci): weight-aware LPT bin-packer + auto SHA cache hash scripts/sharding.ts (NEW) — pure TypeScript LPT bin-packer. Sort weights desc, assign each file to the shard with current minimum total. Worst-case makespan within 4/3 of optimal, O(n log n). Missing weights fall back to corpus median (not 0). New test file → ships immediately without regenerating weights. Pinned by test/scripts/sharding.test.ts (23 cases). scripts/mine-shard-weights.ts (NEW) — scrapes per-file timing from gh run view --log via timestamp delta between ##[group]test/foo.test.ts: headers within a shard. Three input modes: --run <ID>, --from-file <PATH>, stdin. Stable JSON output (sorted keys). Initial weights mined from run 26398061007. Pinned by test/scripts/mine-shard-weights.test.ts (15 cases). scripts/ci-cache-hash.sh (NEW) — deterministic 16-char sha256 over git ls-files -s minus deny-list (CHANGELOG/TODOS/README/LICENSE/ docs/**/*.md). CLAUDE.md, AGENTS.md, skills/**/* deliberately INCLUDED (8+ test files read them; deny-listing would create false-pass holes). ~40ms on 1891 files. Pinned by test/scripts/ci-cache-hash.test.ts (24 cases: 8 CRITICAL false-pass guards + 7 SAFE deny-list invariants + 9 edge cases). scripts/test-weights.json (NEW) — 712 weights. Total 3306s observed runtime; median 30ms; max 6 min outlier. * chore: bump version and changelog (v0.41.6.0) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |