From 1a34245618219d9c7409d7848cca66583c5cff00 Mon Sep 17 00:00:00 2001 From: tommy0103 Date: Wed, 8 Jul 2026 16:11:12 +0800 Subject: [PATCH 01/28] chore: sanitize tests, add CONTEXT.md + ADRs, track tests/docs --- .gitignore | 3 +- CONTEXT.md | 82 ++ app/renderer/src/views/Activity.vue | 231 +++- .../adr/0001-parse-core-and-persist-layers.md | 31 + docs/adr/0002-two-tier-runtime-contract.md | 26 + .../0003-core-typescript-esm-precompiled.md | 22 + ...004-skill-artifact-readable-not-bundled.md | 22 + tests/app-indexer-service.test.mjs | 261 +++++ tests/app-indexer-worker-client.test.mjs | 74 ++ tests/app-indexer.test.mjs | 741 +++++++++++++ tests/app-main-settings.test.mjs | 984 ++++++++++++++++++ tests/db-schema.test.mjs | 125 +++ tests/indexer.test.mjs | 84 ++ tests/query.test.mjs | 297 ++++++ tests/recap-capture-query.test.mjs | 28 + tests/recap-patterns.test.mjs | 356 +++++++ tests/runtime.test.mjs | 461 ++++++++ tests/session-live.test.mjs | 27 + tests/sidebar-projects.test.mjs | 56 + 19 files changed, 3880 insertions(+), 31 deletions(-) create mode 100644 CONTEXT.md create mode 100644 docs/adr/0001-parse-core-and-persist-layers.md create mode 100644 docs/adr/0002-two-tier-runtime-contract.md create mode 100644 docs/adr/0003-core-typescript-esm-precompiled.md create mode 100644 docs/adr/0004-skill-artifact-readable-not-bundled.md create mode 100644 tests/app-indexer-service.test.mjs create mode 100644 tests/app-indexer-worker-client.test.mjs create mode 100644 tests/app-indexer.test.mjs create mode 100644 tests/app-main-settings.test.mjs create mode 100644 tests/db-schema.test.mjs create mode 100644 tests/indexer.test.mjs create mode 100644 tests/query.test.mjs create mode 100644 tests/recap-capture-query.test.mjs create mode 100644 tests/recap-patterns.test.mjs create mode 100644 tests/runtime.test.mjs create mode 100644 tests/session-live.test.mjs create mode 100644 tests/sidebar-projects.test.mjs diff --git a/.gitignore b/.gitignore index e7fa23d..2edadd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,8 @@ .DS_Store plans/ .skillopt-backups -tests/ node_modules/ dist-renderer/ release/ -docs/ +.dev.docs .claude/ diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..f507b50 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,82 @@ +# Obelisk + +Obelisk is explicit memory infrastructure for coding agents: it indexes local +Claude Code and Codex transcripts into a queryable SQLite evidence layer, and a +CodeAct runtime lets an agent write a small query, run it, and answer from real +session history. This glossary pins the terms that are specific to Obelisk; it is +not a spec. + +## Runtime interface + +**Runtime interface**: +The public contract, expressed as four verbs — `build`, `search(text)`, +`query(code)`, `attune(code)`. Skill, CLI, and MCP are transports over this same +shape; none of them add their own retrieval surface. +_Avoid_: API, tool surface + +**CodeAct**: +The interaction style where an agent writes JavaScript that runs inside the +`query(code)` sandbox and returns JSON, rather than calling many fine-grained +tools. This is Obelisk's core design choice. +_Avoid_: tool-calling, function-calling + +**Helper**: +A convenience accessor available only inside the `query(code)` sandbox +(`overview`, `search`, `context`, `sql`, `memories`, …). Helpers are never +promoted to an external tool surface. + +## Indexing + +**Parse core**: +The pure `jsonl -> records` transform. Given a transcript file and a start line, +it yields normalized index records. It does not open, own, or write to a +database, and is shared verbatim by every indexing mode. This is the layer that +must never be duplicated. +_Avoid_: parser, ingest + +**Record**: +One normalized row destined for the index (session, message, tool call, tool +result, summary, subagent, workflow, …), emitted by the parse core before any +persistence happens. + +**Persist layer**: +The thin, binding-specific writer that consumes records from the parse core and +writes them into SQLite inside a transaction. Two persist layers exist and differ +only in binding: `node:sqlite` (skill/CLI) and `better-sqlite3` (app). +_Avoid_: writer, sink, DAO + +**Daemon indexing mode**: +Continuous incremental indexing driven by a long-lived process (the desktop app, +later a CLI daemon) that watches transcript directories and keeps the index fresh +as files change. +_Avoid_: watcher mode, live indexing + +**Passive pull mode**: +On-demand incremental indexing performed by the skill when there is no active +daemon: an invocation of the runtime brings the index up to date, then answers. +_Avoid_: lazy indexing, on-read indexing + +**index_state**: +The bookkeeping table shared by both indexing modes. It records, per transcript +path, the last-seen `mtime` and `lines_processed` (enabling resume-from-line +incremental indexing), plus heartbeat/last-build markers used for daemon +arbitration. + +**Daemon arbitration**: +The mechanism by which the passive pull mode detects a fresh daemon (via +`__app_heartbeat__` and `__app_last_successful_build__` markers in `index_state`) +and skips its own indexing. Because a fresh daemon owns writes, the two persist +layers never write concurrently. + +## Memory + +**Queryable session memory**: +The evidence layer — real sessions, messages, tool calls, subagents, workflows — +that an agent queries on demand. Obelisk deliberately does this instead of +implicit/ambient memory. +_Avoid_: implicit memory, ambient memory, auto-recall + +**Approved durable memory**: +Human-approved conclusions persisted as markdown plus a registry record, via +`attune(code)` calling `remember()`/`forget()`. Auditable and revocable. +_Avoid_: long-term memory, vector memory diff --git a/app/renderer/src/views/Activity.vue b/app/renderer/src/views/Activity.vue index d3c3b6c..1320564 100644 --- a/app/renderer/src/views/Activity.vue +++ b/app/renderer/src/views/Activity.vue @@ -18,6 +18,26 @@ const tooltip = reactive({ text: '', show: false, x: 0, y: 0 }); // --- Constants --- const DAY_MS = 86400000; +const NOISE_PROJECT_RE = /^(od-conn-test|[0-9a-f]{6,})/i; + +function isNoiseSession(s) { + if (!s.title) return true; + const label = formatProjectLabel(s.project) || ''; + return NOISE_PROJECT_RE.test(label); +} + +function splitNoise(arr) { + const normal = [], noise = []; + for (const s of arr || []) { + if (isNoiseSession(s)) noise.push(s); else normal.push(s); + } + return { normal, noise, total: normal.length + noise.length }; +} + +const expandedNoise = reactive({}); +function toggleNoise(key) { + expandedNoise[key] = !expandedNoise[key]; +} function localDateStr(d) { const y = d.getFullYear(); @@ -235,6 +255,26 @@ const daySessions = computed(() => { }; }); +const daySessionsSplit = computed(() => { + if (!daySessions.value) return null; + return { + ...daySessions.value, + newWorkspaces: splitNoise(daySessions.value.newWorkspaces), + newSessions: splitNoise(daySessions.value.newSessions), + continued: splitNoise(daySessions.value.continued), + }; +}); + +const monthBlocksSplit = computed(() => + monthBlocks.value.map((b, bi) => ({ + ...b, + bi, + newWorkspaces: splitNoise(b.newWorkspaces), + newSessions: splitNoise(b.newSessions), + continued: splitNoise(b.continued), + })) +); + // --- Methods --- function switchTab(view) { activeTab.value = view; @@ -502,61 +542,112 @@ onMounted(async () => { -
-
{{ daySessions.dateLabel }}
-
+
+
{{ daySessionsSplit.dateLabel }}
+
-
+
- Created {{ daySessions.newWorkspaces.length }} new workspace{{ daySessions.newWorkspaces.length > 1 ? 's' : '' }} + Created {{ daySessionsSplit.newWorkspaces.total }} new workspace{{ daySessionsSplit.newWorkspaces.total > 1 ? 's' : '' }}
+
-
+
+ - Started {{ daySessions.newSessions.length }} session{{ daySessions.newSessions.length > 1 ? 's' : '' }} in {{ newSessionProjectCount(daySessions.newSessions) }} project{{ newSessionProjectCount(daySessions.newSessions) > 1 ? 's' : '' }} + Started {{ daySessionsSplit.newSessions.total }} session{{ daySessionsSplit.newSessions.total > 1 ? 's' : '' }} in {{ newSessionProjectCount([...daySessionsSplit.newSessions.normal, ...daySessionsSplit.newSessions.noise]) }} project{{ newSessionProjectCount([...daySessionsSplit.newSessions.normal, ...daySessionsSplit.newSessions.noise]) > 1 ? 's' : '' }}
+
-
+
- Continued {{ daySessions.continued.length }} session{{ daySessions.continued.length > 1 ? 's' : '' }} + Continued {{ daySessionsSplit.continued.total }} session{{ daySessionsSplit.continued.total > 1 ? 's' : '' }}
+
@@ -564,58 +655,109 @@ onMounted(async () => {
-