/** * Where this plugin keeps things on disk. * * Everything lives under `$DSH_HOME`, never inside the workspace. That is the * whole point of the plugin's economics: the index holds a vector per code * chunk, which for a 300k-line repository is tens of megabytes — several times * the size of the source it describes. A multi-megabyte binary file appearing * in a repository the user might commit, or that a `git status` might surface * mid-task, is a worse outcome than the tokens it saves. * * The index is also disposable by construction. It is keyed by content hash, so * deleting it costs one re-embed and nothing else — which is what makes it safe * to treat as a cache rather than a document. */ import { createHash } from 'node:crypto' import { homedir } from 'node:os' import { basename, join, resolve } from 'node:path' /** Plugin name. Also the `$DSH_HOME/` directory. */ export const PLUGIN_NAME = 'dsh-recall' /** The dsh home directory: `$DSH_HOME`, else `~/.dsh`. */ export function resolveDshHome(env = process.env) { const configured = typeof env.DSH_HOME === 'string' ? env.DSH_HOME.trim() : '' return configured === '' ? join(homedir(), '.dsh') : resolve(configured) } /** * A stable, filesystem-safe key for one workspace. * * The basename is for a human reading the directory listing; the hash is what * actually identifies it, so two checkouts both named `app` cannot collide and * one checkout moved between parents does not silently inherit the other's * index. */ export function workspaceKey(cwd) { const absolute = resolve(cwd) const name = basename(absolute) .replace(/[^A-Za-z0-9._-]+/g, '-') .replace(/^[-.]+|-+$/g, '') || 'workspace' const hash = createHash('sha256').update(absolute).digest('hex').slice(0, 8) return `${name}-${hash}` } /** Root of this plugin's own data, independent of any workspace. */ export function pluginDataDir(dshHome) { return join(dshHome, PLUGIN_NAME) } /** Per-workspace state directory. */ export function workspaceStateDir(dshHome, cwd) { return join(pluginDataDir(dshHome), 'workspaces', workspaceKey(cwd)) } /** The SQLite index for one workspace. */ /** * The SQLite index for one workspace, optionally narrowed to one focus. * * The workspace-wide index deliberately keeps its original path. Focus is * additive: a deployment that never sets one must find the index it already * built, not silently re-embed the repository into a new file. * * @param {string} dshHome harness home * @param {string} cwd workspace root * @param {string} [focus] focus key, `.` for the workspace itself */ export function indexFile(dshHome, cwd, focus = '.') { const base = workspaceStateDir(dshHome, cwd) return focus === '.' ? join(base, 'index.sqlite') : join(base, 'focus', focus, 'index.sqlite') }