/** * Store-path resolution: where a harness files the transcript for a session, * resolved purely from descriptor template data - the tail reader in the * execution layer consumes this, it is never guessed at a call site. The * session id is shape-checked before it becomes a path segment (a traversal * id must never reach the filesystem), and replacements are literal - no * `$&` pattern semantics from String.replace. */ import type { HarnessDescriptor } from "../knowledge/descriptor.js"; import { md5Hex } from "./md5.js"; import { assertUsableSessionId } from "./session-id.js"; export interface StorePathInputs { readonly home: string; readonly cwd: string; readonly sessionId: string; /** Store root the CLI resolved from rootEnv; absent means the * descriptor defaultRoot applies. Templates without {root} ignore it. */ readonly root?: string; } /** The store slug for a cwd under a descriptor's slugging rule. Exported * for the resume-last guard's per-cwd scope directory, which must name the * same directory the harness files sessions under. */ export const slugFor = (h: HarnessDescriptor, cwd: string): string => { const normalized = cwd.length > 1 ? cwd.replace(/\/+$/, "") : cwd; switch (h.store.cwdSlug) { case "dash-separators": return normalized.replace(/[/.]/g, "-"); case "pi-dash-wrapped": // pi 0.84.2, verified on-disk: leading slash stripped, '/' -> '-', // dots preserved, wrapped in double dashes: // /Users/kevin/dev/x -> --Users-kevin-dev-x-- return `--${normalized.replace(/^\//, "").replace(/\//g, "-")}--`; case "verbatim": return normalized; case "md5-hex": // Cursor chats/: md5 over the UTF-8 bytes of the // physical absolute cwd, no trailing slash (probes 30, 36-38, // 44-46). Vendored pure md5 so the purity gate holds. return md5Hex(normalized); default: { const exhaustive: never = h.store.cwdSlug; return exhaustive; } } }; /** Expand a root-table string (a `defaultRoot`, or a `rootEnv` suffix): * `{home}`, `{cwd}`, and `{cwdSlug}` under the descriptor's slug rule. */ export const expandRoot = ( h: HarnessDescriptor, value: string, home: string, cwd: string, ): string => value .replaceAll("{home}", () => home) .replaceAll("{cwd}", () => cwd) .replaceAll("{cwdSlug}", () => slugFor(h, cwd)); export const storePath = (h: HarnessDescriptor, inputs: StorePathInputs): string => { assertUsableSessionId(inputs.sessionId); const root = inputs.root ?? (h.store.defaultRoot === undefined ? inputs.home : expandRoot(h, h.store.defaultRoot, inputs.home, inputs.cwd)); return h.store.template .replaceAll("{home}", () => inputs.home) .replaceAll("{root}", () => root) .replaceAll("{cwdSlug}", () => slugFor(h, inputs.cwd)) .replaceAll("{sessionId}", () => inputs.sessionId); };