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/critique-storage.mjs
1#!/usr/bin/env node2/**3* Critique persistence helper.4*5* Each run of /impeccable critique writes a per-target snapshot to6* .impeccable/critique/<timestamp>__<slug>.md7* with a small YAML frontmatter carrying the score + P0/P1 counts.8*9* /impeccable polish reads the latest matching snapshot at start as its10* fix backlog. No other skill auto-reads critique output.11*12* The slug is derived mechanically from the *resolved* primary artifact13* (file path or URL), never from the user's natural-language phrasing.14* Slug stability across runs is what lets the trend display work.15*16* CLI entry points (called from skill instructions):17* node critique-storage.mjs slug <resolved-target>18* node critique-storage.mjs write <slug> <snapshot-body-file>19* node critique-storage.mjs latest <slug>20* node critique-storage.mjs trend <slug> [limit]21*22* Note: there is intentionally no `ignore` subcommand. ignore.md is a plain23* markdown file; the model reads it directly with its file-read tool. This24* helper only exists for operations the model can't trivially do inline25* (normalizing paths, generating filenames, globbing + parsing frontmatter).26*/2728import fs from 'node:fs';29import path from 'node:path';30import { fileURLToPath, pathToFileURL } from 'node:url';31import { getCritiqueDir } from './impeccable-paths.mjs';3233const SLUG_MAX = 50;3435/**36* Mechanically derive a slug from a resolved target. Returns null if the37* input doesn't look like a stable identifier (empty, project root, etc).38*39* Accepts file paths and URLs. The model resolves "the homepage" to a40* concrete artifact before calling this — we never slug a natural-language41* phrase.42*/43export function slugFromTarget(resolved, { cwd = process.cwd() } = {}) {44if (!resolved || typeof resolved !== 'string') return null;45const trimmed = resolved.trim();46if (!trimmed) return null;4748// URL49if (/^https?:\/\//i.test(trimmed)) {50let url;51try { url = new URL(trimmed); } catch { return null; }52const hostPath = `${url.hostname}${url.pathname}`;53return kebab(hostPath);54}5556// File path. Make it project-relative so two devs critiquing the same57// checkout get the same slug regardless of where their repo is cloned.58const abs = path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed);59let rel = path.relative(cwd, abs);60// If the target is outside cwd, fall back to the basename so we still61// produce a stable slug (vs the absolute path, which would include62// home dirs / usernames).63if (rel.startsWith('..') || path.isAbsolute(rel)) {64rel = path.basename(abs);65}66if (!rel || rel === '.' || rel === '') return null;67return kebab(rel);68}6970function kebab(s) {71const slug = s72.toLowerCase()73.replace(/[/\\.]+/g, '-')74.replace(/[^a-z0-9-]+/g, '-')75.replace(/-+/g, '-')76.replace(/^-|-$/g, '');77if (!slug) return null;78// Cap from the tail — the tail (filename) is more identifying than the79// top-level directory.80return slug.length <= SLUG_MAX ? slug : slug.slice(slug.length - SLUG_MAX).replace(/^-/, '');81}8283/**84* Filename-safe UTC ISO timestamp: hyphens for separators, trailing Z.85* Plain colons aren't allowed on Windows filesystems.86*/87export function nowFilenameStamp(date = new Date()) {88const iso = date.toISOString(); // 2026-05-12T18:30:00.123Z89return iso.replace(/[:.]/g, '-').replace(/-\d+Z$/, 'Z');90}9192/**93* Write a snapshot for `slug`. `meta` carries the small structured frontmatter94* keys read back by readTrend(). `body` is the human-readable critique95* report (everything below the frontmatter).96*97* Returns the absolute path written.98*/99export function writeSnapshot({ slug, meta, body, cwd = process.cwd(), now = new Date() }) {100if (!slug) throw new Error('writeSnapshot requires a slug');101const dir = getCritiqueDir(cwd);102fs.mkdirSync(dir, { recursive: true });103const timestamp = nowFilenameStamp(now);104const filePath = path.join(dir, `${timestamp}__${slug}.md`);105// Spread `meta` first so internally computed `timestamp` and `slug`106// always win. Otherwise a caller-supplied meta blob (parsed from the107// IMPECCABLE_CRITIQUE_META env var) could clobber them, leaving the108// filename in disagreement with its frontmatter and corrupting trends.109const front = serializeFrontmatter({ ...meta, timestamp, slug });110fs.writeFileSync(filePath, `${front}\n${body.trim()}\n`, 'utf-8');111return filePath;112}113114function serializeFrontmatter(obj) {115const lines = ['---'];116for (const [key, value] of Object.entries(obj)) {117if (value === undefined || value === null) continue;118const str = typeof value === 'string' ? value : String(value);119// Quote strings that contain : or # to keep parsing simple.120const needsQuotes = typeof value === 'string' && /[:#]/.test(str);121lines.push(`${key}: ${needsQuotes ? JSON.stringify(str) : str}`);122}123lines.push('---');124return lines.join('\n');125}126127function parseFrontmatter(text) {128const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);129if (!match) return {};130const out = {};131for (const line of match[1].split(/\r?\n/)) {132const colon = line.indexOf(':');133if (colon < 0) continue;134const key = line.slice(0, colon).trim();135let value = line.slice(colon + 1).trim();136if (/^".*"$/.test(value)) {137try { value = JSON.parse(value); } catch { /* leave as-is */ }138} else if (/^-?\d+$/.test(value)) {139value = Number(value);140}141out[key] = value;142}143return out;144}145146/**147* Return all snapshot files for `slug`, sorted oldest → newest.148*/149function listSnapshotsForSlug(slug, cwd) {150const dir = getCritiqueDir(cwd);151if (!fs.existsSync(dir)) return [];152const suffix = `__${slug}.md`;153return fs.readdirSync(dir)154.filter((f) => f.endsWith(suffix))155.sort()156.map((f) => path.join(dir, f));157}158159/**160* Return the most recent snapshot for `slug`, or null. Polish reads this161* to find its fix backlog when the slug matches.162*/163export function readLatestSnapshot(slug, { cwd = process.cwd() } = {}) {164const all = listSnapshotsForSlug(slug, cwd);165if (!all.length) return null;166const latest = all[all.length - 1];167const body = fs.readFileSync(latest, 'utf-8');168return { path: latest, body, meta: parseFrontmatter(body) };169}170171/**172* Return the last `limit` snapshots' frontmatter, oldest → newest.173* Critique appends a one-line trend to its output using this.174*/175export function readTrend(slug, { limit = 5, cwd = process.cwd() } = {}) {176const all = listSnapshotsForSlug(slug, cwd);177const slice = all.slice(-limit);178return slice.map((file) => parseFrontmatter(fs.readFileSync(file, 'utf-8')));179}180181// ---- CLI ---------------------------------------------------------------182183function main(argv) {184const [cmd, ...args] = argv;185switch (cmd) {186case 'slug': {187const slug = slugFromTarget(args[0]);188if (!slug) { process.stderr.write('no stable slug for input\n'); process.exit(1); }189process.stdout.write(`${slug}\n`);190return;191}192case 'write': {193const [slug, bodyFile] = args;194if (!slug || !bodyFile) { process.stderr.write('usage: write <slug> <body-file>\n'); process.exit(1); }195const raw = fs.readFileSync(bodyFile, 'utf-8');196// The body file may be a full report. The caller passes the meta as197// a JSON object on stdin if it wants structured frontmatter; otherwise198// we write with minimal metadata.199let meta = {};200const metaArg = process.env.IMPECCABLE_CRITIQUE_META;201if (metaArg) {202try { meta = JSON.parse(metaArg); } catch { /* ignore */ }203}204const out = writeSnapshot({ slug, meta, body: raw });205process.stdout.write(`${out}\n`);206return;207}208case 'latest': {209const latest = readLatestSnapshot(args[0]);210if (!latest) { process.exit(2); }211process.stdout.write(latest.body);212return;213}214case 'trend': {215const rows = readTrend(args[0], { limit: args[1] ? Number(args[1]) : 5 });216process.stdout.write(JSON.stringify(rows, null, 2) + '\n');217return;218}219default:220process.stderr.write('usage: critique-storage.mjs <slug|write|latest|trend> [args]\n');221process.exit(1);222}223}224225function isMainModule() {226if (!process.argv[1]) return false;227try {228return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(process.argv[1]);229} catch {230// pathToFileURL normalizes Windows paths; keep it as a fallback for any231// environment where realpath is unavailable.232return import.meta.url === pathToFileURL(process.argv[1]).href;233}234}235236// Why the realpath check: generated skills are often reached through symlinked237// harness directories (for example a demo repo's `.agents` -> source `.agents`).238// Node resolves import.meta.url to the real file, while process.argv[1] keeps239// the symlink path. Comparing canonical paths prevents a silent exit-0 no-op.240if (isMainModule()) {241main(process.argv.slice(2));242}243