Tracking project progress across milestones. Update as work lands.
Prompted by an accidental whole-column delete in the Studio datatable grid (record
01KWT8482F8MX19ZPW8WYBN868, indicadores-clave-edumetrics lost its Fuente column). The
grid's delete-column fired with no confirmation and no undo. Recovered the column losslessly from
context_history (the pre-delete old_body, byte-identical to the post-extract backup) via
wiki update --file --agent recovery, then added two speed-bumps. No schema/migration — restores
reuse the existing event='update' history rows, labeled via agentSource.
- Generic
ConfirmDialog(studio/src/components/common/ConfirmDialog.tsx) — reuses the ConfirmDeleteDialog scrim convention (inlinez-index:80scrim,mw-fade/mw-pop, Escape/scrim-click cancel) without the delete-specific soft/hard checkbox.destructiveflag toggles red vs accent confirm button. - Confirm before deleting a column — the datatable grid's header
×now opensConfirmDialog("Remove '' and all N of its cells? You can undo with ⌘Z.") instead of firing the splice immediately. Cancel keeps the table; Confirm deletes. - Persistent, history-backed undo/redo (⌘Z / ⌘⇧Z) in the grid — a client-owned revision
timeline (
histRef/curRefinDataTableEditor) built fromwikiApi.historynewBodysnapshots, rebuilt only on pageId change / 409 (never oscillates over its own restore writes). Undo/redo issue a labeledupdatePage(agentSource:'undo'|'redo') via the sameifRevCAS path → server-persisted, survives reload. Routed from WikiView's document keydown only while a datatable grid is mounted (dtHandleRef !== null); prose keeps native ⌘Z. - Clickable History-panel restore (all page types incl. prose) — RightPanel History rows
gain a "↩ Restore" affordance (older revisions only) →
ConfirmDialog→onRestoreRevision(entry)=wikiApi.update(id, {body: entry.newBody, ifRev: page.rev, agentSource:'restore'})→detail.reload(). Restoring a datatable reseeds the grid; a prose revision re-renders the editor. -
ifRev+agentSourcethreaded on whole-body writes:studio/src/api/wiki.tsUpdatePageInput+src/studio/routes/wiki.tsupdatePageBodyzod (agentSourceoptional), passed intoupdatePage. StaleifRev→ 409{currentRev}. - Tests + gate: extended
test/studio-api.test.ts(PATCH stale-ifRev→ 409; abody+ifRev+agentSource:'restore'write succeeds and appends a labeled history row). Full gate green (typecheck + typecheck:studio + lint + 222 tests + build). Browser E2E on a seeded isolated store verified all four: (a) delete-column confirm (Cancel keeps / Confirm deletes), (b) grid ⌘Z/⌘⇧Z step through revisions with server body matching + persist across reload, (c) History "Restore" reverts a prose page, (d)undo/redo/restorelabels appear in History. - Datatable keeps the sidebar — the datatable view now forces the
'dual'layout (sidebar + full-width grid) instead of'focus', so the wiki stays navigable from a datatable; the right panel still auto-hides for grid width, and the three-pane layout restores on exit. Browser-verified the round-trip (datatable ⇄ prose sidebar navigation) keeps the sidebar and restores the right panel off-datatable. - Remove an embedded datatable from a page — a
![[Title]]embed rendered in the prose editor (WikiEditor) now carries a "✕ Remove" button in its header (and on the unresolved⚠ no page titled …note), gated by aConfirmDialog("only this reference is removed — the datatable itself is kept"). Confirm removes thecontenteditable=falseembed node andpersist()+flush()es the whole-body PATCH, which drops the![[Title]]marker and re-derives theembeds/wantedlinks server-side. Button hidden on read-only pages. Browser-verified: resolved + unresolved embeds both removable, marker dropped from the stored body, the referenced datatable page + its rows survive, LINKS re-derived, and Cancel keeps the embed. - Fix: embed shows "no page titled" on hard reload —
WikiEditor.hydrateEmbedsresolved![[Title]]against thepagesprop, but on a hard reload the load effect hydrates before the pages list finishes loading, so the embed fell back to the unresolved note AND eagerly setdata-embed-hydrated, locking it in until a full HTML rebuild (nav away/back). Fix: don't mark hydrated whenpagesis still empty, and add a[pages]effect that re-runshydrateEmbedswhen the list arrives (already-resolved nodes are skipped). Reproduced deterministically with a 15s one-shot delay on the pages-list fetch (tempdist/studio/index.htmlshim, since removed): embed = NOTE while pages loading → flips to TABLE the instant pages resolve, no navigation.
Stack: ESM + TypeScript · commander · better-sqlite3 + Kysely · FTS5 · ULID · zod · tsup · vitest · Biome. Binary: bctx.
- Project scaffolding (package.json, tsconfig, tsup, biome, .gitignore)
- Shared
core/data layer (db, paths, types, migrate) - Schema migration
0001_init(contexts, tags, FTS5 + triggers, history, skill_files) - Context CRUD + FTS search with soft-delete and append-only history
- CLI commands:
init,add,get,list,update,rm,search - Bundled CLI skills + progressive disclosure (
bctx skills [get|path] --full) - Strong inline help (
--help, examples) on every command - Bundled skill docs:
skills/braincontext/SKILL.md+references/* - Vitest coverage for core CRUD/search/delete (6 tests passing)
- Verified end-to-end: typecheck + lint + build + smoke test of the
dist/bundle (init/add/get/list/search/update/rm + skills progressive disclosure)
Global linking:
pnpm link --globalneeds pnpm's global bin dir. If it errors withERR_PNPM_NO_GLOBAL_BIN_DIR, runpnpm setuponce (setsPNPM_HOME+ PATH), then re-link. Until then, run the CLI viapnpm dev <args>ornode dist/cli.js <args>.
Stack additions: @modelcontextprotocol/sdk@1.29.0 · yaml@2.9.0. (skill_files BLOB schema, later folded into 0001_init — see v2.) zod stays at 4.x.
- MCP server (
bctx mcp) — stdio server with full-CRUD tools (search/get/list/create/update_context+ soft-onlydelete_context) + abctx://context/{id}resource. stdout is protocol-only; logs to stderr; long-lived DB. - Markdown export (
bctx export) — AGENTS.md (sections by kind, canonical), CLAUDE.md (@AGENTS.mdbridge),.cursor/rules/*.mdc(one per rule, 3-field frontmatter). Idempotent<!-- BEGIN/END braincontext-cli -->fences;--out/--targets/--dry-run/--check. - Full SKILL.md bundles (
bctx skill add|export|list) — import a SKILL.md dir into akind='skill'row +skill_filessidecars (BLOB + exec bit); strictname==folderkebab validation; faithful export with binary round-trip andchmod +x scripts/. -
yaml-based frontmatter parse/serialize (src/lib/frontmatter.ts); replaced the home-grown parser. - Vitest coverage for all three (mcp / export / skillbundle / frontmatter); 18 tests passing.
- Verified end-to-end on the
dist/bundle: export dry-run/write/check, skill add→list→export round-trip (exec bit restored), and a realbctx mcpstdio handshake (clean stdout, 6 tools, resource).
Replaces the previously-deferred "semantic search (vectors)" and "knowledge-graph relations" with a Karpathy-style LLM wiki: interlinked markdown pages + typed links (the graph), retrieved via FTS5/BM25 + link navigation — no embeddings. Greenfield: the whole schema is one base 0001_init (0002 folded in). Stack: yaml reused for frontmatter.
- Schema folded into
0001_init:contexts.page_type+contexts.slug(unique among pages),links(typed edges;to_id NULL+to_title= wanted link),wiki_log. FTS untouched. - Default-exclude boundary:
listContexts/searchContextsdefault topageScope:'context'(page_type IS NULL) — wiki pages never leak intolist/search/AGENTS.md export/MCP.--include-wikiopts in. Regression-guarded by tests. -
core/wiki.ts: createPage/recordSource/getPage(BySlug/ByTitle)/updatePage(source-immutable)/listPages/searchPages, addLink/removeLink/outbound/backlinks,[[Title]]auto-sync (referenceschannel), appendLog/listLog, andlint. -
bctx wiki: new/get/show/link/unlink/backlinks/search/index/log/ingest/lint/export/import. Ingest is mechanical (store source + log + checklist); the agent synthesizes. - MCP:
wiki_search/get/new/link/ingest/lint(6 tools) +bctx://wiki/{id}resource. - Schema-layer skill
skills/braincontext-wiki/(SKILL.md + references/{structure,ingest,query,lint}) — the "disciplined maintainer" doc. - Export/import Obsidian-compatible markdown (
[[Title]]↔[..](slug.md), index.md + log.md), idempotent round-trip. - Vitest: wiki / lint / export-import / regression-guard / mcp-wiki (31 tests passing).
- Verified end-to-end on the
dist/bundle: ingest→pages→links→lint, list regression guard, index/log, export→import, and a realbctx mcpwiki-tool handshake.
A manual files→DB sync (never auto-sync) for non-wiki contexts.
-
storeexport format —bctx export --targets store --out ./ctxwrites one identity-bearing.mdper context (frontmatterid/kind/namespace/scope/title/tags/agent+ body). Reuses thePlannedFilediff machinery (--dry-run/--check).storeis opt-in (not in the default targets); wiki pages + deleted are excluded byselectContexts. -
bctx import <dir>— matches files→contexts by frontmatter id: edits → update (title/body/tags via add/remove diff, metadata merged), new files (no id) → create (honoring a provided id), missing files → kept by default, soft-deleted only with--prune.--pruneis scoped to the namespaces present in the files (a partial export never deletes other namespaces).--dry-runpreviews; ids resolving to wiki pages are skipped. - One
corechange: optionalidonCreateInput(createContextusesinput.id ?? ulid()). - New files:
src/export/store.ts,src/sync/import.ts,src/commands/import.ts; edits toexport/write.ts(+storetarget),commands/export.ts,cli.ts. - Vitest
test/sync.test.ts(round-trip id-honored, edit→update, create, namespace-scoped prune, dry-run, wiki-safety); 36 tests passing. - Verified end-to-end on the
dist/bundle: export store → edit a file → dry-run → import (get reflects edit) → delete file kept →--prunesoft-deletes; plainlist/wiki unaffected.
Not part of the core app. The core stays embedding-free: retrieval is FTS5/BM25 + the
typed links graph (the wiki direction). These belong in an optional plugin layer if/when
one exists, so a user can opt into heavier deps without bloating the local-first core.
- Embeddings / semantic retrieval (e.g.
sqlite-vec) — vector search as an opt-in plugin that could augmentwiki search. Explicitly not a core feature.
storeround-trip syncs content, not identity. On import-update, onlytitle/body/tags(+mergedmetadata) flow back;kind/namespace/scope/idare treated as immutable structural identity (edits to them in a store.mdare ignored on update — though all are honored on create). This is intentional: those axes file contexts for export-by-kind,list --namespace, the wiki firewall, and--prunescoping, so re-homing a row must be deliberate, not a side effect of a text edit. Confirmed correct.
A 9-agent audit (6 dimension auditors → adversarial skeptic + completeness critic → synthesis) found and we fixed:
- Security: skill-bundle symlinks no longer followed (no file exfiltration); crafted wiki
slugis slugified (no path traversal on export); skill reconstruct has a path-traversal guard;.mdcglobscollapsed to one line + hardenedyamlScalar(no YAML-frontmatter injection). - Data-loss / correctness:
import --prunenow scoped by an export manifest (.braincontext-export.json) and refused when scope is unknown (no accidental namespace wipe); wikiexportdeletes stale files (deleted pages can't resurrect); managed-block replace uses a function replacer ($-patterns no longer corrupt bodies); creating a page now resolves pre-existing wanted links (no permanent phantom orphans). - Firewall: by-id context surfaces (
bctx get/update/rm, MCPget/update/delete_context, the context resource) reject wiki pages → addedbctx wiki rm; source pages stay immutable everywhere. - Robustness:
searchContextstolerates FTS5-special input (sanitized fallback, never crashes) and honorsincludeDeleted;referencesreserved for the auto channel (explicit links survive body re-sync); CRLF/BOM frontmatter parsed; bad files skipped (not fatal) on import;.cursor/rulesfilenames de-duped; CLI int options validated; zod errors print cleanly; lint excludes edges from soft-deleted pages; provenance timestamps + sourceuripreserved on wiki re-import; skill exec-bit honored faithfully. - Coverage:
test/audit.test.ts(11 regression tests) added; 47 tests total, all gates green.
The North Star foundation: one project's context shared across sessions, devices, and
members. Driver swapped to libSQL (@libsql/client + @libsql/kysely-libsql),
replacing better-sqlite3 — one URL-driven driver covers local files, embedded replicas,
and remote connections, so "go online" is a config change, not a rewrite. No auth yet.
- libSQL driver —
src/core/db.tsis nowopenStore(DbTarget)over@libsql/client(local file / embedded replica / remote),withDbsyncs a replica before/after the op (--no-syncto skip), andclose()tears down both Kysely and the client. The whole schema (FTS5 external-content + triggers +bm25()) validated on libSQL — Phase 0 risk cleared. Tests run on a temp file db (libSQL recreates its connection per transaction, which discards a:memory:db). BLOBs (skill files) normalized from ArrayBuffer → Buffer. - Project registry —
src/core/registry.ts:~/.braincontext/config.json(projects + currentProject) andcredentials.json(tokens,0600, never in config.json); the legacy globalstore.dbauto-registers as thedefaultproject.resolveTargetprecedence:--db/BCTX_DB>--global/--local>--project/BCTX_PROJECT> current project > cwd./.braincontext> default. Global--project/--no-syncflags added. -
bctx project—create/list/use/current/path/rm(local) +migrate-online/link/sync/status/disconnect(online).migrate-onlineseeds a fresh remote primary from the local store (faithful, ids preserved —src/core/dump.ts), then re-bootstraps the local file as an embedded replica;linkattaches another device to the same remote. - Sync semantics — write-through to one primary (serialized → no multi-master conflict); distinct-ULID inserts merge, same-row edits are last-writer-wins with history retained. Online writes need connectivity (offline-write reconciliation deferred).
- Vitest:
test/registry.test.ts(precedence, token isolation + 0600, default seed) andtest/online.test.ts(FTS5 lock + faithful remote seed); 59 tests total, all gates green. - Verified end-to-end on
dist/: project create/use/isolation/--projectoverride/status/path/rm (local). The replica/sync round-trip needs a reallibsql://remote (manually verifiable); the seed (its core) is unit-tested.
Phase 3: offline-write reconciliation (Turso CDC /
updated_atmerge). S3/R2 = backup only, never a sync backend.
Per-member permissions on a shared project. Migration 0004_access adds principals,
principal_keys (scrypt hashes + a public lookup prefix; the secret is never stored) and
access_log, plus a nullable principal_id on contexts/context_history/files.
Inert until store_config['access.enabled'] is set, so every existing project is
unaffected — resolveSession short-circuits on one indexed config read.
- Core —
src/core/access/:capabilities(9 capabilities × 4 roles + per-user overrides),keys(scrypt,bctxk.<prefix>.<secret>, all failure modes distinguished),principals(CRUD + last-owner / admin-vs-owner policy),session(+AsyncLocalStoragefor attribution),gate(authorize+restrictForSession),readonly(Kysely write-rejecting plugin, allow-list so new node kinds fail closed),audit,joincode(checksummed, so a mangled paste says so),cache(60s revocation window for long-lived surfaces). - CLI — gated inside
withDbvia a command-path → capability map (src/core/access/commands.ts), filled in bydbOptsFrom, so no command handler changed.buildProgram()extracted tosrc/program.tsso a test can walk the tree; unmapped paths fail closed. New:bctx access {init,status,disable,recover,user,key,log},bctx whoami,bctx project join <code>. - MCP —
installAccessGatewrapsregisterTool/registerResourceonce, so all 33 tools (and any added later) are gated; denials come back as a readableisErrormessage an agent can act on. - Studio —
accessGuardbesidelocalOnlyGuard, rule-based route→capability mapping, cookie sessions with local-key adoption (the admin who launched Studio is already signed in),/api/auth/*,/api/access/*, a login gate, and a Users & access settings panel that reveals each secret exactly once. - Latent bug fixed —
SEED_TABLESinsrc/core/dump.tslisted only the0001tables, somigrate-onlinesilently droppedpage_properties,store_config(the storage credentials!) andfiles. Now complete, with a drift test againstsqlite_master. - Vitest:
access,command-caps,mcp-access,studio-auth(+ migration/seed coverage) — 319 tests, all gates green. Verified end-to-end ondist/across two isolatedBCTX_HOMEs (join code → reader → denials → promotion → revocation → recovery) and in the browser.
Advisory, and documented as such. Clients sync against the libSQL primary directly, so the raw database token bypasses all of this and the join code carries that token. What this buys: roles, attribution, audit, revocation, mistake-prevention across every bctx surface.
access.modeis reserved (advisory | token | relay) so the two hard-enforcement upgrades — per-user Turso tokens (real read-only), then a write-relay — are a value change, not a redesign. Still out of scope: binding Studio beyond127.0.0.1(no TLS, no rate limit), and resource-scoped permissions (per-tag / per-namespace).
A 5-dimension concurrency-hazard audit + a multi-process stress harness
(scripts/stress/) drove these fixes. Stress harness spawns N independent OS
processes (each its own libSQL connection = one "agent") and verifies data-integrity
invariants after the burst; a companion remote harness hits a real Turso primary.
- The bleed (CRITICAL): the libSQL swap had dropped WAL + busy_timeout, so concurrent writers failed instantly with
SQLITE_BUSY(562/1067 ops failed at 8 workers). Fixed insrc/core/db.ts:journal_mode=WAL(file-header-persistent) + libSQL Configtimeout: 5000(busy_timeout that survives the client's per-transaction reconnect) +src/core/tx.tswithWriteRetry(capped backoff on busy/locked) around every write transaction. - Migration race (CRITICAL): Kysely's migration lock is a no-op on SQLite/libSQL, so concurrent first-runs crashed with "table already exists" (all workers died, 0 rows). Fixed in
src/core/migrate.ts: a fastisCurrentpre-check (steady-state skips the migrator), a cross-processO_EXCLlockfile (src/core/lock.ts) so only one process migrates a local file, idempotentIF NOT EXISTSDDL, and benign-race tolerance for the remote path. - Lost updates (HIGH):
updateContext/deleteContextread the row OUTSIDE the transaction → concurrent edits clobbered each other and the auditold_bodywas wrong. Fixed: the read-modify-write now happens entirely inside the IMMEDIATE transaction; writes return an in-memory object (no stale replica read-back, fixing the replica read-your-writes gap too). - Missing UNIQUE constraints (HIGH): added (in
0001_init) partial unique indexes for resolved links(from_id,type,to_id), wanted links(from_id,type,lower(to_title)), and one live skill per(kind,title,namespace);addLink/context_tagsinserts useonConflict doNothing;syncBodyLinks/resolveWantedLinksare now atomic;createPage/recordSourceretry on the slug-uniqueness race. - In-process write serialization (found via stress): libSQL opens a fresh connection per
transaction(), so concurrent writes from one process (e.g. the long-lived MCP connection handling concurrent tool calls) become many same-file connections — which SQLite's same-process locking handles poorly (5s busy stalls, even lost updates).src/core/tx.tsnow chains writes per Kysely instance (one in-flight writer per connection): 40 concurrent writes went from a 5s timeout to 42ms, 30 concurrentsetMetadataon one row keep all 30 keys. Cross-process concurrency is unaffected (separate instances; busy_timeout + WAL coordinate). - Verified — local: 16 workers × 200 ops × 3 phases = 9,600 ops, 0 failures; plus lost-update (1200 concurrent setMetadata on one row → 0 keys lost, history chain intact), wiki (589 concurrent pages, 0 duplicate links), skill (1200 concurrent imports of 3 names → exactly 3 live skills). FTS integrity-check passes throughout.
- Verified — remote (Turso): 5 concurrent clients CRUD the primary (0 failures, FTS works remotely); 3 replicas write-through + a fresh replica bootstrap all converge to the identical set.
- Registry & bootstrap atomicity (MEDIUM):
config.json/credentials.jsonwrites are now atomic (temp-file +rename,src/core/registry.ts) so a concurrentprojectcommand can't read a half-written/truncated registry;migrate-online/linkrun under a cross-process bootstrap lockfile so two concurrent runs can't double-seed. - Coverage:
test/concurrency.test.ts(concurrent first-run migration, concurrent writes serialize, lost-update guard). 62 tests, all gates green.
Known limitation (replica single-owner): an embedded-replica file should have one active syncer at a time. Running a long-lived
bctx mcpon a replica project and concurrent CLI commands against the same replica file (two independent syncers to one file) is unsupported and can corrupt the local replica — use separate replica files (devices) or route through the one MCP connection. The remote primary is always safe.
The human-facing surface: bctx studio now serves a full read/write SPA over the
store, replacing the 1065-line mock "personal wiki" reference component with a
decomposed app bound to the braincontext domain (wiki pages + typed links as the
primary workspace, a Contexts tab for note/rule/snippet/decision/skill, an in-UI
project switcher). Keeps the reference's warm paper/Spectral aesthetic; only the
data/taxonomy changed. localhost-only, no auth (writes go through core/).
- Backend API —
src/studio/server.tsis now a thin Hono mount over focused route modules (src/studio/routes/{health,contexts,wiki,projects,tags}.ts) +http.tshelpers; full CRUD/search/history for contexts, pages CRUD + links/backlinks/graph/log for wiki,/api/projects+ switch/sync. Every handler callscore/(anti-drift held). - StoreManager (
src/studio/stores.ts) — aStoreProviderinjected into the app so a runtime project switch swaps the live db under every handler (serialized, opens the next store before closing the old; replica single-owner preserved). Tests use a trivialstaticProvideroverfreshDb(). - Core additions —
listTags/listHistory(core/contexts.ts),wikiGraph(core/wiki.ts);updatePagealready re-syncs[[..]]links on body PATCH. - Frontend decomposition — god file →
studio/src/{api,lib,state,components,editor,views}: typed API client + DTOs, pure libs (markdown ⇄ html, wikilinks, force-graph, editor blocks, theme), hash router + context + hooks, and components for sidebar/topbar/palette/ project-switcher/editor/backlinks/graph/contexts. No heavy state lib.@ts-nocheckdropped; biome a11y relaxed for the inline-styled SPA only. - Editor — contenteditable with slash menu, wikilink autocomplete (insert + create),
selection toolbar, hover previews, dual markdown pane; 10s debounced autosave flushed on
blur / navigate / project-switch /
beforeunload(per product decision — never per keystroke). - AGENTS.md export preview —
previewExport(src/export/preview.ts, filesystem-free, reusesrenderAgentsBody/renderClaudeBody/renderMdc+ canonical managed block) behindGET /api/export/preview?targets; Contexts tab gets an Export toggle → read-only tabbed view of AGENTS.md / CLAUDE.md / .cursor/rules/*.mdc with copy-to-clipboard. Nothing is written. - Vitest:
test/studio-api.test.ts(contexts CRUD+search+history, wiki pages/links/backlinks/ graph/resolve +[[..]]autosync, export preview, projects/tags) viaapp.request(); 71 tests total, green. - Verified end-to-end in a real browser (agent-browser, isolated
BCTX_HOME): page nav + hash deep-links, edit → 10s autosave → DB-persisted,[[..]]autocomplete → insert → server link-sync, graph (constellation/local/by-type), backlinks/properties, contexts list/filter/edit/ delete, export preview (AGENTS.md + .cursor rendering), ⌘K palette, theme toggle, Wiki↔Contexts tabs, project switcher. Two bugs found+fixed (double-hash routing; contexts-tab redirect bounce).
- Driver: libSQL (
@libsql/client) since v4 — one URL-driven driver covers local files, embedded replicas, and remote primaries, so "go online" is a config change, not a rewrite. (v1–v3 used better-sqlite3;node:sqlitewas skipped as still-experimental on Node 22–24 with no migration-tool support — revisit on Node 26.) - Query layer: Kysely (zero-dep, whole-query type-safety, built-in migration runner) over Drizzle for a barebones tool.
- Concurrency: WAL mode + short transactions + history/soft-delete make multi-agent writes recoverable.
- Anti-drift rule: every surface calls
core/, never raw SQL. (MCP, export, and skill bundles all go throughcore/.) - MCP SDK:
@modelcontextprotocol/sdk@1.29.0(stable) supports zod 3 and zod 4 via its compat layer — kept zod 4, no pin. Avoided the@modelcontextprotocol/server2.x alpha. Raw-shapeinputSchema(notz.object). stdout reserved for protocol; all logs to stderr. - Export bridge: AGENTS.md is canonical; CLAUDE.md only imports it (
@AGENTS.md) since Claude Code doesn't read AGENTS.md — single source of truth, no drift. Cursorglobsemitted as an unquoted CSV string (not a YAML list), per spec. - Skill storage:
skill_files.contentis BLOB +is_executableso binaryassets/round-trip andscripts/stay executable. Full frontmatter stored losslessly in the context'smetadataJSON. - Greenfield migrations: no incremental/back-compat migrations — the full schema lives in one base
0001_init; schema changes re-baseline (reset the DB). This collapsed the planned0003_wikiADD-COLUMN/rollback work. - Wiki = no embeddings: retrieval is FTS5/BM25 + the typed
linksgraph (the graph is the knowledge graph). Pages reusecontexts(FTS/history/MCP/export for free);page_typeis the wiki marker, orthogonal tokind, default-excluded so legacy surfaces never regress. Sources (page_type='source') are immutable.[[Title]]resolves case-insensitively to non-deleted pages; unresolved → wanted (lint) link. Lint joinsdeleted_at IS NULLon both endpoints and ignores edges fromindexpages.
A second multi-agent double-check after v5 (an 11-dimension fan-out, 73 agents, every
finding independently adversarially verified — refuted/duplicate dropped) surfaced 62
findings: 0 critical, 8 high, 26 medium, 27 low. The core is sound (per-instance write
serialization, IMMEDIATE-txn read-modify-write, correct FTS triggers, real path-traversal
guards, 0600 credentials, anti-drift through core/). The weaknesses cluster in the
unauthenticated Studio HTTP surface, a handful of data-loss edges, the
retrieval/adoption loop that makes context actually compound, and doc drift. This
milestone now ships all P0, P1, P2, and P3 below (each behavioural fix with a regression
test). Full gate green — 104 tests across 21 files — verified on the real binary
(init/add/export/update/wiki/status/export --watch) and a live Studio server
(percent-malformed path → 400, cross-origin POST + rebind Host → 403). Version bumped to
0.6.0; CI added. Two items are intentionally not shipped and remain backlog: the
namespace-default-to-project taxonomy change (semantic, needs design) and jsdom client-side
React-hook tests (the data-loss-critical round-trip is covered server-side instead).
ON DELETE CASCADEsilently never fires.prepare()setsPRAGMA foreign_keys = ONon the main connection only, but libSQL's sqlite3 client drops/reopens its connection pertransaction()(the same reasonbusy_timeouthad to move to the clientConfig, seedb.ts:24-29). So inside any write transactionforeign_keysis OFF and cascades don't run —skills.tsre-import (// cascades skill_files) orphans the prior bundle'sskill_filesBLOBs. Fix: never rely on CASCADE in app code; delete sidecars explicitly inside the txn (auditlinks/context_tagstoo).
- Studio CSRF / DNS-rebinding: the no-auth localhost read/write API had no
Host/Originvalidation → a visited web page could CSRF-write (Hono parses any content-type, so atext/plainsimple-request POST needs no preflight) or DNS-rebind to read the store; injected contexts later flow intoAGENTS.md/CLAUDE.md= prompt-injection. Added aHost+Originallowlist middleware (src/studio/server.ts), regression-tested (test/studio-api.test.ts) + verified on a live server (403 on cross-origin POST + rebind Host). -
wiki export <dir>deleted unrelated.mdin the target dir — deletes are now scoped to tool-written files (wikislug+typefrontmatter); hand-authored markdown (README/AGENTS.md) is never touched (src/wiki/export.ts). -
import --prunedeleted contexts whose file failed to parse — now refuses to prune when any file is unparseable (src/sync/import.ts). - CASCADE orphan bug (above) — explicit child-row deletes via a shared
deleteContextChildren(src/core/contexts.ts), used byskills.tsre-import and hard delete; test asserts 0 orphanedskill_files. - Studio editor data-loss windows —
beforeunloadnow prompts on dirty + best-effort saves; the editor flushes on blur and on project-switch (registered via the store'sregisterFlush) (studio/src/state/useAutosave.ts,StoreContext.tsx,WikiView.tsx). - Leaked Turso token —
test-config.mdgitignored, creds env-only; token rotation is a manual Turso-dashboard action.
-
wiki_updateMCP tool (+wiki_unlink,wiki_graph) — an MCP-only agent can now edit a page in place instead of spawning duplicate-titled pages; tool descriptions + ingest checklist nudge update-over-create (src/mcp/wiki-tools.ts). - Agent self-onboarding preamble —
exportalways emits a managed AGENTS.md preamble ("retrieve before acting; save durable facts after; MCP available") even for an empty store;initprints the next-step hint (src/export/render.ts,src/commands/init.ts). -
bctx status/doctor— single orient-me command: store/project/mode/location, counts by kind/type, links, tags, and AGENTS.md staleness (src/commands/status.ts). - Env-derived
--agent— auto-attribute from agent env vars (orBCTX_AGENT) elseuser@host, threaded throughadd+wikiwrites (src/lib/agent.ts).
-
searchContextsswallowed all errors as empty — now only FTS5 syntax errors are caught (and the query is sanitized + retried); busy/locked/I-O/corruption re-throw so "no matches" ≠ "search failed" (src/core/contexts.ts). - Wiki
typefilter was applied after SQLLIMIT→ truncated;page_typeis now pushed into the WHERE soLIMITapplies to already-filtered rows (listPages/searchPages). - Managed-block sentinel-in-body corrupted
AGENTS.md& broke idempotency —BEGIN/ENDmarkers in rendered bodies are now neutralized with a zero-width char (src/export/managed.ts). -
export --targets storenever removed stale files → deleted contexts resurrected on re-import;pruneStaleStoreFilesnow removes scope-matching files whoseidis no longer live (id-less files like README/AGENTS.md are never touched) (src/export/store.ts). - Wiki export/import dropped the typed-link graph (only body
[[links]]survived) — explicit links now serialize to frontmatter (slug/title) and re-create on import (src/wiki/export.ts,src/wiki/import.ts). -
updatecan now changekind/scope/namespace(--kind/--scope/--namespace); onimport, divergence in those immutable fields surfaces a skip warning (with thebctx updatehint) instead of a silent "unchanged" (src/commands/update.ts,src/sync/import.ts). - Skill
loadSkill/skill exportnow honor namespace; a bare name living in multiple namespaces throws with the candidates instead of exporting an arbitrary bundle (src/core/skills.ts,src/commands/skill.ts). - Studio SPA: blank pane after project switch fixed (redirect to first live page); read
errors now surface an error UI with Retry instead of an empty state; hover-preview
DOM-XSS sink replaced with
DOMParser/textContent; autosave serialized +beforeunloadguard (studio/src/views/*,studio/src/state/*,studio/src/editor/WikiEditor.tsx). - Taxonomy overload: defaulting
--namespaceto the current project — deferred (semantic change; flagged to avoid a silent cross-project regression).
- New regression tests (104 total, +16): FTS sanitize, wiki type-pushdown-under-LIMIT,
removeLinkcount,updatekind/scope/namespace, managed-sentinel idempotency, store prune, wiki typed-link round-trip, frontmatter blank-line byte round-trip, registry chmod + token-collision, skill-namespace disambiguation,withFileLockSyncsteal/release, studio malformed-path 400. (jsdom clientuseAutosave+ spawn CLI-smoke deferred — the latter is covered by the real-binary e2e.) -
config.jsonnow written0600and~/.braincontextkept0700(registry.ts). - Project token env-var collision fixed: names with
./-(which collapse to_) get no env override — credentials.json (keyed by exact name) is unambiguous (registry.ts). - Registry read-modify-write now runs under a cross-process
withFileLockSync(no lost updates between concurrentbctx projectcommands) (src/core/lock.ts,registry.ts). - Stdio MCP server now exits on client disconnect (stdin EOF) so an orphaned process can't
keep the embedded-replica write-lock (
src/mcp/run.ts). - CLI silent-success foot-guns fixed:
--set-metarejects malformed pairs;export --targetserrors on unknown names;updaterefuses an empty body (wipe guard);wiki unlinkreportsUnlinked (n)/No matching link to remove. - Wiki resource hides soft-deleted pages; frontmatter round-trips the blank line after the
block; studio wiki PATCH returns
409only for the immutable-source conflict (else500); studio project-switch drains in-flight requests before closing the old store. -
bctx export --watch— re-exports whenever the store changes (polls + replica re-sync); exits promptly on Ctrl-C; closes the staleness loop (src/commands/export.ts). -
prepublishOnlynow runs typecheck + studio typecheck + lint + test + build vianpm(not hardcodedpnpm); version → 0.6.0;.github/workflows/ci.ymladded. -
READMEv4→v5 + Studio section;LICENSEshipped; thisprogress.mdrefresh.
A design exploration into a core mechanic — agent-mutable structured tables ("embed JSON instead of markdown tables; reference across pages; let agents search/mutate rows & fields"). Produced by a 12-agent design pass (5 independent proposals → adversarial code-grounded verification → completeness critic → synthesis). Every claim below is verified against the current code. No code written yet — this section is the direction of record; build follows the phased plan.
Retrieval got sophisticated (disclosure ladder index→peek→full, maxTokens,
tokenEstimate everywhere, FTS5/BM25 + link graph, no embeddings). Mutation stayed
whole-body: updateContext replaces the entire body column (src/core/contexts.ts:235)
and there is no section/row/field patch anywhere (CLI/MCP/Studio). Changing one cell of
a 40-row table = fetch full body, regenerate the whole table, PATCH it back (expensive
output tokens + LLM table-corruption risk). Tables are the weakest surface: opaque TEXT
server-side; the only parser is browser-only and lossy (drops alignment,
studio/src/lib/markdown.ts). The datatable is the highest-structure instance of one
axis: sub-document addressability.
- No sidecar
datatable_rowstable. It emptiescontexts.body, breaking what it claims to inherit:pagePeekreadsbody(wiki.ts:378),contexts_ftsmirrors onlytitle+body(0001_init.ts:139),pageFreshnesskeys onupdatedAt(wiki.ts:293). Keep canonical data inside the singlecontextsrow. - No
page_type=NULLblocks — excluded fromwiki_search(page_type IS NOT NULL), so cell text becomes unfindable on the surface agents hit first. - Token win is agent-I/O only, not storage.
context_historylogs fullold_body+new_bodyper update (contexts.ts:292); the row is the LWW sync unit. Realistic warm reduction ~5–15×, not 35×; sync/history bytes flat-to-worse. Framed honestly, not oversold.
Writes are whole-row LWW and
tx.tsserializes only per-Kysely-instance (MCP/Studio/CLI are separate processes; Turso sync is row-LWW), so any fine-grained mutation API invites silent cross-replica clobbers of disjoint edits. The compare-and-swap guard ships first.
- Storage: canonical data inside the single
contextsrow, never a sidecar. Phase 1 = GFM-in-bodyspliced in place (JSON ergonomics, zero schema change); Phase 3 = promoted datatable keeps canonical JSON in a fenced```bctx-tableblock, rendered to GFM on read so FTS/peek stay honest. - Revision handle: reuse
updatedAt(already surfaced on every peek/get/search/resolve — no new column). Content-hash fallback if timestamp granularity bites. - Transclusion: Obsidian-native
![[slug]]. Prereq:WIKILINKregex/\[\[([^\]]+)\]\]/ghas no!guard, duplicated insrc/lib/wikilinks.ts:1andsrc/wiki/export.ts:13→ add(?<!!)lookbehind + dedupe; route![[..]]into a reservedembedslink channel mirroringreferences(kept out ofLINK_TYPES). - Export: Phase 1 byte-stable (table already in
body; one-time whitespace reflow on first splice). Phase 3 object → own<slug>.md, canonical JSON in frontmatter (data:) + rendered GFM body; pages keep![[slug]]. Notsrc/export/managed.ts(single global BEGIN/END can't host N id-scoped fences). - Flagship tools (exactly four, no query language in v1):
wiki_table_get/_set_cell/_add_row/_delete_row, addressed by caption/heading + column-name + row-key (not positional), echoingheadingAbove+header; loud on ambiguous/out-of-range.
| Phase | Scope | Ships |
|---|---|---|
| 0 — Enabling primitives | S | Revision-CAS + fence-aware GFM (de)serializer (src/lib/mdtable.ts). No user surface. |
| 1 — FLAGSHIP: inline table ops | M | src/core/tables.ts cell/row splice via existing write path; wiki_table_* MCP + CLI. Kills the one-cell-edit pain. Zero schema change, byte-stable export. |
| 2 — Studio parity + parser unification | M | Cell-blur endpoint → core/tables.ts; one shared table grammar (browser imports the pure lib). |
| 3 — First-class datatables + transclusion | L | Body-canonical datatable under a searchable page_type (body is the GFM table → reuses FTS/peek/history + Phase-1 table ops, no bespoke read path); ![[Title]] embeds via reserved embeds channel, inlined on read; per-file export w/ verbatim embeds. The literal "reused across pages" ask. |
| 4 — Typed properties + views | L | Derive-on-write page_properties mirror of metadata.props; wiki_query predicate (eq/ne/lt/gt/lte/gte/in/contains/exists + numeric sort); page_type='view' saved queries rendered to a live GFM table. "Database OF pages". |
| 5 — General sub-body mutation | M | wiki_get {section} / wiki_patch_section on a fence-aware heading splice + CAS; wiki_replace {find,replace,occurrence} exact-match-or-refuse diff-write for the prose majority. |
Shipped: Phases 0–5 (COMPLETE) (200 tests green; typecheck + typecheck:studio + lint + build + real-binary e2e).
- Phase 0 — content-hash
revon everyContext(computed intoContext, never stored);ifRevcompare-and-swap inupdateContext(RevConflictError) threaded throughupdatePage+ MCP (wiki_update/update_context) + CLI (wiki update --if-rev)
- Studio PATCH (409 +
currentRev);wiki_get {ifChangedSince}→NOT_MODIFIEDsentinel; new puresrc/lib/mdtable.ts(fence-aware GFM parse/serialize/splice, alignment + escaped-pipe fidelity). Tests:test/{cas,mdtable}.test.ts.- Phase 1 —
src/core/tables.ts(tableGet/tableSetCell/tableAddRow/tableDeleteRowviaupdatePage, loud locator errors); MCPwiki_table_*(withnext[]affordance envelope +ifRev); CLIbctx wiki table get|set|add-row|rm-row; skill-doc "editing tables" section. Tests:test/tables.test.ts+ MCP round-trip intest/mcpwiki.test.ts. Verified on the real binary: one-cell edit is a minimal diff with alignment preserved, FTS still finds the changed cell, stale--if-revrejected with the live rev.- Phase 2 — ONE shared table grammar:
studio/src/lib/markdown.tsimports the puresrc/lib/mdtable.tsvia a@core/*alias (viteresolve.alias+ devserver.fs.allow
- tsconfig
paths); build bundles the single source into both runtimes. The Studio editor's whole-body autosave is now alignment-lossless (rendersdata-align+ re-emits via the sharedserializeTable) — the old path silently dropped alignment. PlusPOST /api/wiki/pages/:id/tablecell endpoint +wikiApi.editCell(backs the Phase-3 grid; contenteditable keeps whole-body autosave to avoid a granular/full-body race). Tests: cell endpoint intest/studio-api.test.ts. Verified end-to-end in a real browser: edit a cell → autosave → stored markdown keeps the:--- | :--: | ---:row.- Phase 3 — datatables + transclusion, body-canonical (refined from the design's JSON-in-
metadata.data): adatatablepage'sbodyis one canonical GFM table, so it reuses FTS/peek/history/export and the Phase-1wiki_table_*cell/row ops verbatim — no bespoke read path, no schema change.src/core/datatable.ts(createDatatable+expandTransclusions);![[Title]]embeds via the reservedembedschannel (EMBEDS_LINK, kept out ofLINK_TYPESlikereferences) —parseTransclusions+syncBodyLinksmirror the references block;parseWikiLinks/exportWIKILINKgained a(?<!!)lookbehind so![[..]]isn't captured/rewritten as a plain link/image.wiki_getfull (MCP + CLI--no-embed) inlines embeds on read (peek stays reference-only); one datatable backs N pages, edit-once-reflect-everywhere. MCPwiki_datatable_new+ CLIbctx wiki datatable new --columns --row. Export writes a datatable as its own<slug>.md(GFM body is canonical,![[..]]verbatim); import re-derives the embed (title-keyedresolveWantedLinkshandles either page ordering). Tests:test/{datatable,transclusion}.test.ts. Verified on the real binary: edit a datatable once → two consumers both reflect it; export/import round-trips type + verbatim embed; wanted-embed resolves when the target is created after the consumer.- Phase 4 — typed properties + views. NEW
page_properties(context_id,key,value,type)table (the only added stored state — ships as the first INCREMENTAL migration0002_page_properties, so existing stores upgrade in place; see the migration note below) is a DERIVED mirror of each page'smetadata.props, rebuilt in the SAME write txn byrebuildPageProperties(folded intocreateContext/updateContext; cleaned up indeleteContextChildren) so it can never drift.src/core/query.ts= a predicate engine:whereobject (keys ANDed; scalar = eq, or{eq,ne,lt,gt,lte,gte,contains,in,exists}), numeric CAST comparisons,sort {key,dir,numeric}, compiled to correlated EXISTS/NOT-EXISTS SQL.queryPages+listProperties+ saved views:page_type='view'whosemetadata.{query,columns}render to a live GFM table (viaserializeTable) on read/export — always reflects the current graph. Surfaces: MCPwiki_query/wiki_list_properties/wiki_set_prop/wiki_view_new+propsonwiki_new/wiki_update; CLIwiki query/list-properties/set-prop/view new+wiki new --prop k=v. Export writes aprops:frontmatter block (+query:/columns:for views); import re-derives them. Tests:test/{properties,query}.test.ts. Verified on the real binary: query/sort, a view reflecting aset-proplive, export→import re-derives props + view in a fresh store.- Phase 4 Studio UI (was deferred, now shipped) —
RightPanelgained an editable Fields section:metadata.propsrender as key/value rows with inline edit / delete /+ field(type-coerced like the CLI), persisted viawikiApi.update({setMetadata:{props}})(merge preserves the other fields; the server re-derives thepage_propertiesmirror).viewpages render their LIVE table (studio route callsrenderViewinGET /pages/:id) and are read-only in the editor (likesource), with a read-only query summary in the panel. StudioPAGE_TYPESgaineddatatable/viewso both surface in the sidebar. Verified in a real browser: add/edit/delete a field end-to-end, view renders live, datatable displays.- Datatable grid editor + embedded-table rendering (Studio) — a
datatablepage now opens in a dedicated full-bleed grid (studio/src/editor/DataTableEditor.tsx): edit cells, add/ delete rows, add/delete/rename columns, cycle column alignment. Every mutation is one index-addressed splice op →POST /pages/:id/table/op→core/tables→updatePage(alignment-preserving, CAS viaifRev, no lossy whole-body PATCH); the grid is server-authoritative (reseeds from each op's response, serialized through one promise chain so rapid edits never race or self-409). NEW pure column mutators (addColumnData/deleteColumnData/renameColumnData/setColumnAlignDatainsrc/lib/mdtable.ts) + index-addressed core ops (tableSetCellAt/tableDeleteRowAt/tableAddColumn/tableDeleteColumn/tableRenameColumn/tableSetColumnAlign) that refuse an immutablesourcepage and out-of-range indices; awriteTablesource guard now centralizes the immutable refusal for ALL table writes. Full surface parity: CLIwiki table add-col/rm-col/rename-col+ MCPwiki_table_add_column/ _delete_column/_rename_column.WikiViewbranches ondatatableto render the grid and auto-collapses the sidebar + right panel (layout toggle restores them). Embeds:![[Title]]now renders inline in the prose editor as the referenced datatable's read-only table + an "Open to edit ↗" header (markdownToHtmlemits acontenteditable=falseplaceholder thatWikiEditorhydrates by fetching the datatable;htmlToMarkdownshort-circuits ondata-embed-titleso the consuming page's stored body keeps the![[Title]]marker — the table is never inlined by autosave). Tests: column mutators + index ops (test/{mdtable,tables}.test.ts), the/table/opendpoint incl. CAS + source refusal (test/studio-api.test.ts). Verified in a real browser on a seeded store: full-bleed grid, cell edit + add-column persist via single/table/opPOSTs, embed renders with Open-to-edit, and editing the consuming page's prose + autosave preserves![[Title]](no inlined table) — the round-trip gate.- Migration to existing data — the model moved from pure-greenfield to INCREMENTAL: schema additions ship as new
src/migrations/000N_*.ts(editing an already-applied migration never reaches an existing store, since they're tracked by name).page_propertiesmoved out of0001_initinto0002_page_properties(registered inmigrate.ts,LATESTbumped). Addedbctx wiki reindex(rebuild the props mirror;--linksre-derives the graph but SKIPS immutablesourcepages, per an adversarial-review finding). Tests:test/{migration,reindex}.test.ts. Applied to the real edumetrics store (472 contexts, 26MB) on 2026-07-05: backed up → migrated to0002→ reindexed → verified byte-identical row counts (zero data loss), FTS + reads intact. Backup:edumetrics.db.bak-2026-07-05-pre-vnext.- Table→datatable extraction — NEW
extractTableToDatatable(src/core/tables.ts) + CLIbctx wiki datatable extract <ref> --title … [--caption|--table-index]: lifts a GFM table out of a page into its owndatatablepage (verbatim: alignment / escaped pipes / in-cell[[links]]) and leaves a![[Title]]embed in its place, so the page reads identically (embed re-renders on read) while the data becomes queryable + reusable. Creates the datatable FIRST (source-update failure never loses the table); refuses an immutablesourcepage or a title collision; ambiguous locator errors instead of guessing. Tests:test/extract.test.ts. Applied to real edumetrics on 2026-07-05 (backupsedumetrics.db.bak-2026-07-05-{pre,post}-extract): extracted the 3 real data-tables →roster-clientes-crm(15×57),precios-vocation-2026(6×3),indicadores-clave-edumetrics(3×16). Verified: all 3 pages read BYTE-IDENTICAL after extraction+embed-expansion, 3embedsedges derived, datatables cell-addressable + FTS-searchable, contexts 472→475 / links 2734→2737 (only +3 embeds), integrity + FTS checks ok. Navigation-index tables (of[[wikilinks]]) and immutable sources deliberately left inline.- Phase 5 — general sub-body mutation for the prose/link majority a table/section can't reach. NEW pure
src/lib/section.ts(fence-aware ATX heading addressing:parseHeadings,findSections— a section runs to the next same-or-higher heading —replaceSection, andreplaceOccurrence).src/core/edit.tswires them throughupdatePage, all EXACT-MATCH-OR-REFUSE via a typedEditError:getPageSection/patchPageSectionrefuse a missing OR ambiguous (>1) heading;editPagefind/replace refuses a miss, and refuses a multi-match unless anoccurrence(1-based) is given — never a silent no-op or wrong-place edit. Surfaces: MCPwiki_get {section}+wiki_patch_section+wiki_replace; CLIwiki get --section+wiki patch-section+wiki replace(find/replace namedreplace, notedit, sincewiki updatealready aliasesedit). All compose withifRevCAS and re-sync[[links]]from the patched text. Tests:test/{section,edit}.test.ts. Verified on the real binary: patch one section (sibling intact), unique replace, absent-find / missing-heading / stale-rev all refuse with exit 1, links re-sync from a patched section.
Two cross-cutting mechanics from the completeness critic fold in: revision-CAS (Phase 0) and affordance-carrying result envelopes (a
next:[{tool,args}]block on tool results handing the agent its next pre-addressed call — added to Phase 1+ responses as each addressing rung lands).
- 0a — Revision CAS (first).
UpdateInputgainsifRev?(contexts.ts:65); inupdateContext, after the in-txn read ofexisting(:245), reject whenifRev !== existing.updated_atwith a typedRevConflictError{currentRev}. Thread throughupdatePage(wiki.ts:229). Read side:ifChangedSinceonwiki_get/pagePeek→ ~3-tokenNOT_MODIFIEDsentinel. Surfaces: MCP/CLI (--if-rev)/Studio PATCH (409 + rev, reuse existing 409 path). Testtest/cas.test.ts. - 0b —
src/lib/mdtable.ts(NEW, pure, no deps/DOM):parseTables(body)→{headingAbove, header, alignments, rows, startLine, endLine}[], fence-aware (consume```before detecting tables, perstudio markdown.ts:145), honoring escaped\|, optional outer pipes, ragged rows;serializeTablepreserving alignment. Testtest/mdtable.test.ts(incl. code-fence tables NOT matched). - 1 —
src/core/tables.ts(NEW):tableGet/tableSetCell/tableAddRow/tableDeleteRow—resolvePageRef→getPage→parse→splice→**updatePage** (history/FTS/syncBodyLinksfire free; anti-drift held). Locate by caption/headingAbovethen index; row by key-column else ordinal; column by header; loud typed errors.tableGetreturns array-of-arrays + header + alignment +rev, no page body. Registerwiki_table_*insrc/mcp/wiki-tools.ts(coaching descriptions); CLIbctx wiki table …insrc/commands/wiki.ts; add a "when to use a datatable vs plain markdown table" note toskills/braincontext-wiki/SKILL.md. Testtest/tables.test.ts.
- Silent cross-replica clobber (row-LWW) → Phase-0 CAS ships before any granular write.
- Storage/history amplification unchanged → document as agent-token + corruption-safety win, not a storage win; opt-in diff-history only if bloat bites.
- Fenced-code false positives / two-parser drift →
mdtable.tsfence-aware + hard tests; Phase 2 makes Studio import the same lib. ![[..]]token collision → Phase 3(?<!!)lookbehind (both copies, deduped) +embeds.- Query-engine scope creep → v1 = get + 3 mutators, no query language; querying/typed properties only Phases 3–4 behind one shared AST.
- Row-granular sidecar storage — the one thing JSON-in-row can't give (two agents on different rows of one big table never clobber). Default no (Phase-0 CAS covers safety; token win identical); revisit at Phase 3 only against a real many-agents-on-shared-large-tables profile.
- Commitment past Phase 3 — Phases 4–5 endorsed but earned against usage; confirm at the Phase 3 boundary.