Treat token_count events without usage as null canonical token values so node:sqlite never receives undefined. Covers issue #1 with a parse-to-persist regression test.
- normalize Kimi thinking parts and omit empty placeholders
- add standalone session-sharing and UI demo artifacts
- add regression coverage and refresh the Kimi index marker
- bump the Electron app and CLI packages to v0.2.1
- ignore generated app build assets
Publish real virtual-row measurements while suppressing momentum-breaking scroll writes, then reconcile the reader anchor once scrolling settles. Keep live patches out of the timeline during active gestures and add Electron coverage for tall-row overlap, residual motion, and existing-message updates.
Cache semantic timeline anchors and disclosures per session instead of relying on KeepAlive. Restore state after virtualized layout stabilization while preserving explicit focus and tail-follow behavior.
Require the bootstrap agent to ask whether the skill should be installed locally or globally, and suppress only SQLite’s ExperimentalWarning in CLI test processes.
Present the bootstrap SKILL.md as the recommended setup path, keep manual installation secondary, and add regression coverage for the documentation order.
Add @obelisk-apps/cli with the existing build, search, query, and attune contract plus official skill installation.
Separate the docs-only skill artifact, bootstrap installer, release layout, cross-platform CI, and package-level regression coverage.
- Add "Run locally" section: clone → cd app → npm ci → npm run dev, with a
note on first-run indexing, source directories, and WSL auto-detection.
- Add "Debug the app" section: HMR, DevTools shortcuts, main-process logs,
Node inspector attachment, and isolated HOME for testing.
- Note that better-sqlite3 may need platform build tools if no prebuilt binary
is available.
- Fix "macOS only" → mention prebuilt for macOS, runnable from source on all
platforms.
- Fix typo: "A surface for human" → "for humans".
- Fix manual install path wording.
Co-Authored-By: Codex (GPT-5) <noreply@openai.com>
Eliminate full-table scans from subagent tool queries and guardian retractions by indexing message_uuid on tool_calls and tool_results. Add query-plan regressions for both access paths.
Defer and coalesce global catalogue refreshes while SessionDetail is active, carry session metadata through incremental patches, and gate in-flight catalogue commits by route. Harden the virtual timeline cold-open and user-scroll lifecycle, and add Electron frame, continuity, metadata, and overlap regressions.
Discard virtualizer measurement corrections suppressed during an active user gesture instead of replaying stale offsets at scrollend. Preserve post-scroll live anchoring and add unit plus Electron coverage for ordinary scrolling without updates.
Track real user scrolling through scrollend with a quiet watchdog, defer timeline commits and virtualizer corrections until settlement, and make SessionDetail the sole tail-follow owner. Add unit and Electron regressions for scroll writes, reader anchoring, near-tail escape, explicit navigation, and flap timing.
Virtualize SessionDetail rows behind stable presentation boundaries and apply typed incremental patches only after visible commits. Preserve reader state across live updates, handle coalesced and reordered patches, and verify the 120Hz append path with Electron tracing.
Render SessionDetail through measured dynamic-height virtual rows while preserving disclosure state, UUID navigation, reader anchoring, and tail-follow across live updates.
Add focused state/reconciliation tests plus a production Electron harness covering long-session DOM bounds, scrolling performance, offscreen navigation, and live viewport stability.
Cache timeline DOM indexes and memoize the timeline so progress and flap updates avoid rescanning or rerendering old messages. Preserve scroll anchors and disclosure state across targeted live message replacements.
Preserve unchanged message identities and isolate Vue row rendering during live refreshes.
Use a tail-only scroll path so updates do not interrupt active readers or expanded tools. Add regression coverage for 908-message appends, changed snapshots, and scroll policy.
Keep message navigation and progress state synchronized, and preserve
sequential split-flap count updates with a bounded queue.
Publish the Obelisk skill under skills/obelisk for npx skills, sharing
the same staging layout between CI and local releases with regression coverage.
SessionDetail live update:
- Extract session-view-state.mjs: capture scroll position, disclosure
(open/skill-md-open) state, and visible-UUID anchor before refresh;
reconcile messages by UUID (in-place update, append tail only); restore
scroll and disclosure state after DOM patch. findLastMessageAtOrAbove uses
binary search (O(log n)) instead of linear scan.
- scrollRevision tracks user scrolls during refresh to avoid stale anchors
overriding manual navigation.
- Throttle onScroll to one rAF per frame.
Tool renderer:
- Extract tool-renderer.js: standalone module for rendering tool call cards
(Read/Write/Edit diffs, Bash terminal output, search results, JS/TS
syntax highlighting). Replaces inline rendering in SessionDetail.
- tests/app-tool-renderer.test.mjs covers escaping, highlighting, and
terminal formatting.
Input tokens semantics migration:
- Claude provider now sums input_tokens + cache_creation_input_tokens +
cache_read_input_tokens into a single input_tokens value (was previously
only the raw field, undercounting when cache tokens are present).
- One-time index-wide re-parse triggered when the marker
__claude_input_tokens_include_cache_v1__ is absent and the DB already
has token data (self-healing on first build after upgrade).
- App indexer.ts carries the same marker check for the app's build path.
Also:
- PRODUCT.md: product register (users, purpose, brand, design principles,
accessibility targets).
- README.md: minor wording updates.
Co-Authored-By: Codex (GPT-5) <noreply@openai.com>
Rewrite README to reflect the current state of the project:
- Describe both surfaces: skill (agent-first retrieval) and app (human browser).
- Add Codex support section (unified schema, source tagging, child-thread mapping).
- Update Structure tree to match the packages/core workspace layout with all
TypeScript modules (providers, persist, tx, write-coordinator, writer-lease).
- Document generated build outputs (packages/core/dist, dist/obelisk-skill).
- License badge corrected to AGPL-3.0 (skill artifact is MIT; source is AGPL).
- Skill invocation examples updated to /obelisk-skill.
- Add recap flow documentation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a GitHub Actions workflow that builds the readable, non-bundled skill
artifact (`npm run build:skill`) and force-pushes it to the dedicated
tommy0103/obelisk-skill public repo whenever the main branch is updated.
The skill repo is a pure derivative — no manual commits, no PRs; the source
of truth stays in tommy0103/obelisk.
- .github/workflows/publish-skill.yml: checkout → npm ci → build:skill →
clone skill repo → replace content → commit + force push. Auth via
SKILL_REPO_DEPLOY_KEY (SSH deploy key with write access to obelisk-skill).
- packaging/skill-README.md: the README placed in the skill repo (install
instructions + link back to source + "auto-published, don't PR here").
- packaging/skill-LICENSE: MIT license for the skill artifact (relicensed
from the AGPL-3.0 source by the copyright holder).
- packaging/publish-skill.sh: local convenience script for manual publish.
- packaging/skill-package.json: license field updated to MIT.
- README.md: install command updated to tommy0103/obelisk-skill.
- package.json: add publish:skill script.
The skill repo is MIT-licensed for zero adoption friction (local tool, no
library API, no derivative works expected); the source repo stays AGPL-3.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implement the full ADR-0006 plan: three-layer separation of transaction
correctness, retry policy, and cross-process writer coordination.
Layer 1 — scripts/tx.ts (transaction correctness):
- runWriteTransaction executes work exactly once; no internal retry.
- BEGIN IMMEDIATE takes the write lock up front (avoids SQLITE_BUSY_SNAPSHOT).
- Guarded rollback: checks inTransaction() via adapter before attempting
ROLLBACK; never masks the primary exception.
- WriteTxDiagnostics attached to errors: phase, code, label,
rollbackSucceeded, rollbackError, transactionActive.
- Binding adapters (betterSqliteTransactionAdapter, nodeSqliteTransactionAdapter)
mapping better-sqlite3's `.inTransaction` and node:sqlite's `.isTransaction`.
- configureConnection centralizes WAL + synchronous + busy_timeout.
Layer 2 — scripts/write-coordinator.ts (retry policy):
- runRetryableWriteTransaction: bounded retry with total time budget.
- Only retries when the transaction confirmed ended (transactionActive=false)
and the error is SQLITE_BUSY during work/commit phase.
- BEGIN-phase BUSY = abort entire build (isBeginBusyFailure); the caller
returns `{ deferred: true, reason: 'writer_busy' }` instead of waiting.
- hasUnusableTransaction detects a still-active transaction after failure;
aborts the build immediately, never retries.
Layer 3 — scripts/writer-lease.ts (cross-process coordination):
- acquireWriterLease: dedicated writer.lock.sqlite with busy_timeout=0 +
BEGIN IMMEDIATE. Non-blocking attempt; bounded wait with retryDelayMs.
- writerLockPathFor derives lock path from the target DB path.
- Lease held for the entire build; released on completion or failure.
- Lock DB uses DELETE journal (not WAL); crash/close auto-releases.
- All consumers obey: skill acquires at build start (returns deferred if
unavailable); app daemon (via worker) acquires for its build cycle.
Build semantics changes:
- affectedSessionIds updated only after successful commit.
- BuildIndexResult gains skipped/skippedFiles for observability.
- Skill finalize failure now fails the build (was silently warned).
- Checkpoint changed to PASSIVE (TRUNCATE reserved for maintenance/exit).
- Skill buildIndex returns { deferred, reason } on lease contention;
indexer-service reschedules the build (deferredRetryMs) without publishing
a heartbeat (so the build-deferred state is visible to cross-process
arbitration).
- Service publishes heartbeat immediately on start() for correct arbitration.
Tests:
- tests/write-transaction.test.mjs: single-shot execution, diagnostics
propagation, auto-rolled-back transaction detected, rollback failure
captured as metadata, BEGIN IMMEDIATE semantics.
- tests/writer-lease.test.mjs: acquire/release, contention returns null,
bounded wait with release during budget.
- tests/app-writer-lease.test.mjs: better-sqlite3 adapter integration.
- tests/app-rollback-guard.test.mjs: rewritten — transient BUSY recovered
by coordinator, persistent BUSY skips file, begin-busy aborts build,
live-transaction aborts build, phantom affectedSessionIds prevented.
- tests/daemon-arbitration.test.mjs: skill defers to fresh app heartbeat,
builds when heartbeat is stale.
- tests/app-indexer-service.test.mjs: new cases for deferred-retry
scheduling and immediate heartbeat on start.
- app/tests/electron-concurrency.mjs + child: dual-child IPC structure for
real better-sqlite3 contention (holder acquires lock → build child starts
→ delayed release → result collected; persistent contention bounded).
ADR-0006 updated to reflect the implemented design.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reported as "cannot rollback - no transaction is active" — a secondary error.
SQLite auto-rolls back certain failures (SQLITE_BUSY / SQLITE_BUSY_SNAPSHOT,
disk full), after which the per-file loop's unguarded ROLLBACK in its catch
threw over the real error and aborted the whole build instead of skipping just
the bad file.
Stopgap only (see docs/adr/0006 for the planned full fix):
- Add safeRollback(db) in both indexers: it swallows only the rollback's own
error, so the true cause surfaces. Per-file failures are logged and the build
continues; the finalize failure still propagates.
- Give the skill's node:sqlite connection an explicit PRAGMA busy_timeout=5000
(it has no default). The app adds none: better-sqlite3 already defaults to
5000ms, so busy_timeout is NOT the root-cause fix and is not treated as one.
Add tests/app-rollback-guard.test.mjs: injects a DB that faithfully reproduces
"a write auto-rolls back the txn, then ROLLBACK errors" and asserts the build
survives (bad file skipped, other file indexed). Revert-checked: without the
guard the test fails with the exact "cannot rollback - no transaction is active".
docs/adr/0006 records the real fix (shared runWriteTransaction, single-writer
coordination, BEGIN IMMEDIATE, whole-transaction retry, PASSIVE checkpointing)
as deferred, two-phase work — and why bumping busy_timeout is not it.
Verified: suite 124/124, typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`npm run build:skill` emits dist/obelisk-skill/: the whole scripts/ Core
compiled to readable JS (module structure + comments preserved, ~1:1 with
source, no bundling/minifying per ADR-0004), plus schema.sql, SKILL.md,
references/, and a package.json. It excludes app/, tests/, and release/, which
is what keeps the artifact small.
- tsconfig.skill.json compiles scripts/**/*.{ts,mjs} (allowJs) with
rewriteRelativeImportExtensions, so the .ts specifiers inside the .mjs shells
(runtime.mjs -> ./core.js, indexer.mjs -> ./providers/*.js, ./persist.js) are
rewritten to .js. declaration:false — the skill needs no .d.ts.
- packaging/skill-package.json provides the artifact's package.json; it sets
"type":"module" because the emitted .ts->.js files are ESM.
- build:skill runs tsc then copies schema.sql/SKILL.md/references/package.json.
Add tests/build-skill.test.mjs: runs the real build:skill, asserts the artifact
structure, that no emitted .js/.mjs still imports a .ts module, and that the
compiled artifact builds an index and answers a search end-to-end under plain
Node (no type-stripping) against a temp HOME.
Verified: suite 123/123, typecheck clean, artifact smoke run green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The skill's `--build` (always a force build) cleared `index_state` and
re-indexed existing files, but never removed rows for transcripts that no
longer exist on disk, so deleted sessions accumulated forever (the dogfood
showed 328 indexed sessions vs 283 current files). A force build is meant to
be a clean rebuild, matching what the app already does.
Drop every derived table (messages, tool_calls, tool_results, sessions,
summaries, subagents, workflows, workflow_agents) in the force path, then
re-index from the current files. `memories` is the durable, human-approved
layer and is never cleared; messages_fts is repopulated by the existing
'rebuild' command in finalize.
Add a test that builds two sessions, deletes one transcript, force-rebuilds,
and asserts the stale session is purged while a seeded memory survives.
Verified the test is discriminating: without the fix it reports
['gone','keep'] instead of ['keep'].
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert the app's main and preload source from .js to .ts (git mv preserves
history), adding types where they carry value: the core-consumption seam
(BuildIndexOptions/BuildIndexResult, FileInfo), the indexer service/worker
factories, and the preload IPC bridge. Module-to-module specifiers use the
real .ts extension (mirroring scripts/, since Node type-stripping does not
rewrite .js->.ts); the worker's runtime path stays indexer-worker.js because
that is the built output.
Toolchain:
- Add app/tsconfig.json: strict but noImplicitAny:false (the app orchestrates
the already-strict core; annotating every SQLite-handle helper is low-value
churn) + allowImportingTsExtensions (safe under noEmit).
- Add @types/better-sqlite3 for the injected binding.
- electron.vite.config.ts inputs -> .ts; refresh the stale CommonJS comment.
- typecheck script runs root + app projects. Root tsconfig excludes the
app-importing tests (app-*.test.mjs, recap-capture-query.test.mjs) so the
lenient app files are not dragged into the strict root program; the app
source is covered by app/tsconfig.json instead. See docs/adr/0005.
Verified: npm run typecheck (root + app) clean; suite 121/121; electron-vite
build emits all 6 main entries + preload with no .ts/node:sqlite residue in
the bundles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>