@druumen/sessions-db 0.1.3 → 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 +115 -0
- package/cli/sessions-db-session-start-main.mjs +97 -25
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,121 @@ 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
|
+
|
|
80
|
+
## [0.1.4] — 2026-05-16
|
|
81
|
+
|
|
82
|
+
Hook gate relaxation so marketplace cockpit users on non-druumen
|
|
83
|
+
workspaces can actually record sessions.
|
|
84
|
+
|
|
85
|
+
### Changed (hook)
|
|
86
|
+
|
|
87
|
+
- `cli/sessions-db-session-start-main.mjs` — the cwd-gate accepts a
|
|
88
|
+
workspace as authorized when EITHER:
|
|
89
|
+
1. a `CLAUDE.md` containing the `Druumen Workspace` sentinel exists
|
|
90
|
+
at cwd or any ancestor (original 0.1.x behavior), OR
|
|
91
|
+
2. `.dru-code/sessions-db.json` or `tickets/_logs/sessions-db.json`
|
|
92
|
+
exists at cwd or any ancestor — i.e. the workspace was already
|
|
93
|
+
opted in via cockpit's Setup Wizard or a manual `initProjection`.
|
|
94
|
+
Either marker counts as explicit user consent for this workspace.
|
|
95
|
+
Workspaces with neither still bail silently — random scratch dirs
|
|
96
|
+
still don't get session events.
|
|
97
|
+
|
|
98
|
+
### Why
|
|
99
|
+
|
|
100
|
+
Cockpit-vscode 0.3.0+ ships the Setup Wizard which creates
|
|
101
|
+
`<workspace>/.dru-code/sessions-db.json` on Enable. Before this fix,
|
|
102
|
+
the hook then rejected every SessionStart in that workspace because no
|
|
103
|
+
CLAUDE.md sentinel was present — events.jsonl stayed empty and the
|
|
104
|
+
SESSIONS panel showed `0 active` forever. The `.dru-code/` file
|
|
105
|
+
already represents user consent; the gate now treats it as such.
|
|
106
|
+
|
|
107
|
+
### Test
|
|
108
|
+
|
|
109
|
+
- New `contract-1b` test in
|
|
110
|
+
`__tests__/hook/sessions-db-session-start.test.mjs` plants a
|
|
111
|
+
`.dru-code/sessions-db.json` in a workspace with NO CLAUDE.md
|
|
112
|
+
sentinel and verifies the hook records a session_seen event.
|
|
113
|
+
Existing `contract-1` still passes (workspace with neither marker
|
|
114
|
+
still rejects).
|
|
115
|
+
- Full suite: 446 tests, 0 fail.
|
|
116
|
+
|
|
117
|
+
### No public API change
|
|
118
|
+
|
|
119
|
+
Same exported surface as 0.1.3. The gate widening is additive;
|
|
120
|
+
consumers that previously passed still pass. Cockpit pin
|
|
121
|
+
`>=0.1.0 <0.2.0` picks up 0.1.4 automatically on `npm install`.
|
|
122
|
+
|
|
8
123
|
## [0.1.3] — 2026-05-15
|
|
9
124
|
|
|
10
125
|
CI metadata patch. **Same source code as 0.1.1 / 0.1.2** — both prior
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
* from a hook that is purely observational.
|
|
10
10
|
*
|
|
11
11
|
* Six-item safety contract (every test below cross-references one item):
|
|
12
|
-
* 1. cwd-gate: bail on any cwd
|
|
13
|
-
*
|
|
12
|
+
* 1. cwd-gate: bail on any cwd that is neither a Druumen Workspace
|
|
13
|
+
* (CLAUDE.md sentinel) nor an opted-in workspace (existing
|
|
14
|
+
* `.dru-code/sessions-db.json` or `tickets/_logs/sessions-db.json`
|
|
15
|
+
* under cwd or any ancestor). No event written when both rejected.
|
|
14
16
|
* 2. < 2 second budget: bootstrap's setTimeout(2000ms).unref() always wins.
|
|
15
17
|
* Each sub-probe respects a single global deadline derived from
|
|
16
18
|
* `gitContext({ totalBudgetMs })` — six probes can never sum past the
|
|
@@ -32,10 +34,11 @@
|
|
|
32
34
|
* concurrent hooks for the same `claude_session_id` will serialize on the
|
|
33
35
|
* lock and observe each other's mint, so identity does not split.
|
|
34
36
|
*
|
|
35
|
-
* cwd discipline: every storage call passes `{
|
|
36
|
-
*
|
|
37
|
-
* cwd
|
|
38
|
-
* `
|
|
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.
|
|
39
42
|
*/
|
|
40
43
|
|
|
41
44
|
import { createHash } from 'node:crypto';
|
|
@@ -76,9 +79,13 @@ async function main() {
|
|
|
76
79
|
process.env.CLAUDE_PROJECT_DIR ||
|
|
77
80
|
process.cwd();
|
|
78
81
|
|
|
79
|
-
// (3) cwd-gate.
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
+
// (3) cwd-gate. Accept the cwd when EITHER of:
|
|
83
|
+
// - a CLAUDE.md "Druumen Workspace" sentinel exists at cwd or ancestor
|
|
84
|
+
// (druumen-monorepo opt-in), OR
|
|
85
|
+
// - a `.dru-code/sessions-db.json` or `tickets/_logs/sessions-db.json`
|
|
86
|
+
// already exists under cwd or ancestor (cockpit Setup Wizard or
|
|
87
|
+
// prior manual init already opted this workspace in).
|
|
88
|
+
// Any other repo bails silently.
|
|
82
89
|
if (!isDruumenWorkspace(cwd)) {
|
|
83
90
|
process.exit(0);
|
|
84
91
|
}
|
|
@@ -97,10 +104,44 @@ async function main() {
|
|
|
97
104
|
process.exit(0);
|
|
98
105
|
}
|
|
99
106
|
|
|
100
|
-
// (5) Resolve the storage root.
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
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
|
+
}
|
|
104
145
|
|
|
105
146
|
// (6) claude_session_id — required input. Without it we cannot reconcile
|
|
106
147
|
// identity at all, so we bail rather than minting a stable_id we can never
|
|
@@ -167,14 +208,13 @@ async function main() {
|
|
|
167
208
|
// ALL the signals we have so the resolver can walk the chain and surface
|
|
168
209
|
// both the matched stable_id and any parent candidates (hub-spoke hints).
|
|
169
210
|
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
// 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).
|
|
174
214
|
try {
|
|
175
215
|
await recordSessionSeen({
|
|
176
216
|
claudeSessionId,
|
|
177
|
-
|
|
217
|
+
...recordTargetOpts,
|
|
178
218
|
lockTimeoutMs: 1500,
|
|
179
219
|
transcriptMeta,
|
|
180
220
|
gitContext: gitCtx,
|
|
@@ -262,27 +302,59 @@ function readStdinJson({ timeoutMs }) {
|
|
|
262
302
|
}
|
|
263
303
|
|
|
264
304
|
/**
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
305
|
+
* Decide whether the hook is allowed to record events for `cwd`.
|
|
306
|
+
*
|
|
307
|
+
* Two acceptance fast-paths, either of which is sufficient:
|
|
268
308
|
*
|
|
269
|
-
*
|
|
309
|
+
* 1. **Druumen Workspace sentinel** — a `CLAUDE.md` at `cwd` or any
|
|
310
|
+
* ancestor whose body contains the literal string "Druumen Workspace".
|
|
311
|
+
* Original 0.1.x gate; how the Druumen monorepo opts in.
|
|
312
|
+
*
|
|
313
|
+
* 2. **Pre-initialized sessions-db storage** — `.dru-code/sessions-db.json`
|
|
314
|
+
* or `tickets/_logs/sessions-db.json` already exists at `cwd` or any
|
|
315
|
+
* ancestor. The cockpit Setup Wizard creates this file when the user
|
|
316
|
+
* explicitly enables sessions tracking for a workspace; an external
|
|
317
|
+
* project that has never opted in will not have either marker.
|
|
318
|
+
*
|
|
319
|
+
* Either marker is treated as user consent for this workspace. The walk
|
|
320
|
+
* is bounded to 12 ancestors so a runaway loop (e.g. weird FS mount)
|
|
321
|
+
* cannot stall us; the loop terminates early as soon as ANY marker is
|
|
322
|
+
* found at the current level.
|
|
323
|
+
*
|
|
324
|
+
* The function name is kept (`isDruumenWorkspace`) for git history clarity
|
|
325
|
+
* even though the semantic has broadened to "authorized workspace". Both
|
|
326
|
+
* acceptance criteria are checked at each ancestor before walking up
|
|
327
|
+
* (cheap stat-only probes for the storage paths).
|
|
328
|
+
*
|
|
329
|
+
* Returns true on the first hit, false after 12 ancestors / filesystem
|
|
330
|
+
* root / read errors with no marker found.
|
|
270
331
|
*/
|
|
271
332
|
function isDruumenWorkspace(cwd) {
|
|
272
333
|
if (typeof cwd !== 'string' || cwd.length === 0) return false;
|
|
273
334
|
let dir = cwd;
|
|
274
335
|
for (let i = 0; i < 12; i++) {
|
|
275
|
-
|
|
276
|
-
|
|
336
|
+
// Fast-path 1: CLAUDE.md sentinel
|
|
337
|
+
const claudeMd = join(dir, 'CLAUDE.md');
|
|
338
|
+
if (existsSync(claudeMd)) {
|
|
277
339
|
try {
|
|
278
340
|
// We only need the first ~8KB to find the sentinel; CLAUDE.md is
|
|
279
341
|
// typically short, so reading the whole file is fine.
|
|
280
|
-
const body = readFileSync(
|
|
342
|
+
const body = readFileSync(claudeMd, 'utf8');
|
|
281
343
|
if (body.includes('Druumen Workspace')) return true;
|
|
282
344
|
} catch {
|
|
283
345
|
// unreadable — keep walking up just in case there's a higher one.
|
|
284
346
|
}
|
|
285
347
|
}
|
|
348
|
+
// Fast-path 2: pre-initialized sessions-db storage. Stat-only — we
|
|
349
|
+
// don't read these files here, just check existence. Either convention
|
|
350
|
+
// (cockpit-marketplace `.dru-code/` or druumen-monorepo `tickets/_logs/`)
|
|
351
|
+
// counts as opt-in.
|
|
352
|
+
if (
|
|
353
|
+
existsSync(join(dir, '.dru-code', 'sessions-db.json')) ||
|
|
354
|
+
existsSync(join(dir, 'tickets', '_logs', 'sessions-db.json'))
|
|
355
|
+
) {
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
286
358
|
const parent = dirname(dir);
|
|
287
359
|
if (parent === dir) return false;
|
|
288
360
|
dir = parent;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@druumen/sessions-db",
|
|
3
|
-
"version": "0.1.
|
|
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",
|