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/lib/impeccable-paths.mjs
1import fs from 'node:fs';2import path from 'node:path';3import { resolveProjectRoot } from '../context.mjs';45export const IMPECCABLE_DIR = '.impeccable';6export const LIVE_DIR = 'live';7export const CRITIQUE_DIR = 'critique';89export function getImpeccableDir(cwd = process.cwd(), options = {}) {10return path.join(resolveProjectRoot(cwd, options), IMPECCABLE_DIR);11}1213export function getDesignSidecarPath(cwd = process.cwd(), options = {}) {14return path.join(getImpeccableDir(cwd, options), 'design.json');15}1617export function getDesignSidecarCandidates(cwd = process.cwd(), contextDir = cwd, options = {}) {18const projectRoot = resolveProjectRoot(cwd, options);19const candidates = [20getDesignSidecarPath(cwd, options),21path.join(projectRoot, 'DESIGN.json'),22];23const contextLegacy = path.join(contextDir, 'DESIGN.json');24if (!candidates.includes(contextLegacy)) candidates.push(contextLegacy);25return candidates;26}2728export function resolveDesignSidecarPath(cwd = process.cwd(), contextDir = cwd, options = {}) {29return firstExisting(getDesignSidecarCandidates(cwd, contextDir, options));30}3132export function getLiveDir(cwd = process.cwd(), options = {}) {33return path.join(getImpeccableDir(cwd, options), LIVE_DIR);34}3536export function getLiveConfigPath(cwd = process.cwd(), options = {}) {37return path.join(getLiveDir(cwd, options), 'config.json');38}3940export function getLegacyLiveConfigPath(scriptsDir) {41return path.join(scriptsDir, 'config.json');42}4344export function resolveLiveConfigPath({ cwd = process.cwd(), scriptsDir, env = process.env, targetPath } = {}) {45if (env.IMPECCABLE_LIVE_CONFIG && env.IMPECCABLE_LIVE_CONFIG.trim()) {46const configured = env.IMPECCABLE_LIVE_CONFIG.trim();47return path.isAbsolute(configured) ? configured : path.resolve(cwd, configured);48}49const primary = getLiveConfigPath(cwd, { targetPath });50if (fs.existsSync(primary)) return primary;51if (scriptsDir) {52const legacy = getLegacyLiveConfigPath(scriptsDir);53if (fs.existsSync(legacy)) return legacy;54}55return primary;56}5758export function getLiveServerPath(cwd = process.cwd(), options = {}) {59return path.join(getLiveDir(cwd, options), 'server.json');60}6162export function getLegacyLiveServerPath(cwd = process.cwd(), options = {}) {63return path.join(resolveProjectRoot(cwd, options), '.impeccable-live.json');64}6566export function readLiveServerInfo(cwd = process.cwd(), options = {}) {67for (const filePath of [getLiveServerPath(cwd, options), getLegacyLiveServerPath(cwd, options)]) {68try {69const info = JSON.parse(fs.readFileSync(filePath, 'utf-8'));70if (info && typeof info.pid === 'number' && !isLiveServerPidReachable(info.pid)) {71try { fs.unlinkSync(filePath); } catch {}72continue;73}74return { info, path: filePath };75} catch {76/* try next */77}78}79return null;80}8182export function isLiveServerPidReachable(pid) {83try {84process.kill(pid, 0);85return true;86} catch (err) {87// ESRCH means "no such process". EPERM means the process exists but this88// user cannot signal it, so the live server info is still valid.89return err?.code !== 'ESRCH';90}91}9293export function writeLiveServerInfo(cwd = process.cwd(), info, options = {}) {94const filePath = getLiveServerPath(cwd, options);95fs.mkdirSync(path.dirname(filePath), { recursive: true });96fs.writeFileSync(filePath, JSON.stringify(info));97return filePath;98}99100export function removeLiveServerInfo(cwd = process.cwd(), options = {}) {101for (const filePath of [getLiveServerPath(cwd, options), getLegacyLiveServerPath(cwd, options)]) {102try { fs.unlinkSync(filePath); } catch {}103}104}105106export function getLiveSessionsDir(cwd = process.cwd(), options = {}) {107return path.join(getLiveDir(cwd, options), 'sessions');108}109110export function getLegacyLiveSessionsDir(cwd = process.cwd(), options = {}) {111return path.join(resolveProjectRoot(cwd, options), '.impeccable-live', 'sessions');112}113114export function getLiveAnnotationsDir(cwd = process.cwd(), options = {}) {115return path.join(getLiveDir(cwd, options), 'annotations');116}117118export function getCritiqueDir(cwd = process.cwd(), options = {}) {119return path.join(getImpeccableDir(cwd, options), CRITIQUE_DIR);120}121122export function getLegacyLiveAnnotationsDir(cwd = process.cwd(), options = {}) {123return path.join(resolveProjectRoot(cwd, options), '.impeccable-live', 'annotations');124}125126function firstExisting(paths) {127return paths.find((filePath) => fs.existsSync(filePath)) || null;128}129