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/load-context.mjs
1/**2* Shared context loader for every impeccable command that needs to know3* "who is this for" and "what does this look like".4*5* Input: project root (process.cwd()).6*7* Output (JSON to stdout):8* {9* hasProduct: boolean, // PRODUCT.md found (or auto-migrated)10* product: string | null, // PRODUCT.md contents11* productPath: string | null, // relative path12* hasDesign: boolean, // DESIGN.md found13* design: string | null, // DESIGN.md contents14* designPath: string | null,15* migrated: boolean, // true if we auto-renamed .impeccable.md -> PRODUCT.md16* contextDir: string, // absolute path of the directory the files were found in17* }18*19* Filename matching is case-insensitive for PRODUCT.md and DESIGN.md. The20* Google DESIGN.md convention is uppercase at repo root; Kiro-style and21* lowercase variants are also matched so users don't get punished for case.22*23* Lookup directory resolution (first match wins):24* 1. process.env.IMPECCABLE_CONTEXT_DIR (absolute or relative to cwd)25* 2. cwd, if PRODUCT.md / DESIGN.md / .impeccable.md is there (back-compat)26* 3. Auto-fallback subdirectories of cwd: .agents/context/, then docs/27* 4. cwd as a default "no context found" location28*29* Legacy `.impeccable.md` -> PRODUCT.md migration only fires at cwd root;30* fallback directories are read-only as far as auto-rename is concerned.31*/3233import fs from 'node:fs';34import path from 'node:path';3536const PRODUCT_NAMES = ['PRODUCT.md', 'Product.md', 'product.md'];37const DESIGN_NAMES = ['DESIGN.md', 'Design.md', 'design.md'];38const LEGACY_NAMES = ['.impeccable.md'];39const FALLBACK_DIRS = ['.agents/context', 'docs'];4041/**42* Resolve the directory that holds PRODUCT.md / DESIGN.md for43* this project. Exported so other scripts (e.g. live-server.mjs) can read the44* design files from the same location the loader uses.45*/46export function resolveContextDir(cwd = process.cwd()) {47// 1. Explicit override48const envDir = process.env.IMPECCABLE_CONTEXT_DIR;49if (envDir && envDir.trim()) {50const trimmed = envDir.trim();51return path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed);52}5354// 2. cwd wins if any canonical or legacy file is there. We check legacy too55// so the auto-migration path in loadContext stays predictable.56if (firstExisting(cwd, [...PRODUCT_NAMES, ...DESIGN_NAMES, ...LEGACY_NAMES])) {57return cwd;58}5960// 3. Auto-fallback subdirs. Match if PRODUCT.md or DESIGN.md is present;61// legacy `.impeccable.md` does not pull the lookup into a fallback dir.62for (const rel of FALLBACK_DIRS) {63const candidate = path.resolve(cwd, rel);64if (firstExisting(candidate, [...PRODUCT_NAMES, ...DESIGN_NAMES])) {65return candidate;66}67}6869// 4. Nothing found — keep the historical "default to cwd" behaviour so the70// caller's `hasProduct === false` branch still fires the same way.71return cwd;72}7374export function loadContext(cwd = process.cwd()) {75let migrated = false;76const contextDir = resolveContextDir(cwd);7778// 1. Look for PRODUCT.md (case-insensitive) in the resolved dir79let productPath = firstExisting(contextDir, PRODUCT_NAMES);8081// 2. Legacy: if no PRODUCT.md but .impeccable.md exists at cwd root, rename82// it in place. We only migrate at the root — fallback dirs are read-only83// so we don't surprise users by mutating files under docs/ or .agents/.84if (!productPath && contextDir === cwd) {85const legacyPath = firstExisting(cwd, LEGACY_NAMES);86if (legacyPath) {87const newPath = path.join(cwd, 'PRODUCT.md');88try {89fs.renameSync(legacyPath, newPath);90productPath = newPath;91migrated = true;92} catch {93// Rename failed (permissions, etc.) — fall back to reading legacy in place94productPath = legacyPath;95}96}97}9899// 3. DESIGN.md (case-insensitive)100const designPath = firstExisting(contextDir, DESIGN_NAMES);101102const product = productPath ? safeRead(productPath) : null;103const design = designPath ? safeRead(designPath) : null;104105return {106hasProduct: !!product,107product,108productPath: productPath ? path.relative(cwd, productPath) : null,109hasDesign: !!design,110design,111designPath: designPath ? path.relative(cwd, designPath) : null,112migrated,113contextDir,114};115}116117function firstExisting(dir, names) {118for (const name of names) {119const abs = path.join(dir, name);120if (fs.existsSync(abs)) return abs;121}122return null;123}124125function safeRead(p) {126try { return fs.readFileSync(p, 'utf-8'); } catch { return null; }127}128129// ---------------------------------------------------------------------------130// CLI mode — print the context as JSON131// ---------------------------------------------------------------------------132133function cli() {134const result = loadContext(process.cwd());135console.log(JSON.stringify(result, null, 2));136}137138const _running = process.argv[1];139if (_running?.endsWith('load-context.mjs') || _running?.endsWith('load-context.mjs/')) {140cli();141}142