refactor(adapter/C2): drop getSessionDBPath/EventsPath from Adapter interface

The two methods were SHALLOW pure derivatives of `getSessionDir() +
projectDir` (interface complexity == implementation complexity). The Codex
override added no behavior — its `normalizeWorktreePath` pre-pass was a
no-op because `resolveSessionDbPath` and `hashProjectDirCanonical` already
normalize internally. The events.md override included a worktree suffix
that nothing in production consumed (server.ts and hooks build their own
events.md path inline).

Apply the deletion test: removing them from BaseAdapter / Adapter / Codex
collapses to ONE site of computation (`resolveSessionDbPath` in
src/session/db.ts). Complexity does NOT reappear in N callers — only one
production caller existed (opencode/plugin.ts), updated to call the helper
directly. Adapter authors can no longer cargo-cult overrides; the methods
do not exist on the interface.

Migration shape (preserved by tests):
  before: new SessionDB({ dbPath: adapter.getSessionDBPath(projectDir) })
  after:  new SessionDB({ dbPath: resolveSessionDbPath({
            projectDir, sessionsDir: adapter.getSessionDir(),
          }) })

Test counts: 2627→2630 passed (+2 new C2 narrowing pins, +1 less flake);
19 baseline failures unchanged (security policy + statusline cross-OS,
unrelated to this change).

Cross-OS validation: `tests/hooks/precompact-snapshot-event.test.ts:81-84`
documents the slash-folding pattern that resolveSessionDbPath performs
internally; refs/platforms/codex/codex-rs/core/src/config/mod.rs:622-627
confirms upstream Codex separates platform dir state (`codex_home`) from
per-project file paths — the separation we are formalizing.

Bundles regenerated via `npm run build`. hooks/*.bundle.mjs unchanged
(they don't import adapter classes).
This commit is contained in:
Mert Koseoglu
2026-05-10 16:34:38 +03:00
parent d474f1d9ce
commit d923716d00
18 changed files with 616 additions and 571 deletions
+252 -252
View File
File diff suppressed because one or more lines are too long
+214 -214
View File
File diff suppressed because one or more lines are too long
+8 -22
View File
@@ -1,13 +1,10 @@
/**
* BaseAdapter — shared implementation for methods identical across all adapters.
*
* Eliminates ~288 lines of duplication across 12 adapters.
* Each concrete adapter extends this and provides platform-specific logic.
*
* Shared methods:
* - getSessionDir() — builds session dir from sessionDirSegments
* - getSessionDBPath() — SHA-256 hash of projectDir → .db file
* - getSessionEventsPath()— SHA-256 hash of projectDir → -events.md file
* - backupSettings() — copies settings file to .bak
*
* Adapters with custom logic override the relevant method:
@@ -15,12 +12,19 @@
* - opencode: overrides getSessionDir (XDG_CONFIG_HOME / APPDATA)
* and backupSettings (calls checkPluginRegistration first)
* - openclaw: overrides backupSettings (searches 3 config paths)
*
* NOTE — C2 narrowing (2026-05): `getSessionDBPath` and `getSessionEventsPath`
* were removed. Both were SHALLOW pure derivatives of `getSessionDir() +
* projectDir` (interface complexity == implementation complexity). All
* adapter-storage path computation now flows through ONE site:
* `resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() })`
* in `src/session/db.ts`. Adapters expose only `getSessionDir()` for
* storage-related path concerns.
*/
import { join } from "node:path";
import { accessSync, copyFileSync, constants, mkdirSync } from "node:fs";
import { homedir } from "node:os";
import { hashProjectDirCanonical, resolveSessionDbPath } from "../session/db.js";
export abstract class BaseAdapter {
constructor(protected readonly sessionDirSegments: string[]) {}
@@ -31,24 +35,6 @@ export abstract class BaseAdapter {
return dir;
}
getSessionDBPath(projectDir: string): string {
// Delegates to resolveSessionDbPath for case-fold + worktree-suffix
// handling and one-shot migration of legacy raw-casing files. All 12
// adapters share this so cross-adapter ctx_stats reads see the same
// file the writer produced.
return resolveSessionDbPath({
projectDir,
sessionsDir: this.getSessionDir(),
});
}
getSessionEventsPath(projectDir: string): string {
// Sidecar to getSessionDBPath — same canonical hash so they live next
// to each other on disk. No migration helper for .md sidecars yet
// (they get rewritten on every session); the canonical hash is enough.
return join(this.getSessionDir(), `${hashProjectDirCanonical(projectDir)}-events.md`);
}
/**
* Default: build config dir from sessionDirSegments rooted at $HOME.
*
+6 -15
View File
@@ -26,7 +26,6 @@ import { resolve, dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { BaseAdapter } from "../base.js";
import { getWorktreeSuffix, hashProjectDirCanonical, normalizeWorktreePath, resolveSessionDbPath } from "../../session/db.js";
import { resolveCodexConfigDir } from "./paths.js";
import {
@@ -312,20 +311,12 @@ export class CodexAdapter extends BaseAdapter implements HookAdapter {
return dir;
}
getSessionDBPath(projectDir: string): string {
return resolveSessionDbPath({
projectDir: normalizeWorktreePath(projectDir),
sessionsDir: this.getSessionDir(),
});
}
getSessionEventsPath(projectDir: string): string {
const normalized = normalizeWorktreePath(projectDir);
return join(
this.getSessionDir(),
`${hashProjectDirCanonical(normalized)}${getWorktreeSuffix(normalized)}-events.md`,
);
}
// C2 narrowing (2026-05): the historical `getSessionDBPath` /
// `getSessionEventsPath` overrides were removed. Both delegated to the
// same canonical helpers (`resolveSessionDbPath` / `hashProjectDirCanonical`
// + `getWorktreeSuffix`) which already normalize the path internally —
// the explicit `normalizeWorktreePath` here was a no-op. Callers now reach
// the helpers directly through `adapter.getSessionDir()`.
getInstructionFiles(): string[] {
// Codex CLI honors AGENTS.md plus an optional override file.
+8 -2
View File
@@ -25,7 +25,7 @@ import { dirname, resolve, join } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { existsSync, readFileSync } from "node:fs";
import { SessionDB } from "../../session/db.js";
import { resolveSessionDbPath, SessionDB } from "../../session/db.js";
import { extractEvents, extractUserEvents } from "../../session/extract.js";
import type { HookInput } from "../../session/extract.js";
import { buildResumeSnapshot } from "../../session/snapshot.js";
@@ -281,7 +281,13 @@ async function createContextModePlugin(ctx: PluginContext) {
// process-global UUID would (a) never match prior-session resume rows and
// (b) collide across multi-session reuse (Mickey / PR #376 root cause).
const projectDir = ctx?.directory ?? process.cwd();
const db = new SessionDB({ dbPath: adapter.getSessionDBPath(projectDir) });
// C2 narrowing: resolve DB path through the canonical helper directly.
// BaseAdapter no longer exposes getSessionDBPath; the adapter only owns
// the sessions DIR (per-platform), the helper owns the per-project FILE
// (case-fold + worktree-suffix + one-shot legacy migration).
const db = new SessionDB({
dbPath: resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() }),
});
// Clean up old sessions on startup (no SessionStart hook to do this).
db.cleanupOldSessions(7);
+10 -7
View File
@@ -209,15 +209,18 @@ export interface HookAdapter {
/** Path to the platform's settings file (e.g., ~/.claude/settings.json). */
getSettingsPath(): string;
/** Directory where session data is stored. */
/**
* Directory where session data is stored.
*
* NOTE — C2 narrowing (2026-05): this is the ONLY storage-path concern an
* adapter exposes. Per-project DB paths are derived by callers via
* `resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() })`
* (see `src/session/db.ts`). Per-project events.md paths follow the same
* `<sessionDir>/<hash><suffix>-events.md` shape and are computed inline at
* the small number of call sites that need them (server.ts, hooks).
*/
getSessionDir(): string;
/** Compute per-project session DB path. */
getSessionDBPath(projectDir: string): string;
/** Compute per-project session events file path. */
getSessionEventsPath(projectDir: string): string;
/**
* Platform config directory.
*
+3 -2
View File
@@ -3,6 +3,7 @@ import { describe, it, expect, beforeEach } from "vitest";
import { homedir } from "node:os";
import { join, resolve } from "node:path";
import { AntigravityAdapter } from "../../src/adapters/antigravity/index.js";
import { hashProjectDirCanonical, resolveSessionDbPath } from "../../src/session/db.js";
describe("AntigravityAdapter", () => {
let adapter: AntigravityAdapter;
@@ -134,13 +135,13 @@ describe("AntigravityAdapter", () => {
});
it("session DB path contains project hash", () => {
const dbPath = adapter.getSessionDBPath("/test/project");
const dbPath = resolveSessionDbPath({ projectDir: "/test/project", sessionsDir: adapter.getSessionDir() });
expect(dbPath).toMatch(/[a-f0-9]{16}\.db$/);
expect(dbPath).toContain(".gemini");
});
it("session events path contains project hash with -events.md suffix", () => {
const eventsPath = adapter.getSessionEventsPath("/test/project");
const eventsPath = join(adapter.getSessionDir(), `${hashProjectDirCanonical("/test/project")}-events.md`);
expect(eventsPath).toMatch(/[a-f0-9]{16}-events\.md$/);
expect(eventsPath).toContain(".gemini");
});
@@ -45,3 +45,26 @@ describe("BaseAdapter memory/config defaults", () => {
expect(adapter.getMemoryDir()).toBe(join(homedir(), ".claude", "memory"));
});
});
// C2 narrowing — BaseAdapter MUST NOT expose path helpers that are pure
// derivatives of `getSessionDir() + projectDir`. Those derivatives belong
// in `src/session/db.ts:resolveSessionDbPath` (single site of computation,
// case-fold migration, worktree-suffix handling). Exposing them on every
// adapter is a SHALLOW interface — its complexity equals its implementation
// — and tempts adapter authors to override for cargo-cult reasons (e.g. the
// pre-narrowing CodexAdapter override that just delegated to the same
// helper). Deletion test: collapses to ONE call site, complexity does NOT
// reappear in N callers.
describe("BaseAdapter — adapter-storage interface narrowing (C2)", () => {
it("does NOT expose getSessionDBPath — callers go through resolveSessionDbPath", () => {
const adapter = new TestAdapter([".claude"]);
// Use Reflect to interrogate the runtime shape — the cast is intentional;
// we are pinning that the public surface no longer carries this method.
expect((adapter as unknown as Record<string, unknown>).getSessionDBPath).toBeUndefined();
});
it("does NOT expose getSessionEventsPath — events.md path lives in callers/server", () => {
const adapter = new TestAdapter([".claude"]);
expect((adapter as unknown as Record<string, unknown>).getSessionEventsPath).toBeUndefined();
});
});
+20 -13
View File
@@ -1,10 +1,10 @@
import "../setup-home";
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { createHash } from "node:crypto";
import { homedir, tmpdir } from "node:os";
import { join, resolve, sep } from "node:path";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { ClaudeCodeAdapter } from "../../src/adapters/claude-code/index.js";
import { hashProjectDirCanonical, resolveSessionDbPath } from "../../src/session/db.js";
import { fakeHome, realHome } from "../setup-home";
import {
PRE_TOOL_USE_MATCHERS,
@@ -231,13 +231,15 @@ describe("ClaudeCodeAdapter", () => {
expect(sessionDir.startsWith(join(realHome, ".claude", "context-mode"))).toBe(false);
});
it("DB path uses sha256 hash of projectDir", () => {
// C2 narrowing: per-project DB path is composed by callers via
// resolveSessionDbPath + adapter.getSessionDir().
it("DB path uses canonical hash of projectDir", () => {
const projectDir = "/my/project";
const hash = createHash("sha256")
.update(projectDir)
.digest("hex")
.slice(0, 16);
const dbPath = adapter.getSessionDBPath(projectDir);
const hash = hashProjectDirCanonical(projectDir);
const dbPath = resolveSessionDbPath({
projectDir,
sessionsDir: adapter.getSessionDir(),
});
expect(dbPath).toBe(
join(homedir(), ".claude", "context-mode", "sessions", `${hash}.db`),
);
@@ -267,14 +269,19 @@ describe("ClaudeCodeAdapter", () => {
rmSync(customDir, { recursive: true, force: true });
});
it("getSessionDBPath lands the DB under $CLAUDE_CONFIG_DIR (not ~/.claude)", () => {
// C2 narrowing (2026-05): the test now composes the DB path through
// resolveSessionDbPath + adapter.getSessionDir() — this is the SAME
// composition production callers (server.ts, opencode plugin, hooks)
// perform. The regression pin still holds: $CLAUDE_CONFIG_DIR must
// route the file out of ~/.claude.
it("DB path lands under $CLAUDE_CONFIG_DIR (not ~/.claude)", () => {
process.env.CLAUDE_CONFIG_DIR = customDir;
const projectDir = "/test/project";
const hash = createHash("sha256")
.update(projectDir)
.digest("hex")
.slice(0, 16);
const dbPath = adapter.getSessionDBPath(projectDir);
const hash = hashProjectDirCanonical(projectDir);
const dbPath = resolveSessionDbPath({
projectDir,
sessionsDir: adapter.getSessionDir(),
});
expect(dbPath).toBe(
join(customDir, "context-mode", "sessions", `${hash}.db`),
);
+9 -3
View File
@@ -5,7 +5,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync,
import { homedir, tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { CodexAdapter } from "../../src/adapters/codex/index.js";
import { SessionDB } from "../../src/session/db.js";
import { resolveSessionDbPath, SessionDB } from "../../src/session/db.js";
describe("CodexAdapter", () => {
let adapter: CodexAdapter;
@@ -605,7 +605,10 @@ describe("Codex precompact hook script", () => {
process.env.CODEX_HOME = codexHome;
try {
const dbPath = new CodexAdapter().getSessionDBPath(projectDir);
const dbPath = resolveSessionDbPath({
projectDir,
sessionsDir: new CodexAdapter().getSessionDir(),
});
const db = new SessionDB({ dbPath });
db.ensureSession(sessionId, projectDir);
db.insertEvent(sessionId, {
@@ -664,7 +667,10 @@ describe("Codex sessionstart hook script", () => {
process.env.CODEX_HOME = codexHome;
try {
const dbPath = new CodexAdapter().getSessionDBPath(projectDir);
const dbPath = resolveSessionDbPath({
projectDir,
sessionsDir: new CodexAdapter().getSessionDir(),
});
const db = new SessionDB({ dbPath });
db.ensureSession(sessionId, projectDir);
db.upsertResume(sessionId, snapshot, 1);
+11 -10
View File
@@ -2,8 +2,8 @@ import "../setup-home";
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { homedir } from "node:os";
import { resolve, join } from "node:path";
import { createHash } from "node:crypto";
import { JetBrainsCopilotAdapter } from "../../src/adapters/jetbrains-copilot/index.js";
import { hashProjectDirCanonical, resolveSessionDbPath } from "../../src/session/db.js";
describe("JetBrainsCopilotAdapter", () => {
let adapter: JetBrainsCopilotAdapter;
@@ -60,17 +60,18 @@ describe("JetBrainsCopilotAdapter", () => {
});
});
// ── getSessionDBPath ──────────────────────────────────
// ── per-project DB path (C2 narrowing) ────────────────
// BaseAdapter no longer exposes getSessionDBPath; callers go through
// resolveSessionDbPath + adapter.getSessionDir(). These pins assert that
// composition lands the .db inside JetBrains' sessionDir with the
// canonical project hash.
describe("getSessionDBPath", () => {
describe("per-project DB path via resolveSessionDbPath", () => {
it("produces correct hash-based path", () => {
const projectDir = "/home/user/my-project";
const dbPath = adapter.getSessionDBPath(projectDir);
const dbPath = resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() });
const expectedHash = createHash("sha256")
.update(projectDir)
.digest("hex")
.slice(0, 16);
const expectedHash = hashProjectDirCanonical(projectDir);
expect(dbPath).toContain(expectedHash);
expect(dbPath).toMatch(/\.db$/);
@@ -79,8 +80,8 @@ describe("JetBrainsCopilotAdapter", () => {
});
it("produces different paths for different project dirs", () => {
const path1 = adapter.getSessionDBPath("/project/a");
const path2 = adapter.getSessionDBPath("/project/b");
const path1 = resolveSessionDbPath({ projectDir: "/project/a", sessionsDir: adapter.getSessionDir() });
const path2 = resolveSessionDbPath({ projectDir: "/project/b", sessionsDir: adapter.getSessionDir() });
expect(path1).not.toBe(path2);
});
});
+13 -2
View File
@@ -4,6 +4,7 @@ import { homedir, tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { KiroAdapter } from "../../src/adapters/kiro/index.js";
import { hashProjectDirCanonical, resolveSessionDbPath } from "../../src/session/db.js";
import {
PRE_TOOL_USE_MATCHER_PATTERN,
PRE_TOOL_USE_MATCHERS,
@@ -253,14 +254,24 @@ describe("KiroAdapter", () => {
);
});
// C2 narrowing: per-project DB path is computed by callers via
// resolveSessionDbPath + adapter.getSessionDir(). Test pins that the
// composition lands the file inside Kiro's sessionDir (~/.kiro/...).
it("session DB path contains project hash", () => {
const dbPath = adapter.getSessionDBPath("/test/project");
const dbPath = resolveSessionDbPath({
projectDir: "/test/project",
sessionsDir: adapter.getSessionDir(),
});
expect(dbPath).toMatch(/[a-f0-9]{16}\.db$/);
expect(dbPath).toContain(".kiro");
});
it("session events path contains project hash with -events.md suffix", () => {
const eventsPath = adapter.getSessionEventsPath("/test/project");
// events.md sidecar shape mirrors server.ts/hooks: <sessionDir>/<hash>-events.md
const eventsPath = join(
adapter.getSessionDir(),
`${hashProjectDirCanonical("/test/project")}-events.md`,
);
expect(eventsPath).toMatch(/[a-f0-9]{16}-events\.md$/);
expect(eventsPath).toContain(".kiro");
});
+3 -2
View File
@@ -3,6 +3,7 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { homedir } from "node:os";
import { join, resolve } from "node:path";
import { OMPAdapter } from "../../src/adapters/omp/index.js";
import { hashProjectDirCanonical, resolveSessionDbPath } from "../../src/session/db.js";
describe("OMPAdapter", () => {
let adapter: OMPAdapter;
@@ -132,14 +133,14 @@ describe("OMPAdapter", () => {
});
it("session DB path contains project hash and lives under .omp", () => {
const dbPath = adapter.getSessionDBPath("/test/project");
const dbPath = resolveSessionDbPath({ projectDir: "/test/project", sessionsDir: adapter.getSessionDir() });
expect(dbPath).toMatch(/[a-f0-9]{16}\.db$/);
expect(dbPath).toContain(".omp");
expect(dbPath).not.toContain(".claude");
});
it("session events path contains project hash and lives under .omp", () => {
const eventsPath = adapter.getSessionEventsPath("/test/project");
const eventsPath = join(adapter.getSessionDir(), `${hashProjectDirCanonical("/test/project")}-events.md`);
expect(eventsPath).toMatch(/[a-f0-9]{16}-events\.md$/);
expect(eventsPath).toContain(".omp");
expect(eventsPath).not.toContain(".claude");
+3 -2
View File
@@ -3,6 +3,7 @@ import { describe, it, expect, beforeEach } from "vitest";
import { homedir } from "node:os";
import { join, resolve } from "node:path";
import { OpenClawAdapter } from "../../src/adapters/openclaw/index.js";
import { hashProjectDirCanonical, resolveSessionDbPath } from "../../src/session/db.js";
describe("OpenClawAdapter", () => {
let adapter: OpenClawAdapter;
@@ -276,7 +277,7 @@ describe("OpenClawAdapter", () => {
});
it("session DB path includes project hash", () => {
const dbPath = adapter.getSessionDBPath("/test/project");
const dbPath = resolveSessionDbPath({ projectDir: "/test/project", sessionsDir: adapter.getSessionDir() });
expect(dbPath).toContain(".openclaw");
expect(dbPath).toContain("context-mode");
expect(dbPath).toContain("sessions");
@@ -284,7 +285,7 @@ describe("OpenClawAdapter", () => {
});
it("session events path includes project hash", () => {
const eventsPath = adapter.getSessionEventsPath("/test/project");
const eventsPath = join(adapter.getSessionDir(), `${hashProjectDirCanonical("/test/project")}-events.md`);
expect(eventsPath).toContain(".openclaw");
expect(eventsPath).toMatch(/-events\.md$/);
});
+3 -2
View File
@@ -5,6 +5,7 @@ import { join, resolve } from "node:path";
import { PiAdapter } from "../../src/adapters/pi/index.js";
import { ClaudeCodeAdapter } from "../../src/adapters/claude-code/index.js";
import { getAdapter, getSessionDirSegments } from "../../src/adapters/detect.js";
import { hashProjectDirCanonical, resolveSessionDbPath } from "../../src/session/db.js";
describe("PiAdapter — Pi platform adapter", () => {
let adapter: PiAdapter;
@@ -73,14 +74,14 @@ describe("PiAdapter — Pi platform adapter", () => {
});
it("session DB path contains project hash and lives under .pi", () => {
const dbPath = adapter.getSessionDBPath("/test/project");
const dbPath = resolveSessionDbPath({ projectDir: "/test/project", sessionsDir: adapter.getSessionDir() });
expect(dbPath).toMatch(/[a-f0-9]{16}\.db$/);
expect(dbPath).toContain(".pi");
expect(dbPath).not.toContain(".claude");
});
it("session events path contains project hash and lives under .pi", () => {
const eventsPath = adapter.getSessionEventsPath("/test/project");
const eventsPath = join(adapter.getSessionDir(), `${hashProjectDirCanonical("/test/project")}-events.md`);
expect(eventsPath).toMatch(/[a-f0-9]{16}-events\.md$/);
expect(eventsPath).toContain(".pi");
expect(eventsPath).not.toContain(".claude");
+9 -7
View File
@@ -1,10 +1,10 @@
import "../setup-home";
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { createHash } from "node:crypto";
import { homedir, tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { QwenCodeAdapter } from "../../src/adapters/qwen-code/index.js";
import { hashProjectDirCanonical, resolveSessionDbPath } from "../../src/session/db.js";
import { fakeHome, realHome } from "../setup-home";
describe("QwenCodeAdapter", () => {
@@ -156,13 +156,15 @@ describe("QwenCodeAdapter", () => {
expect(sessionDir.startsWith(join(realHome, ".qwen", "context-mode"))).toBe(false);
});
it("DB path uses sha256 hash of projectDir", () => {
// C2 narrowing: per-project DB path is composed by callers via
// resolveSessionDbPath + adapter.getSessionDir().
it("DB path uses canonical hash of projectDir", () => {
const projectDir = "/my/project";
const hash = createHash("sha256")
.update(projectDir)
.digest("hex")
.slice(0, 16);
const dbPath = adapter.getSessionDBPath(projectDir);
const hash = hashProjectDirCanonical(projectDir);
const dbPath = resolveSessionDbPath({
projectDir,
sessionsDir: adapter.getSessionDir(),
});
expect(dbPath).toBe(
join(homedir(), ".qwen", "context-mode", "sessions", `${hash}.db`),
);
+9 -4
View File
@@ -2292,10 +2292,15 @@ describe("Platform-aware session paths via adapter", () => {
expect(statsMatch![0]).not.toMatch(/["']\.claude["']/);
});
// ── Adapter methods used for session paths ──
test("session paths derived from adapter.getSessionDir or getSessionDBPath", () => {
// Either directly uses adapter methods or a helper that delegates to them
expect(serverSrc).toMatch(/getSessionDir\(\)|getSessionDBPath\(/);
// ── Adapter methods used for session paths (post-C2 narrowing) ──
test("session paths derived from adapter.getSessionDir + resolveSessionDbPath", () => {
// C2 narrowing (2026-05): adapter no longer exposes getSessionDBPath /
// getSessionEventsPath. server.ts must derive per-project DB paths via
// resolveSessionDbPath while reading the adapter ONLY for the platform
// sessionDir. Pin both calls so an accidental regression to a missing
// helper or a deleted adapter method is caught at the test boundary.
expect(serverSrc).toMatch(/getSessionDir\(/);
expect(serverSrc).toMatch(/resolveSessionDbPath\(/);
});
// ── Comprehensive projectDir detection ──
+12 -12
View File
@@ -708,10 +708,10 @@ describe("ContextModePlugin", () => {
);
// Verify SessionDB has the event
const { SessionDB } = await import("../src/session/db.js");
const { resolveSessionDbPath, SessionDB } = await import("../src/session/db.js");
const { OpenCodeAdapter } = await import("../src/adapters/opencode/index.js");
const adapter = new OpenCodeAdapter("opencode");
const db = new SessionDB({ dbPath: adapter.getSessionDBPath(projectDir) });
const db = new SessionDB({ dbPath: resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() }) });
const events = db.getEvents("oc2-sess") as any[];
db.close();
const userPromptEvent = events.find((e: any) => e.type === "user_prompt");
@@ -730,10 +730,10 @@ describe("ContextModePlugin", () => {
{ message: { role: "user" } as any, parts: [{ type: "text", text: synthetic }] } as any,
);
const { SessionDB } = await import("../src/session/db.js");
const { resolveSessionDbPath, SessionDB } = await import("../src/session/db.js");
const { OpenCodeAdapter } = await import("../src/adapters/opencode/index.js");
const adapter = new OpenCodeAdapter("opencode");
const db = new SessionDB({ dbPath: adapter.getSessionDBPath(projectDir) });
const db = new SessionDB({ dbPath: resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() }) });
const events = db.getEvents("oc2-skip") as any[];
db.close();
const userPromptEvent = events.find((e: any) => e.type === "user_prompt");
@@ -807,10 +807,10 @@ describe("ContextModePlugin", () => {
{ title: "Read", output: "x", metadata: {} },
);
const { SessionDB } = await import("../src/session/db.js");
const { resolveSessionDbPath, SessionDB } = await import("../src/session/db.js");
const { OpenCodeAdapter } = await import("../src/adapters/opencode/index.js");
const adapter = new OpenCodeAdapter("opencode");
const db = new SessionDB({ dbPath: adapter.getSessionDBPath(projectDir) });
const db = new SessionDB({ dbPath: resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() }) });
const events = db.getEvents("oc4-sess-1") as any[];
db.close();
@@ -839,10 +839,10 @@ describe("ContextModePlugin", () => {
{ title: "Read", output: "b", metadata: {} },
);
const { SessionDB } = await import("../src/session/db.js");
const { resolveSessionDbPath, SessionDB } = await import("../src/session/db.js");
const { OpenCodeAdapter } = await import("../src/adapters/opencode/index.js");
const adapter = new OpenCodeAdapter("opencode");
const db = new SessionDB({ dbPath: adapter.getSessionDBPath(projectDir) });
const db = new SessionDB({ dbPath: resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() }) });
const events = db.getEvents("oc4-idem") as any[];
db.close();
@@ -868,10 +868,10 @@ describe("ContextModePlugin", () => {
),
).resolves.toBeUndefined();
const { SessionDB } = await import("../src/session/db.js");
const { resolveSessionDbPath, SessionDB } = await import("../src/session/db.js");
const { OpenCodeAdapter } = await import("../src/adapters/opencode/index.js");
const adapter = new OpenCodeAdapter("opencode");
const db = new SessionDB({ dbPath: adapter.getSessionDBPath(projectDir) });
const db = new SessionDB({ dbPath: resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() }) });
const events = db.getEvents("oc4-missing") as any[];
db.close();
@@ -891,10 +891,10 @@ describe("ContextModePlugin", () => {
{ title: "Read", output: "c", metadata: {} },
);
const { SessionDB } = await import("../src/session/db.js");
const { resolveSessionDbPath, SessionDB } = await import("../src/session/db.js");
const { OpenCodeAdapter } = await import("../src/adapters/opencode/index.js");
const adapter = new OpenCodeAdapter("opencode");
const db = new SessionDB({ dbPath: adapter.getSessionDBPath(projectDir) });
const db = new SessionDB({ dbPath: resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() }) });
const events = db.getEvents("oc4-claude") as any[];
db.close();