@druumen/sessions-db 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,78 @@ All notable changes to `@druumen/sessions-db` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.5] — 2026-05-16
9
+
10
+ Hook now writes to the same storage location its reader is watching,
11
+ instead of always carving a fresh `tickets/_logs/` subdirectory into
12
+ whichever workspace it happens to fire in.
13
+
14
+ ### Changed (hook)
15
+
16
+ - `cli/sessions-db-session-start-main.mjs` — storage location strategy
17
+ is now three-tier (in order):
18
+
19
+ 1. **`DRUUMEN_SESSIONS_DB_ROOT` env var** — explicit override forwarded
20
+ as `{ rootPath }`. Treated as the bare storage directory (no
21
+ `tickets/_logs/` prefix added). Cockpit's Setup Wizard plumbs the
22
+ workspace's chosen storage path through this env so a single source
23
+ of truth controls both reader and writer location.
24
+
25
+ 2. **Auto-detect `<workspaceRoot>/.dru-code/sessions-db.json`** — when
26
+ this file exists (i.e. cockpit Setup Wizard or a prior init created
27
+ it), the hook writes alongside it via `{ rootPath: <ws>/.dru-code }`.
28
+ Marketplace cockpit users no longer end up with a phantom
29
+ `tickets/_logs/` subdirectory in their non-druumen project.
30
+
31
+ 3. **Legacy `{ root: workspaceRoot }`** — falls back to the
32
+ pre-existing tickets/_logs/ layout anchored on the workspace root.
33
+ Druumen monorepo (which has a `tickets/_logs/` already) keeps
34
+ accumulating in the same place.
35
+
36
+ ### Why
37
+
38
+ Before this fix, cockpit-marketplace users who clicked Setup Wizard's
39
+ Enable button got `<ws>/.dru-code/sessions-db.json` created by the
40
+ extension, but the SessionStart hook (running in a separate process)
41
+ ignored that location and wrote every event to a fresh
42
+ `<ws>/tickets/_logs/` subtree. Two consequences:
43
+
44
+ - **Visible split-brain**: cockpit's path-discovery priority chain
45
+ picks `tickets/_logs/` once it exists (priority 3 > priority 4),
46
+ but until the user did a window reload the orchestrator kept
47
+ watching the wizard's empty `.dru-code/` stub. SESSIONS panel
48
+ appeared frozen at `0 active` even though events were being written.
49
+
50
+ - **Workspace pollution**: an academic thesis or any non-druumen
51
+ project ended up with a `tickets/_logs/` subdirectory it had no
52
+ reason to own. With 0.1.5 the hook honors whichever convention was
53
+ set up — cockpit-marketplace users stay clean inside `.dru-code/`.
54
+
55
+ ### Test
56
+
57
+ - New `contract-1c` covers the env-override path: with
58
+ `DRUUMEN_SESSIONS_DB_ROOT` set to a tmp dir outside the workspace,
59
+ the hook writes there and creates no `tickets/_logs/` in the
60
+ workspace.
61
+ - Existing `contract-1b` updated: when `.dru-code/sessions-db.json`
62
+ pre-exists, the hook now writes to `.dru-code/` (NOT
63
+ `tickets/_logs/` which the old version did).
64
+ - `happy path` and druumen-monorepo tests unchanged — workspaces with
65
+ no `.dru-code/` marker still get the legacy `tickets/_logs/` layout.
66
+ - Full suite: 447 tests, 0 fail.
67
+
68
+ ### Backward compatibility
69
+
70
+ - Druumen monorepo: zero change (no `.dru-code/` exists at any
71
+ ancestor → step (3) legacy path).
72
+ - Cockpit-marketplace 0.3.0–0.3.2 users with `.dru-code/`: hook now
73
+ writes to `.dru-code/`. Their old data (if any) in
74
+ `<ws>/tickets/_logs/` is NOT migrated automatically; it stays as
75
+ historical record. Future events go to `.dru-code/`. After upgrading
76
+ cockpit to 0.3.3, the new wizard will additionally write a
77
+ `DRUUMEN_SESSIONS_DB_ROOT` env prefix into the hook command so
78
+ future enables explicitly pin the location.
79
+
8
80
  ## [0.1.4] — 2026-05-16
9
81
 
10
82
  Hook gate relaxation so marketplace cockpit users on non-druumen
@@ -34,10 +34,11 @@
34
34
  * concurrent hooks for the same `claude_session_id` will serialize on the
35
35
  * lock and observe each other's mint, so identity does not split.
36
36
  *
37
- * cwd discipline: every storage call passes `{ root: storageRoot }` so the
38
- * events.jsonl + projection cache + lock file all anchor on the project
39
- * cwd (resolved via CLAUDE.md walk + git common-dir), NOT on the random
40
- * `process.cwd()` Claude Code happened to spawn the hook from.
37
+ * cwd discipline: every storage call passes one of `{ rootPath }` or
38
+ * `{ root }` derived from the gated cwd / git common-dir, NOT from the
39
+ * random `process.cwd()` Claude Code happened to spawn the hook from.
40
+ * `DRUUMEN_SESSIONS_DB_ROOT` env > auto-detected `.dru-code/` > legacy
41
+ * `tickets/_logs/` anchored on workspace root.
41
42
  */
42
43
 
43
44
  import { createHash } from 'node:crypto';
@@ -103,10 +104,44 @@ async function main() {
103
104
  process.exit(0);
104
105
  }
105
106
 
106
- // (5) Resolve the storage root. Prefer the worktree root (so different
107
- // worktrees of the same repo each accumulate their own events.jsonl) and
108
- // fall back to the gated cwd. NEVER fall back to process.cwd() — see (2).
109
- const storageRoot = gitCtx.worktreePath || cwd;
107
+ // (5) Resolve the storage root. Three-tier strategy so cockpit-marketplace
108
+ // users (who have a `.dru-code/` storage dir) and druumen-monorepo users
109
+ // (who have a `tickets/_logs/` storage dir) BOTH have their hooks write
110
+ // into the exact location their reader is watching:
111
+ //
112
+ // (5a) `DRUUMEN_SESSIONS_DB_ROOT` env var overrides everything. Cockpit
113
+ // Setup Wizard writes this into the hook command line so the hook
114
+ // knows the precise storage dir (typically `<ws>/.dru-code`) the
115
+ // user opted into via Enable. Forwarded as `{ rootPath }` —
116
+ // recordSessionSeen treats it as the bare storage dir (no
117
+ // `tickets/_logs/` prefix added).
118
+ //
119
+ // (5b) Auto-detect `<workspaceRoot>/.dru-code/sessions-db.json` when no
120
+ // env override is set. If the user (or wizard) opted in via the
121
+ // new-convention layout we honor it without polluting their repo
122
+ // with a `tickets/_logs/` subdir.
123
+ //
124
+ // (5c) Fall back to the historic `{ root: workspaceRoot }` form which
125
+ // writes under `<workspaceRoot>/tickets/_logs/`. Druumen monorepo
126
+ // relies on this; existing `tickets/_logs/sessions-db.json` data
127
+ // keeps accumulating in the same place.
128
+ //
129
+ // NEVER fall back to process.cwd() — see (2).
130
+ const workspaceRoot = gitCtx.worktreePath || cwd;
131
+ const envRoot = process.env.DRUUMEN_SESSIONS_DB_ROOT;
132
+
133
+ let recordTargetOpts;
134
+ if (typeof envRoot === 'string' && envRoot.length > 0) {
135
+ // (5a) env override — new-convention rootPath shape.
136
+ recordTargetOpts = { rootPath: envRoot };
137
+ } else if (existsSync(join(workspaceRoot, '.dru-code', 'sessions-db.json'))) {
138
+ // (5b) auto-detect .dru-code/ — new-convention rootPath shape pointing
139
+ // at the storage subdir, not the workspace root.
140
+ recordTargetOpts = { rootPath: join(workspaceRoot, '.dru-code') };
141
+ } else {
142
+ // (5c) legacy fallback — tickets/_logs/ anchored at workspace root.
143
+ recordTargetOpts = { root: workspaceRoot };
144
+ }
110
145
 
111
146
  // (6) claude_session_id — required input. Without it we cannot reconcile
112
147
  // identity at all, so we bail rather than minting a stable_id we can never
@@ -173,14 +208,13 @@ async function main() {
173
208
  // ALL the signals we have so the resolver can walk the chain and surface
174
209
  // both the matched stable_id and any parent candidates (hub-spoke hints).
175
210
  //
176
- // Every storage path is anchored on `storageRoot` — events.jsonl,
177
- // projection cache, and lock file all land in <storageRoot>/tickets/_logs/.
178
- // This is the cwd-plumb-through fix: process.cwd() is NEVER read by
179
- // storage when called this way.
211
+ // Storage location: `recordTargetOpts` carries either `{ rootPath }` (env
212
+ // override or auto-detected `.dru-code/`) or `{ root }` (legacy
213
+ // tickets/_logs/ anchored on workspace root) see step (5).
180
214
  try {
181
215
  await recordSessionSeen({
182
216
  claudeSessionId,
183
- root: storageRoot,
217
+ ...recordTargetOpts,
184
218
  lockTimeoutMs: 1500,
185
219
  transcriptMeta,
186
220
  gitContext: gitCtx,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@druumen/sessions-db",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Cross-session traceability for Claude Code — events.jsonl SSoT + JSON projection cache + 3-priority identity reconciliation",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",