* feat(bootstrap): TTY DX exploration harness + Krug onboarding fix wave
Add a real-PTY exploration harness and land 16 verified "Don't Make Me
Think" fixes on the paste-in install experience for Claude Code and Codex.
Harness:
- test/helpers/tty-harness.ts — spawns any CLI (gbrain/claude/codex) under a
real pseudo-terminal (Bun terminal: spawn), timestamps every output burst,
and turns silence windows into a measurable stall report. Hermetic; pure
helpers unit-tested in test/tty-harness.test.ts.
- scripts/dx-explore.ts — drives the fresh-user funnel (help / init / real
claude-install / real codex-install / manual drive mode), writing
transcripts to .context/dx-runs/ (gitignored).
Fixes (all adversarially verified against the code first):
- Keyless bare `gbrain init` completes in keyless mode instead of exit 1;
multi-key non-TTY auto-picks the canonical default; typo stays fail-loud.
- Provider picker probe-gates ollama (daemon-up != model-pulled) and offers
an explicit "continue keyless" option that is the bare-Enter default.
- Fresh-brain init prints one schema-setup line instead of ~240 migration
names (GBRAIN_MIGRATE_VERBOSE=1 restores detail).
- Init epilogue: memory-verbs funnel is last-on-screen; skills advisory
compacted for init; Mod Status trimmed.
- PGLite live-serve lock error names the fix (close the agent session).
- Mode-picker banner interpolates the applied mode; expansion-key gate is
Anthropic/OpenAI/Google, not OpenAI-only.
- Missing `claude` binary skips MCP but still installs hooks; honest copy.
- Foreign MCP-registration removal targets the conflicting scope and fails
loud if it does not land.
- Upgrade marker compares the running binary to latest and self-spawns via
execPath, so a current/newer binary no longer nags from a stale cache.
- interview --set/--skip after --confirm warns it voided the confirmation.
- init --help matches behavior; init --supabase fails loud on non-TTY.
- Provider capabilities attributed per provider across README / runbook /
questions bank / bootstrap.md.
- First-run tour: restart-first, prompt 3 true on day one, withheld on FAIL;
README gives Codex the same scripted magic moment.
- Empty-brain "0 takes" onboard nudge suppressed.
- Broken settings.local.json aborts the hooks write fail-closed instead of
silently dropping the user's permissions.
Regenerated cli-flag-registry.generated.ts and llms-full.txt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(bootstrap): second DX polish wave — clean the success screen + honest copy (F17-F21)
Follow-up to the DX fix wave, closing the top-5 remaining gaps the scorecard
flagged (all human-facing polish, not survival):
F17 — machine markers no longer leak to humans:
- verify report drops the `[D3.6]` plan-tag from the first_run_tour detail.
- the raw `UPGRADE_AVAILABLE <cur> <latest>` marker line prints ONLY on a
non-TTY stderr (parsers still get it); an interactive human sees just the
"gbrain X -> Y available" sentence.
- per-migration "what changed" notices (v123/v124, incl. the #2704 ref) are
suppressed on a FRESH-install replay via a module quiet flag; upgrades still
narrate. (GBRAIN_MIGRATE_VERBOSE=1 restores them.)
F18 — one obvious next action on the init success screen: the memory-verbs
demo is the single "→ Do this next" hero, last on screen; import/migrate/doctor
collapse into one terse "More:" footer; the graph block only shows for a
non-empty brain.
F19 — README "moment it clicks" is now the genuine cross-session brain
round-trip (remember → restart → recall), explicitly distinguished from the
identity-file recall, on both the Codex and Claude Code paths.
F20 — the compact init skills advisory is human-voiced (no `[AGENT]`
stage-direction on the human-facing success screen; the mode-picker's
agent-directed block stays gated to the non-TTY channel).
F21 — time promise reconciled: headline is ~15 min (personal-agent path) /
~30 min (always-on OpenClaw/Hermes); the runbook's search-mode line no longer
claims "balanced" when keyless applies "conservative". README hooks copy says
"on by default, with an opt-out" to match the runbook.
Regenerated llms-full.txt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(bootstrap): address two-model adversarial review of the DX wave
Fixes the regressions the 5-specialist + red-team + Claude/Codex adversarial
pass found in the F1–F21 changes, each with a test:
- Keyless upgrade hint pointed at `config set embedding_model`, which config.ts
hard-refuses as a schema-sizing no-op — now names the working re-init recipe
(`gbrain init --force --pglite --embedding-model <id>`), zero-key AND multi-key
paths.
- Multi-key TTY picker offered "continue keyless" but the caller aborted on it —
now honors keyless like the zero-key path.
- Detached update-refresh spawn used a `/gbrain$/` basename check that misfires
for a renamed/official-named compiled binary (`gbrain-darwin-arm64`) and
prepends the /$bunfs entrypoint — now detects dev-vs-compiled by the runtime
basename (bun|node) so the refresh always runs.
- `bootstrap status` reported the wire phase "done" on a hooks-only receipt
(host CLI missing at wire time) — now "partial" with a re-run hint, so a
resuming agent doesn't trust a false complete.
- Post-repair MCP mismatch re-verifies and aborts instead of blessing a
registration a racing writer may have re-claimed.
- probeOpenAICompat's abort timer now spans the body read (was cleared before
it), so a stalled `/v1/models` body can't hang init past the 1s cap.
- Centralized the 4-copy stale-cache upgrade predicate into
`pendingUpgradeVersion`; UPGRADE_AVAILABLE gains a GBRAIN_FORCE_UPGRADE_MARKER
override for PTY-based agent harnesses.
- Mode picker's expansion-key gate adds GEMINI_API_KEY; picker prompt is
article-aware ("an embedding" / "a chat"); dead `!brainEmpty` clause removed;
migrate.ts try/finally widened + stamp failures named in quiet mode.
- DX harness: credential copies scrubbed even on SIGINT/interrupt (+chmod 600),
child process TREE reaped on teardown, advisory made fail-open, KEY_MAP typed
as a literal union.
New tests: migrate quiet-replay, self-upgrade pending predicate + negative
cache cases, bootstrap 127/scoped-remove/broken-settings dispatch, interview
invalidation flag, verify tour-withheld-on-FAIL, init keyless/supabase/multi-key,
init-nudge branches, ai-probes model parsing. Regenerated flag registry +
template-repo.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* v0.45.8.0 fix(bootstrap): onboarding DX polish wave (F17-F21) + review fixes
DX fix wave on the paste-in install/first-run experience for Claude Code and
Codex, driven by a new real-PTY exploration harness. Keyless init completes
instead of erroring, the migration wall collapses to one line, the success
screen leads with one action, and the "magic moment" copy points at the genuine
cross-session round-trip. Full detail in CHANGELOG.
Version trio + openclaw manifest + runbook stamp bumped to 0.45.8.0; CHANGELOG
release entry; TODOS onboarding-DX follow-ups filed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(bootstrap): OOBE hand-off — you own the brain, cold-start is skill #1
A working install now ends by making the two facts that matter actually land:
- `gbrain bootstrap verify` prints (and returns as `handoff` in --json) an
ownership block — the actual private-repo URL with what owning it means
(read it, `gbrain bootstrap attach` on machine two, delete it and the brain
is gone), or the local-only variant pointing at `gbrain bootstrap repo` —
followed by the ONE next action: run the cold-start skill (Gmail/calendar/
contacts via ClawVisor, an OAuth vault so the agent never holds raw tokens;
or offline archives), one consented phase at a time. Withheld on FAIL like
the tour; shape stays unconditional for machine consumers.
- cold-start ships in the downstream bundle (61 skills): its plugin exclusion
("host onboarding flow") predated the v0.45 personal-agent bootstrap and is
deliberately reversed — the paste-in audience is exactly who day-one
onboarding is for. It now LEADS the recommended set (ahead of book-mirror:
every flagship skill only becomes magical once the brain holds the user's
real life).
- New drift guard: every recommended slug must be scaffoldable from the
plugin bundle — recommended-but-unscaffoldable is a dead-end CTA and now
fails the suite.
- Runbook Hand off rewritten around the two must-land facts + the on-the-spot
cold-start offer; README's Codex and Claude Code paths carry the same two
follow-ups.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* v0.45.10.0 feat(bootstrap): the OOBE hand-off release
Version trio + runbook stamp + template tree to 0.45.10.0; CHANGELOG entry;
llms bundles regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(ci): stop memory-verbs-conformance leaking a fake-keyed gateway into shard-mates
The deterministic-embedder helper configures the MODULE-GLOBAL gateway with a
fake OpenAI key; the file's afterAll never reset it. The bunfig preload's
per-test restore only fires when the gateway is UNCONFIGURED, so the fake-keyed
config persisted for every later file in the shard process — turn-context's
corpus writes then embedded against real OpenAI and 401'd (CI shard-8 failure;
shard re-binning from this branch's new test files exposed it).
Fix both sides: conformance's afterAll now resetGateway()s back to the preload
baseline and nulls both test transports; turn-context's beforeAll does the same
defensively so it stays hermetic regardless of shard composition.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Wave-assembled from PR #3548 by @time-attack. Composition conflicts vs wave
item #3993 resolved: EmbedResult keeps both chunkless_pages_healed and the
PR's lock_skipped; KEY_FILES.md keeps the #3993 embed entry and takes this
PR's migrate-embeddings entry.
Co-Authored-By: Garry Tan <garrytan@gmail.com>
Wave-assembled from PR #3888 by @RerankerGuo. Conflict resolution: KEY_FILES.md
check-resolvable/skill-frontmatter/skill-trigger-index entries taken as the
PR's current-state prose; the PR's stale pre-fold 'doctor.ts extension' entry
dropped (master already folded it into the main doctor entry).
Co-Authored-By: RerankerGuo <121015044+RerankerGuo@users.noreply.github.com>
Wave-assembled from PR #3936 by @dovstern. Conflict resolution: kept master's
resolveRepoRoot() block AND the PR's exported currentBranch in
src/core/brain-repo-durability.ts. Adaptation: the new serial test now writes
the simulated push log under $GBRAIN_HOME/.gbrain (CX2-8 parent-dir semantics
landed on master after the PR's base).
Co-Authored-By: Dov Stern <dovstern@users.noreply.github.com>
Wave-assembled from PR #3976 by @clement0909472. Rider: four toContain pins
(claude-fable-5, claude-opus-5, claude-opus-4-8, claude-sonnet-5) in
test/claude-cli-recipe.test.ts.
Co-Authored-By: Clément Barberousse <clement.barberousse.pro@gmail.com>
Wave-assembled from PR #3782 by @JonMcCutchen. Rider-check: verified the
non-default pageRoot join (repoPath/.sources/<sourceId>) matches how
pages.source_path is recorded (source-root-relative via importFile's
relative(dir, filePath)); no mismatch, no change needed.
Co-Authored-By: Jon McCutchen <jmmccutchen1@gmail.com>
Wave-assembled from PR #3762 by @awilhite. Rider: PGLite round-trip chunk-count
assertion in test/extract-atoms-chunk-embed.test.ts (verified fails without the
src change).
Co-Authored-By: Austin Wilhite <austinw80@gmail.com>
* fix(bootstrap): preview source_id + create brain/ eagerly in hooks phase
`bootstrap render`/`hooks` never told a human what source_id the
workspace expects until `verify` (the only engine-holding phase) ran.
A human who hand-registered a source before that point would guess an
"intuitive" name, hit an FK error on the first `verify` roundtrip (the
guessed id has no `sources` row), then hit `overlapping_path` on the
retry (their first guess still claims the same brain/ dir) — three
round trips to land the right id.
`hooks` is the last ENGINE-FREE phase before `verify`, and already
knows both the manifest's current source_id and the workspace path, so
it now:
- creates `<ws>/brain` eagerly (idempotent mkdir), removing the
manual-mkdir step before `git init && sources add`
- prints the exact `gbrain sources add <source_id> --path <brain>`
command
- previews the collision-fallback id verify would derive
(`workspace-<hash>`) — a pure function of the workspace's real
path, so it needs no DB lookup and is safe to preview engine-free
The collision-fallback derivation itself is unchanged; it is now
factored into an exported `deriveWorkspaceSourceId()` in verify.ts so
both call sites (the new hooks preview and the existing
`resolveSourceIdCollision`) share one formula instead of two copies
drifting apart.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016JNV9ABwb32DZEwvo7P4ay
* fix(bootstrap): --force the preview command; quote paths; fix runbook
Codex review round 2 caught three issues with the source_id preview
added in the prior commit:
- The printed `gbrain sources add <id> --path <brain>` command failed
immediately on a pristine bootstrap: the brain/ dir this phase just
created is empty (no git history), so `sources add --path` fail-fasts
as `not_a_git_repo` (#2707). Fixed by appending `--force` — the same
sanctioned opt-in `test/bootstrap-verify.serial.test.ts` already uses
to register a brand-new brain/ before any content exists
(`addSource(engine, { id: 'workspace', localPath: ..., force: true })`).
Safe here specifically because brainDir is the fixed
`<workspace>/brain` path this phase just created, not an arbitrary
user path.
- brainDir was interpolated unquoted; a workspace path containing a
space broke the printed command. Added a local
`shellQuoteForDisplay()` (mirroring the existing private `shellQuote`
already duplicated in hooks.ts / sources-ops.ts / connect.ts).
- The dispatcher test only pattern-matched the collision-fallback id's
shape (`workspace-[0-9a-f]{8}`) instead of pinning exact equality
with `deriveWorkspaceSourceId()`, so preview/verify drift could pass
silently. Now asserts exact equality, plus a new test for the space-
quoting fix.
Also corrects BOOTSTRAP_FOR_AGENTS.md's runbook step 5, which claimed
skill scaffolding "registers `brain/` as the workspace source" — no
code path does this automatically (confirmed by grep); the step now
points at the `hooks` phase's actual preview + --force command instead
of telling the installing agent there is "nothing to judge" on a step
that silently never ran.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016JNV9ABwb32DZEwvo7P4ay
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
`gbrain bootstrap <subcommand> --help` (a help token AFTER the subcommand
name, e.g. `gbrain bootstrap uninstall --help`) fell through into the
subcommand's own arg parsing instead of printing help, since none of the
mutating handlers (repo/hooks/verify/attach/uninstall/render/interview)
checked for --help/-h/help themselves. `uninstall --help` ran a real
uninstall; `repo --help` created a real private GitHub repo; etc.
Add a SUBCOMMAND_HELP usage map plus a pre-dispatch hasHelpToken() guard in
runBootstrap so a help token anywhere in the subcommand's args short-circuits
before any lock/runner/engine/handler call. Bare `help` (no dashes) is also
recognized, except for `interview` (its --set KEY value free-text answers
could legitimately be the literal word "help").
New test/bootstrap-subcommand-help.serial.test.ts arms fixtures so the real
operation would reach its side effect if the guard were removed (an
already-rendered workspace for render/hooks/attach, an operational verify
config, an isolated uninstall home with a real receipt-tracked file, a fresh
interview workspace) and asserts nothing mutates.
* fix(doctor): honor the recorded pid_file in supervisor_singleton check
`gbrain doctor`'s `supervisor_singleton` check (#1849) compares the local
pidfile holder against the queue-scoped DB lock holder. It read
`readSupervisorPid(DEFAULT_PID_FILE)` unconditionally, even though the
supervisor's own 'started' audit event already records the pid-file path
actually in use (`this.opts.pidFile`). A supervisor launched with a custom
`--pid-file` (e.g. a launchd-managed deployment) would then get a false
"singleton mismatch" warning against its own healthy, single instance,
because the pidfile doctor read was never the one the supervisor wrote.
- doctor.ts now prefers `lastStarted.pid_file` when present, falling back
to `DEFAULT_PID_FILE` for events that predate the field.
- supervisor.ts resolves `pid_file` to an absolute path at emit time (the
only cwd context in which a relative `--pid-file` is meaningful), so a
later reader running from a different cwd doesn't misresolve it. The
process's own internal pidfile guard/read/write paths are untouched.
The DB lock (`gbrain_cycle_locks`) remains the sole singleton authority per
#1849 — this only corrects which pidfile the diagnostic display reads.
Added test/doctor-supervisor-singleton-pidfile.test.ts covering the fixed
path, the still-mismatching absent-pidfile case, and a source-grep pin for
the compatibility fallback.
* docs(comments): correct pid_file fallback rationale and drop unsupported claims
The 'started' audit event has carried pid_file since the supervisor's
introduction, so the fallback comment no longer claims a predate case;
DEFAULT_PID_FILE is env-overridable (GBRAIN_SUPERVISOR_PID_FILE), so the
comments stop calling it HOME-derived; and the custom --pid-file scenario
is now described as an example rather than asserted as common.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126D3zLWL5RE3CVxnPANiiU
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
#1586 threaded the cycle's resolved source through the synthesize phase so
subagent-written pages land in that source's (source_id, slug) rows. The
patterns phase was not updated and kept the pre-#1586 shape: it stamped a
literal 'default' on every collected ref and compared the reverse-write path
against a literal 'default'.
On a per-source cycle that files the page against the wrong source. The row is
created under 'default' (the child had no source_id to scope its put_page
calls) while the reverse-write drops the file into the named source's checkout,
because source_id === 'default' selects the brainDir/<slug>.md branch and
brainDir IS that source's checkout. Row and file then disagree about which
source owns the page — what doctor reports as multi_source_drift.
Applies the same threading synthesize.ts already uses: PatternsPhaseOpts gains
sourceId, cycle.ts passes cycleSourceId at the patterns call site the way it
already does for synthesize, the child carries SubagentHandlerData.source_id,
and reverseWriteRefs takes the cycle source as its native source. Unset stays
'default', so unscoped callers are unchanged.
* fix(bootstrap): Gate 2 checks only the active gh account, not every registered one
createPrivateRepo's Gate 2 ran bare `gh auth status` and treated any
non-zero exit as "not authenticated". That command aggregates every
registered account across every host and exits 1 if even one of them
has auth issues — so a stale, unused, expired account (or one on an
unrelated GitHub Enterprise host) false-blocks `gbrain bootstrap repo`
even while the actual active account works fine.
Gate 2 now scopes the check with `--hostname github.com` (this flow is
already github.com-only end to end: parseGithubOwnerRepo, the
repo-create URL fallback, etc.) and, when the installed `gh` supports
it, `--active` as well (added in cli/cli v2.57.0 — confirmed present at
v2.57.0 and absent at v2.56.0 by diffing status.go across tags on
cli/cli). Support is detected from the `gh --version` output Gate 1
already captures, so an older `gh` falls back to the host-scoped bare
form instead of hard-failing on an unrecognized flag.
Verified `gh auth status --active`'s semantics directly against
cli/cli's pkg/cmd/auth/status/status.go: passing --active skips the
per-host loop over non-active accounts entirely (`if opts.Active {
continue }`), so only the active account's entry can affect the exit
code.
* fix(bootstrap): regenerate flag registry for the new gh --active/--hostname literals
The prior commit's Gate 2 change added the string literals `--active` and
`--hostname` inside src/core/bootstrap/repo.ts (both in the gh argv and in
comments). src/commands/bootstrap.ts statically imports repo.ts, and
scripts/generate-flag-registry.ts scans one level of relative imports from
each CLI_ONLY case block to build the committed, freshness-pinned
src/core/cli-flag-registry.generated.ts (#2185) — so the 'bootstrap' entry
was stale relative to a fresh `bun run build:flag-registry` run, failing
test/cli-flag-validation.test.ts's freshness guard in CI.
Regenerated via `bun run build:flag-registry`; the only change is 'bootstrap'
gaining '--active' and '--hostname' alongside its existing ~70 entries. This
is the generator's documented, deliberately over-inclusive behavior (accepting
an unused flag is the pre-#2185 status quo) — neither flag is
security-sensitive or read from user input; both are hardcoded in the `gh`
subprocess invocation, not accepted from `gbrain bootstrap`'s own CLI args.
Confirmed via `git fetch upstream && git log HEAD..upstream/master --oneline`
(0 commits) that this is not upstream drift — purely caused by this PR's own
diff.
Fixes#3962
Return the structured extraction result for --json callers while preserving the existing human summary. Add a behavior-level regression test that proves stdout is parseable JSON.
* fix(facts): make transcript pages facts-extraction eligible
`gbrain extract-conversation-facts`'s ALLOWED_TYPES allowlist omitted the
`transcript` page type, so gbrain's own nightly transcript-ingest pages
were silently skipped by both the CLI `--types` validation and the
`cycle.conversation_facts_backfill.types` config filter. Even with the
type allowed, the built-in conversation-parser had no pattern for the
`## User` / `## Assistant` markdown-heading turn shape that transcript
ingest writes into `compiled_truth`, so parsing would still yield 0
segments.
This PR makes an explicit decision: transcript pages ARE now
facts-extraction eligible. That is a real behavioral change (a new,
potentially large corpus starts flowing through the extraction +
segment-cost path), not a no-op bugfix — flagging it plainly rather than
padding out the change as narrower than it is.
Changes:
- `src/commands/extract-conversation-facts.ts`: add `'transcript'` to
`ALLOWED_TYPES` / `ALLOWED_TYPE_ALIASES` (the single source of truth
for this allowlist).
- `src/core/conversation-parser/builtins.ts`: add the `markdown-heading-turn`
builtin pattern recognizing heading-only `## User` / `## Assistant` /
`## Human` / `## System` lines as turn openers, with D5 continuation-line
body absorption. `quick_reject` is deliberately scoped to the role-prefix
(not a bare `#{2,3}` heading check) so a message body that happens to
paste unrelated markdown headings doesn't starve the D18 scorer's
anchor-candidate ratio.
- `src/commands/jobs.ts`, `src/commands/doctor.ts` (x2 checks),
`src/commands/sources.ts`: these each carried their own hand-copied
literal of the same allowed-types list (background-job type filter,
`conversation_facts_backlog` doctor check, `conversation_format_coverage`
doctor check, `facts_backfill_estimate`). Switched each to import
`ALLOWED_TYPES` from the command module instead of re-listing it, so this
class of drift (a type added in one place, silently excluded everywhere
else) can't recur.
- `docs/architecture/KEY_FILES.md`: updated the two stale mentions (pattern
count 17→18, allowlist list) to current-state per this repo's own
reference-doc convention.
Known limitation (not fixed here, scope-bounded intentionally): parsing is
context-free, same as every other multi-line builtin in this registry — a
message body that contains a literal `## User` line (e.g. someone pasting
a markdown transcript excerpt into their own message) would be read as a
turn boundary. This is a pre-existing property of the whole parser
(`applyPattern`'s per-line scan has no fence-awareness), not something
this PR introduces or could fix without a much larger, separate change to
the shared orchestrator affecting all 18 patterns. Flagging it here rather
than silently shipping the same limitation as the other 17 builtins.
Tests: 4 new tests (2 in test/extract-conversation-facts.test.ts, 2 in
test/conversation-parser/parse.test.ts) covering the allowlist, the new
pattern's positive match + continuation absorption, and that ordinary
`## Summary`-style headings are correctly rejected. Full targeted suite
(conversation-parser + facts-extraction + doctor backlog + build-llms
freshness): 263 pass / 0 fail. typecheck clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126D3zLWL5RE3CVxnPANiiU
* fix(facts): read the type allowlist from core, not the command module
CI caught this: the known-flags registry drifted for doctor, sources, and
repos. The obvious remedy the guard prints -- regenerate and commit -- would
have been a regression, so this takes the other route.
The generator walks one level of a command module's relative imports and
harvests every flag-shaped string it finds, help text included, and is
deliberately over-inclusive. Importing extract-conversation-facts.ts just to
read ALLOWED_TYPES therefore spliced that command's entire flag vocabulary
(--types, --sleep, --slug, --segment-limit, --override-disabled, ...) into
the allowlists of three commands that implement none of it: `gbrain doctor
--types foo` would have passed validation and been silently ignored. That is
the exact defect class #2185 exists to close.
(jobs.ts is unaffected: it already imported the command module on one line
for runExtractConversationFactsCore, so those flags were already in its
registry entry before this branch.)
ALLOWED_TYPES + ALLOWED_TYPE_ALIASES now live in
src/core/conversation-facts-types.ts, a constants-only module with no CLI
text to harvest. extract-conversation-facts.ts re-exports both so its
existing importers are unchanged.
Verified: registry regenerates to zero drift (was doctor/repos/sources),
cli-flag-validation 24 pass, typecheck clean, 287 pass across the touched
areas. Confirmed against a clean upstream/master worktree that the drift was
introduced by this branch and is not pre-existing.
* fix(conversation-parser): reduce to the parser pattern only
Withdraws the `transcript` allowlist half of this branch. The premise was
wrong: `transcript` is not an upstream page type. `ALL_PAGE_TYPES` does not
contain it, `gbrain-base.yaml` declares `conversation` for "long-running
chat/transcript pages" and marks it `extractable: true` precisely so
extract-conversation-facts walks it, and `gbrain-base-v2.yaml` lists
`transcript` as an alias of `source` (a media primitive). Pages typed
`transcript` are a convention of my own ingest pipeline, not something
upstream produces — the fix for that belongs on my side, by emitting
`conversation`.
That takes the four call-site de-duplications with it (they existed only to
keep the allowlist in sync), and with them the flag-registry drift: no
imports are added, so the registry regenerates to zero drift with no
constants module needed.
What remains is the half that stands on its own: a `conversation` page whose
body uses `## User` / `## Assistant` headings matches none of the 17 builtins
and parses to 0 segments. `markdown-heading-turn` is an 18th pattern in the
same shape as the iMessage/Circleback additions before it.
Verified: typecheck clean, 181 pass / 0 fail across the parser, extraction,
flag-registry and llms-freshness suites, registry drift zero.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(skills): company-brainify — close three sanitization-defeating defects
1. Scope truncation: the first structural grep used '>' and overwrote the
retrieval-discovered scope list; Phase 1 now writes retrieval paths to
/tmp/brainify-scope.txt explicitly and both greps append.
2. Facts reconciliation: the skill claimed 'gbrain sync' makes the DB stop
serving deleted Facts-fence rows. Sync's convergence contract covers page
import only — fact extraction is explicitly decoupled (src/commands/sync.ts
CONVERGENCE CONTRACT); the reconcile lives in the extract-facts sweep
(src/core/cycle/extract-facts.ts). The procedure now triggers the sweep and
verifies removal with 'gbrain recall --grep' before certifying.
3. Backup retention glob: the backup is created as
shared-brain-history-backup-<ts>.git but cleanup documented
brain-history-backup-<date>.git — a pattern that matches nothing, silently
retaining the pre-sanitization history mirror forever. Globs now agree.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(skills): regenerate skills.lock.json after master fix-wave
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(skills): citation-graph-ingest — check-backlinks requires a subcommand
Bare 'gbrain check-backlinks' exits with a usage error; the CLI requires
'check' or 'fix' (src/commands/backlinks.ts runBacklinks). The hygiene step
now invokes 'check-backlinks check', matching every other invocation in the
skill pack.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(skills): regenerate skills.lock.json after master fix-wave
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
runServe()'s stdio path flips console-prefix's module-global
stdout→stderr redirect (#3844). bun test runs every file in one
process, so after sweep.test.ts's serve-wiring tests the flag stays
on and any later file pinning slog's stdout routing fails
(test/sync-all-parallel.test.ts, test/console-prefix.test.ts) —
shard-composition dependent, so it surfaces as a flake. Same reset
the donor harness (test/serve-stdio-lifecycle.test.ts) already
carries.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(core): execution-environment detection — local | cloud-sandbox | ephemeral-container
detectExecutionEnvironment() + isCredentialInjectingProxy() with injected
signals (CLAUDE_CODE_REMOTE, cse_ session-id prefix, proxy-injected token
placeholder, anthropic-egress proxy JWT, container markers). binaryOnPath
moves here as the canonical PATH probe. autopilot's detectInstallTarget
ephemeral branch now routes through the shared detector.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(bootstrap): repo-visibility ladder + per-turn Stop push + same-session failure banner
The durability lane. One repo-visibility verdict for every consumer (REST
first — never GraphQL, which sandbox proxies pin; authed ls-remote + an
attributed anonymous probe as the git-protocol fallback), replacing three
drifted probes. A 200 counts as public only with advertisement proof; a
401/404 counts as private-signal only with an auth challenge — fail-closed
in BOTH directions. Private verdicts cache 1h (private-only, per origin).
gbrain hook stop now spawns a debounced detached push per turn (per-root
state; cloud-sandbox defaults to every turn, elsewhere 5 min; a failing
status bypasses the debounce), closing the /exit and VM-reclaim gaps.
Push status is per workspace root, read through one shared reader by the
user-prompt banner (additionalContext + systemMessage — visible to the
human, not just the model), the SessionStart note, and doctor. Escape
hatches for self-hosted git: flag > env > file-plane config key.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(bootstrap): environment-aware install — honest cron skip, cloud repo-create guard, execution_env surfaces
installDurabilityCron probes for crontab before writing anything (containers
and cloud sandboxes ship without one — expected, reported as an honest skip
naming the event-driven pushes that still cover persistence). The repo phase
installs the container-friendly harden half (post-commit hook, no scheduler)
outside local machines. createPrivateRepo fails fast in cloud sandboxes with
the flow that works (create outside, open the session ON the repo, attach).
bootstrap verify gains a never-gating execution_env check; bootstrap status
--json carries execution_environment for installing agents to branch on.
Also fixes a live-PATH resolution class: binaryOnPath and the crontab execs
now pass the current env explicitly (Bun resolves against the startup
snapshot otherwise).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(bootstrap): lifecycle hygiene — uninstall teardown, doctor job liveness, .mcp.json out of the repo, honest persistence copy
uninstall now tears down the durability wiring it installed (launchd/cron
job, untracked post-commit hook, credential wiring — the committed helper
and AGENTS rules stay). doctor gains bootstrap_durability_job: presence +
LIVENESS (launchctl load-state, crontab line, pull-log freshness) — a plist
on disk with a dead job no longer reads as healthy. Rendered .gitignore now
covers .mcp.json (absolute machine paths must not land in the private
repo); verify warns on pre-fix installs that committed it; the never-built
state/mcp.json promise is gone from GITHUB.md. Persistence copy tells the
truth everywhere: event-driven pushes do the durability work, the 30-min
job is a multi-machine pull freshener. Post-commit hook install/removal is
worktree-safe (git-path resolution; the git marker is a FILE there) and the
cron wrapper's self-disable tests the repo dir, not its git marker. Two
follow-up TODOs filed (plugin hook distribution; Channels push lane).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(bootstrap): committed hook carrier for cloud + cloud-setup-script emitter + cloud runbook
Cloud sessions clone fresh and snapshot hook config at session start — the
gitignored settings.local.json never exists there, so hooks never fired in
cloud at all. Cloud installs now write the repo-COMMITTED .claude/settings.json
with PATH-resolved, fail-open commands (no machine paths; a host without the
binary no-ops); local installs keep settings.local.json; the writers enforce
that one event never fires from both carriers, and removal cleans both.
New: gbrain bootstrap cloud-setup-script prints the paste-ready environment
setup script (npm transport — bun fetching is proxy-incompatible in cloud;
never the unrelated npm-registry package). Runbook gains a NEVER FABRICATE
TOOLING hard rule, a cloud-sandbox section (expected degradations as facts to
relay, the attach-first flow), and failure-table rows for the proxy-403 and
missing-crontab signatures. Codex lane stated honestly: AGENTS.md Gate 2 now
has the pull-side push-health check. Guide documents the new knobs and the
cloud contract; llms bundles regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(bootstrap): per-turn push e2e chain, cloud-sandbox sim, per-root status reader migration + registry regen
bootstrap-persistence e2e gains the Stop-hook chain (one stop banks the turn
to the real bare remote; the debounce holds across stops; debounce-0 lands
consecutive turns) and fixes a pre-existing post-#4024 break: repoPhaseComplete
required a github-parseable origin, so session-end pushes deferred FOREVER for
self-hosted/file-transport origins — non-github repo_urls now bind by exact
URL equality (redirect protection preserved). Degraded-modes e2e gains the
cloud-sandbox simulation (status reports the environment; repo creation
refuses with the attach flow). workspace-push tests read per-root status
through the shared reader. Flag registry regenerated (new git argv literals
from the git-path/ls-files calls — the accepted argv-bleed class).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(doctor): categorize bootstrap_durability_job (categories drift guard)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(bootstrap): adversarial-review hardening — fail-closed privacy, no push-storm, no exfil paths
Both Claude and Codex adversarial passes ran; 11 findings fixed (union):
- Non-github 401+WWW-Authenticate no longer reads as private (RFC 7235 makes
the header mandatory on every 401, so a middlebox 401s identically) — the
top exfil path both models flagged; now unverifiable/fail-closed, operator
confirms via the escape hatch. github.com still needs x-github-request-id.
- Escape hatches downgrade ONLY 'unverifiable' — a PROVEN-public origin still
refuses (hatches never authorize a public push).
- treeNeedsPush measures against origin/<branch> (the push's own ref), not
@{u}: a no-upstream branch no longer reports a committed-but-unpushed tree
as push_clean and silently strands it.
- [D20] failing-retry uses a fixed 60s floor, not min(debounce,60s) — cloud
debounce=0 no longer re-runs the network ladder every turn.
- Committed hook carrier: dedupe/suppress only on the EXACT portable-command
shape, not a 'gbrain hook' substring (blocks the suppress-local-run-evil
supply-chain vector); GBRAIN_HOME refused in the committed carrier.
- push-status reason sanitized (charset+length) at every surface (banner,
doctor, status blob) so remote git stderr can't inject via the remediation.
- Per-root state: ghost-root records (deleted workspaces) filtered so a dead
failing record can't re-fire the banner forever; uninstall removes them.
- statusReport support blob reads push status through the shared per-root
reader; visibility cache strips URL userinfo (no PAT persisted).
- anonProbe strips userinfo + redirect:manual + SSRF flags + --end-of-options
on ls-remote; cron self-disable uses git rev-parse (worktree-safe both
ways); cloud-setup-script fails loud on a broken update; durability liveness
won't certify a never-run crontab as live; config get/unset resolve the
dotted file-plane keys; .bak/.broken gitignored; typed config fields.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* v0.45.8.0 feat(bootstrap): first-class cloud-sandbox install + per-turn persistence + fail-closed privacy ladder
VERSION + package.json + CHANGELOG + TODOS + llms bundles.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: sync KEY_FILES + README for the cloud-DX wave (v0.45.8.0)
New KEY_FILES entries for execution-env.ts and repo-visibility.ts; updated the
bootstrap repo/hooks, workspace-push, brain-repo-durability, and hook.ts
entries to current behavior (ladder verification, committed cloud hook carrier,
per-root push status, crontab probe + liveness, per-turn stop push + banner).
README's Claude Code line now states per-turn + cloud persistence honestly.
llms bundles regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(bootstrap): doc-review pass — attach records repo_url (enables cloud persistence), verify reads per-root push status, honest docs
Cross-model doc review (Codex) caught that the headline cloud-persistence
claim was hollow and two docs over-claimed:
- attach now records repo_url from the adopted origin, so the no-daemon push
gate (repoPhaseComplete) recognizes the repo phase as done — WITHOUT this,
the per-turn/session-end pushes deferred forever after an attach, which is
the ONLY install path in a cloud sandbox (repo is refused there). Privacy is
still enforced at push time by the ladder.
- bootstrap verify's push_probe reads the shared per-root reader [D8], not the
legacy single file — a fresh v0.45.8 install no longer reports 'no push
recorded' when per-root status exists.
- Docs corrected to match code: runbook stamp → 0.45.8.0; README states the
per-turn cadence honestly (debounced local, next-turn failure notice);
GITHUB.md qualifies auto-push as Claude Code (Codex is pull); KEY_FILES says
repo/status use REST (not the full ladder) and drops the stale --push;
bootstrap.md hooks-location covers both carriers; README verbs surface says
seven. llms bundles regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(bootstrap): regenerate vendored template-repo tree + isolate env-mutating tests (verify gate)
The verify CI gate caught two things the piecemeal local runs missed:
- templates/bootstrap/template-repo/ is a GENERATED tree; the source-template
edits (AGENTS.md Gate 2, CLAUDE.md cloud note) plus the version stamp had
drifted. Regenerated from source (check:bootstrap-templates green).
- test/bootstrap-repo.test.ts + test/durability-cron.test.ts newly mutated
process.env in non-serial files; converted to withEnv() (check:test-isolation
green) — no .serial rename needed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(ambient-recall): pin a keyless gateway so delta/context_pack writes never fire a real embed
Root cause of the shard-2 CI red after the 0.45.9.0 re-bump: adding this
wave's test files reshuffled the weight-packed shards, moving
ambient-recall.test.ts next to a neighbor that leaks CI's dummy
OPENAI_API_KEY (sk-test-*) into the gateway singleton (the bunfig preload
configures with env:{...process.env}, and a present-but-invalid key turns
remember's keyless-degrade embed into a hard 401). The delta/context_pack
tests exercise cursor + budget logic, not embedding quality, so this pins a
keyless gateway (env:{}) in beforeAll — isAvailable('embedding') is false,
writeSingleFact degrades (degraded_dedup) with no HTTP call, and the file is
deterministic regardless of shard bin-packing. Verified: passes with
OPENAI_API_KEY=sk-test-* set (the CI condition). Master's own comment in
legacy-embedding-preload.ts already warns 'adding any test file reshuffles
the mines'; this makes the file immune.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Both are CI-enforced on every VERSION bump (check-bootstrap-tag.sh,
check-bootstrap-templates.sh) but were missing from the table that
release flows read first.
Co-authored-by: test <test@test.co>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Every fix reviewed, tested in isolation, adversarially verified, security
reviewed, and re-tested as one composed branch. Full attribution preserved
via per-commit Co-Authored-By trailers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
file_list now routes through sqlQueryForEngine(ctx.engine); the pin keeps
the identical BigInt-normalization invariant but mocks the new seam
instead of the retired module-global db connection.
The wave's sync changes make the cycle sync phase lazily import
commands/sync.ts, whose static embedding.ts imports must all resolve
against the test's module mock (a missing name is a load-time
SyntaxError). Stubs mirror the real pure functions.