import { join, dirname } from "path" import { readdir, rename } from "fs/promises" import { homedir } from "os" export interface Session { branch: string worktree: string created_at: string prompt?: string in_review?: boolean } export interface State { sessions: Record } function statePath(repoRoot: string): string { return join(repoRoot, ".sandlot", "state.json") } export async function load(repoRoot: string): Promise { const path = statePath(repoRoot) const file = Bun.file(path) if (await file.exists()) { return await file.json() } return { sessions: {} } } export async function save(repoRoot: string, state: State): Promise { const path = statePath(repoRoot) const tmpPath = path + ".tmp" const dir = join(repoRoot, ".sandlot") await Bun.write(join(dir, ".gitkeep"), "") // ensure dir exists await Bun.write(tmpPath, JSON.stringify(state, null, 2) + "\n") await rename(tmpPath, path) } export async function getSession(repoRoot: string, branch: string): Promise { const state = await load(repoRoot) return state.sessions[branch] } export async function setSession(repoRoot: string, session: Session): Promise { const state = await load(repoRoot) state.sessions[session.branch] = session await save(repoRoot, state) } export async function removeSession(repoRoot: string, branch: string): Promise { const state = await load(repoRoot) delete state.sessions[branch] await save(repoRoot, state) } export interface GlobalSession extends Session { repoRoot: string } /** Discover all sessions across all repos by scanning ~/.sandlot/ */ export async function loadAll(): Promise { const sandlotDir = join(homedir(), ".sandlot") const all: GlobalSession[] = [] let repoDirs try { repoDirs = await readdir(sandlotDir, { withFileTypes: true }) } catch { return [] } for (const entry of repoDirs) { if (!entry.isDirectory() || entry.name.startsWith(".")) continue const repoDir = join(sandlotDir, entry.name) // Find the main repo root from a worktree's .git pointer let repoRoot: string | null = null const branchEntries = await readdir(repoDir, { withFileTypes: true }).catch(() => []) for (const be of branchEntries) { if (!be.isDirectory() || be.name.startsWith(".")) continue const dotGit = await Bun.file(join(repoDir, be.name, ".git")).text().catch(() => "") const m = dotGit.match(/^gitdir:\s*(.+)/m) if (m) { // gitdir: /path/to/repo/.git/worktrees/ const mainGit = m[1].trim().replace(/\/worktrees\/[^/]+$/, "") repoRoot = dirname(mainGit) break } } if (repoRoot) { try { const st = await load(repoRoot) for (const session of Object.values(st.sessions)) { all.push({ ...session, repoRoot }) } } catch {} } } return all }