Skip to content

feat: add opencode plugin and xdg defaults#9

Merged
oritwoen merged 22 commits into
mainfrom
opencode-plugin-xdg-defaults
Mar 12, 2026
Merged

feat: add opencode plugin and xdg defaults#9
oritwoen merged 22 commits into
mainfrom
opencode-plugin-xdg-defaults

Conversation

@oritwoen

@oritwoen oritwoen commented Mar 11, 2026

Copy link
Copy Markdown
Owner

What

I added the obsxa/opencode plugin hooks for observation capture and context injection, then moved default DB paths to XDG locations across API and CLI. The push for this was practical - local runs kept dropping obsxa.db into project roots.

There is one runtime caveat for local OpenCode plugin tests: Bun cannot load better-sqlite3, so this plugin path is verified in Node runtime.

Closes

Closes #8

@oritwoen oritwoen added this to the v0.1.0 milestone Mar 11, 2026
@oritwoen oritwoen self-assigned this Mar 11, 2026
@coderabbitai

coderabbitai Bot commented Mar 11, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new OpenCode plugin export (createObsxaPlugin) with hooks for chat/tool/event/system; centralizes default DB resolution to an XDG-compliant path via getDefaultDbPath(); removes hardcoded "./obsxa.db" defaults across APIs and CLI; adds backup toon output option; adds observation lookup by input hash and extensive tests.

Changes

Cohort / File(s) Summary
Changelog & Packaging
CHANGELOG.md, build.config.ts, package.json
Inserted Unreleased changelog entry; added src/opencode.ts to bundle inputs; exported ./opencode in package exports; added @opencode-ai/plugin dev/peer dependency (optional).
Default DB path logic
src/core/db-path.ts, src/types.ts, src/ai.ts, src/index.ts
New getDefaultDbPath() with XDG/platform fallbacks; ObsxaOptions.db made optional; replaced hardcoded "./obsxa.db" defaults with options.db ?? getDefaultDbPath(); updated DB URL/backup path derivation and added index on (project_id, input_hash).
CLI commands
src/commands/_db.ts, src/commands/backup.ts
Removed hard default for db arg; made db open param optional; backup create/restore accept new toon flag; commands use args.db ?? getDefaultDbPath() and route JSON/TOON output via output(result, args.toon).
Observation store
src/core/observation.ts
Added `getByInputHash(projectId, inputHash): Promise<Observation
Opencode plugin
src/opencode.ts
New OpenCode plugin module (new file): exports createObsxaPlugin, ObsxaPluginOptions; implements hooks for chat.message, tool.execute.after, event, and experimental.chat.system.transform; includes input-hash deduplication, derived_from relations, bounded in-memory caches, session buffers, and error-tolerant DB interactions.
Tests
test/db-path.test.ts, test/opencode.test.ts
Added unit tests for XDG/default DB path behavior and comprehensive tests for plugin lifecycle, hooks, deduplication, relations, system injection, and end-to-end scenarios.

Sequence Diagram

sequenceDiagram
    participant Agent as Agent
    participant Plugin as OpenCode Plugin
    participant Obsxa as Obsxa API
    participant DB as SQLite DB
    participant LLM as LLM/System

    Agent->>Plugin: chat.message(payload)
    activate Plugin
    Plugin->>Obsxa: computeInputHash(text)
    Obsxa->>DB: SELECT WHERE (project_id,input_hash)
    DB-->>Obsxa: row or null
    alt not found
        Obsxa->>DB: INSERT observation
        DB-->>Obsxa: inserted row
    else found
        Obsxa->>DB: UPDATE frequency (optional)
        DB-->>Obsxa: updated row
    end
    Obsxa-->>Plugin: observation result
    deactivate Plugin

    Agent->>Plugin: tool.execute.after(result)
    activate Plugin
    Plugin->>Obsxa: computeInputHash(tool output)
    Obsxa->>DB: SELECT by (project_id,input_hash)
    DB-->>Obsxa: row or null
    alt not exists and tool writable
        Obsxa->>DB: INSERT measurement
        Obsxa->>DB: INSERT relation (derived_from -> message_obs)
    end
    deactivate Plugin

    Agent->>Plugin: experimental.chat.system.transform(request)
    activate Plugin
    Plugin->>Obsxa: query recent observations (possibly cross-project)
    Obsxa->>DB: SELECT recent observations
    DB-->>Obsxa: matching observations
    Obsxa-->>Plugin: formatted observations
    Plugin->>LLM: inject observations into system prompt
    deactivate Plugin
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

  • #8 — Add OpenCode plugin and XDG defaults — Directly implements the OpenCode plugin export and centralizes XDG default DB path resolution described in the issue.
  • oritwoen/obsxa#5 — Backup toon flag routing — Adds toon flag and unified output routing in backup create/restore matching the issue's objective.

Possibly related PRs

Poem

🧭 XDG tucks the DB out of sight,
hashes hush repeats into the night.
Hooks catch chats, tools, and events,
relations stitch the threads in dense.
A plugin serves context — sharp and light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title directly matches main changes: opencode plugin export and XDG default paths.
Description check ✅ Passed Description covers the what and why; mentions XDG defaults, plugin hooks, and the Bun/better-sqlite3 caveat. Issue link present.
Linked Issues check ✅ Passed Code implements opencode plugin export with hooks [#8], XDG default paths for API and CLI [#8], and documents Bun runtime limitation [#8].
Out of Scope Changes check ✅ Passed All changes align with #8 objectives: plugin hooks, XDG defaults, test coverage. No unrelated refactors or scope creep detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch opencode-plugin-xdg-defaults
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch opencode-plugin-xdg-defaults

Comment @coderabbitai help to get the list of available commands and usage tips.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 13 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/opencode.test.ts">

<violation number="1" location="test/opencode.test.ts:101">
P2: Hook instances created in this suite are not tracked/destroyed, which can leak open DB handles and make temp directory cleanup flaky.</violation>
</file>

<file name="src/opencode.ts">

<violation number="1" location="src/opencode.ts:151">
P2: System-context injection uses a global last-message buffer, which can leak/mix context across concurrent sessions.</violation>

<violation number="2" location="src/opencode.ts:198">
P2: When a chat message dedupes, the session mapping is skipped, so subsequent tool observations may miss `derived_from` links.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread test/opencode.test.ts
Comment thread src/opencode.ts Outdated
Comment thread src/opencode.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 48f24d00ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/ai.ts
Comment thread src/opencode.ts Outdated
Comment thread src/opencode.ts Outdated
@augmentcode

augmentcode Bot commented Mar 11, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: Adds an OpenCode plugin export to capture hook-based observations and inject recent context into chat system prompts.

Changes:

  • Introduced src/opencode.ts implementing OpenCode hook handlers (chat/tool/event/system) with dedup via inputHash
  • Extended observation store with getByInputHash() and added an index on (project_id, input_hash)
  • Standardized default database location to an XDG-compliant data directory via getDefaultDbPath()
  • Updated CLI commands (including backup) to default to the new DB path and added TOON output where missing
  • Updated build/exports to publish the new obsxa/opencode entry point
  • Added comprehensive tests for DB-path resolution and OpenCode plugin lifecycle/dedup/injection behavior

Technical Notes: createObsxa() now creates parent directories for on-disk DBs and keeps :memory: behavior intact; OpenCode plugin avoids a runtime dependency on @opencode-ai/plugin by inlining types.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread src/opencode.ts Outdated
Comment thread src/opencode.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 58: The devDependency "@opencode-ai/plugin" is pinned to the "latest" tag
which yields non-reproducible installs; update the package.json entry for
"@opencode-ai/plugin" to a specific semver version or a constrained range (e.g.,
a concrete version like "1.2.3" or a caret/tilde range) to ensure deterministic
CI and local installs and commit the updated package.json (and lockfile) so
installs remain reproducible.

In `@src/core/db-path.ts`:
- Around line 9-21: The current guard throws if home is empty before considering
environment roots; change the logic so you first check for an env-provided data
root (env.XDG_DATA_HOME on POSIX or env.LOCALAPPDATA on Windows) and only
require/validate home when no env data root exists and you need to build a
platform-specific fallback. Concretely: compute/trim xdgDataHome/localAppData
first, if that value exists use it as dataHome; otherwise validate that home is
non-empty and then compute fallbackDataHome (using platform, win32.join or join)
and assign dataHome accordingly — update the code around the variables env,
xdgDataHome, fallbackDataHome and dataHome to follow this flow.
- Around line 2-3: The code uses the OS-native join (imported as join) which
ignores the explicit platform argument; update getDefaultDbPath so Windows
branches use win32.join and all non-Windows branches use path.posix.join (import
posix from "node:path" or reference path.posix) instead of the generic join,
ensuring you choose the join implementation based on the platform parameter
(e.g., when platform === "win32" use win32.join, otherwise use posix.join) so
returned paths match the requested platform.

In `@src/index.ts`:
- Line 130: Move the raw CREATE INDEX statement for
idx_observations_project_input_hash out of runtime code and add it as a proper
drizzle-kit migration so schema changes are recorded in the drizzle journal and
SCHEMA_VERSION; create a new migration that runs CREATE INDEX IF NOT EXISTS
idx_observations_project_input_hash ON observations(project_id, input_hash) and
ensure the migration is applied during startup (via your existing drizzle
migration runner) instead of executing the index creation in src/index.ts at
runtime.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 391ccb12-38b8-4623-b64f-587d5747abfa

📥 Commits

Reviewing files that changed from the base of the PR and between c878b4a and 48f24d0.

📒 Files selected for processing (13)
  • CHANGELOG.md
  • build.config.ts
  • package.json
  • src/ai.ts
  • src/commands/_db.ts
  • src/commands/backup.ts
  • src/core/db-path.ts
  • src/core/observation.ts
  • src/index.ts
  • src/opencode.ts
  • src/types.ts
  • test/db-path.test.ts
  • test/opencode.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript with strict mode enabled
Do not use as any, @ts-ignore, or @ts-expect-error type assertions

Files:

  • src/types.ts
  • test/db-path.test.ts
  • src/core/db-path.ts
  • src/core/observation.ts
  • src/opencode.ts
  • src/index.ts
  • build.config.ts
  • src/ai.ts
  • test/opencode.test.ts
  • src/commands/_db.ts
  • src/commands/backup.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use oxlint + oxfmt for linting and formatting

Files:

  • src/types.ts
  • src/core/db-path.ts
  • src/core/observation.ts
  • src/opencode.ts
  • src/index.ts
  • src/ai.ts
  • src/commands/_db.ts
  • src/commands/backup.ts
src/types.ts

📄 CodeRabbit inference engine (AGENTS.md)

Place shared types in src/types.ts

Files:

  • src/types.ts
**/*.{ts,tsx,js,mjs,mts}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM only with "type": "module" in package.json

Files:

  • src/types.ts
  • test/db-path.test.ts
  • src/core/db-path.ts
  • src/core/observation.ts
  • src/opencode.ts
  • src/index.ts
  • build.config.ts
  • src/ai.ts
  • test/opencode.test.ts
  • src/commands/_db.ts
  • src/commands/backup.ts
test/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use vitest for testing

Files:

  • test/db-path.test.ts
  • test/opencode.test.ts
src/core/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/core/**/*.ts: Use SQLite via better-sqlite3 + drizzle-orm for database operations
Place database, schema, and CRUD operations in src/core/

Files:

  • src/core/db-path.ts
  • src/core/observation.ts
src/index.ts

📄 CodeRabbit inference engine (AGENTS.md)

Export public API from src/index.ts as a barrel export

Files:

  • src/index.ts
src/ai.ts

📄 CodeRabbit inference engine (AGENTS.md)

Export AI SDK tools from src/ai.ts

Files:

  • src/ai.ts
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Use @mention syntax in documentation to reference agents (e.g., @AGENTS.md)

Files:

  • CHANGELOG.md
src/commands/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/commands/**/*.ts: Place CLI command handlers in src/commands/ using citty framework
Support --json and --toon output formats for CLI commands

Files:

  • src/commands/_db.ts
  • src/commands/backup.ts
🧠 Learnings (15)
📚 Learning: 2026-03-10T19:06:40.289Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/observation.ts:375-393
Timestamp: 2026-03-10T19:06:40.289Z
Learning: In the obsxa project (`src/core/observation.ts` and related files), the database driver is `better-sqlite3` — a synchronous, single-connection SQLite driver. There is no concurrency possible, so read-modify-write patterns (e.g., read frequency, increment, write back) are safe and do not lose updates. Concurrency-based race condition comments are not applicable here.

Applied to files:

  • src/types.ts
  • src/index.ts
  • src/ai.ts
  • src/commands/_db.ts
📚 Learning: 2026-03-10T19:06:20.854Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/cluster.ts:84-97
Timestamp: 2026-03-10T19:06:20.854Z
Learning: In the obsxa project (`src/core/cluster.ts` and similar files), the database driver is `better-sqlite3`, which is fully synchronous and single-connection. Concurrent insert races (TOCTOU) cannot occur within the same process, so check-then-insert patterns are safe and do not need duplicate-key error handling.

Applied to files:

  • src/types.ts
  • src/index.ts
  • src/ai.ts
  • src/commands/_db.ts
📚 Learning: 2026-03-10T19:31:33.966Z
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-10T19:31:33.966Z
Learning: Applies to src/core/**/*.ts : Use SQLite via better-sqlite3 + drizzle-orm for database operations

Applied to files:

  • src/types.ts
  • src/core/db-path.ts
  • src/index.ts
  • package.json
  • src/ai.ts
  • src/commands/_db.ts
📚 Learning: 2026-03-10T19:31:33.966Z
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-10T19:31:33.966Z
Learning: Applies to src/core/**/*.ts : Place database, schema, and CRUD operations in src/core/

Applied to files:

  • src/core/db-path.ts
  • src/index.ts
📚 Learning: 2026-03-10T19:06:24.096Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/db.ts:81-93
Timestamp: 2026-03-10T19:06:24.096Z
Learning: In the obsxa repo (TypeScript, Node.js, better-sqlite3), read-then-insert patterns discussed in src/core/dedup.ts (scan()) and src/core/cluster.ts are intentionally safe from race conditions due to better-sqlite3 being fully synchronous and blocking the event loop, preventing concurrent interleaving within the same process. Do not flag these as race conditions. Apply this understanding to TypeScript files under src/core/ when evaluating similar read-then-insert patterns; they may be safe in this environment, but still verify that there are no cross-process or multi-threaded interactions that could reintroduce race-like scenarios.

Applied to files:

  • src/core/db-path.ts
  • src/core/observation.ts
📚 Learning: 2026-03-10T19:06:20.854Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/cluster.ts:84-97
Timestamp: 2026-03-10T19:06:20.854Z
Learning: In the obsxa project, for files under src/core that use the better-sqlite3 driver, inserts using a check-then-insert pattern are considered safe from TOCTOU issues and do not require duplicate-key error handling, provided there is a single Database instance per process and no concurrent connections. Apply this guideline to src/core/cluster.ts and similar files. If multiple connections or multi-process usage is possible, retain appropriate duplicate-key error handling.

Applied to files:

  • src/core/db-path.ts
  • src/core/observation.ts
📚 Learning: 2026-03-10T19:31:33.966Z
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-10T19:31:33.966Z
Learning: Applies to src/backup.ts : Implement database backup/restore functionality in src/backup.ts

Applied to files:

  • src/index.ts
  • src/ai.ts
  • src/commands/_db.ts
  • src/commands/backup.ts
📚 Learning: 2026-03-10T19:31:33.966Z
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-10T19:31:33.966Z
Learning: Applies to drizzle/**/*.sql : Use drizzle-kit for schema migrations

Applied to files:

  • src/index.ts
📚 Learning: 2026-03-10T19:31:33.967Z
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-10T19:31:33.967Z
Learning: Applies to drizzle/**/*.sql : Route all SQLite schema changes through drizzle-kit migrations

Applied to files:

  • src/index.ts
📚 Learning: 2026-03-10T19:31:33.966Z
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-10T19:31:33.966Z
Learning: Applies to src/cli.ts : Use src/cli.ts as the CLI entrypoint

Applied to files:

  • build.config.ts
📚 Learning: 2026-03-10T19:31:33.966Z
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-10T19:31:33.966Z
Learning: Applies to src/index.ts : Export public API from src/index.ts as a barrel export

Applied to files:

  • build.config.ts
📚 Learning: 2026-03-10T19:31:33.966Z
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-10T19:31:33.966Z
Learning: Applies to src/ai.ts : Export AI SDK tools from src/ai.ts

Applied to files:

  • build.config.ts
  • package.json
  • src/ai.ts
📚 Learning: 2026-03-10T19:31:33.966Z
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-10T19:31:33.966Z
Learning: Applies to **/*.{ts,tsx,js,mjs,mts} : Use ESM only with "type": "module" in package.json

Applied to files:

  • package.json
📚 Learning: 2026-03-10T19:06:24.096Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/db.ts:81-93
Timestamp: 2026-03-10T19:06:24.096Z
Learning: In the `obsxa` repository (TypeScript, Node.js, better-sqlite3), read-then-insert patterns (e.g., in `src/core/dedup.ts` scan() and `src/core/cluster.ts`) are intentionally safe without DB-level unique constraints because `better-sqlite3` is fully synchronous — it blocks the event loop on every call, making concurrent interleaving within the same process impossible. Do not flag these as race conditions.

Applied to files:

  • src/ai.ts
  • test/opencode.test.ts
📚 Learning: 2026-03-10T19:31:33.967Z
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-10T19:31:33.967Z
Learning: Applies to src/commands/**/*.ts : Support --json and --toon output formats for CLI commands

Applied to files:

  • src/commands/backup.ts
🧬 Code graph analysis (7)
test/db-path.test.ts (1)
src/core/db-path.ts (1)
  • getDefaultDbPath (4-25)
src/core/observation.ts (4)
src/index.ts (1)
  • Observation (32-32)
src/types.ts (1)
  • Observation (50-78)
src/core/db.ts (1)
  • observations (15-45)
src/core/mappers.ts (1)
  • toObservation (45-75)
src/opencode.ts (2)
src/index.ts (2)
  • ObsxaInstance (173-182)
  • createObsxa (197-257)
src/core/db-path.ts (1)
  • getDefaultDbPath (4-25)
src/index.ts (3)
src/types.ts (1)
  • ObsxaOptions (2-11)
src/core/db-path.ts (1)
  • getDefaultDbPath (4-25)
src/backup.ts (1)
  • backupDatabase (38-62)
src/ai.ts (1)
src/core/db-path.ts (1)
  • getDefaultDbPath (4-25)
test/opencode.test.ts (3)
src/opencode.ts (1)
  • createObsxaPlugin (138-388)
src/index.ts (2)
  • createObsxaPlugin (55-55)
  • createObsxa (197-257)
src/core/db.ts (1)
  • observations (15-45)
src/commands/backup.ts (2)
src/core/db-path.ts (1)
  • getDefaultDbPath (4-25)
src/commands/_db.ts (1)
  • output (23-25)
🔇 Additional comments (14)
src/core/observation.ts (1)

124-131: Looks good.

Clean implementation following the existing get(id) pattern. Query logic is correct. One minor note: there's no composite index on (projectId, inputHash) in the schema, so this could be slow on large tables. If this gets called frequently in hot paths (like the opencode plugin dedup), consider adding an index.

build.config.ts (1)

8-13: LGTM.

New entry point added correctly. Matches the ./opencode export in package.json.

package.json (1)

33-36: Export looks correct.

Paths align with build.config.ts output. Good to go.

src/opencode.ts (5)

64-89: LRU cache implementation is correct.

The delete-then-set pattern on access moves the key to the end, and eviction removes the oldest (first) key. This works because Map maintains insertion order.


170-229: chat.message hook looks solid.

Good dedup logic, appropriate guards for null/short messages, and proper context capture. The 20-char minimum threshold is reasonable for filtering noise.


279-286: Good defensive handling of duplicate relations.

Silently dropping UNIQUE constraint violations here is the right call - prevents noise when the same derived_from relation is attempted twice.


362-385: System transform injection looks correct.

Always pushes the instruction, conditionally appends observations. The cross-project search (undefined projectId) matches the comment intent.


147-149: ⚠️ Potential issue | 🟡 Minor

Project creation could silently succeed on race.

If two plugin instances start simultaneously with the same projectId, both call get() → null, both call add(). The second add will throw a UNIQUE constraint error. Might want to wrap this in try/catch or use INSERT OR IGNORE semantics.

That said, this is unlikely in practice since plugins are typically singleton per process.

Possible fix
-    if (!obsxa.project.get(projectId)) {
-      obsxa.project.add({ id: projectId, name: projectName });
-    }
+    if (!obsxa.project.get(projectId)) {
+      try {
+        obsxa.project.add({ id: projectId, name: projectName });
+      } catch (err) {
+        // Ignore if another process created it first
+        const msg = err instanceof Error ? err.message : String(err);
+        if (!msg.includes("UNIQUE constraint") && !msg.includes("SQLITE_CONSTRAINT")) {
+          throw err;
+        }
+      }
+    }
⛔ Skipped due to learnings
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/db.ts:81-93
Timestamp: 2026-03-10T19:06:24.096Z
Learning: In the `obsxa` repository (TypeScript, Node.js, better-sqlite3), read-then-insert patterns (e.g., in `src/core/dedup.ts` scan() and `src/core/cluster.ts`) are intentionally safe without DB-level unique constraints because `better-sqlite3` is fully synchronous — it blocks the event loop on every call, making concurrent interleaving within the same process impossible. Do not flag these as race conditions.
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/cluster.ts:84-97
Timestamp: 2026-03-10T19:06:20.854Z
Learning: In the obsxa project (`src/core/cluster.ts` and similar files), the database driver is `better-sqlite3`, which is fully synchronous and single-connection. Concurrent insert races (TOCTOU) cannot occur within the same process, so check-then-insert patterns are safe and do not need duplicate-key error handling.
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/observation.ts:375-393
Timestamp: 2026-03-10T19:06:40.289Z
Learning: In the obsxa project (`src/core/observation.ts` and related files), the database driver is `better-sqlite3` — a synchronous, single-connection SQLite driver. There is no concurrency possible, so read-modify-write patterns (e.g., read frequency, increment, write back) are safe and do not lose updates. Concurrency-based race condition comments are not applicable here.
Learnt from: aeitwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/observation.ts:94-117
Timestamp: 2026-03-10T19:17:03.432Z
Learning: In the obsxa project (src/core/observation.ts), `addMany` is intentionally designed for partial success on large batch imports (e.g., agent pipelines with messy data). The design intent is: if some records fail validation, keep the ones that succeeded and report the ones that failed. Do NOT suggest wrapping the entire addMany loop in a transaction. However, note that the current implementation using Array.prototype.map with throws does not actually achieve partial success — a throw inside .map() propagates and the caller receives an exception rather than partial results.
src/types.ts (1)

3-4: Type change aligns with implementation.

Making db optional here matches createObsxa now using getDefaultDbPath() as fallback. Doc comment is accurate.

CHANGELOG.md (1)

3-9: Good breaking change documentation.

Clear migration path for users who want the old behavior. This is exactly what changelog entries should look like.

src/commands/_db.ts (1)

6-12: Clean delegation of default path.

Removing the hardcoded default here and letting createObsxa handle it via getDefaultDbPath() is the right approach. Single source of truth.

src/ai.ts (2)

3-3: Import change looks correct.

Centralizing the default path logic is good. Consistent with changes in other files.


7-12: Remove this comment - the premise is factually incorrect.

This file is new (first commit 48f24d0), so there was never an isAbsolute check to remove. The review appears based on inaccurate summary information. The current code is fine: getDefaultDbPath() handles safe XDG paths, user input gets validated for .. (path traversal) and .db extension. No issue here.

test/opencode.test.ts (1)

77-82: ⚠️ Potential issue | 🟠 Major

Only track file-backed DB tests; skip :memory: cases.

The test at lines 77-82 uses :memory:, so untracked hooks there don't cause rmSync failures. The real issue is in "createObsxaPlugin factory" (lines 99-205), where file-backed tests call await plugin(...) directly without pushing to trackedHooks. This leaves better-sqlite3 handles open, causing rmSync(dbDir) to fail with EBUSY on Windows.

Tests like "ensures project exists after factory call" (line 111), "returns hooks object with expected keys" (line 130+), and others in that block don't track hooks and don't manually call destroy(). They need:

 const plugin = createObsxaPlugin({ db: dbPath, projectId: "test-project" });
-await plugin({ project: { id: "test-project" }, directory: "/tmp", worktree: "/tmp" });
+trackedHooks.push(await plugin({ project: { id: "test-project" }, directory: "/tmp", worktree: "/tmp" }));

or manually call hooks.destroy() before the test ends. Skipping this leaves the DB locked during cleanup.

⛔ Skipped due to learnings
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/db.ts:81-93
Timestamp: 2026-03-10T19:06:24.096Z
Learning: In the `obsxa` repository (TypeScript, Node.js, better-sqlite3), read-then-insert patterns (e.g., in `src/core/dedup.ts` scan() and `src/core/cluster.ts`) are intentionally safe without DB-level unique constraints because `better-sqlite3` is fully synchronous — it blocks the event loop on every call, making concurrent interleaving within the same process impossible. Do not flag these as race conditions.
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/cluster.ts:84-97
Timestamp: 2026-03-10T19:06:20.854Z
Learning: In the obsxa project (`src/core/cluster.ts` and similar files), the database driver is `better-sqlite3`, which is fully synchronous and single-connection. Concurrent insert races (TOCTOU) cannot occur within the same process, so check-then-insert patterns are safe and do not need duplicate-key error handling.
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/observation.ts:375-393
Timestamp: 2026-03-10T19:06:40.289Z
Learning: In the obsxa project (`src/core/observation.ts` and related files), the database driver is `better-sqlite3` — a synchronous, single-connection SQLite driver. There is no concurrency possible, so read-modify-write patterns (e.g., read frequency, increment, write back) are safe and do not lose updates. Concurrency-based race condition comments are not applicable here.
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-10T19:31:33.966Z
Learning: Applies to src/core/**/*.ts : Use SQLite via better-sqlite3 + drizzle-orm for database operations

Comment thread package.json Outdated
Comment thread src/core/db-path.ts Outdated
Comment thread src/core/db-path.ts Outdated
Comment thread src/index.ts Outdated
@codeant-ai

codeant-ai Bot commented Mar 11, 2026

Copy link
Copy Markdown

CodeAnt AI is reviewing your PR.

@codeant-ai

codeant-ai Bot commented Mar 11, 2026

Copy link
Copy Markdown

Sequence Diagram

This PR adds an OpenCode plugin that opens obsxa using an XDG-compliant default database path, captures observations from chat messages, tools, and events, and then injects relevant observations back into the model system prompt.

sequenceDiagram
    participant OpenCode
    participant ObsxaPlugin
    participant ObsxaCore

    OpenCode->>ObsxaPlugin: Initialize plugin with project info
    ObsxaPlugin->>ObsxaCore: Open or create database using XDG default path and ensure project

    OpenCode->>ObsxaPlugin: chat.message hook with user message
    ObsxaPlugin->>ObsxaCore: Deduplicate and record pattern observation for session

    OpenCode->>ObsxaPlugin: tool.execute.after hook with tool result
    ObsxaPlugin->>ObsxaCore: Record measurement observation and link to chat observation

    OpenCode->>ObsxaPlugin: system.transform hook before model call
    ObsxaPlugin->>ObsxaCore: Search recent observations relevant to query
    ObsxaCore-->>ObsxaPlugin: Return matching observations
    ObsxaPlugin-->>OpenCode: Inject observation instruction and context into system messages
Loading

Generated by CodeAnt AI

Comment thread src/core/db-path.ts Outdated
Comment thread src/index.ts
@codeant-ai

codeant-ai Bot commented Mar 11, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/db-path.ts`:
- Around line 12-35: In getDefaultDbPath, currently XDG_DATA_HOME and
localAppData are only checked for non-empty strings, allowing relative paths;
update the checks to ensure the selected base is absolute using the
platform-appropriate isAbsolute (use win32.isAbsolute when platform === "win32",
otherwise posix.isAbsolute) and treat relative values as absent (ignore them and
continue to the next fallback) or throw a clear error; modify the validation for
XDG_DATA_HOME, localAppData, and home to use the isAbsolute checks before
constructing paths like win32.join(..., "obsxa", "obsxa.db") or posix.join(...).

In `@src/opencode.ts`:
- Around line 422-427: The search call is currently unscoped
(obsxa.search.search(query, undefined, maxObs)) which allows cross-project
recall; change the lookup to default to the current project scope by passing the
current projectId (from the surrounding context) as the scope argument instead
of undefined, e.g. use obsxa.search.search(query, projectId, maxObs), and only
allow cross-project/global recall when an explicit flag is passed; update the
call sites that build `query` (using latestMessageBufferBySession,
latestMessageBuffer, projectName and _sysInput.sessionID) to use the projectId
parameter so recalls are confined to the current project unless explicitly
requested.
- Around line 120-123: Sanitize observation titles before embedding them into
the obsxa-context wrapper: in the mapping that produces the lines constant (the
lines variable that uses r.observation.title) and the code path that injects the
title as system text (the obsxa-context insertion around lines ~430-431),
collapse any newlines to a single space and escape HTML/tag delimiters (e.g.,
replace '<' with '&lt;' and '>' with '&gt;' or otherwise neutralize sequences
like '</obsxa-context>') so titles cannot break out of the wrapper or inject
instructions.
- Around line 292-295: The dedupe branch currently increments the existing
observation's frequency and returns, which omits adding the new derived_from
relation; update the branch where findByHash(...) returns an existingId so that
after calling obsxa.observation.incrementFrequency(existingId) you also add the
same derived_from relation the non-dedupe path creates (use the same
relation-method and parameters used later in the non-dedup path) before
returning, ensuring the new message/session is linked to the reused observation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7491fdd9-8ed5-46b8-8b16-de3829417227

📥 Commits

Reviewing files that changed from the base of the PR and between afe611f and 978c941.

📒 Files selected for processing (4)
  • src/ai.ts
  • src/core/db-path.ts
  • src/opencode.ts
  • test/opencode.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/ai.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.ts: Use TypeScript with strict mode enabled (all strict checks enabled)
Do not use as any, @ts-ignore, or @ts-expect-error TypeScript escape hatches

Files:

  • src/opencode.ts
  • src/core/db-path.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM only with "type": "module" configuration

Files:

  • src/opencode.ts
  • src/core/db-path.ts
src/core/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use drizzle-orm with SQLite via libsql as the database layer

Files:

  • src/core/db-path.ts
🧠 Learnings (6)
📚 Learning: 2026-03-10T19:06:28.832Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/db.ts:81-93
Timestamp: 2026-03-10T19:06:28.832Z
Learning: In the `obsxa` repository (TypeScript, Node.js, better-sqlite3), read-then-insert patterns (e.g., in `src/core/dedup.ts` scan() and `src/core/cluster.ts`) are intentionally safe without DB-level unique constraints because `better-sqlite3` is fully synchronous — it blocks the event loop on every call, making concurrent interleaving within the same process impossible. Do not flag these as race conditions.

Applied to files:

  • test/opencode.test.ts
  • src/opencode.ts
📚 Learning: 2026-03-10T19:06:35.925Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/cluster.ts:84-97
Timestamp: 2026-03-10T19:06:35.925Z
Learning: In the obsxa project (`src/core/cluster.ts` and similar files), the database driver is `better-sqlite3`, which is fully synchronous and single-connection. Concurrent insert races (TOCTOU) cannot occur within the same process, so check-then-insert patterns are safe and do not need duplicate-key error handling.

Applied to files:

  • src/opencode.ts
📚 Learning: 2026-03-10T19:06:48.549Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/observation.ts:375-393
Timestamp: 2026-03-10T19:06:48.549Z
Learning: In the obsxa project (`src/core/observation.ts` and related files), the database driver is `better-sqlite3` — a synchronous, single-connection SQLite driver. There is no concurrency possible, so read-modify-write patterns (e.g., read frequency, increment, write back) are safe and do not lose updates. Concurrency-based race condition comments are not applicable here.

Applied to files:

  • src/opencode.ts
📚 Learning: 2026-03-11T17:33:57.939Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 11
File: src/core/observation.ts:94-126
Timestamp: 2026-03-11T17:33:57.939Z
Learning: In the obsxa project (`src/core/observation.ts`), `addMany` is intentionally fail-fast: if any record fails validation (e.g., promoted status missing `promotedTo`) or any awaited call (`this.add`, `this.dismiss`, `this.archive`, `this.promote`) throws, the entire batch aborts and the error propagates to the caller. Do NOT suggest changing this to partial success, as it would alter the established contract and caller expectations.

Applied to files:

  • src/opencode.ts
📚 Learning: 2026-03-10T19:06:24.096Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/db.ts:81-93
Timestamp: 2026-03-10T19:06:24.096Z
Learning: In the obsxa repo (TypeScript, Node.js, better-sqlite3), read-then-insert patterns discussed in src/core/dedup.ts (scan()) and src/core/cluster.ts are intentionally safe from race conditions due to better-sqlite3 being fully synchronous and blocking the event loop, preventing concurrent interleaving within the same process. Do not flag these as race conditions. Apply this understanding to TypeScript files under src/core/ when evaluating similar read-then-insert patterns; they may be safe in this environment, but still verify that there are no cross-process or multi-threaded interactions that could reintroduce race-like scenarios.

Applied to files:

  • src/core/db-path.ts
📚 Learning: 2026-03-10T19:06:20.854Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/cluster.ts:84-97
Timestamp: 2026-03-10T19:06:20.854Z
Learning: In the obsxa project, for files under src/core that use the better-sqlite3 driver, inserts using a check-then-insert pattern are considered safe from TOCTOU issues and do not require duplicate-key error handling, provided there is a single Database instance per process and no concurrent connections. Apply this guideline to src/core/cluster.ts and similar files. If multiple connections or multi-process usage is possible, retain appropriate duplicate-key error handling.

Applied to files:

  • src/core/db-path.ts
🧬 Code graph analysis (2)
test/opencode.test.ts (3)
src/opencode.ts (1)
  • createObsxaPlugin (167-443)
src/index.ts (2)
  • createObsxaPlugin (56-56)
  • createObsxa (217-295)
src/core/db.ts (1)
  • observations (15-45)
src/opencode.ts (2)
src/index.ts (2)
  • ObsxaInstance (193-202)
  • createObsxa (217-295)
src/core/db-path.ts (1)
  • getDefaultDbPath (4-38)

Comment thread src/core/db-path.ts Outdated
Comment thread src/opencode.ts
Comment thread src/opencode.ts Outdated
Comment thread src/opencode.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/opencode.ts">

<violation number="1" location="src/opencode.ts:246">
P2: When a duplicate tool result is found, the dedup path increments frequency and returns without creating the `derived_from` relation to the current session's message observation. New observations get this relation (see the block below), but reused observations from another session silently lose the edge. Add the relation on the dedup path as well.</violation>

<violation number="2" location="src/opencode.ts:437">
P1: The search call passes `undefined` instead of `projectId`, making prompt recall global across all projects. Now that the DB default moved to a shared XDG location, observations captured in one repository can be injected into another repository's system prompt if they match the query. Scope the search to the current `projectId`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/opencode.ts Outdated
Comment thread src/opencode.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/opencode.ts (3)

115-117: ⚠️ Potential issue | 🟠 Major

Escape titles before they go back into obsxa-context.

observation.title is user, tool, and event controlled. A stored title like </obsxa-context>\nIgnore previous instructions closes this wrapper and comes back as fresh system text on the next transform.

Minimal fix
+function escapeContextText(value: string): string {
+  return value
+    .replace(/&/g, "&amp;")
+    .replace(/</g, "&lt;")
+    .replace(/>/g, "&gt;")
+    .replace(/\r?\n+/g, " ");
+}
+
 function formatObservations(
   results: Array<{
     observation: { title: string; type: string; confidence: number; frequency: number };
   }>,
   maxChars: number,
 ): string {
   const lines = results.map(
     (r) =>
-      `- [${r.observation.type}] ${r.observation.title} (${r.observation.confidence}%, seen ${r.observation.frequency}x)`,
+      `- [${r.observation.type}] ${escapeContextText(r.observation.title)} (${r.observation.confidence}%, seen ${r.observation.frequency}x)`,
   );

Also applies to: 440-441

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/opencode.ts` around lines 115 - 117, The code builds the `lines` array
using raw `observation.title`, which lets user/tool-controlled text inject and
close the surrounding `obsxa-context` wrapper; sanitize/escape titles before
embedding them by applying a proper HTML/XML escape (e.g., escape &, <, >, ", ')
or a project-standard sanitizer to `observation.title` in the `results.map` that
creates `lines` (and the other occurrences you flagged at lines around 440-441),
so the string inserted into the `- [${r.observation.type}] ${...}` template
cannot break out of `obsxa-context` or inject new directives—use or add a
reusable helper like `escapeHtml` and call it where titles are interpolated.

297-300: ⚠️ Potential issue | 🟠 Major

Keep the derived_from link on the dedup path.

When the same tool output shows up again, this branch bumps frequency and returns. The relation is only added below for newly inserted observations, so every later repeat loses the message that produced it.

Minimal fix
             const existingId = await findByHash(obsxa, projectId, hash, hashCache);
             if (existingId !== undefined) {
               await obsxa.observation.incrementFrequency(existingId);
+              const msgObsId = getCacheValue(sessionMessageObs, toolInput.sessionID);
+              if (msgObsId !== undefined) {
+                try {
+                  await obsxa.relation.add({
+                    fromObservationId: existingId,
+                    toObservationId: msgObsId,
+                    type: "derived_from",
+                  });
+                } catch (err) {
+                  if (!isSqliteConstraintError(err)) {
+                    logHookError("tool.execute.after.relation", err);
+                  }
+                }
+              }
               return;
             }

Also applies to: 325-341

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/opencode.ts` around lines 297 - 300, The dedup branch increments
frequency but omits adding the derived_from relation; update the branch where
existingId is returned (the code using findByHash, existingId, and
obsxa.observation.incrementFrequency) to also create or ensure a derived_from
relation from the new message/observation to existingId (using the same relation
creation call used for new inserts on the non-duplicate path), passing projectId
and the source message id/hash so repeated occurrences retain the derived_from
link.

432-437: ⚠️ Potential issue | 🟠 Major

Do not search every project by default here.

The DB now defaults to a shared XDG location. Passing undefined here pulls hits from every project and injects them into this project's system prompt, so repo B can see repo A's observations if the query overlaps.

Minimal fix
-            const results = await obsxa.search.search(query, undefined, maxObs);
+            const results = await obsxa.search.search(query, projectId, maxObs);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/opencode.ts` around lines 432 - 437, The call to
obsxa.search.search(query, undefined, maxObs) leaves the project/workspace
filter undefined, which causes searches to return hits from every project;
replace the undefined workspace/project argument with the current project's
identifier (e.g., projectName or the appropriate project-scoped ID available in
this module) so obsxa.search.search(query, projectName, maxObs) (or the
equivalent project-scoped variable) is used; ensure the changed call is made
where query is computed (using _sysInput.sessionID,
latestMessageBufferBySession, latestMessageBuffer, projectName) so results are
restricted to this project only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ai.ts`:
- Around line 9-14: The current guard only rejects filesystem absolute paths but
allows DB URLs like "file:///..." or "https://..." — update the validation
around dbPath/path so any string that looks like a URL scheme is rejected before
falling back to getDefaultDbPath(); e.g., check the original path variable (not
just the fallback dbPath) against a URL-scheme pattern such as
/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\// (or explicitly test for file: and http(s):) and
throw the same "Database path must be relative" error if it matches, then keep
the existing absolute, ".." and ".db" checks on dbPath. Ensure you reference and
modify the existing validation block that uses path, dbPath, isAbsolute(...) and
the following includes/endsWith checks.

---

Duplicate comments:
In `@src/opencode.ts`:
- Around line 115-117: The code builds the `lines` array using raw
`observation.title`, which lets user/tool-controlled text inject and close the
surrounding `obsxa-context` wrapper; sanitize/escape titles before embedding
them by applying a proper HTML/XML escape (e.g., escape &, <, >, ", ') or a
project-standard sanitizer to `observation.title` in the `results.map` that
creates `lines` (and the other occurrences you flagged at lines around 440-441),
so the string inserted into the `- [${r.observation.type}] ${...}` template
cannot break out of `obsxa-context` or inject new directives—use or add a
reusable helper like `escapeHtml` and call it where titles are interpolated.
- Around line 297-300: The dedup branch increments frequency but omits adding
the derived_from relation; update the branch where existingId is returned (the
code using findByHash, existingId, and obsxa.observation.incrementFrequency) to
also create or ensure a derived_from relation from the new message/observation
to existingId (using the same relation creation call used for new inserts on the
non-duplicate path), passing projectId and the source message id/hash so
repeated occurrences retain the derived_from link.
- Around line 432-437: The call to obsxa.search.search(query, undefined, maxObs)
leaves the project/workspace filter undefined, which causes searches to return
hits from every project; replace the undefined workspace/project argument with
the current project's identifier (e.g., projectName or the appropriate
project-scoped ID available in this module) so obsxa.search.search(query,
projectName, maxObs) (or the equivalent project-scoped variable) is used; ensure
the changed call is made where query is computed (using _sysInput.sessionID,
latestMessageBufferBySession, latestMessageBuffer, projectName) so results are
restricted to this project only.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cd36d306-b3fc-4f22-893c-cfa6d7ecb570

📥 Commits

Reviewing files that changed from the base of the PR and between 978c941 and a3e35cd.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • CHANGELOG.md
  • src/ai.ts
  • src/index.ts
  • src/opencode.ts
  • test/opencode.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.ts: Use TypeScript with strict mode enabled (all strict checks enabled)
Do not use as any, @ts-ignore, or @ts-expect-error TypeScript escape hatches

Files:

  • src/ai.ts
  • src/index.ts
  • src/opencode.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM only with "type": "module" configuration

Files:

  • src/ai.ts
  • src/index.ts
  • src/opencode.ts
🧠 Learnings (8)
📓 Common learnings
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 9
File: package.json:58-58
Timestamp: 2026-03-11T19:03:02.039Z
Learning: In `package.json` of `oritwoen/obsxa`, `opencode-ai/plugin` is intentionally pinned to `"latest"` in devDependencies to track plugin API movement. Do not flag this as a non-reproducible build issue.
📚 Learning: 2026-03-10T19:06:35.925Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/cluster.ts:84-97
Timestamp: 2026-03-10T19:06:35.925Z
Learning: In the obsxa project (`src/core/cluster.ts` and similar files), the database driver is `better-sqlite3`, which is fully synchronous and single-connection. Concurrent insert races (TOCTOU) cannot occur within the same process, so check-then-insert patterns are safe and do not need duplicate-key error handling.

Applied to files:

  • src/ai.ts
  • src/index.ts
  • src/opencode.ts
📚 Learning: 2026-03-10T19:06:48.549Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/observation.ts:375-393
Timestamp: 2026-03-10T19:06:48.549Z
Learning: In the obsxa project (`src/core/observation.ts` and related files), the database driver is `better-sqlite3` — a synchronous, single-connection SQLite driver. There is no concurrency possible, so read-modify-write patterns (e.g., read frequency, increment, write back) are safe and do not lose updates. Concurrency-based race condition comments are not applicable here.

Applied to files:

  • src/ai.ts
  • src/index.ts
  • src/opencode.ts
📚 Learning: 2026-03-10T19:06:28.832Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/db.ts:81-93
Timestamp: 2026-03-10T19:06:28.832Z
Learning: In the `obsxa` repository (TypeScript, Node.js, better-sqlite3), read-then-insert patterns (e.g., in `src/core/dedup.ts` scan() and `src/core/cluster.ts`) are intentionally safe without DB-level unique constraints because `better-sqlite3` is fully synchronous — it blocks the event loop on every call, making concurrent interleaving within the same process impossible. Do not flag these as race conditions.

Applied to files:

  • test/opencode.test.ts
  • src/index.ts
  • src/opencode.ts
📚 Learning: 2026-03-11T19:03:02.039Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 9
File: package.json:58-58
Timestamp: 2026-03-11T19:03:02.039Z
Learning: In `package.json` of `oritwoen/obsxa`, `opencode-ai/plugin` is intentionally pinned to `"latest"` in devDependencies to track plugin API movement. Do not flag this as a non-reproducible build issue.

Applied to files:

  • src/index.ts
  • src/opencode.ts
📚 Learning: 2026-03-11T18:13:51.875Z
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-11T18:13:51.875Z
Learning: Applies to src/core/**/*.ts : Use drizzle-orm with SQLite via libsql as the database layer

Applied to files:

  • src/index.ts
📚 Learning: 2026-03-11T18:13:51.875Z
Learnt from: CR
Repo: oritwoen/obsxa PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-11T18:13:51.875Z
Learning: Applies to drizzle/**/*.ts : Database schema changes must go through drizzle-kit migrations

Applied to files:

  • src/index.ts
📚 Learning: 2026-03-11T17:33:57.939Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 11
File: src/core/observation.ts:94-126
Timestamp: 2026-03-11T17:33:57.939Z
Learning: In the obsxa project (`src/core/observation.ts`), `addMany` is intentionally fail-fast: if any record fails validation (e.g., promoted status missing `promotedTo`) or any awaited call (`this.add`, `this.dismiss`, `this.archive`, `this.promote`) throws, the entire batch aborts and the error propagates to the caller. Do NOT suggest changing this to partial success, as it would alter the established contract and caller expectations.

Applied to files:

  • src/opencode.ts
🧬 Code graph analysis (4)
src/ai.ts (1)
src/core/db-path.ts (1)
  • getDefaultDbPath (4-38)
test/opencode.test.ts (3)
src/opencode.ts (1)
  • createObsxaPlugin (162-453)
src/index.ts (2)
  • createObsxaPlugin (56-56)
  • createObsxa (217-298)
src/core/db.ts (1)
  • observations (15-45)
src/index.ts (2)
src/types.ts (1)
  • ObsxaOptions (2-11)
src/core/db-path.ts (1)
  • getDefaultDbPath (4-38)
src/opencode.ts (2)
src/index.ts (2)
  • ObsxaInstance (193-202)
  • createObsxa (217-298)
src/core/db-path.ts (1)
  • getDefaultDbPath (4-38)

Comment thread src/ai.ts
@coderabbitai

coderabbitai Bot commented Mar 11, 2026

Copy link
Copy Markdown

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 1 file(s) based on 1 unresolved review comment.

Files modified:

  • src/ai.ts

Commit: 19c5282a015495cfd6c230d156074cf9cbee4505

The changes have been pushed to the opencode-plugin-xdg-defaults branch.

Time taken: 3m 50s

coderabbitai Bot and others added 2 commits March 11, 2026 19:23
Fixed 1 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/opencode.ts`:
- Around line 128-132: The truncation branch for output when output.length >
maxChars currently always appends "..." which violates tiny caps
(maxInjectedChars/maxChars < 3); update the logic in the block using variables
output and maxChars so that if maxChars <= 0 return empty string, if maxChars is
1 or 2 return a trimmed ellipsis-safe string (e.g., "." or ".." up to maxChars)
rather than "..." and only append "..." when maxChars >= 3; ensure you still
prefer cutting at the last newline (lastNewline) when available before applying
the tiny-limit handling.
- Around line 379-386: The code incorrectly casts props.file and props.name to
string (in the evt.type === "file.edited" and "command.executed" branches),
which yields "[object Object]" for non-string payloads; update these branches in
the handler for evt.type to perform runtime checks (e.g., typeof props.file ===
"string" ? props.file : extract a useful field like props.file.path if present,
or JSON.stringify(props.file) or "unknown") and similarly for props.name, then
set title and source from the sanitized value (refer to evt.type, props.file,
props.name, title, and source to locate and change the logic).
- Around line 14-38: The inline PluginInput and Hooks types (PluginInput, Hooks,
chat.message, event, destroy) are out of sync with `@opencode-ai/plugin`@1.2.24;
replace the handwritten contract by importing types from the real package using
a type-only import (e.g., import type { PluginInput, Hooks } from
"@opencode-ai/plugin") or update the inline definitions to match the published
declarations (add client, serverUrl, $,
config/auth/tool/chat.params/shell.env/experimental.* hooks and correct
signatures for event and chat.message including model { providerID, modelID,
variant }), and add a CI type-check that fails if the local inline types diverge
from the package types.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9a5ecc00-0b0f-4bfc-83c7-4e060f994ef1

📥 Commits

Reviewing files that changed from the base of the PR and between a3e35cd and c6be4f0.

📒 Files selected for processing (3)
  • src/ai.ts
  • src/core/db-path.ts
  • src/opencode.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/db-path.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.ts: Use TypeScript with strict mode enabled (all strict checks enabled)
Do not use as any, @ts-ignore, or @ts-expect-error TypeScript escape hatches

Files:

  • src/ai.ts
  • src/opencode.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM only with "type": "module" configuration

Files:

  • src/ai.ts
  • src/opencode.ts
🧠 Learnings (6)
📓 Common learnings
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 9
File: package.json:58-58
Timestamp: 2026-03-11T19:03:02.039Z
Learning: In `package.json` of `oritwoen/obsxa`, `opencode-ai/plugin` is intentionally pinned to `"latest"` in devDependencies to track plugin API movement. Do not flag this as a non-reproducible build issue.
📚 Learning: 2026-03-10T19:06:28.832Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/db.ts:81-93
Timestamp: 2026-03-10T19:06:28.832Z
Learning: In the `obsxa` repository (TypeScript, Node.js, better-sqlite3), read-then-insert patterns (e.g., in `src/core/dedup.ts` scan() and `src/core/cluster.ts`) are intentionally safe without DB-level unique constraints because `better-sqlite3` is fully synchronous — it blocks the event loop on every call, making concurrent interleaving within the same process impossible. Do not flag these as race conditions.

Applied to files:

  • src/ai.ts
  • src/opencode.ts
📚 Learning: 2026-03-10T19:06:35.925Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/cluster.ts:84-97
Timestamp: 2026-03-10T19:06:35.925Z
Learning: In the obsxa project (`src/core/cluster.ts` and similar files), the database driver is `better-sqlite3`, which is fully synchronous and single-connection. Concurrent insert races (TOCTOU) cannot occur within the same process, so check-then-insert patterns are safe and do not need duplicate-key error handling.

Applied to files:

  • src/ai.ts
  • src/opencode.ts
📚 Learning: 2026-03-10T19:06:48.549Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 2
File: src/core/observation.ts:375-393
Timestamp: 2026-03-10T19:06:48.549Z
Learning: In the obsxa project (`src/core/observation.ts` and related files), the database driver is `better-sqlite3` — a synchronous, single-connection SQLite driver. There is no concurrency possible, so read-modify-write patterns (e.g., read frequency, increment, write back) are safe and do not lose updates. Concurrency-based race condition comments are not applicable here.

Applied to files:

  • src/ai.ts
  • src/opencode.ts
📚 Learning: 2026-03-11T19:03:02.039Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 9
File: package.json:58-58
Timestamp: 2026-03-11T19:03:02.039Z
Learning: In `package.json` of `oritwoen/obsxa`, `opencode-ai/plugin` is intentionally pinned to `"latest"` in devDependencies to track plugin API movement. Do not flag this as a non-reproducible build issue.

Applied to files:

  • src/opencode.ts
📚 Learning: 2026-03-11T17:33:57.939Z
Learnt from: oritwoen
Repo: oritwoen/obsxa PR: 11
File: src/core/observation.ts:94-126
Timestamp: 2026-03-11T17:33:57.939Z
Learning: In the obsxa project (`src/core/observation.ts`), `addMany` is intentionally fail-fast: if any record fails validation (e.g., promoted status missing `promotedTo`) or any awaited call (`this.add`, `this.dismiss`, `this.archive`, `this.promote`) throws, the entire batch aborts and the error propagates to the caller. Do NOT suggest changing this to partial success, as it would alter the established contract and caller expectations.

Applied to files:

  • src/opencode.ts
🧬 Code graph analysis (2)
src/ai.ts (1)
src/core/db-path.ts (1)
  • getDefaultDbPath (8-42)
src/opencode.ts (2)
src/index.ts (2)
  • ObsxaInstance (193-202)
  • createObsxa (217-298)
src/core/db-path.ts (1)
  • getDefaultDbPath (8-42)
🔇 Additional comments (2)
src/ai.ts (2)

8-18: URL scheme validation looks good — past review concern addressed.

The regex on line 10 now catches file://, https://, libsql://, etc. before they can reach createObsxa(). The logic is sound:

  • User provides path → must pass URL scheme check AND absolute path check
  • User provides nothing → getDefaultDbPath() returns an absolute XDG path, which is allowed since validation gates only fire when path is truthy

One small note: file:obsxa.db (no double slash) still passes since the regex requires ://. In practice this resolves to local file obsxa.db anyway, so it's not a bypass — just worth knowing if you ever tighten this further.


4-4: Import aligns with the new XDG default path strategy.

Clean delegation to the centralized getDefaultDbPath() utility.

Comment thread src/opencode.ts Outdated
Comment thread src/opencode.ts
Comment thread src/opencode.ts
@codeant-ai

codeant-ai Bot commented Mar 11, 2026

Copy link
Copy Markdown

CodeAnt AI is running Incremental review

@codeant-ai

codeant-ai Bot commented Mar 11, 2026

Copy link
Copy Markdown

Sequence Diagram

This PR adds an OpenCode plugin that records sessions into the obsxa database and injects recent observations back into system prompts, while centralizing database location handling via XDG-compliant default paths used by both the core library and CLI backup/restore commands.

sequenceDiagram
    participant CLI
    participant OpenCode
    participant ObsxaPlugin
    participant ObsxaCore as Obsxa core
    participant Database

    CLI->>ObsxaCore: Backup or restore using db arg or default XDG path
    ObsxaCore->>Database: Open SQLite file and read or write data

    OpenCode->>ObsxaPlugin: Initialize plugin with project and optional db path
    ObsxaPlugin->>ObsxaCore: createObsxa using provided or default XDG db path
    OpenCode->>ObsxaPlugin: Send chat, tool, and event hooks for session
    ObsxaPlugin->>ObsxaCore: Deduplicate and upsert observations and relations
    OpenCode->>ObsxaPlugin: Request system prompt transform for model
    ObsxaPlugin->>ObsxaCore: Search observations by project and session context
    ObsxaCore-->>ObsxaPlugin: Return matching observations
    ObsxaPlugin-->>OpenCode: Return system messages including agent guidance and recent observations
Loading

Generated by CodeAnt AI

@codeant-ai

codeant-ai Bot commented Mar 11, 2026

Copy link
Copy Markdown

CodeAnt AI Incremental review completed.

@codeant-ai

codeant-ai Bot commented Mar 12, 2026

Copy link
Copy Markdown

CodeAnt AI is running Incremental review

@codeant-ai

codeant-ai Bot commented Mar 12, 2026

Copy link
Copy Markdown

Sequence Diagram

This PR adds an OpenCode plugin flow that records chat, tool, and event activity as observations, then injects recalled context into system prompts. It also routes plugin and core initialization through an XDG-based default database path so local runs no longer default to project-root database files.

sequenceDiagram
    participant OpenCode
    participant ObsxaPlugin
    participant DbPathResolver
    participant ObsxaCore
    participant Database

    OpenCode->>ObsxaPlugin: Initialize plugin for project
    ObsxaPlugin->>DbPathResolver: Resolve default database path
    ObsxaPlugin->>ObsxaCore: Open obsxa instance
    ObsxaCore->>Database: Ensure project and open stores

    OpenCode->>ObsxaPlugin: Send chat tool and event hook data
    ObsxaPlugin->>Database: Upsert deduplicated observations and relations

    OpenCode->>ObsxaPlugin: Request system prompt transform
    ObsxaPlugin->>Database: Search recent project observations
    ObsxaPlugin-->>OpenCode: Return instruction and recalled context
Loading

Generated by CodeAnt AI

Comment thread src/opencode.ts Outdated
@oritwoen oritwoen merged commit 18934c4 into main Mar 12, 2026
4 checks passed
@oritwoen oritwoen deleted the opencode-plugin-xdg-defaults branch March 12, 2026 11:15
@oritwoen oritwoen restored the opencode-plugin-xdg-defaults branch March 12, 2026 13:55
@oritwoen oritwoen deleted the opencode-plugin-xdg-defaults branch March 12, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add OpenCode plugin and XDG defaults

1 participant