import path from 'node:path'; import { readdirSync } from 'node:fs'; function resolveDataDir(): string { // Absolutize against cwd: run.sh sets a RELATIVE `DATA_DIR=data-`, and a relative path breaks // any consumer that isn't cwd-relative — notably the dynamic `import(configPath)` in // shraga-config.ts, which resolves a relative specifier against the IMPORTING MODULE // (`node_modules/shraga/src/server/`), not the process cwd. if (process.env.DATA_DIR) return path.resolve(process.env.DATA_DIR); const root = process.cwd(); const hasNamed = readdirSync(root).some(f => f.startsWith('data-')); if (hasNamed) { throw new Error( `DATA_DIR not set but named data dirs exist (data-*). ` + `Run via 'bun run dev ' or set DATA_DIR explicitly.` ); } return path.resolve(root, 'data'); } export const DATA_DIR = resolveDataDir(); export const dataPath = (...segments: string[]) => path.join(DATA_DIR, ...segments); // ── Two distinct roots. Conflating them is what broke npm-consumer deployments. ────────────────── // // PACKAGE_ROOT — where the SHRAGA PACKAGE's own shipped assets live (`defaults/`, `dist/client/`, // `package.json` — everything in package.json `files`). Package-relative is CORRECT here: in an npm // consumer these really do live under `node_modules/shraga/`. Do not "fix" this to APP_ROOT. export const PACKAGE_ROOT = path.resolve(import.meta.dirname, '..', '..'); /** * APP_ROOT — the DEPLOYMENT/consumer root: where `vendor/`, `secrets/` and `data/` live, and where * the agent's project filesystem is rooted. NOT shipped in the package. * * In a source checkout this equals PACKAGE_ROOT. In an npm consumer (`shraga-circles`, `shraga-ee`) * it is the CONSUMER root, while PACKAGE_ROOT is `/node_modules/shraga` — resolving vendor * from PACKAGE_ROOT is what silently killed ~21/25 MCPs in prod. * * Signal, in precedence order: * 1. SHRAGA_APP_ROOT env — explicit escape hatch for any layout the heuristics get wrong. * 2. node_modules ancestor — if this file sits under `.../node_modules/shraga/...`, the app root is * the directory CONTAINING that `node_modules`. Independent of cwd, so it survives a server * started from anywhere (systemd, a cron shell, `bun --cwd`). * 3. process.cwd() — the source-checkout case, and already this module's established app-root signal * (see resolveDataDir above; run.sh `cd`s to the app root before launching). * * Failure modes, explicit: * - Hoisted/pnpm layouts where `shraga` resolves to a store dir outside the consumer's own * node_modules: rule 2 picks the hoisting root, which may not be the dir holding `vendor/`. * - A nested `node_modules/x/node_modules/shraga`: rule 2 stops at the INNERMOST node_modules. * Both are exactly why rule 1 exists — set SHRAGA_APP_ROOT and the heuristics are bypassed. */ function resolveAppRoot(): string { const explicit = process.env.SHRAGA_APP_ROOT?.trim(); if (explicit) return path.resolve(explicit); const marker = `${path.sep}node_modules${path.sep}`; const idx = PACKAGE_ROOT.lastIndexOf(marker); if (idx !== -1) return PACKAGE_ROOT.slice(0, idx); return process.cwd(); } export const APP_ROOT = resolveAppRoot(); /** * @deprecated Ambiguous name — use APP_ROOT (vendor/secrets/data, agent cwd) or PACKAGE_ROOT * (shipped assets) explicitly. * * Aliased to APP_ROOT, not PACKAGE_ROOT, deliberately: every remaining external consumer of this * export (shraga-ee's optional engines, e.g. `engine/cursor.ts`) uses it as the agent's project root — * i.e. they meant APP_ROOT and were hitting the same npm-layout bug. Pointing the alias here fixes * them without an EE change. Nothing in this package reads shipped assets through it. */ export const PROJECT_ROOT = APP_ROOT;