Commit Graph
94 Commits
Author SHA1 Message Date
tommy0103 f79f1b3e3b refactor: add canonical transcript assembly seam 2026-07-21 00:58:34 +08:00
tommy0103 3ee44de4e5 feat: add registry-driven Kimi provider 2026-07-20 22:43:23 +08:00
tommy0103 21c3a1b9fc chore(release): prepare Obelisk v0.2.0 2026-07-20 01:58:50 +08:00
tommy0103 8864a36955 chore(release): prepare Obelisk v0.2.0 2026-07-20 01:58:22 +08:00
tommy0103 d399ab154f fix(app): stabilize timeline scrolling during updates
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.
2026-07-17 02:05:22 +08:00
tommy0103 d9f31bbe8a fix(app): preserve session reader state across navigation
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.
2026-07-16 23:22:47 +08:00
tommy0103 4d8f201d22 test(cli): launch npm through shell on Windows 2026-07-16 21:25:05 +08:00
tommy0103 5da5987bee test(cli): normalize project path expectations on Windows 2026-07-16 21:11:25 +08:00
tommy0103 cc01df9fec fix(cli): clarify skill scope and stabilize Node 22 tests
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.
2026-07-16 20:51:58 +08:00
tommy0103 0d3797b55d docs: prioritize agent-led Obelisk installation
Present the bootstrap SKILL.md as the recommended setup path, keep manual installation secondary, and add regression coverage for the documentation order.
2026-07-16 20:16:55 +08:00
tommy0103 90191e4604 feat(cli): extract Obelisk runtime into npm package
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.
2026-07-16 17:26:23 +08:00
tommy0103andCodex 49158e9b9c docs(readme): add local development and debug instructions for the app
- 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>
2026-07-16 03:18:23 +08:00
tommy0103 c2ecc59cc7 fix(db): index tool payloads by message
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.
2026-07-16 03:02:03 +08:00
tommy0103 cec66d0656 fix: add session_id index in tool_results 2026-07-16 02:54:15 +08:00
tommy0103 1e4e5ec2d8 fix(app): isolate live session updates from scrolling
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.
2026-07-15 22:12:40 +08:00
tommy0103 5294d76f07 fix(app): prevent scrollend position rollback
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.
2026-07-15 02:19:55 +08:00
tommy0103 82d9fbf657 fix(app): preserve momentum during live session 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.
2026-07-15 01:35:05 +08:00
tommy0103 b58c34d3af perf(app): stabilize live session timeline updates
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.
2026-07-14 18:11:51 +08:00
tommy0103 2cad3b554d feat(app): virtualize session timeline
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.
2026-07-14 03:45:39 +08:00
tommy0103 bbf16d8f9f fix(app): keep live session updates responsive
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.
2026-07-14 00:53:38 +08:00
tommy0103 7e6e9d9fc3 fix(app): keep live session updates incremental
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.
2026-07-13 22:31:12 +08:00
tommy0103 f963f14b10 fix: stabilize live session UI and skill publishing
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.
2026-07-13 22:02:44 +08:00
tommy0103andCodex a9687ba8b7 feat(app): live session update + tool renderer + input_tokens migration
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>
2026-07-13 21:02:38 +08:00
tommy0103 78aacfac6c feat(session): animate total count with split flap 2026-07-12 20:00:07 +08:00
tommy0103 1742d73b32 fix(activity): align ledger typography 2026-07-12 15:24:28 +08:00
tommy0103 48f1a4c225 feat(activity): add contribution ledger 2026-07-12 15:15:48 +08:00
tommy0103 644942202e Complete Vue renderer migration 2026-07-12 02:26:01 +08:00
tommy0103 dbf3ea4b51 Fix activity session navigation 2026-07-12 01:47:32 +08:00
tommy0103 32a9b8d424 Merge branch 'refactor/migrate' into main
Phases 0–7 of the Obelisk engineering refactor: provider-adapter architecture
(ADR-0001), shared persist + tx module, TypeScript workspace (packages/core),
electron-vite app migration, writer-lease concurrency, and CI skill publish.
2026-07-12 01:12:32 +08:00
tommy0103andClaude Opus 4.8 410b0a8df3 docs(readme): rewrite for dual-surface product (skill + app) and workspace structure
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>
2026-07-12 00:59:33 +08:00
tommy0103 8af0f9a31d refactor(core): type the persist database seam 2026-07-12 00:46:04 +08:00
tommy0103 c4c458fa8f refactor(core): share sqlite boundary types 2026-07-12 00:44:06 +08:00
tommy0103 3a6e3a00a7 fix(core): ship schema with package build 2026-07-12 00:39:57 +08:00
tommy0103 535d8edb4d refactor(core): finish TypeScript workspace migration 2026-07-12 00:37:32 +08:00
tommy0103 83d5703f7b refactor(core): move shared runtime into workspace package 2026-07-12 00:28:17 +08:00
tommy0103andClaude Opus 4.8 6edaf5231f ci: auto-publish skill artifact to tommy0103/obelisk-skill on push to main (Phase 7)
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>
2026-07-11 16:18:43 +08:00
tommy0103andClaude Opus 4.8 e3e61cc7ab fix: coordinate sqlite index writers
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>
2026-07-11 00:42:43 +08:00
tommy0103andClaude Opus 4.8 44029676d4 fix(indexer): guard cleanup rollback so it never masks the real build error
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>
2026-07-10 16:36:36 +08:00
tommy0103andClaude Opus 4.8 e44ab7a1da build(skill): add build:skill for a readable, non-bundled skill artifact (Phase 6)
`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>
2026-07-10 16:36:01 +08:00
tommy0103andClaude Opus 4.8 c964653362 fix(indexer): force build purges stale sessions instead of only clearing index_state
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>
2026-07-10 03:18:52 +08:00
tommy0103andClaude Opus 4.8 01a390fa10 refactor(app): migrate main + preload to TypeScript with typed seams (Phase 5d-3c-ii)
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>
2026-07-09 16:25:59 +08:00
tommy0103andClaude Opus 4.8 60d47a852e refactor(app): consume shared indexing core, remove duplicated indexer (Phase 5d-3c-i)
The desktop app now indexes through the shared provider adapters + persist
layer (scripts/providers/{claude,codex}, scripts/persist, scripts/parsing)
instead of maintaining its own parallel indexer. buildIndex shrinks from
~1173 to ~592 lines, eliminating the skill<->app parse duplication that
Phase 5 set out to remove. electron-vite bundles the .ts core from source
with better-sqlite3 injected; the provider->parsing graph stays
node:sqlite-free so nothing drags node:sqlite into the app.

Also fix a misleading log: when a manual rebuild tears down the worker
mid-build, the cancelled background build is a deliberate stop, not a
failure. Guard the service's failure log with the stopped flag so it no
longer prints "Obelisk index build failed: Indexer worker stopped" on
every rebuild.

- CONTEXT.md: provider-adapter + single-persist + node:sqlite-free parsing.
- docs/adr/0005: app builds with electron-vite (TS+ESM), packages with
  electron-builder; preload CJS for sandbox; app consumes core from source.

Verified: full suite 121/121; a node:sqlite-adapter dogfood of the rebuild
path over real data (969 files, 285 sessions, FTS rebuilt) runs clean; app
Rebuild confirmed in real Electron/better-sqlite3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 12:46:02 +08:00
tommy0103 905c10789a refactor(app): migrate to ESM; preload emitted as CJS for sandbox (Phase 5d-3b)
App source (main/preload/worker/renderer) -> ESM; app is now type: module;
__dirname via import.meta.url; worker spawned with type module. Preload is built
as CJS (electron-vite output format) because the sandboxed renderer does not
support ESM preload; main loads ../preload/index.js. Removed dead imports
(nativeImage, readline) and the obsolete scripts/dev.js.

Tests: 4 app tests require->import; app-main-settings rewritten with node:test
mock.module + dynamic import (replacing CJS Module._load mocking); test script
adds --experimental-test-module-mocks. electron-vite build clean, 119/119, and
npm run dev verified: app launches, preload bridges IPC, data loads.
2026-07-09 11:05:00 +08:00
tommy0103 80c125a572 build(app): migrate to electron-vite (main/preload/renderer), keep electron-builder (Phase 5d-3a)
Restructure app into src/{main,preload,renderer}; electron.vite.config.ts builds
all three (each main module its own input so CJS requires + the indexer worker
resolve; better-sqlite3 externalized; Vue plugin for renderer). main/index.js
paths updated for the out/ layout + ELECTRON_RENDERER_URL. Still JS/CJS — TS+ESM
and core consumption are the next stages. Verified: npm run dev launches clean;
electron-vite build succeeds; root suite 119/119.
2026-07-09 10:20:18 +08:00
tommy0103 e04c07de05 build: add build:core to compile the app-consumable core to dist/ (Phase 5d-2)
tsconfig.build.json emits providers/* + persist (+ their parsing.mjs/types deps)
to dist/ as ESM JS + .d.ts. Output is verified node:sqlite-free and functionally
runnable (parse+persist against an injected db). Foundation for the app importing
the shared core.
> obelisk@0.1.0 build:core
> rm -rf dist && tsc -p tsconfig.build.json; dist/ is gitignored.
2026-07-09 09:34:25 +08:00
tommy0103 1bda4da170 refactor: extract node:sqlite-free parsing.mjs so the core can be app-consumable (Phase 5d-1)
Move all pure parse/discover helpers (message extraction, project-path, codex
helpers, discovery) out of db.mjs/indexer.mjs into scripts/parsing.mjs, which
imports only node:fs/path/os. Providers now import from parsing.mjs, so the
provider import graph no longer transitively loads node:sqlite — a prerequisite
for the app (Electron/Node without node:sqlite) to consume the compiled core.
Verbatim move, no behavior change; indexer.mjs 840→209 lines. 119/119.
2026-07-09 09:20:37 +08:00
tommy0103 0598c29aad feat(providers): migrate codex indexing to adapter + persist, remove legacy indexers (Phase 5c)
Complete the skill-side provider migration: codex now goes through a pure adapter
and the shared persist layer, and the two original monolithic indexers are gone.

New:
- scripts/providers/codex.ts — pure codex adapter. Full-reparse (buffers the whole
  file) because the event_msg↔response_item dedup needs whole-file, bidirectional
  knowledge; emits SessionRecord with countMode 'total'. Handles guardian threads
  (→ delete-session), agent spawns/tool calls (→ tool_call/subagent), token_count
  (patched onto the message record) and task_complete (→ message-turn-duration).

Contract:
- SessionRecord.countMode ('total' | 'delta') tells persist whether to replace or
  accumulate message_count — claude is line-incremental (delta), codex full-reparse
  (total). SubagentRecord non-key fields are optional; persist merges them
  column-wise with COALESCE. MessageTurnDurationRecord.turn_duration_ms is nullable.

Orchestration:
- buildIndex's codex branch parses via the adapter and writes via persist. An
  unchanged file is skipped but still swept for stale guardian rows (routed through
  persist as a delete-session), preserving prior behavior.

Cleanup:
- Remove the now-unused indexJsonl, indexCodexJsonl, deleteCodexThreadRows and
  upsertCodexSubagent — their semantics now live in the adapters + persist.
  indexer.mjs drops from ~840 to 428 lines. Codex pure helpers stay exported for
  codex.ts and the guardian sweep (physical move deferred to the app-side reorg).
- Migrate the upsert drift test off indexJsonl to the claude.parse + persist path,
  keeping the rowid-stability and count-replace regression guards.

Tests: tests/codex-parse.test.mjs (record-stream golden: dedup, tools, token patch,
turn-duration, guardian→delete) and tests/codex-index.test.mjs (full buildIndex
path: fresh build + incremental full-reparse, total-count replace, no duplicates).

Verified equivalent on the real ~/.obelisk index: codex messages 82476 and
subagents 522 identical before/after, zero guardian leakage; real incremental
confirmed (touch a codex file → reparsed idempotently, unchanged files skipped).
lint + typecheck clean, 119/119.
2026-07-08 20:43:54 +08:00
tommy0103 1c346039f1 refactor(indexer): route claude indexing through adapter + persist (Phase 5b-2b)
buildIndex's claude branch now parses via providers/claude.ts and writes via
persist.ts instead of the inlined indexJsonl. Behavior is equivalent — the full
buildIndex integration suite (runtime.test.mjs) stays green, 116/116 — and a
force rebuild of the real ~/.obelisk index (327 sessions, 119k messages)
reproduced identical session counts with project_path fully populated.

codex, indexSubagentMeta, workflows, history and the project_path pass are
untouched. indexJsonl is now unused by buildIndex (kept for its drift test;
removed once codex is migrated).

Adds tests/incremental-index.test.mjs: verifies resume/accumulate through the
full buildIndex path (append new lines to an indexed session → incremental
build resumes from the cursor, message_count accumulates, no duplicates). The
30s shouldSkipBuild debounce is cleared in-test so the incremental run fires.
2026-07-08 19:46:34 +08:00
tommy0103 1d652d823f feat(persist): add shared record-stream persist layer (Phase 5b-2a)
Introduce scripts/persist.ts — the single, provider- and binding-agnostic
layer that consumes an adapter's IndexRecord stream and writes rows into an
injected SQLite handle (node:sqlite for skill/CLI, better-sqlite3 for the app).
It is the only layer that touches the database.

Write semantics are the canonical ones reconciled from the earlier drift:
- messages upsert via ON CONFLICT (turn_duration_ms not in the column list, so
  it is never clobbered)
- sessions merge with the existing row: started_at MIN, ended_at MAX,
  message_count reset-or-accumulate by resume state, fill-if-null for the rest;
  project_path is preserved and left to refreshSessionProjectPaths
- message-turn-duration applies as a targeted UPDATE
- delete-session cascades across all tables
- the generator's return cursor is written back to index_state (mtime:lines →
  the two existing columns; no schema migration yet)

Purely additive — buildIndex still uses the old indexJsonl path, so existing
behavior is unchanged. Rewiring happens in 5b-2b.

Adds tests/persist.test.mjs: all record kinds written, resume does not
double-count message_count, fresh re-scan resets it, delete-session cascades.
Full suite 115/115, lint + typecheck green.
2026-07-08 19:01:54 +08:00
tommy0103 2b30d9596d feat(providers): add pure claude adapter with record-stream golden tests (5b-1)
claude.parse mirrors indexJsonl line-for-line but yields IndexRecords (no db).
Adds MessageTurnDurationRecord op. Purely additive — buildIndex still uses the
old path. 111/111 green.
2026-07-08 18:27:28 +08:00