@druumen/sessions-db 0.1.0 → 0.1.4

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,254 @@ 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.4] — 2026-05-16
9
+
10
+ Hook gate relaxation so marketplace cockpit users on non-druumen
11
+ workspaces can actually record sessions.
12
+
13
+ ### Changed (hook)
14
+
15
+ - `cli/sessions-db-session-start-main.mjs` — the cwd-gate accepts a
16
+ workspace as authorized when EITHER:
17
+ 1. a `CLAUDE.md` containing the `Druumen Workspace` sentinel exists
18
+ at cwd or any ancestor (original 0.1.x behavior), OR
19
+ 2. `.dru-code/sessions-db.json` or `tickets/_logs/sessions-db.json`
20
+ exists at cwd or any ancestor — i.e. the workspace was already
21
+ opted in via cockpit's Setup Wizard or a manual `initProjection`.
22
+ Either marker counts as explicit user consent for this workspace.
23
+ Workspaces with neither still bail silently — random scratch dirs
24
+ still don't get session events.
25
+
26
+ ### Why
27
+
28
+ Cockpit-vscode 0.3.0+ ships the Setup Wizard which creates
29
+ `<workspace>/.dru-code/sessions-db.json` on Enable. Before this fix,
30
+ the hook then rejected every SessionStart in that workspace because no
31
+ CLAUDE.md sentinel was present — events.jsonl stayed empty and the
32
+ SESSIONS panel showed `0 active` forever. The `.dru-code/` file
33
+ already represents user consent; the gate now treats it as such.
34
+
35
+ ### Test
36
+
37
+ - New `contract-1b` test in
38
+ `__tests__/hook/sessions-db-session-start.test.mjs` plants a
39
+ `.dru-code/sessions-db.json` in a workspace with NO CLAUDE.md
40
+ sentinel and verifies the hook records a session_seen event.
41
+ Existing `contract-1` still passes (workspace with neither marker
42
+ still rejects).
43
+ - Full suite: 446 tests, 0 fail.
44
+
45
+ ### No public API change
46
+
47
+ Same exported surface as 0.1.3. The gate widening is additive;
48
+ consumers that previously passed still pass. Cockpit pin
49
+ `>=0.1.0 <0.2.0` picks up 0.1.4 automatically on `npm install`.
50
+
51
+ ## [0.1.3] — 2026-05-15
52
+
53
+ CI metadata patch. **Same source code as 0.1.1 / 0.1.2** — both prior
54
+ versions stayed tombstone tags because separate npm-side gates
55
+ rejected the publish. This release fixes the second one. Cockpit pin
56
+ `>=0.1.0 <0.2.0` will pick up 0.1.3.
57
+
58
+ ### Fixed (CI / supply chain)
59
+
60
+ - `package.json` `repository.url` switched from
61
+ `git+ssh://git@gitlab.tinfant.org:8922/druumen/sessions-db.git` to
62
+ `git+https://github.com/druumen/sessions-db.git`. Required by npm
63
+ registry's provenance verification: when a package is published with
64
+ `--provenance` from GitHub Actions, npm rejects (HTTP 422) if the
65
+ signed provenance source URL doesn't match `repository.url` in the
66
+ shipped tarball's package.json. Defense against supply-chain attacks
67
+ where attestation comes from a different repo than the metadata
68
+ claims.
69
+
70
+ ### Repo SSoT vs publish-canonical mirror
71
+
72
+ - **Source-of-truth (development)**: `gitlab.tinfant.org/druumen/sessions-db`
73
+ (private to tinfant org, where MRs land + CI runs first).
74
+ - **Publish-canonical (npm-visible)**: `github.com/druumen/sessions-db`
75
+ (public mirror, what `npm publish --provenance` attests to + what
76
+ `npm view @druumen/sessions-db | grep repository` shows consumers).
77
+
78
+ This split is now documented in `README.md` and `RELEASING.md`. Issues
79
+ + MRs continue to live on GitLab; the GitHub mirror is for `npm
80
+ install` consumers' "view source" link + provenance attestation.
81
+
82
+ ### Tombstone tag note (0.1.2)
83
+
84
+ - `v0.1.2` exists on GitLab + GitHub mirror as a permanent tag against
85
+ commit `d908df77` but is **NOT published to npm**. The OIDC publish
86
+ attempt got past the auth fix from 0.1.2's CI change (npm 11.5.x),
87
+ signed provenance to Sigstore log `1549510274` (immutable), but
88
+ failed at the registry PUT with `422 Unprocessable Entity` because
89
+ of the repo URL mismatch fixed in 0.1.3.
90
+ - Together with `v0.1.1` (logIndex 1547090299 / 1549427812), there are
91
+ now **3 sigstore provenance records** on the public transparency log
92
+ for failed-publish attempts of this package's 0.1.x series. They
93
+ prove the GitHub Actions runner attempted publishes and serve as
94
+ audit trail.
95
+
96
+ ### `[ASSUMPTION]` lessons recorded (per `feedback_tag_vendor_assumptions_in_plans`)
97
+
98
+ Day-of-OIDC-bringup assumption miss #2 (after the npm-version one):
99
+ - "Provenance verification accepts any repository.url in package.json"
100
+ → wrong. npm rejects 422 if signed provenance source doesn't match
101
+ metadata. This is npm's documented behavior but wasn't surfaced in
102
+ the original D15 design or RELEASING.md.
103
+
104
+ Will save a second feedback memory specifically for "OIDC publish
105
+ requires repo-URL alignment with provenance signer" so future plans
106
+ flag it.
107
+
108
+ ## [0.1.2] — 2026-05-15
109
+
110
+ CI-only patch. **Same source code as 0.1.1** — but 0.1.1 never actually
111
+ landed on the npm registry; it stayed a tombstone tag because the
112
+ GitHub Actions OIDC publish workflow used npm 10.8.2 (default for
113
+ Node 20), which signs sigstore provenance fine but lacks the OIDC
114
+ trusted-publisher token-exchange flow that npm registry requires
115
+ (added in npm **11.5.1**). The publish PUT got 404 (npm-style auth
116
+ masking) twice in a row.
117
+
118
+ This release fixes the workflow + ships the same library code under
119
+ 0.1.2. Cockpit pinning `>=0.1.0 <0.2.0` will pick this up
120
+ automatically; no manual install change needed.
121
+
122
+ ### Fixed (CI / supply chain)
123
+
124
+ - `.github/workflows/publish.yml` now runs `npm install -g npm@latest`
125
+ before the publish step. Picks up OIDC trusted publisher support
126
+ (≥11.5.1) without changing the runner's Node version (constrained
127
+ by `engines: ">=18.0.0"` in package.json).
128
+
129
+ ### Tombstone tag note (0.1.1)
130
+
131
+ - `v0.1.1` exists on GitLab + GitHub mirror as a permanent tag against
132
+ commit `4350814e` but is **NOT published to npm**. Two failed OIDC
133
+ publish attempts left two sigstore provenance records on the public
134
+ transparency log (`logIndex 1547090299` and `logIndex 1549427812`),
135
+ immutable forever — they prove the GitHub Actions runner attempted
136
+ to publish that tag. Consumers should ignore `v0.1.1` entirely.
137
+ - The 0.1.1 CHANGELOG entry is preserved below as the full record of
138
+ the packaging fixes that **shipped under 0.1.2**.
139
+
140
+ ### `[ASSUMPTION]` lesson recorded
141
+
142
+ - Per memory `feedback_tag_vendor_assumptions_in_plans` (saved
143
+ 2026-05-15): the assumption "npm CLI shipped with Node 20 supports
144
+ OIDC trusted publisher" was written as fact in the original D15
145
+ publish.yml. Both Codex round-2 review and the cockpit owner's
146
+ bootstrap step 7 missed it because both audited "trusted publisher
147
+ is configured" without checking "is the runner's npm version
148
+ capable of using it." Real-world v0.1.1 publish surfaced the gap.
149
+
150
+ ## [0.1.1] — 2026-05-15
151
+
152
+ Packaging-only patch release. Fixes 3 independent bugs surfaced by the
153
+ first real consumer (Druumen Cockpit Phase 3 B1 integration) within
154
+ hours of 0.1.0 publish. **Zero runtime/library code changes** — same
155
+ public API surface, same test coverage. The fix is in how the package
156
+ is shipped, not what it does.
157
+
158
+ This is also the **first OIDC publish path test** — released via
159
+ GitHub Actions trusted publisher (no NPM_TOKEN_BOOTSTRAP), with
160
+ `--provenance` attestations. Consumers can now verify provenance via
161
+ `npm view @druumen/sessions-db@0.1.1 --json | jq .dist.attestations`.
162
+
163
+ ### Fixed
164
+
165
+ - **Bug A — Node16 module resolution ignores top-level `types`** when
166
+ `exports` map is present. 0.1.0 had bare-string-form
167
+ `"exports": { ".": "./lib/index.mjs" }` plus a top-level
168
+ `"types": "./types/index.d.ts"` — the top-level was silently
169
+ dropped under cockpit's `moduleResolution: "Node16"`. Symptom:
170
+ `TS7016: Could not find a declaration file for module '@druumen/sessions-db'`.
171
+ Fix: conditional exports map with explicit `types` + `import` +
172
+ `require` + `default` per entry. The top-level `types` is kept as
173
+ legacy fallback for `moduleResolution: "node"` (older TypeScript).
174
+
175
+ - **Bug B — `types/index.d.ts` re-exported type aliases not values**.
176
+ 0.1.0 had a hand-crafted `types/index.d.ts` with patterns like
177
+ `export type LoadProjection = typeof import('./storage.d.mts').loadProjection`
178
+ — these are TYPE ALIASES, not VALUE re-exports. Consumer could write
179
+ `import type { LoadProjection }` but not `import { loadProjection }`.
180
+ Symptom: `TS2305: Module '@druumen/sessions-db' has no exported
181
+ member 'loadProjection'`. Root cause: stale Day-2 artifact when
182
+ `lib/index.mjs` was a stub; never updated when Day 3 added real
183
+ value re-exports to `lib/index.mjs`. Fix: replace with a barrel
184
+ pattern that stitches `./index.d.mts` (auto-emitted value re-exports
185
+ mirroring lib/index.mjs) + `./types.d.mts` (auto-emitted type
186
+ declarations from lib/types.mjs `@typedef` block).
187
+
188
+ - **Bug C — pure ESM rejected by Node16 CJS context**. 0.1.0 was pure
189
+ ESM (`"type": "module"` + only `.mjs` source). Cockpit (Node16 +
190
+ no `"type":"module"` → CJS context) hit `TS1479: ECMAScript module
191
+ cannot be imported with require`. Fix: dual CJS+ESM build via
192
+ esbuild — `lib/index.cjs` (62 KB bundle) is generated alongside
193
+ `lib/index.mjs` by `npm run build:cjs`. Exports map's `require`
194
+ condition routes CJS consumers to the bundle, `import` condition
195
+ keeps ESM consumers on the per-file structure. The bundle is
196
+ regenerated at `prepublishOnly` time so it always matches the
197
+ current `lib/index.mjs` exports.
198
+
199
+ ### Added
200
+
201
+ - **Regression guards** so Bug A / B / C class issues surface at
202
+ publish time, not consumer integration time:
203
+ - `__tests__/pack-install-smoke/pack-install-smoke.test.mjs` —
204
+ end-to-end packaged-consumer smoke. `npm pack`s the source,
205
+ installs the tarball into a temp consumer dir, then exercises
206
+ the actual `package.json` exports map via 3 consumer styles:
207
+ (a) CJS `require('@druumen/sessions-db')`, (b) ESM
208
+ `import('@druumen/sessions-db')`, (c) TypeScript
209
+ `moduleResolution: "Node16"` with both type and value imports.
210
+ This is the canonical "consumer's POV" test — it would have
211
+ caught all 3 of 0.1.0's Bug A / B / C at publish time. The other
212
+ smokes complement it but bypass the exports map.
213
+ - `__tests__/cjs-smoke/cjs-smoke.test.mjs` — runtime CJS smoke
214
+ against `lib/index.cjs` directly. Asserts 35+ functions + 7+
215
+ constants are callable.
216
+ - `__tests__/types-smoke/cockpit-import.ts` — added VALUE imports
217
+ block (was type-imports-only).
218
+ - `__tests__/types-smoke/tsconfig.json` switched from
219
+ `moduleResolution: "Bundler"` to `"Node16"`.
220
+ - **CI build-freshness gate** — both GitLab `test-linux` and GitHub
221
+ Actions `Windows CI` now run `npm run build` followed by
222
+ `git diff --exit-code lib/index.cjs types/`. If a contributor
223
+ edits `lib/*.mjs` (changing exported signatures) but forgets to
224
+ rerun the build before commit, CI fails fast at PR time instead
225
+ of shipping a stale bundle to npm.
226
+
227
+ ### Build
228
+
229
+ - **`esbuild` ^0.25.x** added as a devDependency (single dep, no
230
+ runtime cost — bundle output has zero deps). `npm run build:cjs`
231
+ produces `lib/index.cjs` from `lib/index.mjs`. `npm run build`
232
+ runs both `build:types` (tsc) and `build:cjs` (esbuild).
233
+ `prepublishOnly` runs `npm run build` so the tarball always
234
+ contains the freshly-bundled CJS + freshly-emitted .d.mts.
235
+
236
+ - `package.json` `"main"` switched to `./lib/index.cjs` (CJS entry
237
+ for legacy tooling). `"module"` field added pointing to
238
+ `./lib/index.mjs` (legacy bundler hint, e.g. webpack 4).
239
+
240
+ ### Tarball delta vs 0.1.0
241
+
242
+ - 50 → 51 files (+1: `lib/index.cjs`)
243
+ - 108.6 KB → 123.6 KB (+15 KB, all CJS bundle)
244
+ - 371.2 KB → 432.8 KB unpacked (still well under target)
245
+
246
+ ### Codex round + lessons
247
+
248
+ - Codex adversarial review applied (agentId: see commit message of
249
+ the round-2 fix commit).
250
+ - Per `feedback_tag_vendor_assumptions_in_plans` saved 2026-05-15:
251
+ the assumption "ESM-only is fine for npm publishing" should have
252
+ been tagged `[ASSUMPTION]` in the original D-path plan, not
253
+ written as fact. Real-world consumer (cockpit Node16 CJS) surfaced
254
+ the gap. Documented for future plan-drafting discipline.
255
+
8
256
  ## [0.1.0] — 2026-05-15
9
257
 
10
258
  First public release. Extracted from the Druumen monorepo as a
@@ -41,7 +289,7 @@ dependencies.
41
289
  never walks to `/` on a slow networked mount.
42
290
  - **TypeScript types**: hand-curated `types/index.d.ts` re-export hub
43
291
  plus auto-emitted `.d.mts` siblings via `tsc --emitDeclarationOnly`
44
- + JSDoc on the source `.mjs` files. Cockpit and other TS consumers
292
+ driven by JSDoc on the source `.mjs` files. Cockpit and other TS consumers
45
293
  can `import type { KnownSession, Projection } from '@druumen/sessions-db'`.
46
294
  - **Cross-platform**: macOS / Linux / Windows all supported and
47
295
  CI-gated. Linux runs on GitLab `test-linux` (Node 20). Windows runs
@@ -88,6 +336,13 @@ dependencies.
88
336
  using a one-time `NPM_TOKEN_BOOTSTRAP` Granular Access Token (48h
89
337
  expiry, `@druumen` scope, masked + protected + environment-scoped
90
338
  variable, revoked immediately after publish).
339
+ - **v0.1.0 published WITHOUT provenance attestations** — intentional.
340
+ The bootstrap path runs from GitLab CI which has no GitHub-Actions-
341
+ style OIDC token issuer for npm; the npm registry only accepts
342
+ provenance from a recognized OIDC publisher (currently GitHub Actions
343
+ and GitLab.com SaaS). `npm view @druumen/sessions-db@0.1.0 --json | jq
344
+ .dist.attestations` returns `{}`. This is a one-time gap covering
345
+ only the bootstrap release; v0.1.1 onwards have full provenance.
91
346
  - **v0.1.1 onwards** publish from GitHub Actions
92
347
  (`.github/workflows/publish.yml`) via npm **OIDC trusted publishing**
93
348
  — no long-lived secrets, short-lived OIDC tokens validated by npm
@@ -247,3 +502,44 @@ contract.
247
502
  releases, uses `id-token: write` + `--provenance` flag for npm
248
503
  attestations. Inactive until trusted publisher configured on npm
249
504
  web (Bootstrap step 7).
505
+
506
+ ### Day 8 — 2026-05-15 (v0.1.0 published — 3 lessons learned)
507
+
508
+ - **Published**: `@druumen/sessions-db@0.1.0` live on npm registry at
509
+ 2026-05-15T08:22:56Z, Apache-2.0, `dist.shasum a70980a7…`. Pipeline
510
+ #429 (post-release-prep merge `645a8a4e`): `test-linux` 9.4s +
511
+ `mirror-to-github` 5.2s + `publish-npm` 12.6s. Trusted publisher
512
+ configured on npm web (org=druumen, repo=sessions-db, workflow=
513
+ publish.yml, env=npm-publish) immediately after bootstrap revoke,
514
+ arming OIDC path for 0.1.1+.
515
+
516
+ - **Lesson 1 (Δ35 — protected `v*` tag gap)**: First v0.1.0 tag
517
+ pipeline `mirror-to-github` failed because `GITHUB_MIRROR_TOKEN`
518
+ (protected variable) wasn't accessible from `v*` tag pipelines —
519
+ only `master` + `fix/*` were in the protected refs list. Fixed by
520
+ adding `v*` to GitLab Protected Tags (Maintainers can create).
521
+ RELEASING.md pre-flight now explicitly lists the protected-refs
522
+ audit including `v*`.
523
+
524
+ - **Lesson 2 (Δ36 — npm Granular "Bypass 2FA" checkbox)**: 5
525
+ consecutive `EOTP npm error code EOTP` failures during initial
526
+ publish attempts. Root cause discovered: npm removed Classic
527
+ Automation tokens in November 2025; only Granular Access Tokens
528
+ are now supported, and Granular tokens require an OTP at publish
529
+ time **even when the account is in `auth-only` 2FA mode**, unless
530
+ the explicit "Bypass two-factor authentication (2FA)" checkbox is
531
+ ticked at token creation. Token regenerated with the checkbox →
532
+ immediate publish success. RELEASING.md Step 1 now flags this as
533
+ a `MUST be checked` item with prominent ⚠️ marker.
534
+
535
+ - **Lesson 3 (Δ37 — v0.1.0 has no provenance)**: Documented in the
536
+ Supply chain section above. Bootstrap path runs from GitLab CI
537
+ which lacks GitHub-Actions-style npm OIDC integration; v0.1.0 ships
538
+ without `dist.attestations`. Intentional and one-time — v0.1.1+ via
539
+ OIDC restores full provenance.
540
+
541
+ - **Cockpit Phase 3 unblocked**: B1-B14 implementation begins
542
+ immediately on cockpit side. `npm install @druumen/sessions-db`
543
+ works for marketplace prep. Expect 1-2 minor patch releases
544
+ (0.1.1 / 0.1.2) shaking out integration corner cases — these will
545
+ be the first real exercise of the OIDC publish path.
package/README.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  Cross-session traceability for [Claude Code](https://claude.com/claude-code).
4
4
 
5
+ > **Repository note**: development happens at
6
+ > `gitlab.tinfant.org/druumen/sessions-db` (private to tinfant org —
7
+ > file MRs there). The `github.com/druumen/sessions-db` mirror is
8
+ > public and is the source-of-truth for npm provenance attestations
9
+ > + the consumer-facing "view source" link from
10
+ > <https://www.npmjs.com/package/@druumen/sessions-db>. Both are kept
11
+ > in sync via GitLab CI's `mirror-to-github` job.
12
+
5
13
  ## What it does
6
14
 
7
15
  Records every Claude Code session start (cwd, branch, transcript file,
@@ -120,6 +128,26 @@ The hook is bootstrap-safe by design:
120
128
  - Errors are logged to stderr (visible in Claude Code's session log)
121
129
  but never surfaced as user-facing failures.
122
130
 
131
+ #### Subpath imports `./cli` and `./hook` are ESM-only
132
+
133
+ The `@druumen/sessions-db/cli` and `@druumen/sessions-db/hook` exports
134
+ resolve to `.mjs` entry points and are intended to be **executed as
135
+ processes** (via the `bin` field's shim, or invoked directly with
136
+ `node <path>`). They are NOT intended for programmatic `require()` /
137
+ `import` from a consumer's runtime code.
138
+
139
+ If you need to run the CLI from your code, spawn it as a child process:
140
+
141
+ ```js
142
+ // CJS or ESM consumer — spawn the CLI as a process
143
+ import { spawnSync } from 'node:child_process';
144
+ spawnSync('npx', ['sessions-db', 'find', '...'], { stdio: 'inherit' });
145
+ ```
146
+
147
+ The main library entry (`@druumen/sessions-db`) IS dual-published as
148
+ both CJS and ESM and works from either context. Only the bin-style
149
+ subpaths are ESM-only.
150
+
123
151
  ## Path resolution
124
152
 
125
153
  When you don't pass an explicit `rootPath`, sessions-db walks a 5-priority
@@ -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 whose nearest CLAUDE.md does not declare a
13
- * "Druumen Workspace". No event written.
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
@@ -76,9 +78,13 @@ async function main() {
76
78
  process.env.CLAUDE_PROJECT_DIR ||
77
79
  process.cwd();
78
80
 
79
- // (3) cwd-gate. We walk up from cwd looking for a CLAUDE.md that contains
80
- // the "Druumen Workspace" sentinel. Any other repo (admin, blog, a random
81
- // scratch dir) bails silently.
81
+ // (3) cwd-gate. Accept the cwd when EITHER of:
82
+ // - a CLAUDE.md "Druumen Workspace" sentinel exists at cwd or ancestor
83
+ // (druumen-monorepo opt-in), OR
84
+ // - a `.dru-code/sessions-db.json` or `tickets/_logs/sessions-db.json`
85
+ // already exists under cwd or ancestor (cockpit Setup Wizard or
86
+ // prior manual init already opted this workspace in).
87
+ // Any other repo bails silently.
82
88
  if (!isDruumenWorkspace(cwd)) {
83
89
  process.exit(0);
84
90
  }
@@ -262,27 +268,59 @@ function readStdinJson({ timeoutMs }) {
262
268
  }
263
269
 
264
270
  /**
265
- * Walk up from `cwd` looking for a CLAUDE.md whose body contains the
266
- * "Druumen Workspace" sentinel. Bounded to 12 ancestors so a runaway loop
267
- * (e.g. weird filesystem mount) cannot stall us.
271
+ * Decide whether the hook is allowed to record events for `cwd`.
268
272
  *
269
- * Returns true when sentinel found, false otherwise (incl. read errors).
273
+ * Two acceptance fast-paths, either of which is sufficient:
274
+ *
275
+ * 1. **Druumen Workspace sentinel** — a `CLAUDE.md` at `cwd` or any
276
+ * ancestor whose body contains the literal string "Druumen Workspace".
277
+ * Original 0.1.x gate; how the Druumen monorepo opts in.
278
+ *
279
+ * 2. **Pre-initialized sessions-db storage** — `.dru-code/sessions-db.json`
280
+ * or `tickets/_logs/sessions-db.json` already exists at `cwd` or any
281
+ * ancestor. The cockpit Setup Wizard creates this file when the user
282
+ * explicitly enables sessions tracking for a workspace; an external
283
+ * project that has never opted in will not have either marker.
284
+ *
285
+ * Either marker is treated as user consent for this workspace. The walk
286
+ * is bounded to 12 ancestors so a runaway loop (e.g. weird FS mount)
287
+ * cannot stall us; the loop terminates early as soon as ANY marker is
288
+ * found at the current level.
289
+ *
290
+ * The function name is kept (`isDruumenWorkspace`) for git history clarity
291
+ * even though the semantic has broadened to "authorized workspace". Both
292
+ * acceptance criteria are checked at each ancestor before walking up
293
+ * (cheap stat-only probes for the storage paths).
294
+ *
295
+ * Returns true on the first hit, false after 12 ancestors / filesystem
296
+ * root / read errors with no marker found.
270
297
  */
271
298
  function isDruumenWorkspace(cwd) {
272
299
  if (typeof cwd !== 'string' || cwd.length === 0) return false;
273
300
  let dir = cwd;
274
301
  for (let i = 0; i < 12; i++) {
275
- const candidate = join(dir, 'CLAUDE.md');
276
- if (existsSync(candidate)) {
302
+ // Fast-path 1: CLAUDE.md sentinel
303
+ const claudeMd = join(dir, 'CLAUDE.md');
304
+ if (existsSync(claudeMd)) {
277
305
  try {
278
306
  // We only need the first ~8KB to find the sentinel; CLAUDE.md is
279
307
  // typically short, so reading the whole file is fine.
280
- const body = readFileSync(candidate, 'utf8');
308
+ const body = readFileSync(claudeMd, 'utf8');
281
309
  if (body.includes('Druumen Workspace')) return true;
282
310
  } catch {
283
311
  // unreadable — keep walking up just in case there's a higher one.
284
312
  }
285
313
  }
314
+ // Fast-path 2: pre-initialized sessions-db storage. Stat-only — we
315
+ // don't read these files here, just check existence. Either convention
316
+ // (cockpit-marketplace `.dru-code/` or druumen-monorepo `tickets/_logs/`)
317
+ // counts as opt-in.
318
+ if (
319
+ existsSync(join(dir, '.dru-code', 'sessions-db.json')) ||
320
+ existsSync(join(dir, 'tickets', '_logs', 'sessions-db.json'))
321
+ ) {
322
+ return true;
323
+ }
286
324
  const parent = dirname(dir);
287
325
  if (parent === dir) return false;
288
326
  dir = parent;