Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
One-time setup that gathers your project's design context and saves it to CLAUDE.md for future sessions.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/live/session-store.mjs
1import fs from 'node:fs';2import path from 'node:path';3import { getLegacyLiveSessionsDir, getLiveSessionsDir } from '../lib/impeccable-paths.mjs';45const COMPLETED_PHASES = new Set(['completed', 'discarded']);67export function createLiveSessionStore({ cwd = process.cwd(), sessionId } = {}) {8const rootDir = getLiveSessionsDir(cwd);9const legacyRootDir = getLegacyLiveSessionsDir(cwd);10fs.mkdirSync(rootDir, { recursive: true });11const snapshotCache = new Map();1213function loadCachedOrRebuild(id) {14const cached = snapshotCache.get(id);15if (cached) return cached;16const journalPath = getReadableJournalPath(id);17const rebuilt = rebuildSnapshotFromJournal(journalPath, id);18snapshotCache.set(id, rebuilt);19return rebuilt;20}2122function getReadableJournalPath(id) {23const primary = getJournalPath(rootDir, id);24if (fs.existsSync(primary)) return primary;25const legacy = getJournalPath(legacyRootDir, id);26if (fs.existsSync(legacy)) return legacy;27return primary;28}2930return {31rootDir,32legacyRootDir,33appendEvent(event) {34const normalized = normalizeEvent(event, sessionId);35const journalPath = getJournalPath(rootDir, normalized.id);36const snapshotPath = getSnapshotPath(rootDir, normalized.id);37const legacyJournalPath = getJournalPath(legacyRootDir, normalized.id);38if (!fs.existsSync(journalPath) && fs.existsSync(legacyJournalPath)) {39fs.copyFileSync(legacyJournalPath, journalPath);40}41const prior = loadCachedOrRebuild(normalized.id);42const seq = prior.nextSeq;43const entry = {44seq,45id: normalized.id,46type: normalized.type,47ts: new Date().toISOString(),48event: normalized,49};50fs.appendFileSync(journalPath, JSON.stringify(entry) + '\n');51const next = applyEvent(prior.snapshot, entry, prior.diagnostics);52snapshotCache.set(normalized.id, { snapshot: next, diagnostics: next.diagnostics || [], nextSeq: seq + 1 });53writeSnapshot(snapshotPath, next);54return next;55},56getSnapshot(id = sessionId, opts = {}) {57if (!id) throw new Error('session id required');58const journalPath = getReadableJournalPath(id);59const snapshotPath = getSnapshotPath(rootDir, id);60const rebuilt = rebuildSnapshotFromJournal(journalPath, id);61snapshotCache.set(id, rebuilt);62writeSnapshot(snapshotPath, rebuilt.snapshot);63if (!opts.includeCompleted && COMPLETED_PHASES.has(rebuilt.snapshot.phase)) return null;64return rebuilt.snapshot;65},66listActiveSessions() {67const ids = new Set();68for (const dir of [legacyRootDir, rootDir]) {69if (!fs.existsSync(dir)) continue;70for (const name of fs.readdirSync(dir)) {71if (name.endsWith('.jsonl')) ids.add(name.slice(0, -'.jsonl'.length));72}73}74return [...ids]75.sort()76.map((id) => this.getSnapshot(id))77.filter(Boolean);78},79};80}8182function normalizeEvent(event, fallbackId) {83if (!event || typeof event !== 'object') throw new Error('event object required');84const id = event.id || fallbackId;85if (!id || typeof id !== 'string') throw new Error('event id required');86if (!event.type || typeof event.type !== 'string') throw new Error('event type required');87return { ...event, id };88}8990function getJournalPath(rootDir, id) {91return path.join(rootDir, safeSessionId(id) + '.jsonl');92}9394function getSnapshotPath(rootDir, id) {95return path.join(rootDir, safeSessionId(id) + '.snapshot.json');96}9798function safeSessionId(id) {99if (!/^[A-Za-z0-9_-]{1,128}$/.test(id)) throw new Error('invalid session id: ' + id);100return id;101}102103function baseSnapshot(id) {104return {105id,106phase: 'new',107pageUrl: null,108sourceFile: null,109previewFile: null,110previewMode: null,111expectedVariants: 0,112arrivedVariants: 0,113visibleVariant: null,114paramValues: {},115pendingEventSeq: null,116pendingEvent: null,117deliveryLease: null,118checkpointRevision: 0,119activeOwner: null,120sourceMarkers: {},121fallbackMode: null,122annotationArtifacts: [],123diagnostics: [],124updatedAt: null,125};126}127128function rebuildSnapshotFromJournal(journalPath, id) {129let snapshot = baseSnapshot(id);130const diagnostics = [];131let nextSeq = 1;132if (!fs.existsSync(journalPath)) return { snapshot, diagnostics, nextSeq };133134const lines = fs.readFileSync(journalPath, 'utf-8').split('\n');135for (let i = 0; i < lines.length; i++) {136const line = lines[i];137if (!line.trim()) continue;138try {139const entry = JSON.parse(line);140if (!entry || typeof entry !== 'object') throw new Error('entry is not object');141if (Number.isInteger(entry.seq)) nextSeq = Math.max(nextSeq, entry.seq + 1);142snapshot = applyEvent(snapshot, entry);143} catch (err) {144diagnostics.push({145error: 'journal_parse_failed',146line: i + 1,147message: err.message,148});149}150}151snapshot.diagnostics = [...snapshot.diagnostics, ...diagnostics];152return { snapshot, diagnostics, nextSeq };153}154155function applyEvent(snapshot, entry, inheritedDiagnostics = []) {156const event = entry.event || entry;157const next = {158...snapshot,159paramValues: { ...(snapshot.paramValues || {}) },160sourceMarkers: { ...(snapshot.sourceMarkers || {}) },161annotationArtifacts: [...(snapshot.annotationArtifacts || [])],162diagnostics: [...(snapshot.diagnostics || [])],163updatedAt: entry.ts || new Date().toISOString(),164};165166if (inheritedDiagnostics.length && next.diagnostics.length === 0) {167next.diagnostics = [...inheritedDiagnostics];168}169170switch (event.type) {171case 'generate':172next.phase = 'generate_requested';173next.pageUrl = event.pageUrl ?? next.pageUrl;174next.expectedVariants = event.count ?? next.expectedVariants;175next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;176next.pendingEvent = toPendingEvent(event);177if (event.screenshotPath) upsertArtifact(next.annotationArtifacts, { type: 'screenshot', path: event.screenshotPath });178break;179case 'variants_ready':180case 'agent_done':181next.phase = event.carbonize === true ? 'carbonize_required' : 'variants_ready';182next.sourceFile = event.sourceFile ?? event.file ?? next.sourceFile;183next.previewFile = event.previewFile ?? next.previewFile;184next.previewMode = event.previewMode ?? next.previewMode;185next.arrivedVariants = event.arrivedVariants ?? (next.expectedVariants || next.arrivedVariants || 0);186next.pendingEventSeq = null;187next.pendingEvent = null;188if (event.carbonize === true) {189next.diagnostics.push({190error: 'carbonize_cleanup_required',191file: event.file || null,192message: 'Accepted variant still has carbonize markers that must be folded into source CSS.',193});194}195break;196case 'checkpoint':197if (COMPLETED_PHASES.has(next.phase)) {198next.diagnostics.push({ error: 'checkpoint_after_terminal_ignored', phase: event.phase ?? null, revision: event.revision ?? null });199break;200}201if ((event.revision ?? 0) >= (next.checkpointRevision ?? 0)) {202next.phase = event.phase ?? next.phase;203next.checkpointRevision = event.revision ?? next.checkpointRevision;204next.activeOwner = event.owner ?? next.activeOwner;205next.arrivedVariants = event.arrivedVariants ?? next.arrivedVariants;206next.visibleVariant = event.visibleVariant ?? next.visibleVariant;207next.sourceFile = event.sourceFile ?? next.sourceFile;208next.previewFile = event.previewFile ?? next.previewFile;209next.previewMode = event.previewMode ?? next.previewMode;210if (event.paramValues) next.paramValues = { ...event.paramValues };211} else {212next.diagnostics.push({ error: 'stale_checkpoint_ignored', revision: event.revision });213}214break;215case 'accept':216case 'accept_intent':217next.phase = 'accept_requested';218next.visibleVariant = Number(event.variantId ?? next.visibleVariant);219if (event.paramValues) next.paramValues = { ...event.paramValues };220next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;221next.pendingEvent = toPendingEvent(event);222break;223case 'manual_edit_apply':224next.phase = 'manual_edit_apply_requested';225next.pageUrl = event.pageUrl ?? next.pageUrl;226next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;227next.pendingEvent = toPendingEvent(event);228break;229case 'steer':230next.phase = 'steer_requested';231next.pageUrl = event.pageUrl ?? next.pageUrl;232next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;233next.pendingEvent = toPendingEvent(event);234break;235case 'steer_done':236next.phase = 'steer_done';237next.sourceFile = event.sourceFile ?? event.file ?? next.sourceFile;238next.previewFile = event.previewFile ?? next.previewFile;239next.previewMode = event.previewMode ?? next.previewMode;240next.message = event.message ?? next.message;241next.pendingEventSeq = null;242next.pendingEvent = null;243break;244case 'discard':245next.phase = 'discard_requested';246next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;247next.pendingEvent = toPendingEvent(event);248break;249case 'discarded':250next.phase = 'discarded';251next.pendingEventSeq = null;252next.pendingEvent = null;253break;254case 'complete':255next.phase = 'completed';256next.sourceFile = event.sourceFile ?? event.file ?? next.sourceFile;257next.previewFile = event.previewFile ?? next.previewFile;258next.previewMode = event.previewMode ?? next.previewMode;259next.pendingEventSeq = null;260next.pendingEvent = null;261break;262case 'agent_error':263next.phase = 'agent_error';264next.pendingEventSeq = null;265next.pendingEvent = null;266next.diagnostics.push({ error: 'agent_error', message: event.message || 'unknown agent error' });267break;268default:269next.diagnostics.push({ error: 'unknown_event_type', type: event.type });270break;271}272return next;273}274275function toPendingEvent(event) {276const pending = { ...event };277delete pending.token;278return pending;279}280281function upsertArtifact(artifacts, artifact) {282if (!artifacts.some((existing) => existing.path === artifact.path && existing.type === artifact.type)) {283artifacts.push(artifact);284}285}286287function writeSnapshot(snapshotPath, snapshot) {288fs.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2) + '\n');289}290