@shortwind/cli 0.1.0-beta.1 → 0.1.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{bench-NKKDz3ld.js → bench-BGTQAha8.js} +337 -82
- package/dist/bench-BGTQAha8.js.map +1 -0
- package/dist/bin.js +41 -5
- package/dist/bin.js.map +1 -1
- package/dist/catalog.generated-B_ds7MPV.js +83 -0
- package/dist/catalog.generated-B_ds7MPV.js.map +1 -0
- package/dist/index.d.ts +28 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/package.json +13 -3
- package/dist/bench-NKKDz3ld.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bench-BGTQAha8.js","names":[],"sources":["../src/fingerprint.ts","../src/detect.ts","../src/registry-source.ts","../src/lockfile.ts","../src/theme.ts","../src/bundler-config.ts","../src/agents-file.ts","../src/init.ts","../src/project.ts","../src/commands/add.ts","../src/commands/remove.ts","../src/commands/new.ts","../src/commands/reseal.ts","../src/commands/preset.ts","../src/commands/ls.ts","../src/commands/build.ts","../src/commands/dev.ts","../src/commands/upgrade.ts","../src/commands/verify.ts","../src/bench-corpus/default-recipes.ts","../src/bench-corpus/corpus.ts","../src/commands/lint.ts","../src/commands/bench.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n normalizeRecipeBody,\n PLACEHOLDER_SHA,\n RECIPE_SHA_HEX_LENGTH,\n} from \"@shortwind/core\";\n\n// Accept either the canonical short form or the legacy em-dash trailer\n// `— DO NOT EDIT THIS LINE`. The two-hyphen ASCII variant (`-- DO NOT…`) is\n// rejected on purpose: writeFamily has never emitted it, so accepting it would\n// let hand-edited files silently round-trip into the canonical form.\nconst HEADER_PATTERN = /^\\/\\*\\s*shortwind:\\s+(\\S+)@(\\S+)\\s+sha:([^\\s*]+)(?:\\s+—\\s+DO NOT EDIT THIS LINE)?\\s*\\*\\/\\s*$/;\n\nexport type RecipeHeader = {\n family: string;\n version: string;\n sha: string;\n};\n\nexport function extractHeader(source: string): RecipeHeader | null {\n const eol = source.indexOf(\"\\n\");\n const firstLine = (eol === -1 ? source : source.slice(0, eol)).replace(/\\r$/, \"\");\n const m = firstLine.match(HEADER_PATTERN);\n if (!m) return null;\n // All three capture groups are guaranteed non-empty when the regex matches\n // (the pattern uses `\\S+`/`[^\\s*]+`); the `!` asserts what the regex shape\n // already requires so we don't paper over an impossible-null with \"\".\n return { family: m[1]!, version: m[2]!, sha: m[3]! };\n}\n\nexport function bodyAfterHeader(source: string): string {\n const eol = source.indexOf(\"\\n\");\n return eol === -1 ? \"\" : source.slice(eol + 1);\n}\n\nexport function computeBodySha(source: string): string {\n // Normalization and width come from core (RECIPE_SHA_HEX_LENGTH), shared with\n // the registry sealer so a downloaded family's header sha can be verified\n // against its body. Previously this truncated to 6 hex (24 bits) — forgeable\n // by brute force in seconds — and normalized differently from the registry,\n // so the two could never agree.\n const normalized = normalizeRecipeBody(bodyAfterHeader(source));\n return createHash(\"sha256\").update(normalized).digest(\"hex\").slice(0, RECIPE_SHA_HEX_LENGTH);\n}\n\n// A fingerprint written by an older CLI (the 6-hex / 24-bit form), as opposed to\n// the current 16-hex form or the `000000` placeholder. Used to give projects\n// sealed before the width change a \"run `shortwind reseal`\" message instead of a\n// false \"tampered\" — the body is fine, only the seal format is stale.\nexport function isLegacyFingerprint(sha: string): boolean {\n return (\n sha !== PLACEHOLDER_SHA &&\n sha.length < RECIPE_SHA_HEX_LENGTH &&\n /^[0-9a-f]+$/.test(sha)\n );\n}\n\n// Verify a family fetched from a registry before trusting/resealing its bytes.\n// A built registry seals each family with a real content sha; if the header sha\n// doesn't match the body we recompute, the response was tampered with or\n// corrupted in transit (integrity is otherwise TLS-only). Unsealed content and\n// the `000000` source placeholder have no real fingerprint, so they pass.\nexport function verifyFetchedFamily(source: string, family: string): void {\n const header = extractHeader(source);\n if (!header || header.sha === PLACEHOLDER_SHA) return;\n // The header's family name is also part of the seal: a registry serving\n // `button.css` in response to a request for `card` is a mismatch even if its\n // own sha is internally consistent.\n if (header.family !== family) {\n throw new Error(\n `integrity check failed for \"${family}\": registry returned a recipe sealed as \"${header.family}\" — wrong family or a tampered/corrupted response`,\n );\n }\n const actual = computeBodySha(source);\n if (header.sha !== actual) {\n throw new Error(\n `integrity check failed for \"${family}\": header sha ${header.sha} does not match content sha ${actual} — the registry response was tampered with or corrupted in transit`,\n );\n }\n}\n\nexport function buildHeaderLine(family: string, version: string, sha: string): string {\n return `/* shortwind: ${family}@${version} sha:${sha} — DO NOT EDIT THIS LINE */`;\n}\n\nexport function rewriteHeaderSha(source: string, sha: string): string {\n const header = extractHeader(source);\n if (!header) return source;\n const newHeader = buildHeaderLine(header.family, header.version, sha);\n const eol = source.indexOf(\"\\n\");\n if (eol === -1) return newHeader;\n return newHeader + source.slice(eol);\n}\n\nexport function sealRecipeFile(source: string, family: string, version: string): string {\n const sha = computeBodySha(source);\n const header = buildHeaderLine(family, version, sha);\n const eol = source.indexOf(\"\\n\");\n const rest = eol === -1 ? \"\" : source.slice(eol);\n return header + rest;\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport type PackageManager = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\nexport type Bundler = \"vite\" | \"next\" | \"astro\" | \"unknown\";\nexport type Framework = \"react\" | \"vue\" | \"svelte\" | \"astro\" | \"plain\";\n\nexport type ProjectShape = {\n packageManager: PackageManager;\n tailwindVersion: string | null;\n tailwindMajor: 3 | 4 | null;\n bundler: Bundler;\n framework: Framework;\n hasPackageJson: boolean;\n};\n\n// Parse a local package.json into a plain object, attaching the file path to a\n// malformed-JSON error so the user sees what to fix rather than a bare\n// `Unexpected token` with no context. A non-object payload is treated as empty.\nfunction parsePackageJson(pkgPath: string): {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n packageManager?: string;\n} {\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(pkgPath, \"utf8\"));\n } catch (err) {\n throw new Error(`${pkgPath}: invalid JSON — ${(err as Error).message}`);\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) return {};\n return parsed as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n packageManager?: string;\n };\n}\n\nexport function detectProject(cwd: string): ProjectShape {\n const pkgPath = path.join(cwd, \"package.json\");\n const hasPackageJson = existsSync(pkgPath);\n type Pkg = {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n packageManager?: string;\n };\n const pkg: Pkg = hasPackageJson ? parsePackageJson(pkgPath) : {};\n\n const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };\n\n const packageManager = detectPackageManager(cwd, pkg.packageManager);\n const tailwindVersion = deps[\"tailwindcss\"] ?? null;\n const tailwindMajor = parseMajor(tailwindVersion);\n const bundler = detectBundler(deps);\n const framework = detectFramework(deps);\n\n return {\n packageManager,\n tailwindVersion,\n tailwindMajor,\n bundler,\n framework,\n hasPackageJson,\n };\n}\n\nfunction detectPackageManager(cwd: string, declared: string | undefined): PackageManager {\n if (declared) {\n const name = declared.split(\"@\")[0];\n if (name === \"pnpm\" || name === \"yarn\" || name === \"bun\" || name === \"npm\") return name;\n }\n if (existsSync(path.join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (existsSync(path.join(cwd, \"yarn.lock\"))) return \"yarn\";\n if (existsSync(path.join(cwd, \"bun.lockb\"))) return \"bun\";\n if (existsSync(path.join(cwd, \"package-lock.json\"))) return \"npm\";\n return \"npm\";\n}\n\nfunction parseMajor(version: string | null): 3 | 4 | null {\n if (!version) return null;\n const m = version.match(/(\\d+)/);\n if (!m) return null;\n const n = Number(m[1]);\n if (n === 3) return 3;\n if (n === 4) return 4;\n return null;\n}\n\nfunction detectBundler(deps: Record<string, string>): Bundler {\n if (deps[\"next\"]) return \"next\";\n if (deps[\"astro\"]) return \"astro\";\n if (deps[\"vite\"] || deps[\"@vitejs/plugin-react\"] || deps[\"@vitejs/plugin-vue\"]) return \"vite\";\n return \"unknown\";\n}\n\nfunction detectFramework(deps: Record<string, string>): Framework {\n if (deps[\"astro\"]) return \"astro\";\n if (deps[\"react\"] || deps[\"next\"]) return \"react\";\n if (deps[\"vue\"]) return \"vue\";\n if (deps[\"svelte\"] || deps[\"@sveltejs/kit\"]) return \"svelte\";\n return \"plain\";\n}\n","import { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport type Presets = Record<string, string[] | \"*\">;\n\nexport type RegistrySource = {\n origin: string;\n loadPresets: () => Promise<Presets>;\n loadFamily: (family: string) => Promise<string>;\n listAllFamilies: () => Promise<string[]>;\n};\n\n// Family names are surfaced from network/file inputs and are interpolated\n// into filesystem and HTTP paths; restrict to a safe alphabet to refuse\n// \"../etc\", \"foo/bar\", whitespace, and other traversal/injection attempts.\nconst FAMILY_RE = /^[a-z0-9][a-z0-9-]{0,63}$/i;\n\nexport function assertValidFamilyName(family: string): void {\n if (!FAMILY_RE.test(family)) {\n throw new Error(\n `invalid family name: ${JSON.stringify(family)} (must match ${FAMILY_RE})`,\n );\n }\n}\n\nexport function createRegistrySource(origin: string): RegistrySource {\n if (origin.startsWith(\"http://\")) {\n // Plaintext: recipe content arrives unauthenticated and the header sha we\n // verify can be rewritten in transit alongside the body. Warn loudly; TLS\n // is the only transport integrity we have for a remote registry.\n console.warn(\n `[shortwind] registry origin ${origin} uses plaintext http:// — recipe content is unauthenticated and tamperable in transit; prefer https://`,\n );\n return httpSource(origin);\n }\n if (origin.startsWith(\"https://\")) {\n return httpSource(origin);\n }\n return fileSource(origin);\n}\n\n// Resolve a registry origin to a source. The default (absent, or the bundled\n// sentinel) prefers the latest published @shortwind/catalog (fetched from\n// jsDelivr's immutable CDN) and falls back to the catalog bundled in the CLI\n// when offline. An http(s) URL or filesystem path is a custom/BYO registry.\n// This is what every command should call.\nexport async function resolveSource(origin: string | undefined): Promise<RegistrySource> {\n if (origin && origin !== BUNDLED_ORIGIN) return createRegistrySource(origin);\n return defaultCatalogSource();\n}\n\nconst CATALOG_PACKAGE = \"@shortwind/catalog\";\nconst NPM_TIMEOUT_MS = 3000;\n// Bound every registry content fetch so a stalled host can't hang init/add.\nconst FETCH_TIMEOUT_MS = 10000;\n\n// Prefer the newest published catalog; fall back to the embedded snapshot when\n// the network is unavailable. One decision per run (all-CDN or all-bundle) so a\n// preset can't reference a family resolved at a different version.\nasync function defaultCatalogSource(): Promise<RegistrySource> {\n try {\n const version = await resolveCatalogVersion();\n // jsDelivr serves npm package files at immutable, versioned URLs — cached\n // for free, correctly. The catalog tarball nests its output under\n // dist/registry, matching the http source's path layout.\n const base = `https://cdn.jsdelivr.net/npm/${CATALOG_PACKAGE}@${version}/dist/registry`;\n const cdn = httpSource(base);\n await cdn.loadPresets(); // probe — throws if unreachable, triggering fallback\n return cdn;\n } catch {\n return bundledSource();\n }\n}\n\nasync function resolveCatalogVersion(): Promise<string> {\n const res = await fetch(`https://registry.npmjs.org/${CATALOG_PACKAGE}`, {\n signal: AbortSignal.timeout(NPM_TIMEOUT_MS),\n headers: { accept: \"application/vnd.npm.install-v1+json\" },\n });\n if (!res.ok) throw new Error(`npm: ${res.status}`);\n const body = (await res.json()) as { \"dist-tags\"?: Record<string, string> };\n const tags = body[\"dist-tags\"] ?? {};\n const version = tags[\"latest\"] ?? tags[\"beta\"];\n if (!version) throw new Error(\"no published catalog version\");\n return version;\n}\n\n// The default catalog, embedded in the CLI (see catalog.generated.ts). Resolves\n// presets/families/recipes with zero network, so `init`/`add` always work even\n// offline or if a registry host is down — the failure that made an agent\n// reimplement Shortwind from scratch in a dogfood run.\nexport const BUNDLED_ORIGIN = \"bundled:@shortwind/catalog\";\n\nexport function bundledSource(): RegistrySource {\n // Imported lazily so the (large) generated module only loads when the bundled\n // catalog is actually used, not for every CLI invocation.\n let cache: Promise<typeof import(\"./catalog.generated.js\")> | null = null;\n const load = (): Promise<typeof import(\"./catalog.generated.js\")> => {\n cache ??= import(\"./catalog.generated.js\");\n return cache;\n };\n return {\n origin: BUNDLED_ORIGIN,\n async loadPresets() {\n return (await load()).CATALOG_PRESETS;\n },\n async loadFamily(family) {\n assertValidFamilyName(family);\n const { CATALOG_RECIPES } = await load();\n const css = CATALOG_RECIPES[family];\n if (css === undefined) throw new Error(`unknown family: ${family}`);\n return css;\n },\n async listAllFamilies() {\n return [...(await load()).CATALOG_FAMILIES];\n },\n };\n}\n\nfunction fileSource(origin: string): RegistrySource {\n const root = origin.startsWith(\"file://\") ? fileURLToPath(origin) : origin;\n return {\n origin,\n async loadPresets() {\n const body = await readFile(path.join(root, \"presets.json\"), \"utf8\");\n return JSON.parse(body) as Presets;\n },\n async loadFamily(family) {\n assertValidFamilyName(family);\n return readFile(path.join(root, \"recipes\", `${family}.css`), \"utf8\");\n },\n async listAllFamilies() {\n const { readdir } = await import(\"node:fs/promises\");\n const files = await readdir(path.join(root, \"recipes\"));\n return files\n .filter((f) => f.endsWith(\".css\"))\n .map((f) => f.replace(/\\.css$/, \"\"))\n .filter((name) => FAMILY_RE.test(name))\n .sort();\n },\n };\n}\n\nfunction httpSource(origin: string): RegistrySource {\n const base = origin.replace(/\\/+$/, \"\");\n // Every content fetch is bounded: without a signal a stalled connection hangs\n // `init`/`add` forever, defeating the offline-fallback (the bundled catalog\n // only triggers when the probe rejects, not when it hangs).\n const get = (url: string): Promise<Response> =>\n fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n // The reachability probe (defaultCatalogSource) fetches presets.json, then\n // `init` immediately fetches it again — cache the first result so the second\n // call is free.\n let presetsCache: Promise<Presets> | null = null;\n return {\n origin,\n loadPresets() {\n presetsCache ??= (async () => {\n const res = await get(`${base}/presets.json`);\n if (!res.ok) throw new Error(`presets.json: ${res.status} ${res.statusText}`);\n return (await res.json()) as Presets;\n })();\n return presetsCache;\n },\n async loadFamily(family) {\n assertValidFamilyName(family);\n const res = await get(`${base}/recipes/${family}.css`);\n if (!res.ok) throw new Error(`${family}.css: ${res.status} ${res.statusText}`);\n return res.text();\n },\n async listAllFamilies() {\n const res = await get(`${base}/index.json`);\n if (!res.ok) throw new Error(`index.json: ${res.status} ${res.statusText}`);\n const body = (await res.json()) as { families: string[] };\n return body.families.filter((name) => FAMILY_RE.test(name));\n },\n };\n}\n\nexport function resolvePresetFamilies(\n preset: string,\n presets: Presets,\n allFamilies: string[],\n): string[] {\n if (preset === \"none\") return [];\n const entry = presets[preset];\n if (entry === undefined) {\n throw new Error(\n `Unknown preset '${preset}'. Available: ${Object.keys(presets).join(\", \")}`,\n );\n }\n if (entry === \"*\") return allFamilies;\n return entry;\n}\n","import { existsSync } from \"node:fs\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport type LockEntry = { version: string; sha: string };\nexport type Lockfile = {\n version: number;\n registry: string;\n families: Record<string, LockEntry>;\n};\n\nexport const LOCK_FILENAME = \".shortwind-lock.json\";\nexport const LOCK_VERSION = 1;\n\nexport function lockPath(recipesDir: string): string {\n return path.join(recipesDir, LOCK_FILENAME);\n}\n\nexport async function readLockfile(recipesDir: string): Promise<Lockfile> {\n const p = lockPath(recipesDir);\n if (!existsSync(p)) {\n return { version: LOCK_VERSION, registry: \"\", families: {} };\n }\n const body = await readFile(p, \"utf8\");\n const raw = JSON.parse(body) as unknown;\n if (typeof raw !== \"object\" || raw === null) {\n throw new Error(`${p}: lockfile must be a JSON object`);\n }\n const r = raw as Record<string, unknown>;\n const families: Record<string, LockEntry> = {};\n if (r[\"families\"] !== undefined) {\n if (typeof r[\"families\"] !== \"object\" || r[\"families\"] === null) {\n throw new Error(`${p}: \"families\" must be an object`);\n }\n for (const [name, entry] of Object.entries(r[\"families\"] as Record<string, unknown>)) {\n if (typeof entry !== \"object\" || entry === null) {\n throw new Error(`${p}: families[\"${name}\"] must be an object`);\n }\n const e = entry as Record<string, unknown>;\n if (typeof e[\"version\"] !== \"string\" || typeof e[\"sha\"] !== \"string\") {\n throw new Error(\n `${p}: families[\"${name}\"] must have string \"version\" and \"sha\"`,\n );\n }\n families[name] = { version: e[\"version\"], sha: e[\"sha\"] };\n }\n }\n return {\n version: typeof r[\"version\"] === \"number\" ? r[\"version\"] : LOCK_VERSION,\n registry: typeof r[\"registry\"] === \"string\" ? r[\"registry\"] : \"\",\n families,\n };\n}\n\nexport async function writeLockfile(recipesDir: string, lock: Lockfile): Promise<void> {\n const sorted: Lockfile = {\n version: lock.version || LOCK_VERSION,\n registry: lock.registry,\n families: Object.fromEntries(\n Object.entries(lock.families).sort(([a], [b]) => a.localeCompare(b)),\n ),\n };\n await writeFile(lockPath(recipesDir), JSON.stringify(sorted, null, 2) + \"\\n\");\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { glob } from \"tinyglobby\";\n\n// The recipe catalog is authored against semantic color tokens (bg-card,\n// text-foreground, border-border, …). Tailwind generates nothing for those\n// unless the project defines the matching CSS variables + @theme map, so\n// without this block a freshly-initialised project renders every recipe\n// colorless. We scaffold a neutral default theme so recipes work on first run;\n// it's plain CSS the user owns and edits.\n\nexport const THEME_MARKER = \"/* shortwind:theme\";\n\nconst THEME_BLOCK = `${THEME_MARKER} — default tokens for the recipe catalog. Edit freely. */\n@custom-variant dark (&:is(.dark *));\n\n:root {\n --radius: 0.625rem;\n --background: oklch(1 0 0);\n --foreground: oklch(0.145 0 0);\n --card: oklch(1 0 0);\n --card-foreground: oklch(0.145 0 0);\n --popover: oklch(1 0 0);\n --popover-foreground: oklch(0.145 0 0);\n --primary: oklch(0.205 0 0);\n --primary-foreground: oklch(0.985 0 0);\n --secondary: oklch(0.97 0 0);\n --secondary-foreground: oklch(0.205 0 0);\n --muted: oklch(0.97 0 0);\n --muted-foreground: oklch(0.556 0 0);\n --accent: oklch(0.97 0 0);\n --accent-foreground: oklch(0.205 0 0);\n --destructive: oklch(0.577 0.245 27.325);\n --destructive-foreground: oklch(0.985 0 0);\n --border: oklch(0.922 0 0);\n --input: oklch(0.922 0 0);\n --ring: oklch(0.708 0 0);\n}\n\n.dark {\n --background: oklch(0.145 0 0);\n --foreground: oklch(0.985 0 0);\n --card: oklch(0.205 0 0);\n --card-foreground: oklch(0.985 0 0);\n --popover: oklch(0.205 0 0);\n --popover-foreground: oklch(0.985 0 0);\n --primary: oklch(0.922 0 0);\n --primary-foreground: oklch(0.205 0 0);\n --secondary: oklch(0.269 0 0);\n --secondary-foreground: oklch(0.985 0 0);\n --muted: oklch(0.269 0 0);\n --muted-foreground: oklch(0.708 0 0);\n --accent: oklch(0.269 0 0);\n --accent-foreground: oklch(0.985 0 0);\n --destructive: oklch(0.704 0.191 22.216);\n --destructive-foreground: oklch(0.985 0 0);\n --border: oklch(1 0 0 / 10%);\n --input: oklch(1 0 0 / 15%);\n --ring: oklch(0.556 0 0);\n}\n\n@theme inline {\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --color-card: var(--card);\n --color-card-foreground: var(--card-foreground);\n --color-popover: var(--popover);\n --color-popover-foreground: var(--popover-foreground);\n --color-primary: var(--primary);\n --color-primary-foreground: var(--primary-foreground);\n --color-secondary: var(--secondary);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-muted: var(--muted);\n --color-muted-foreground: var(--muted-foreground);\n --color-accent: var(--accent);\n --color-accent-foreground: var(--accent-foreground);\n --color-destructive: var(--destructive);\n --color-destructive-foreground: var(--destructive-foreground);\n --color-border: var(--border);\n --color-input: var(--input);\n --color-ring: var(--ring);\n --radius-sm: calc(var(--radius) - 4px);\n --radius-md: calc(var(--radius) - 2px);\n --radius-lg: var(--radius);\n --radius-xl: calc(var(--radius) + 4px);\n}\n\n@layer base {\n body {\n @apply bg-background text-foreground;\n }\n}\n/* end shortwind theme */\n`;\n\nconst TAILWIND_IMPORT_RE = /@import\\s+[\"']tailwindcss[\"'][^;\\n]*;?/;\n\nexport type ThemeAction = \"injected\" | \"created\" | \"skipped\";\nexport type ThemeResult = {\n themePath: string | null;\n action: ThemeAction;\n reason?: string;\n};\n\n// Scaffold the default theme into the project. Preference order:\n// 1. inject into the existing Tailwind CSS entry (the file with\n// `@import \"tailwindcss\"`), right after the import\n// 2. otherwise, on a Tailwind v4 project, write src/index.css with the import\n// plus the theme\n// 3. otherwise skip (v3 themes live in tailwind.config, not CSS)\n// Always idempotent and never clobbers a theme the user already defined.\nexport async function scaffoldTheme(cwd: string): Promise<ThemeResult> {\n const cssFiles = await glob([\"**/*.css\"], {\n cwd,\n absolute: true,\n onlyFiles: true,\n ignore: [\"**/node_modules/**\", \"**/dist/**\", \"**/.next/**\", \"**/.output/**\", \"recipes/**\"],\n });\n\n for (const file of cssFiles) {\n const source = await readFile(file, \"utf8\");\n if (!TAILWIND_IMPORT_RE.test(source)) continue;\n if (source.includes(THEME_MARKER)) {\n return { themePath: file, action: \"skipped\", reason: \"already scaffolded\" };\n }\n // Respect a theme the user already wrote rather than fighting it.\n if (/--background\\s*:/.test(source) || /@theme\\b/.test(source)) {\n return { themePath: file, action: \"skipped\", reason: \"project already defines a theme\" };\n }\n const m = source.match(TAILWIND_IMPORT_RE)!;\n const at = (m.index ?? 0) + m[0].length;\n const next = source.slice(0, at) + \"\\n\\n\" + THEME_BLOCK + source.slice(at);\n await writeFile(file, next);\n return { themePath: file, action: \"injected\" };\n }\n\n // No Tailwind CSS entry found — only scaffold one for v4 (v3 colors live in\n // tailwind.config.js, which is out of scope here).\n if (!isTailwindV4(cwd)) {\n return { themePath: null, action: \"skipped\", reason: \"no Tailwind v4 CSS entry found\" };\n }\n const target = path.join(cwd, \"src\", \"index.css\");\n if (existsSync(target)) {\n return { themePath: target, action: \"skipped\", reason: \"src/index.css exists without a tailwindcss import\" };\n }\n await mkdir(path.dirname(target), { recursive: true });\n await writeFile(target, `@import \"tailwindcss\";\\n\\n${THEME_BLOCK}`);\n return { themePath: target, action: \"created\" };\n}\n\nfunction isTailwindV4(cwd: string): boolean {\n try {\n const pkg = JSON.parse(readFileSync(path.join(cwd, \"package.json\"), \"utf8\")) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const range = pkg.devDependencies?.[\"tailwindcss\"] ?? pkg.dependencies?.[\"tailwindcss\"] ?? \"\";\n const m = range.match(/(\\d+)/);\n return m ? Number(m[1]) >= 4 : false;\n } catch {\n return false;\n }\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { Bundler } from \"./detect.js\";\n\n// init installs the bundler adapter and copies recipes, but the plugin still\n// has to be added to the bundler config. Agents (and humans) shouldn't have to\n// reverse-engineer that — wire Vite automatically, and for Next/Astro hand back\n// the exact snippet to paste.\n\nexport type BundlerWireAction = \"patched\" | \"manual\" | \"skipped\";\nexport type BundlerWireResult = {\n configPath: string | null;\n action: BundlerWireAction;\n snippet?: string;\n reason?: string;\n};\n\nconst VITE_CONFIGS = [\n \"vite.config.ts\",\n \"vite.config.mts\",\n \"vite.config.cts\",\n \"vite.config.js\",\n \"vite.config.mjs\",\n \"vite.config.cjs\",\n];\n\nconst VITE_SNIPPET = [\n `import { shortwind } from \"@shortwind/vite\";`,\n `// add shortwind() to the Vite plugins array — it runs in the pre phase,`,\n `// before Tailwind's scan:`,\n `// plugins: [shortwind(), tailwindcss(), react()]`,\n].join(\"\\n\");\n\nexport async function wireBundler(cwd: string, bundler: Bundler): Promise<BundlerWireResult> {\n if (bundler === \"vite\") return wireVite(cwd);\n if (bundler === \"next\") {\n return {\n configPath: null,\n action: \"manual\",\n snippet: `import { withShortwind } from \"@shortwind/next\";\\n// wrap your Next config: export default withShortwind(nextConfig);`,\n reason: \"Next config wiring is manual\",\n };\n }\n if (bundler === \"astro\") {\n return {\n configPath: null,\n action: \"manual\",\n snippet: `import shortwind from \"@shortwind/astro\";\\n// add to integrations: integrations: [shortwind()]`,\n reason: \"Astro config wiring is manual\",\n };\n }\n return { configPath: null, action: \"skipped\", reason: \"no supported bundler detected\" };\n}\n\nasync function wireVite(cwd: string): Promise<BundlerWireResult> {\n const configPath = VITE_CONFIGS.map((f) => path.join(cwd, f)).find((p) => existsSync(p));\n if (!configPath) {\n return { configPath: null, action: \"manual\", snippet: VITE_SNIPPET, reason: \"no vite config found\" };\n }\n\n const source = await readFile(configPath, \"utf8\");\n if (/@shortwind\\/vite/.test(source)) {\n return { configPath, action: \"skipped\", reason: \"plugin already wired\" };\n }\n\n // Inject `shortwind()` at the head of the plugins array so it's visibly\n // first; the plugin's own `enforce: \"pre\"` guarantees it runs ahead of\n // Tailwind regardless of position.\n const pluginsMatch = source.match(/plugins\\s*:\\s*\\[/);\n if (!pluginsMatch) {\n // Unusual config shape — don't risk corrupting it; hand back the snippet.\n return { configPath, action: \"manual\", snippet: VITE_SNIPPET, reason: \"no plugins array found\" };\n }\n\n const withImport = addImport(source, `import { shortwind } from \"@shortwind/vite\";`);\n const at = withImport.indexOf(pluginsMatch[0]) + pluginsMatch[0].length;\n const patched = withImport.slice(0, at) + \"shortwind(), \" + withImport.slice(at);\n await writeFile(configPath, patched);\n return { configPath, action: \"patched\" };\n}\n\n// Insert an import after the last existing top-level import, or at the top.\nfunction addImport(source: string, line: string): string {\n const importRe = /^[ \\t]*import[\\s\\S]*?from\\s+[\"'][^\"']+[\"'];?[ \\t]*$/gm;\n let lastEnd = -1;\n for (const m of source.matchAll(importRe)) {\n lastEnd = (m.index ?? 0) + m[0].length;\n }\n if (lastEnd === -1) return `${line}\\n${source}`;\n return source.slice(0, lastEnd) + `\\n${line}` + source.slice(lastEnd);\n}\n","import { existsSync } from \"node:fs\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\n// Even with SKILL.md present, agents under-reach for recipes (writing raw\n// `flex items-center gap-*` instead of `@row`). A one-line pointer in the\n// project's agent-instructions file — where coding agents already look — nudges\n// them to the catalog without bloating their context.\n\n// Recognisable substring used to detect a pointer we already wrote, so the line\n// is never duplicated on re-init.\nconst MARKER = \"skills/shortwind/SKILL.md\";\n\nfunction line(skillRel: string): string {\n return `For UI, prefer Shortwind \\`@recipe\\` class names (e.g. \\`@card\\`, \\`@btn-primary\\`, \\`@row\\`) over raw Tailwind where a recipe fits — full catalog in \\`${skillRel}\\`.`;\n}\n\n// Files coding agents read for project instructions, in preference order.\nconst CANDIDATES = [\"AGENTS.md\", \"CLAUDE.md\"];\n\nexport type AgentsFileAction = \"appended\" | \"created\" | \"skipped\";\nexport type AgentsFileResult = {\n path: string | null;\n action: AgentsFileAction;\n};\n\nexport async function wireAgentsInstructions(\n cwd: string,\n skillPath: string,\n): Promise<AgentsFileResult> {\n const skillRel = path.relative(cwd, skillPath).split(path.sep).join(\"/\");\n const pointer = line(skillRel);\n\n // Append to every existing agent-instructions file (idempotently), so the\n // nudge lands wherever the project's agent actually looks.\n let touched: AgentsFileResult | null = null;\n for (const name of CANDIDATES) {\n const file = path.join(cwd, name);\n if (!existsSync(file)) continue;\n const current = await readFile(file, \"utf8\");\n if (current.includes(MARKER)) {\n touched ??= { path: file, action: \"skipped\" };\n continue;\n }\n const sep = current.endsWith(\"\\n\\n\") ? \"\" : current.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n await writeFile(file, current + sep + pointer + \"\\n\");\n return { path: file, action: \"appended\" };\n }\n if (touched) return touched;\n\n // None exist — create AGENTS.md (the cross-tool standard).\n const target = path.join(cwd, \"AGENTS.md\");\n await writeFile(target, `# AGENTS.md\\n\\n${pointer}\\n`);\n return { path: target, action: \"created\" };\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { applyEdits, modify, parse as parseJsonc } from \"jsonc-parser\";\nimport { buildRegistry, parseRecipeFile, renderSkillMarkdown } from \"@shortwind/core\";\nimport type { Recipe } from \"@shortwind/core\";\nimport {\n computeBodySha,\n extractHeader,\n rewriteHeaderSha,\n verifyFetchedFamily,\n} from \"./fingerprint.js\";\nimport { detectProject, type PackageManager } from \"./detect.js\";\nimport {\n BUNDLED_ORIGIN,\n resolveSource,\n resolvePresetFamilies,\n type RegistrySource,\n} from \"./registry-source.js\";\nimport { readLockfile, writeLockfile } from \"./lockfile.js\";\nimport { scaffoldTheme, type ThemeAction } from \"./theme.js\";\nimport { wireBundler, type BundlerWireAction } from \"./bundler-config.js\";\nimport { wireAgentsInstructions, type AgentsFileAction } from \"./agents-file.js\";\n\n// Default to the catalog bundled in the CLI — no network, always available.\n// Pass --registry <url> (or a path) for a custom/BYO registry.\nexport const DEFAULT_REGISTRY = BUNDLED_ORIGIN;\n\nexport type InitOptions = {\n cwd: string;\n preset: string;\n registry?: string;\n installPackages?: InstallPackages;\n};\n\nexport type InstallPackages = (\n pm: PackageManager,\n packages: string[],\n cwd: string,\n) => Promise<void>;\n\nexport type InitResult = {\n packageManager: PackageManager;\n preset: string;\n registry: string;\n families: string[];\n installedPackages: string[];\n installedFamilies: string[];\n skippedFamilies: string[];\n configPath: string;\n vscodePath: string;\n huskyPath: string;\n skillPath: string;\n themePath: string | null;\n themeAction: ThemeAction;\n bundlerConfigPath: string | null;\n bundlerConfigAction: BundlerWireAction;\n bundlerConfigSnippet?: string;\n agentsFilePath: string | null;\n agentsFileAction: AgentsFileAction;\n installOk: boolean;\n installError: string | null;\n};\n\nexport async function init(options: InitOptions): Promise<InitResult> {\n const cwd = path.resolve(options.cwd);\n const registry = options.registry ?? DEFAULT_REGISTRY;\n const source = await resolveSource(registry);\n\n const shape = detectProject(cwd);\n\n const families = await resolveFamilies(options.preset, source);\n const pkgs = pickPackages(shape.bundler);\n\n // Pin adapters to this CLI's own version so a released CLI always installs the\n // adapters it shipped with — `latest` can lag the `beta` tag (or vice-versa),\n // and a bare `add @shortwind/tailwind` would resolve `latest` and drift out of\n // sync with the CLI/core/catalog.\n const version = cliVersion();\n const specs = version ? pkgs.map((p) => `${p}@${version}`) : pkgs;\n\n // The adapter install is a convenience, not the point — copying recipes and\n // generating SKILL.md is. A peer-dep conflict or pnpm's non-zero exit on\n // ERR_PNPM_IGNORED_BUILDS must not abort the whole scaffold, so failures here\n // are surfaced (installOk/installError) rather than thrown.\n const installer = options.installPackages ?? defaultInstall;\n let installOk = true;\n let installError: string | null = null;\n if (specs.length > 0) {\n try {\n await installer(shape.packageManager, specs, cwd);\n } catch (err) {\n installOk = false;\n installError = err instanceof Error ? err.message : String(err);\n }\n }\n\n const recipesDir = path.join(cwd, \"recipes\");\n const { installed, skipped } = await copyRecipes(source, families, recipesDir);\n await updateLockfile(recipesDir, registry, installed);\n\n const configPath = path.join(cwd, \"shortwind.config.json\");\n await writeConfig(configPath, { registry, recipesDir: \"recipes\" });\n\n const vscodePath = path.join(cwd, \".vscode\", \"settings.json\");\n await wireVscodeClassRegex(vscodePath);\n\n const huskyPath = path.join(cwd, \".husky\", \"pre-commit\");\n await installHuskyHook(huskyPath);\n\n const skillPath = path.join(cwd, \"skills\", \"shortwind\", \"SKILL.md\");\n await writeSkillMd(skillPath, recipesDir, families);\n\n // Recipes reference semantic color tokens; scaffold the default theme so they\n // render with color on first run instead of as colorless markup.\n const theme = await scaffoldTheme(cwd);\n\n // Wire the plugin into the bundler config (Vite auto-patches; Next/Astro\n // return a snippet for the summary).\n const bundlerConfig = await wireBundler(cwd, shape.bundler);\n\n // Point coding agents at the recipe catalog with a one-liner.\n const agentsFile = await wireAgentsInstructions(cwd, skillPath);\n\n return {\n packageManager: shape.packageManager,\n preset: options.preset,\n registry,\n families,\n installedPackages: pkgs,\n installedFamilies: installed,\n skippedFamilies: skipped,\n configPath,\n vscodePath,\n huskyPath,\n skillPath,\n themePath: theme.themePath,\n themeAction: theme.action,\n bundlerConfigPath: bundlerConfig.configPath,\n bundlerConfigAction: bundlerConfig.action,\n ...(bundlerConfig.snippet ? { bundlerConfigSnippet: bundlerConfig.snippet } : {}),\n agentsFilePath: agentsFile.path,\n agentsFileAction: agentsFile.action,\n installOk,\n installError,\n };\n}\n\nasync function resolveFamilies(preset: string, source: RegistrySource): Promise<string[]> {\n if (preset === \"none\") return [];\n const presets = await source.loadPresets();\n const all = await source.listAllFamilies();\n return resolvePresetFamilies(preset, presets, all);\n}\n\n// The CLI's own version, read from its package.json at runtime (one dir above\n// the compiled dist/ — and above src/ under vitest). Returns null if it can't be\n// read, in which case install falls back to bare (latest-tag) specs.\nexport function cliVersion(): string | null {\n try {\n const pkgUrl = new URL(\"../package.json\", import.meta.url);\n const pkg = JSON.parse(readFileSync(fileURLToPath(pkgUrl), \"utf8\")) as { version?: string };\n return pkg.version ?? null;\n } catch {\n return null;\n }\n}\n\nfunction pickPackages(bundler: ReturnType<typeof detectProject>[\"bundler\"]): string[] {\n const base = [\"@shortwind/tailwind\"];\n switch (bundler) {\n case \"vite\":\n return [...base, \"@shortwind/vite\"];\n case \"next\":\n return [...base, \"@shortwind/next\"];\n case \"astro\":\n return [...base, \"@shortwind/astro\"];\n default:\n return base;\n }\n}\n\nconst defaultInstall: InstallPackages = async (pm, packages, cwd) => {\n const { spawn } = await import(\"node:child_process\");\n const args = installArgs(pm, packages);\n await new Promise<void>((resolve, reject) => {\n const child = spawn(pm, args, { cwd, stdio: \"inherit\" });\n child.on(\"error\", reject);\n child.on(\"exit\", (code) =>\n code === 0 ? resolve() : reject(new Error(`${pm} ${args.join(\" \")} exited ${code}`)),\n );\n });\n};\n\nfunction installArgs(pm: PackageManager, packages: string[]): string[] {\n switch (pm) {\n case \"pnpm\":\n return [\"add\", \"-D\", ...packages];\n case \"yarn\":\n return [\"add\", \"-D\", ...packages];\n case \"bun\":\n return [\"add\", \"-d\", ...packages];\n case \"npm\":\n default:\n return [\"install\", \"-D\", ...packages];\n }\n}\n\nasync function updateLockfile(\n recipesDir: string,\n registry: string,\n newlyInstalled: string[],\n): Promise<void> {\n const lock = await readLockfile(recipesDir);\n if (!lock.registry) lock.registry = registry;\n for (const family of newlyInstalled) {\n const target = path.join(recipesDir, `${family}.css`);\n if (!existsSync(target)) continue;\n const source = readFileSync(target, \"utf8\");\n const header = extractHeader(source);\n if (!header) {\n // Recipes without a fingerprint header are not lockable — fail\n // loudly so the user can fix the recipe rather than discover\n // later that `shortwind upgrade` skips this family silently.\n throw new Error(\n `recipe \"${family}\" has no fingerprint header — refusing to add to lockfile`,\n );\n }\n lock.families[family] = { version: header.version, sha: header.sha };\n }\n await writeLockfile(recipesDir, lock);\n}\n\nasync function copyRecipes(\n source: RegistrySource,\n families: string[],\n recipesDir: string,\n): Promise<{ installed: string[]; skipped: string[] }> {\n await mkdir(recipesDir, { recursive: true });\n const installed: string[] = [];\n const skipped: string[] = [];\n for (const family of families) {\n const target = path.join(recipesDir, `${family}.css`);\n if (existsSync(target)) {\n skipped.push(family);\n continue;\n }\n const body = await source.loadFamily(family);\n // Reject a tampered/corrupted registry response before resealing.\n verifyFetchedFamily(body, family);\n const sha = computeBodySha(body);\n const sealed = rewriteHeaderSha(body, sha);\n await writeFile(target, sealed);\n installed.push(family);\n }\n return { installed, skipped };\n}\n\nasync function writeConfig(\n configPath: string,\n next: { registry: string; recipesDir: string },\n): Promise<void> {\n const desired = {\n registry: next.registry,\n recipesDir: next.recipesDir,\n outputPath: \"skills/shortwind/SKILL.md\",\n };\n if (!existsSync(configPath)) {\n await writeFile(configPath, JSON.stringify(desired, null, 2) + \"\\n\");\n return;\n }\n let current: unknown;\n try {\n current = JSON.parse(await readFile(configPath, \"utf8\"));\n } catch (err) {\n throw new Error(`${configPath}: invalid JSON — ${(err as Error).message}`);\n }\n const base =\n current !== null && typeof current === \"object\" && !Array.isArray(current)\n ? (current as Record<string, unknown>)\n : {};\n const merged = { ...base, ...desired };\n await writeFile(configPath, JSON.stringify(merged, null, 2) + \"\\n\");\n}\n\nconst CLASS_REGEX_KEY = [\"tailwindCSS.experimental.classRegex\"];\nconst CLASS_REGEX_VALUE = [\n [\"class\\\\s*[=:]\\\\s*['\\\"]([^'\\\"]*)['\\\"]\", \"([\\\\w-@/:]+)\"],\n [\"className\\\\s*=\\\\s*['\\\"]([^'\\\"]*)['\\\"]\", \"([\\\\w-@/:]+)\"],\n];\n\nasync function wireVscodeClassRegex(vscodePath: string): Promise<void> {\n await mkdir(path.dirname(vscodePath), { recursive: true });\n let body: string;\n if (existsSync(vscodePath)) {\n body = await readFile(vscodePath, \"utf8\");\n } else {\n body = \"{}\\n\";\n }\n const edits = modify(body, CLASS_REGEX_KEY, CLASS_REGEX_VALUE, {\n formattingOptions: { tabSize: 2, insertSpaces: true },\n });\n const next = applyEdits(body, edits);\n // sanity — make sure it's parseable\n parseJsonc(next);\n await writeFile(vscodePath, next.endsWith(\"\\n\") ? next : next + \"\\n\");\n}\n\nconst HUSKY_LINE = \"npx shortwind build\";\n\nasync function installHuskyHook(huskyPath: string): Promise<void> {\n await mkdir(path.dirname(huskyPath), { recursive: true });\n if (!existsSync(huskyPath)) {\n await writeFile(huskyPath, `${HUSKY_LINE}\\n`, { mode: 0o755 });\n return;\n }\n const current = await readFile(huskyPath, \"utf8\");\n if (current.includes(HUSKY_LINE)) return;\n const next = current.endsWith(\"\\n\") ? current + HUSKY_LINE + \"\\n\" : current + \"\\n\" + HUSKY_LINE + \"\\n\";\n await writeFile(huskyPath, next, { mode: 0o755 });\n}\n\nasync function writeSkillMd(\n skillPath: string,\n recipesDir: string,\n families: string[],\n): Promise<void> {\n const allRecipes: Recipe[] = [];\n const guidance: Record<string, string> = {};\n const problems: string[] = [];\n for (const family of families) {\n const filePath = path.join(recipesDir, `${family}.css`);\n if (!existsSync(filePath)) continue;\n const source = readFileSync(filePath, \"utf8\");\n const parsed = parseRecipeFile(source, `${family}.css`);\n if (parsed.ok) {\n allRecipes.push(...parsed.value.recipes);\n if (parsed.value.guidance) guidance[family] = parsed.value.guidance;\n } else {\n problems.push(`${family}.css: ${parsed.errors.map((e) => e.message).join(\"; \")}`);\n }\n }\n const resolved = buildRegistry(allRecipes, { guidance });\n if (problems.length > 0 || !resolved.ok) {\n // Don't write a degraded/empty SKILL.md from broken recipes; surface the\n // problem instead (the installed catalog should always be valid here).\n const all = resolved.ok ? problems : [...problems, ...resolved.errors.map((e) => e.message)];\n console.warn(`[shortwind] SKILL.md not generated — recipe errors:\\n ${all.join(\"\\n \")}`);\n return;\n }\n await mkdir(path.dirname(skillPath), { recursive: true });\n await writeFile(skillPath, renderSkillMarkdown(resolved.value, { order: families }));\n}\n","import { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { buildRegistry, parseRecipeFile, renderSkillMarkdown } from \"@shortwind/core\";\nimport type { Recipe } from \"@shortwind/core\";\nimport { BUNDLED_ORIGIN } from \"./registry-source.js\";\n\nexport type ShortwindConfig = {\n registry: string;\n recipesDir: string;\n outputPath: string;\n};\n\nexport const DEFAULT_CONFIG: ShortwindConfig = {\n // Default to the bundled catalog (CDN-first with an offline fallback, via\n // resolveSource) rather than a hardcoded URL — a project with no explicit\n // registry must not hit the network for `add`, which was the exact failure\n // the bundled catalog exists to prevent. The old default also pointed at a\n // /registry endpoint that 404s in production.\n registry: BUNDLED_ORIGIN,\n recipesDir: \"recipes\",\n outputPath: \"skills/shortwind/SKILL.md\",\n};\n\n// shortwind.config.json is committed to a repo and read when the tool runs in a\n// fresh checkout, so it's untrusted input. `recipesDir`/`outputPath` are joined\n// with cwd and then read/written/`rm`'d — a `../` or absolute value would let a\n// cloned repo make `shortwind build`/`add` clobber files outside the project.\nfunction assertConfigString(value: unknown, field: string, configPath: string): string {\n if (typeof value !== \"string\") {\n throw new Error(`${configPath}: \"${field}\" must be a string`);\n }\n return value;\n}\n\nfunction assertWithinCwd(cwd: string, value: string, field: string, configPath: string): string {\n const rel = path.relative(cwd, path.resolve(cwd, value));\n if (rel === \"\" || rel.startsWith(\"..\") || path.isAbsolute(rel)) {\n throw new Error(\n `${configPath}: \"${field}\" (${JSON.stringify(value)}) must be a path inside the project directory`,\n );\n }\n return value;\n}\n\nexport async function readConfig(cwd: string): Promise<ShortwindConfig> {\n const configPath = path.join(cwd, \"shortwind.config.json\");\n if (!existsSync(configPath)) return DEFAULT_CONFIG;\n const body = await readFile(configPath, \"utf8\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch (err) {\n throw new Error(`${configPath}: invalid JSON — ${(err as Error).message}`);\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(`${configPath}: expected a JSON object`);\n }\n const merged = { ...DEFAULT_CONFIG, ...(parsed as Partial<ShortwindConfig>) };\n const registry = assertConfigString(merged.registry, \"registry\", configPath);\n const recipesDir = assertWithinCwd(\n cwd,\n assertConfigString(merged.recipesDir, \"recipesDir\", configPath),\n \"recipesDir\",\n configPath,\n );\n const outputPath = assertWithinCwd(\n cwd,\n assertConfigString(merged.outputPath, \"outputPath\", configPath),\n \"outputPath\",\n configPath,\n );\n return { registry, recipesDir, outputPath };\n}\n\nexport function installedFamilies(recipesDir: string): string[] {\n if (!existsSync(recipesDir)) return [];\n return readdirSync(recipesDir)\n .filter((f) => f.endsWith(\".css\"))\n .map((f) => f.replace(/\\.css$/, \"\"))\n .sort();\n}\n\nexport function parseInstalledFamily(\n recipesDir: string,\n family: string,\n): { recipes: Recipe[]; header: { family: string; version: string; sha: string } | null } | null {\n const filePath = path.join(recipesDir, `${family}.css`);\n if (!existsSync(filePath)) return null;\n const source = readFileSync(filePath, \"utf8\");\n const result = parseRecipeFile(source, `${family}.css`);\n if (!result.ok) return null;\n return { recipes: result.value.recipes, header: result.value.header };\n}\n\nexport async function regenerateSkillMd(cwd: string, config: ShortwindConfig): Promise<string> {\n const recipesDir = path.join(cwd, config.recipesDir);\n const families = installedFamilies(recipesDir);\n const skillPath = path.join(cwd, config.outputPath);\n\n const allRecipes: Recipe[] = [];\n const guidance: Record<string, string> = {};\n const problems: string[] = [];\n for (const family of families) {\n const filePath = path.join(recipesDir, `${family}.css`);\n const source = readFileSync(filePath, \"utf8\");\n const parsed = parseRecipeFile(source, `${family}.css`);\n if (parsed.ok) {\n allRecipes.push(...parsed.value.recipes);\n if (parsed.value.guidance) guidance[family] = parsed.value.guidance;\n } else {\n problems.push(`${family}.css: ${parsed.errors.map((e) => e.message).join(\"; \")}`);\n }\n }\n const resolved = buildRegistry(allRecipes, { guidance });\n\n // Don't overwrite a populated SKILL.md with a degraded/empty one when recipes\n // fail to parse or resolve (a cycle, an unknown reference). Leave the existing\n // file untouched and surface what to fix — silently writing an empty SKILL.md\n // is data loss, and `build` rejects the same state.\n if (problems.length > 0 || !resolved.ok) {\n const all = resolved.ok ? problems : [...problems, ...resolved.errors.map((e) => e.message)];\n console.warn(\n `[shortwind] SKILL.md not regenerated — fix these recipe errors first:\\n ${all.join(\n \"\\n \",\n )}\\n ${path.relative(cwd, skillPath)} left unchanged.`,\n );\n return skillPath;\n }\n\n const { mkdir } = await import(\"node:fs/promises\");\n await mkdir(path.dirname(skillPath), { recursive: true });\n await writeFile(skillPath, renderSkillMarkdown(resolved.value, { order: families }));\n return skillPath;\n}\n\n/**\n * Rewrite a recipe file so that every occurrence of `<from>` is replaced with `<to>`:\n * - fingerprint header family slot\n * - recipe names (after `@recipe `)\n * - cross-recipe `@from` / `@from-*` references in bodies\n *\n * Descriptions and unrelated text are untouched.\n */\nexport function renameFamilyInSource(source: string, from: string, to: string): string {\n const escape = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const f = escape(from);\n let out = source;\n out = out.replace(new RegExp(`(\\\\bshortwind:\\\\s+)${f}(\\\\b|@)`, \"g\"), `$1${to}$2`);\n out = out.replace(new RegExp(`(@recipe\\\\s+)${f}(\\\\b|-)`, \"g\"), `$1${to}$2`);\n out = out.replace(new RegExp(`@${f}(\\\\b|-)`, \"g\"), `@${to}$1`);\n return out;\n}\n","import { existsSync, readdirSync } from \"node:fs\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { resolveSource, assertValidFamilyName } from \"../registry-source.js\";\nimport {\n computeBodySha,\n extractHeader,\n rewriteHeaderSha,\n verifyFetchedFamily,\n} from \"../fingerprint.js\";\nimport { readLockfile, writeLockfile, type Lockfile } from \"../lockfile.js\";\nimport {\n readConfig,\n regenerateSkillMd,\n renameFamilyInSource,\n parseInstalledFamily,\n} from \"../project.js\";\n\nexport type AddOptions = {\n cwd: string;\n families: string[];\n as?: string;\n all?: boolean;\n force?: boolean;\n registry?: string;\n};\n\nexport type AddResult = {\n added: string[];\n skipped: string[];\n overwritten: string[];\n missingDependencies: { family: string; references: string[] }[];\n lockfile: Lockfile;\n skillPath: string;\n};\n\nexport async function add(options: AddOptions): Promise<AddResult> {\n const cwd = path.resolve(options.cwd);\n const config = await readConfig(cwd);\n const registry = options.registry ?? config.registry;\n const source = await resolveSource(registry);\n const recipesDir = path.join(cwd, config.recipesDir);\n await mkdir(recipesDir, { recursive: true });\n\n const lock = await readLockfile(recipesDir);\n if (!lock.registry) lock.registry = registry;\n\n const requested = options.all ? await source.listAllFamilies() : options.families;\n if (options.all && options.as) {\n throw new Error(\"--as cannot be combined with --all\");\n }\n if (options.as && requested.length !== 1) {\n throw new Error(\"--as requires exactly one family argument\");\n }\n // The source family is validated by loadFamily, but the --as target flows\n // into the written filename and into renameFamilyInSource's replacement\n // strings — an unvalidated `../../x` writes outside recipesDir and `$&`/`$1`\n // patterns corrupt the output. Hold it to the same family-name alphabet.\n if (options.as !== undefined) assertValidFamilyName(options.as);\n\n const added: string[] = [];\n const skipped: string[] = [];\n const overwritten: string[] = [];\n const missingDependencies: { family: string; references: string[] }[] = [];\n\n // Scan the installed-recipe namespace once before the loop, then update\n // it as each family lands. Previously collectMissingCrossFamilyDeps()\n // re-walked the whole recipes dir for every requested family, which is\n // O(n²) on `--all` installs.\n const installedRecipeNames = readAllInstalledRecipeNames(recipesDir);\n\n for (const family of requested) {\n const targetName = options.as ?? family;\n const targetPath = path.join(recipesDir, `${targetName}.css`);\n const exists = existsSync(targetPath);\n if (exists && !options.force) {\n skipped.push(targetName);\n continue;\n }\n\n const sourceCss = await source.loadFamily(family);\n // Verify the publisher's seal on the bytes that arrived before resealing\n // them under our own header — a tampered registry/CDN response must not be\n // silently re-sealed and trusted.\n verifyFetchedFamily(sourceCss, family);\n const renamed = options.as ? renameFamilyInSource(sourceCss, family, options.as) : sourceCss;\n const sha = computeBodySha(renamed);\n const finalCss = rewriteHeaderSha(renamed, sha);\n await writeFile(targetPath, finalCss);\n\n const header = extractHeader(finalCss);\n if (header) {\n lock.families[targetName] = { version: header.version, sha };\n }\n\n if (exists) overwritten.push(targetName);\n else added.push(targetName);\n\n const parsed = parseInstalledFamily(recipesDir, targetName);\n if (parsed) {\n for (const r of parsed.recipes) installedRecipeNames.add(r.name);\n const ownNames = new Set(parsed.recipes.map((r) => r.name));\n const missing = new Set<string>();\n for (const recipe of parsed.recipes) {\n for (const ref of recipe.references) {\n if (ownNames.has(ref)) continue;\n if (installedRecipeNames.has(ref)) continue;\n missing.add(ref);\n }\n }\n if (missing.size > 0) {\n missingDependencies.push({\n family: targetName,\n references: Array.from(missing).sort(),\n });\n }\n }\n }\n\n await writeLockfile(recipesDir, lock);\n const skillPath = await regenerateSkillMd(cwd, config);\n\n return { added, skipped, overwritten, missingDependencies, lockfile: lock, skillPath };\n}\n\nfunction readAllInstalledRecipeNames(recipesDir: string): Set<string> {\n const names = new Set<string>();\n for (const fam of readDirFamilies(recipesDir)) {\n const p = parseInstalledFamily(recipesDir, fam);\n if (!p) continue;\n for (const r of p.recipes) names.add(r.name);\n }\n return names;\n}\n\nfunction readDirFamilies(recipesDir: string): string[] {\n if (!existsSync(recipesDir)) return [];\n return readdirSync(recipesDir)\n .filter((f) => f.endsWith(\".css\"))\n .map((f) => f.replace(/\\.css$/, \"\"));\n}\n","import { existsSync } from \"node:fs\";\nimport { rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n readConfig,\n regenerateSkillMd,\n installedFamilies,\n parseInstalledFamily,\n} from \"../project.js\";\nimport { readLockfile, writeLockfile, type Lockfile } from \"../lockfile.js\";\n\nexport type RemoveOptions = {\n cwd: string;\n families: string[];\n};\n\nexport type RemoveResult = {\n removed: string[];\n notFound: string[];\n brokenDependents: { dependent: string; references: string[] }[];\n lockfile: Lockfile;\n skillPath: string;\n};\n\nexport async function remove(options: RemoveOptions): Promise<RemoveResult> {\n const cwd = path.resolve(options.cwd);\n const config = await readConfig(cwd);\n const recipesDir = path.join(cwd, config.recipesDir);\n\n const lock = await readLockfile(recipesDir);\n const removed: string[] = [];\n const notFound: string[] = [];\n // Snapshot every recipe name in each family *before* deleting the file, so\n // broken-dependent detection can look for exact ref matches instead of\n // splitting the ref on the first `-` (which mis-classifies hyphenated\n // family names like `text-stack`).\n const removedRecipeNames = new Set<string>();\n\n for (const family of options.families) {\n const target = path.join(recipesDir, `${family}.css`);\n if (!existsSync(target)) {\n notFound.push(family);\n continue;\n }\n const parsed = parseInstalledFamily(recipesDir, family);\n if (parsed) {\n for (const r of parsed.recipes) removedRecipeNames.add(r.name);\n }\n await rm(target);\n delete lock.families[family];\n removed.push(family);\n }\n\n await writeLockfile(recipesDir, lock);\n\n const brokenDependents = collectBrokenDependents(recipesDir, removedRecipeNames);\n const skillPath = await regenerateSkillMd(cwd, config);\n\n return { removed, notFound, brokenDependents, lockfile: lock, skillPath };\n}\n\nfunction collectBrokenDependents(\n recipesDir: string,\n removedRecipeNames: Set<string>,\n): { dependent: string; references: string[] }[] {\n if (removedRecipeNames.size === 0) return [];\n const out: { dependent: string; references: string[] }[] = [];\n for (const family of installedFamilies(recipesDir)) {\n const parsed = parseInstalledFamily(recipesDir, family);\n if (!parsed) continue;\n const broken = new Set<string>();\n for (const recipe of parsed.recipes) {\n for (const ref of recipe.references) {\n if (removedRecipeNames.has(ref)) broken.add(ref);\n }\n }\n if (broken.size > 0) out.push({ dependent: family, references: Array.from(broken).sort() });\n }\n return out;\n}\n","import { existsSync } from \"node:fs\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { readConfig, regenerateSkillMd } from \"../project.js\";\nimport { assertValidFamilyName } from \"../registry-source.js\";\n\nexport type NewOptions = {\n cwd: string;\n family: string;\n force?: boolean;\n};\n\nexport type NewResult = {\n familyPath: string;\n skillPath: string;\n};\n\nexport class NewFamilyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"NewFamilyError\";\n }\n}\n\n// A minimal, valid starter family: the fingerprint header (with the source\n// placeholder sha), an @guide stub, and one example recipe so the file parses,\n// resolves, and shows up in SKILL.md immediately.\nfunction template(family: string): string {\n return [\n `/* shortwind: ${family}@0.0.1 sha:000000 */`,\n ``,\n `/* @guide`,\n ` TODO: one or two lines on when to reach for these recipes, and which`,\n ` easy-to-confuse name to prefer. */`,\n ``,\n `/* TODO: describe this recipe. */`,\n `@recipe ${family} {`,\n ` p-4`,\n `}`,\n ``,\n ].join(\"\\n\");\n}\n\nexport async function newFamily(options: NewOptions): Promise<NewResult> {\n assertValidFamilyName(options.family);\n const cwd = path.resolve(options.cwd);\n const config = await readConfig(cwd);\n const recipesDir = path.join(cwd, config.recipesDir);\n const familyPath = path.join(recipesDir, `${options.family}.css`);\n\n if (existsSync(familyPath) && !options.force) {\n throw new NewFamilyError(\n `${path.join(config.recipesDir, `${options.family}.css`)} already exists (use --force to overwrite)`,\n );\n }\n\n await mkdir(recipesDir, { recursive: true });\n await writeFile(familyPath, template(options.family));\n\n // Fold the new family into SKILL.md so the agent/editor sees it right away.\n const skillPath = await regenerateSkillMd(cwd, config);\n return { familyPath, skillPath };\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { computeBodySha, extractHeader, rewriteHeaderSha } from \"../fingerprint.js\";\nimport { readLockfile, writeLockfile } from \"../lockfile.js\";\nimport { installedFamilies, readConfig } from \"../project.js\";\n\nexport type ResealOptions = {\n cwd: string;\n families?: string[];\n};\n\nexport type ResealResult = {\n resealed: string[];\n unchanged: string[];\n notFound: string[];\n noHeader: string[];\n};\n\n// You own the recipes (shadcn-style), but `verify` fingerprints them — so an\n// intentional edit trips the header sha + lockfile. `reseal` recomputes each\n// family's body sha and rewrites the header and lockfile to match, making\n// `verify` pass again. The clean way to bless an edit instead of hand-patching\n// hashes.\nexport async function reseal(options: ResealOptions): Promise<ResealResult> {\n const cwd = path.resolve(options.cwd);\n const config = await readConfig(cwd);\n const recipesDir = path.join(cwd, config.recipesDir);\n const families =\n options.families && options.families.length > 0\n ? options.families\n : installedFamilies(recipesDir);\n\n const lock = await readLockfile(recipesDir);\n const resealed: string[] = [];\n const unchanged: string[] = [];\n const notFound: string[] = [];\n const noHeader: string[] = [];\n\n for (const family of families) {\n const file = path.join(recipesDir, `${family}.css`);\n if (!existsSync(file)) {\n notFound.push(family);\n continue;\n }\n const source = readFileSync(file, \"utf8\");\n const header = extractHeader(source);\n if (!header) {\n noHeader.push(family);\n continue;\n }\n const sha = computeBodySha(source);\n if (sha === header.sha && lock.families[family]?.sha === sha) {\n unchanged.push(family);\n continue;\n }\n await writeFile(file, rewriteHeaderSha(source, sha));\n lock.families[family] = { version: header.version, sha };\n resealed.push(family);\n }\n\n await writeLockfile(recipesDir, lock);\n return { resealed, unchanged, notFound, noHeader };\n}\n","import path from \"node:path\";\nimport { resolveSource, resolvePresetFamilies } from \"../registry-source.js\";\nimport { readConfig } from \"../project.js\";\nimport { add, type AddResult } from \"./add.js\";\n\nexport type PresetOptions = {\n cwd: string;\n name: string;\n registry?: string;\n};\n\nexport type PresetResult = AddResult & { preset: string };\n\nexport async function preset(options: PresetOptions): Promise<PresetResult> {\n const cwd = path.resolve(options.cwd);\n const config = await readConfig(cwd);\n const registry = options.registry ?? config.registry;\n const source = await resolveSource(registry);\n\n if (options.name === \"none\") {\n throw new Error(\"Use `shortwind remove` to uninstall families; preset 'none' is for `init` only.\");\n }\n\n const presets = await source.loadPresets();\n const all = await source.listAllFamilies();\n const families = resolvePresetFamilies(options.name, presets, all);\n\n const addOptions: Parameters<typeof add>[0] = {\n cwd,\n families,\n };\n if (options.registry !== undefined) addOptions.registry = options.registry;\n\n const result = await add(addOptions);\n return { ...result, preset: options.name };\n}\n","import path from \"node:path\";\nimport { resolveSource } from \"../registry-source.js\";\nimport { readConfig, installedFamilies } from \"../project.js\";\nimport { readLockfile } from \"../lockfile.js\";\n\nexport type LsOptions = {\n cwd: string;\n registry?: string;\n installedOnly?: boolean;\n availableOnly?: boolean;\n};\n\nexport type LsResult = {\n installed: { family: string; version: string | null }[];\n available: string[];\n};\n\nexport async function ls(options: LsOptions): Promise<LsResult> {\n const cwd = path.resolve(options.cwd);\n const config = await readConfig(cwd);\n const recipesDir = path.join(cwd, config.recipesDir);\n const lock = await readLockfile(recipesDir);\n\n const installed = options.availableOnly\n ? []\n : installedFamilies(recipesDir).map((family) => {\n const entry = lock.families[family];\n return { family, version: entry?.version ?? null };\n });\n\n let available: string[] = [];\n if (!options.installedOnly) {\n const registry = options.registry ?? config.registry;\n const source = await resolveSource(registry);\n try {\n available = await source.listAllFamilies();\n } catch {\n available = [];\n }\n }\n\n return { installed, available };\n}\n\nexport function formatLsText(result: LsResult): string {\n const installedSet = new Set(result.installed.map((i) => i.family));\n const lines: string[] = [];\n lines.push(\"Installed:\");\n if (result.installed.length === 0) lines.push(\" (none)\");\n for (const { family, version } of result.installed) {\n lines.push(` ${family}${version ? ` ${version}` : \"\"}`);\n }\n lines.push(\"\");\n lines.push(\"Available:\");\n if (result.available.length === 0) lines.push(\" (registry unreachable)\");\n for (const family of result.available) {\n const marker = installedSet.has(family) ? \"*\" : \" \";\n lines.push(` ${marker} ${family}`);\n }\n return lines.join(\"\\n\");\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { buildRegistry, parseRecipeFile, renderSkillMarkdown } from \"@shortwind/core\";\nimport type { Diagnostic, Recipe } from \"@shortwind/core\";\nimport { installedFamilies, readConfig } from \"../project.js\";\n\nexport type BuildOptions = {\n cwd: string;\n};\n\nexport type BuildResult = {\n changed: boolean;\n families: string[];\n skillPath: string;\n};\n\nexport class BuildError extends Error {\n readonly diagnostics: Diagnostic[];\n\n constructor(diagnostics: Diagnostic[]) {\n super(\n `shortwind build failed:\\n${diagnostics\n .map((d) => ` ${d.file}:${d.line}${d.column ? `:${d.column}` : \"\"} ${d.code} — ${d.message}`)\n .join(\"\\n\")}`,\n );\n this.diagnostics = diagnostics;\n this.name = \"BuildError\";\n }\n}\n\nexport async function build(options: BuildOptions): Promise<BuildResult> {\n const cwd = path.resolve(options.cwd);\n const config = await readConfig(cwd);\n const recipesDir = path.join(cwd, config.recipesDir);\n const families = installedFamilies(recipesDir);\n\n const allRecipes: Recipe[] = [];\n const guidance: Record<string, string> = {};\n const errors: Diagnostic[] = [];\n\n for (const family of families) {\n const filePath = path.join(recipesDir, `${family}.css`);\n const source = readFileSync(filePath, \"utf8\");\n const parsed = parseRecipeFile(source, `${family}.css`);\n if (!parsed.ok) {\n errors.push(...parsed.errors);\n continue;\n }\n allRecipes.push(...parsed.value.recipes);\n if (parsed.value.guidance) guidance[family] = parsed.value.guidance;\n }\n\n if (errors.length > 0) throw new BuildError(errors);\n\n const resolved = buildRegistry(allRecipes, { guidance });\n if (!resolved.ok) throw new BuildError(resolved.errors);\n\n const skillPath = path.join(cwd, config.outputPath);\n const next = renderSkillMarkdown(resolved.value, { order: families });\n const current = existsSync(skillPath) ? readFileSync(skillPath, \"utf8\") : null;\n let changed = false;\n if (current !== next) {\n await mkdir(path.dirname(skillPath), { recursive: true });\n await writeFile(skillPath, next);\n changed = true;\n }\n\n return { changed, families, skillPath };\n}\n","import path from \"node:path\";\nimport chokidar from \"chokidar\";\nimport { readConfig } from \"../project.js\";\nimport { build, BuildError } from \"./build.js\";\n\nexport type DevOptions = {\n cwd: string;\n signal?: AbortSignal;\n onStatus?: (status: DevStatus) => void;\n debounceMs?: number;\n reconcileIntervalMs?: number;\n};\n\nexport type DevStatus =\n | { kind: \"ready\"; recipesDir: string }\n | { kind: \"rebuilt\"; families: string[]; changed: boolean }\n | { kind: \"error\"; message: string };\n\nexport async function dev(options: DevOptions): Promise<{ stop: () => Promise<void> }> {\n options.signal?.throwIfAborted();\n const cwd = path.resolve(options.cwd);\n const config = await readConfig(cwd);\n const recipesDir = path.join(cwd, config.recipesDir);\n const debounceMs = options.debounceMs ?? 50;\n const reconcileIntervalMs = options.reconcileIntervalMs ?? Math.max(1000, debounceMs * 5);\n\n const status = (s: DevStatus): void => options.onStatus?.(s);\n\n const watcher = chokidar.watch(recipesDir, {\n ignoreInitial: true,\n awaitWriteFinish: { stabilityThreshold: 25, pollInterval: 10 },\n });\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n let reconcileTimer: ReturnType<typeof setInterval> | null = null;\n let running = false;\n let pending = false;\n let pendingSilent = true;\n\n const runBuild = async (silentNoChange = false): Promise<void> => {\n if (running) {\n pending = true;\n pendingSilent = pendingSilent && silentNoChange;\n return;\n }\n running = true;\n try {\n let currentSilent = silentNoChange;\n do {\n pending = false;\n pendingSilent = true;\n try {\n const result = await build({ cwd });\n if (!currentSilent || result.changed) {\n status({ kind: \"rebuilt\", families: result.families, changed: result.changed });\n }\n } catch (err) {\n if (err instanceof BuildError) status({ kind: \"error\", message: err.message });\n else status({ kind: \"error\", message: err instanceof Error ? err.message : String(err) });\n }\n currentSilent = pendingSilent;\n } while (pending);\n } finally {\n running = false;\n }\n };\n\n const schedule = (): void => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => void runBuild(false), debounceMs);\n };\n\n watcher.on(\"add\", schedule).on(\"change\", schedule).on(\"unlink\", schedule);\n // chokidar emits `error` on EPERM/ENOENT bursts; an unhandled `error` event\n // on an EventEmitter throws and kills the process. Report it as a dev status\n // instead so the watcher stays up.\n watcher.on(\"error\", (err: unknown) => {\n status({ kind: \"error\", message: err instanceof Error ? err.message : String(err) });\n });\n\n let stopped = false;\n const stop = async (): Promise<void> => {\n if (stopped) return;\n stopped = true;\n if (timer) clearTimeout(timer);\n if (reconcileTimer) clearInterval(reconcileTimer);\n await watcher.close();\n };\n\n if (options.signal) {\n if (options.signal.aborted) {\n await stop();\n return { stop };\n }\n options.signal.addEventListener(\"abort\", () => void stop(), { once: true });\n }\n\n await new Promise<void>((resolve) => watcher.once(\"ready\", () => resolve()));\n if (stopped) return { stop };\n\n // initial build so SKILL.md is current at start\n await runBuild();\n if (stopped) return { stop };\n status({ kind: \"ready\", recipesDir });\n reconcileTimer = setInterval(() => void runBuild(true), reconcileIntervalMs);\n\n return { stop };\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { open, rename } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { resolveSource, type RegistrySource } from \"../registry-source.js\";\nimport {\n computeBodySha,\n extractHeader,\n isLegacyFingerprint,\n rewriteHeaderSha,\n verifyFetchedFamily,\n} from \"../fingerprint.js\";\nimport { readLockfile, writeLockfile, type Lockfile } from \"../lockfile.js\";\nimport { installedFamilies, readConfig, regenerateSkillMd } from \"../project.js\";\n\nexport type UpgradeChoice = \"accept\" | \"keep\" | \"skip\";\n\nexport type TouchedContext = {\n family: string;\n local: string;\n baseline: { version: string; sha: string };\n incoming: { version: string; body: string };\n};\n\nexport type UpgradeResolver = (ctx: TouchedContext) => Promise<UpgradeChoice>;\n\nexport type UpgradeOptions = {\n cwd: string;\n families?: string[];\n registry?: string;\n force?: boolean;\n check?: boolean;\n resolver?: UpgradeResolver;\n source?: RegistrySource;\n};\n\nexport type FamilyState = \"pristine\" | \"touched\" | \"unchanged\" | \"missing\" | \"untracked\";\n\nexport type FamilyOutcome =\n | { family: string; action: \"updated\"; from: string; to: string; state: FamilyState }\n | { family: string; action: \"kept\"; reason: \"unchanged\" | \"user-chose-keep\"; state: FamilyState }\n | { family: string; action: \"skipped\"; reason: string; state: FamilyState }\n | { family: string; action: \"would-update\"; from: string; to: string; state: FamilyState }\n | { family: string; action: \"would-review\"; from: string; to: string; state: FamilyState };\n\nexport type UpgradeResult = {\n outcomes: FamilyOutcome[];\n hasUpdates: boolean;\n hasTouched: boolean;\n lockfile: Lockfile;\n skillPath: string | null;\n};\n\nexport class UpgradeError extends Error {\n readonly errors: { family: string; message: string }[];\n constructor(errors: { family: string; message: string }[]) {\n super(`shortwind upgrade failed:\\n${errors.map((e) => ` ${e.family}: ${e.message}`).join(\"\\n\")}`);\n this.errors = errors;\n this.name = \"UpgradeError\";\n }\n}\n\nexport async function upgrade(options: UpgradeOptions): Promise<UpgradeResult> {\n const cwd = path.resolve(options.cwd);\n const config = await readConfig(cwd);\n const registry = options.registry ?? config.registry;\n const source = options.source ?? (await resolveSource(registry));\n const recipesDir = path.join(cwd, config.recipesDir);\n\n const installed = installedFamilies(recipesDir);\n const targets = options.families && options.families.length > 0 ? options.families : installed;\n const lock = await readLockfile(recipesDir);\n let lockfileDirty = false;\n if (!lock.registry) {\n lock.registry = registry;\n lockfileDirty = true;\n }\n\n const outcomes: FamilyOutcome[] = [];\n const errors: { family: string; message: string }[] = [];\n let hasUpdates = false;\n let hasTouched = false;\n let anyWritten = false;\n\n for (const family of targets) {\n const filePath = path.join(recipesDir, `${family}.css`);\n if (!existsSync(filePath)) {\n outcomes.push({\n family,\n action: \"skipped\",\n reason: \"not installed\",\n state: \"missing\",\n });\n continue;\n }\n\n let incomingBody: string;\n try {\n incomingBody = await source.loadFamily(family);\n // Reject a tampered/corrupted registry response before it's resealed.\n verifyFetchedFamily(incomingBody, family);\n } catch (err) {\n errors.push({ family, message: (err as Error).message });\n continue;\n }\n const incomingHeader = extractHeader(incomingBody);\n if (!incomingHeader) {\n errors.push({ family, message: \"registry recipe has no fingerprint header\" });\n continue;\n }\n const incomingVersion = incomingHeader.version;\n\n const localBody = readFileSync(filePath, \"utf8\");\n const localHeader = extractHeader(localBody);\n const recordedSha = localHeader?.sha ?? \"\";\n const actualSha = computeBodySha(localBody);\n const lockedEntry = lock.families[family];\n const lockedVersion = lockedEntry?.version ?? localHeader?.version ?? \"\";\n\n // A legacy 6-hex seal can't be re-derived with the current hash, so a sha\n // difference is a FORMAT mismatch, not a local edit — don't flag it as\n // \"locally modified\". Tell the user to reseal; the upgrade still proceeds.\n const recordedIsLegacy = isLegacyFingerprint(recordedSha);\n if (recordedIsLegacy) {\n console.warn(\n `[shortwind] ${family}.css uses an older fingerprint format — run \\`shortwind reseal\\` to upgrade its seal (the recipe body is unchanged).`,\n );\n }\n const isTouched = recordedSha !== \"\" && recordedSha !== actualSha && !recordedIsLegacy;\n const state: FamilyState = isTouched\n ? \"touched\"\n : lockedVersion === incomingVersion\n ? \"unchanged\"\n : \"pristine\";\n\n if (state === \"unchanged\" && !isTouched) {\n outcomes.push({\n family,\n action: \"kept\",\n reason: \"unchanged\",\n state: \"unchanged\",\n });\n continue;\n }\n\n if (options.check) {\n if (isTouched) {\n hasTouched = true;\n if (lockedVersion !== incomingVersion) hasUpdates = true;\n outcomes.push({\n family,\n action: \"would-review\",\n from: lockedVersion,\n to: incomingVersion,\n state: \"touched\",\n });\n } else {\n hasUpdates = true;\n outcomes.push({\n family,\n action: \"would-update\",\n from: lockedVersion,\n to: incomingVersion,\n state: \"pristine\",\n });\n }\n continue;\n }\n\n if (isTouched && !options.force) {\n hasTouched = true;\n const choice = options.resolver\n ? await options.resolver({\n family,\n local: localBody,\n baseline: { version: lockedVersion, sha: recordedSha },\n incoming: { version: incomingVersion, body: incomingBody },\n })\n : \"skip\";\n if (choice === \"keep\") {\n outcomes.push({ family, action: \"kept\", reason: \"user-chose-keep\", state: \"touched\" });\n continue;\n }\n if (choice === \"skip\") {\n outcomes.push({ family, action: \"skipped\", reason: \"touched\", state: \"touched\" });\n continue;\n }\n }\n\n const newSha = computeBodySha(incomingBody);\n const sealed = rewriteHeaderSha(incomingBody, newSha);\n await atomicWrite(filePath, sealed);\n lock.families[family] = { version: incomingVersion, sha: newSha };\n outcomes.push({\n family,\n action: \"updated\",\n from: lockedVersion,\n to: incomingVersion,\n state: isTouched ? \"touched\" : \"pristine\",\n });\n hasUpdates = true;\n anyWritten = true;\n lockfileDirty = true;\n }\n\n if (errors.length > 0) throw new UpgradeError(errors);\n\n let skillPath: string | null = null;\n if (!options.check) {\n if (lockfileDirty) await writeLockfile(recipesDir, lock);\n if (anyWritten) skillPath = await regenerateSkillMd(cwd, config);\n }\n\n return { outcomes, hasUpdates, hasTouched, lockfile: lock, skillPath };\n}\n\nlet atomicWriteSeq = 0;\nasync function atomicWrite(filePath: string, body: string): Promise<void> {\n // Unique temp name: a fixed `.tmp` suffix collides across concurrent runs (two\n // processes writing the same family clobber each other's temp file). pid +\n // counter keeps each write isolated.\n const tmp = `${filePath}.${process.pid}.${atomicWriteSeq++}.tmp`;\n const fh = await open(tmp, \"w\");\n try {\n await fh.writeFile(body);\n await fh.sync();\n } finally {\n await fh.close();\n }\n await rename(tmp, filePath);\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { computeBodySha, extractHeader, isLegacyFingerprint } from \"../fingerprint.js\";\nimport { readLockfile } from \"../lockfile.js\";\nimport { installedFamilies, readConfig } from \"../project.js\";\n\nexport type VerifyIssue =\n | { family: string; kind: \"missing-header\"; file: string }\n | { family: string; kind: \"header-tampered\"; file: string; recorded: string; actual: string }\n | { family: string; kind: \"legacy-fingerprint\"; file: string; recorded: string }\n | { family: string; kind: \"lockfile-mismatch\"; file: string; locked: string; actual: string }\n | { family: string; kind: \"missing-lock-entry\"; file: string }\n | { family: string; kind: \"missing-file\"; file: string };\n\nexport type VerifyOptions = {\n cwd: string;\n};\n\nexport type VerifyResult = {\n ok: boolean;\n checked: string[];\n issues: VerifyIssue[];\n};\n\nexport async function verify(options: VerifyOptions): Promise<VerifyResult> {\n const cwd = path.resolve(options.cwd);\n const config = await readConfig(cwd);\n const recipesDir = path.join(cwd, config.recipesDir);\n\n const installed = installedFamilies(recipesDir);\n const lock = await readLockfile(recipesDir);\n const issues: VerifyIssue[] = [];\n\n const seen = new Set<string>();\n for (const family of installed) {\n seen.add(family);\n const filePath = path.join(recipesDir, `${family}.css`);\n const source = readFileSync(filePath, \"utf8\");\n const header = extractHeader(source);\n if (!header) {\n issues.push({ family, kind: \"missing-header\", file: filePath });\n continue;\n }\n const actual = computeBodySha(source);\n if (header.sha !== actual) {\n // A 6-hex header was sealed by an older CLI; the body is fine, the seal\n // format is just stale. Tell the user to reseal instead of crying tamper,\n // and skip the (also-legacy) lockfile comparison for this family.\n if (isLegacyFingerprint(header.sha)) {\n issues.push({ family, kind: \"legacy-fingerprint\", file: filePath, recorded: header.sha });\n continue;\n }\n issues.push({\n family,\n kind: \"header-tampered\",\n file: filePath,\n recorded: header.sha,\n actual,\n });\n }\n const locked = lock.families[family];\n if (!locked) {\n issues.push({ family, kind: \"missing-lock-entry\", file: filePath });\n } else if (locked.sha !== actual) {\n issues.push({\n family,\n kind: \"lockfile-mismatch\",\n file: filePath,\n locked: locked.sha,\n actual,\n });\n }\n }\n\n for (const family of Object.keys(lock.families)) {\n if (seen.has(family)) continue;\n const filePath = path.join(recipesDir, `${family}.css`);\n if (!existsSync(filePath)) {\n issues.push({ family, kind: \"missing-file\", file: filePath });\n }\n }\n\n return { ok: issues.length === 0, checked: installed, issues };\n}\n","// Generated by scripts/generate-bench-recipes.ts. Do not edit directly —\n// re-run `pnpm --filter shortwind gen:bench` after editing files under\n// packages/registry/recipes/.\nexport const DEFAULT_RECIPES_CSS: Record<string, string> = {\n \"badge.css\": \"/* shortwind: badge@0.0.1 sha:000000 */\\n\\n/* @guide\\n @badge is the neutral default; tone variants @badge-success/warning/danger/\\n info carry their own color; @badge-outline is unfilled. One tone per badge.\\n @badge-base is a color-less shell for building custom tones — not for direct\\n use.\\n*/\\n\\n/* Badge shell — shape, sizing, focus ring. No bg/text/border color so\\n variants can supply their own tone without conflicts. */\\n@recipe badge-base {\\n inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\\n}\\n\\n/* Default neutral badge. */\\n@recipe badge {\\n inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground\\n}\\n\\n/* Success tone badge. */\\n@recipe badge-success {\\n inline-flex items-center gap-1 rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800 dark:bg-green-900 dark:text-green-200\\n}\\n\\n/* Warning tone badge. */\\n@recipe badge-warning {\\n inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-800 dark:bg-amber-900 dark:text-amber-200\\n}\\n\\n/* Danger tone badge. */\\n@recipe badge-danger {\\n inline-flex items-center gap-1 rounded-full bg-destructive/15 px-2 py-0.5 text-xs font-medium text-destructive\\n}\\n\\n/* Info tone badge. */\\n@recipe badge-info {\\n inline-flex items-center gap-1 rounded-full bg-primary/15 px-2 py-0.5 text-xs font-medium text-primary\\n}\\n\\n/* Outline badge — no fill. */\\n@recipe badge-outline {\\n inline-flex items-center gap-1 rounded-full border border-border px-2 py-0.5 text-xs font-medium text-foreground\\n}\\n\",\n \"button.css\": \"/* shortwind: button@0.0.1 sha:000000 */\\n\\n/* @guide\\n Name order is @btn-<intent>[-<size>]: intent first (primary/secondary/ghost/\\n danger/outline), size second (sm/lg; omit for default). One intent per\\n button — never combine @btn-primary with @btn-danger. @btn-ghost is text-only,\\n @btn-outline is bordered with no fill, @btn-icon is a square icon button.\\n @btn-base is the shared shell; don't use it on its own.\\n*/\\n\\n/* Shared button base — sizing, focus ring, disabled state. */\\n@recipe btn-base {\\n inline-flex items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:cursor-not-allowed disabled:opacity-50\\n}\\n\\n/* Primary call-to-action button. */\\n@recipe btn-primary {\\n @btn-base bg-primary text-primary-foreground hover:bg-primary/90\\n}\\n\\n/* Small primary button. */\\n@recipe btn-primary-sm {\\n @btn-primary px-3 py-1.5 text-xs\\n}\\n\\n/* Large primary button. */\\n@recipe btn-primary-lg {\\n @btn-primary px-6 py-3 text-base\\n}\\n\\n/* Secondary button — bordered surface tone. */\\n@recipe btn-secondary {\\n @btn-base border border-border bg-secondary text-secondary-foreground hover:bg-secondary/80\\n}\\n\\n/* Small secondary button. */\\n@recipe btn-secondary-sm {\\n @btn-secondary px-3 py-1.5 text-xs\\n}\\n\\n/* Large secondary button. */\\n@recipe btn-secondary-lg {\\n @btn-secondary px-6 py-3 text-base\\n}\\n\\n/* Ghost button — text only, no background. */\\n@recipe btn-ghost {\\n @btn-base text-foreground hover:bg-muted\\n}\\n\\n/* Small ghost button. */\\n@recipe btn-ghost-sm {\\n @btn-ghost px-3 py-1.5 text-xs\\n}\\n\\n/* Large ghost button. */\\n@recipe btn-ghost-lg {\\n @btn-ghost px-6 py-3 text-base\\n}\\n\\n/* Destructive button. */\\n@recipe btn-danger {\\n @btn-base bg-destructive text-destructive-foreground hover:bg-destructive/90\\n}\\n\\n/* Outline button — bordered without fill. */\\n@recipe btn-outline {\\n @btn-base border border-primary text-primary hover:bg-primary/10\\n}\\n\\n/* Square icon-only button. */\\n@recipe btn-icon {\\n inline-flex h-9 w-9 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:cursor-not-allowed disabled:opacity-50\\n}\\n\",\n \"card.css\": \"/* shortwind: card@0.0.1 sha:000000 */\\n\\n/* @guide\\n Pick exactly one container: @card (default), @card-elevated (raised shadow),\\n @card-flat (no border), or @card-interactive (clickable hover/focus) — don't\\n stack two container variants on one element. Lay out the inside with\\n @card-header / @card-body / @card-footer.\\n*/\\n\\n/* Default content card with border, padding, and surface color. */\\n@recipe card {\\n rounded-lg border border-border bg-card text-card-foreground p-4\\n}\\n\\n/* Card with raised shadow for emphasis. */\\n@recipe card-elevated {\\n @card shadow-md\\n}\\n\\n/* Card without border, on a muted surface. */\\n@recipe card-flat {\\n rounded-lg bg-muted text-foreground p-4\\n}\\n\\n/* Clickable card with hover and focus-visible states. */\\n@recipe card-interactive {\\n @card cursor-pointer transition-shadow hover:shadow-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\\n}\\n\\n/* Card header region with bottom divider. */\\n@recipe card-header {\\n mb-3 border-b border-border pb-3\\n}\\n\\n/* Card body region. */\\n@recipe card-body {\\n py-1\\n}\\n\\n/* Card footer with top divider and right-aligned actions. */\\n@recipe card-footer {\\n mt-3 flex items-center justify-end gap-2 border-t border-border pt-3\\n}\\n\",\n \"code.css\": \"/* shortwind: code@0.0.1 sha:000000 */\\n\\n/* @guide\\n @code-inline for a code span inside prose, @code-block for a multi-line\\n preformatted block, @kbd for a keyboard-shortcut hint. Pick by context, not\\n by size.\\n*/\\n\\n/* Inline code span. */\\n@recipe code-inline {\\n rounded bg-muted px-1.5 py-0.5 font-mono text-[0.875em] text-foreground\\n}\\n\\n/* Block of preformatted code. */\\n@recipe code-block {\\n overflow-x-auto rounded-md border border-border bg-muted p-4 font-mono text-sm leading-6 text-foreground\\n}\\n\\n/* Keyboard shortcut hint. */\\n@recipe kbd {\\n inline-flex items-center rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-xs text-foreground shadow-sm\\n}\\n\",\n \"dialog.css\": \"/* shortwind: dialog@0.0.1 sha:000000 */\\n\\n/* @guide\\n A modal is three layers: @dialog-overlay (dimmed backdrop), @dialog (the\\n centering wrapper), and @dialog-content (the panel). Structure the panel with\\n @dialog-header and @dialog-footer. Don't put content styling on @dialog\\n itself — it's only the positioner.\\n*/\\n\\n/* Modal dialog wrapper — covers the viewport, centers content. */\\n@recipe dialog {\\n fixed inset-0 z-50 flex items-center justify-center p-4\\n}\\n\\n/* Dimmed overlay behind the dialog. */\\n@recipe dialog-overlay {\\n fixed inset-0 z-40 bg-black/50\\n}\\n\\n/* Dialog content panel. */\\n@recipe dialog-content {\\n relative z-50 w-full max-w-md rounded-lg border border-border bg-popover text-popover-foreground p-6 shadow-xl\\n}\\n\\n/* Dialog header region with title. */\\n@recipe dialog-header {\\n mb-4 flex flex-col gap-1\\n}\\n\\n/* Dialog footer with right-aligned actions. */\\n@recipe dialog-footer {\\n mt-6 flex items-center justify-end gap-2\\n}\\n\",\n \"empty.css\": \"/* shortwind: empty@0.0.1 sha:000000 */\\n\\n/* @guide\\n @empty is the container for a no-data state; fill it with @empty-icon,\\n @empty-title, and @empty-description. These are slots for that pattern — use\\n @heading-* / @body from the text family for ordinary copy.\\n*/\\n\\n/* Empty-state container. */\\n@recipe empty {\\n flex flex-col items-center justify-center gap-3 rounded-md border border-dashed border-border p-8 text-center\\n}\\n\\n/* Empty-state icon slot. */\\n@recipe empty-icon {\\n flex h-12 w-12 items-center justify-center rounded-full bg-muted text-muted-foreground\\n}\\n\\n/* Empty-state title text. */\\n@recipe empty-title {\\n text-base font-semibold text-foreground\\n}\\n\\n/* Empty-state supporting description. */\\n@recipe empty-description {\\n max-w-sm text-sm text-muted-foreground\\n}\\n\",\n \"feedback.css\": \"/* shortwind: feedback@0.0.1 sha:000000 */\\n\\n/* @guide\\n Inline messages use @alert (neutral) or a tone variant @alert-success/\\n warning/danger/info — one tone each. @callout is a left-accent inline note,\\n @toast is a floating notification, @banner spans the full viewport width.\\n*/\\n\\n/* Default informational alert. */\\n@recipe alert {\\n flex items-start gap-3 rounded-md border border-border bg-card p-4 text-sm text-card-foreground\\n}\\n\\n/* Success alert. */\\n@recipe alert-success {\\n flex items-start gap-3 rounded-md border border-green-200 bg-green-50 p-4 text-sm text-green-900 dark:border-green-900 dark:bg-green-950 dark:text-green-100\\n}\\n\\n/* Warning alert. */\\n@recipe alert-warning {\\n flex items-start gap-3 rounded-md border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-100\\n}\\n\\n/* Danger alert. */\\n@recipe alert-danger {\\n flex items-start gap-3 rounded-md border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive\\n}\\n\\n/* Informational alert. */\\n@recipe alert-info {\\n flex items-start gap-3 rounded-md border border-primary/30 bg-primary/10 p-4 text-sm text-primary\\n}\\n\\n/* Inline callout — flush left edge accent. */\\n@recipe callout {\\n border-l-4 border-primary bg-primary/10 p-4 text-sm text-foreground\\n}\\n\\n/* Floating toast notification. */\\n@recipe toast {\\n pointer-events-auto flex items-start gap-3 rounded-md border border-border bg-popover p-4 text-sm text-popover-foreground shadow-lg\\n}\\n\\n/* Full-width banner spanning the viewport. */\\n@recipe banner {\\n w-full bg-primary px-4 py-2 text-center text-sm font-medium text-primary-foreground\\n}\\n\",\n \"form.css\": \"/* shortwind: form@0.0.1 sha:000000 */\\n\\n/* @guide\\n Wrap each label+control+message in @field (use @field-error for the invalid\\n state); group related fields with @fieldset. Controls are bare: @input,\\n @textarea, @select, @checkbox, @radio, plus @input-error for invalid text\\n and @input-shell for the transparent shadcn-style shell. Helper text is\\n @help. There is no @form-group (use @field), @form-input (use @input),\\n @form-helper (use @help) or @form-checkbox (use @checkbox); the field label\\n recipe is @label, in the text family.\\n*/\\n\\n/* Text input field. */\\n@recipe input {\\n block w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-2 focus:outline-offset-2 focus:outline-ring disabled:cursor-not-allowed disabled:opacity-50\\n}\\n\\n/* shadcn/dinachi-style input shell — transparent background, h-9, file/\\n placeholder/selection/aria-invalid/focus-visible states baked in. */\\n@recipe input-shell {\\n flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs outline-none transition-[color,box-shadow] placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:border-destructive aria-invalid:ring-destructive/20 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40\\n}\\n\\n/* Input in error state. */\\n@recipe input-error {\\n @input border-destructive focus:border-destructive focus:outline-destructive\\n}\\n\\n/* Multi-line textarea. */\\n@recipe textarea {\\n block w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-2 focus:outline-offset-2 focus:outline-ring disabled:cursor-not-allowed disabled:opacity-50\\n}\\n\\n/* Native select control. */\\n@recipe select {\\n block w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus:border-ring focus:outline-2 focus:outline-offset-2 focus:outline-ring disabled:cursor-not-allowed disabled:opacity-50\\n}\\n\\n/* Checkbox input. */\\n@recipe checkbox {\\n h-4 w-4 rounded border-input text-primary focus:outline-2 focus:outline-offset-2 focus:outline-ring\\n}\\n\\n/* Radio input. */\\n@recipe radio {\\n h-4 w-4 border-input text-primary focus:outline-2 focus:outline-offset-2 focus:outline-ring\\n}\\n\\n/* Form field wrapper — label + input + help/error. */\\n@recipe field {\\n flex flex-col gap-1.5\\n}\\n\\n/* Form field in error state. */\\n@recipe field-error {\\n flex flex-col gap-1.5\\n}\\n\\n/* Grouped form section with optional legend. */\\n@recipe fieldset {\\n flex flex-col gap-4 rounded-md border border-border p-4\\n}\\n\\n/* Field-level helper text. */\\n@recipe help {\\n text-xs text-muted-foreground\\n}\\n\",\n \"icon.css\": \"/* shortwind: icon@0.0.1 sha:000000 */\\n\\n/* @guide\\n Size an icon with @icon-sm/md/lg (16/20/24px) — these set width and height\\n only; add @icon-muted for secondary color. They're for SVG/icon elements, not\\n to be confused with @btn-icon (the icon button in the button family).\\n*/\\n\\n/* Small icon — 16px. */\\n@recipe icon-sm {\\n h-4 w-4 shrink-0\\n}\\n\\n/* Default icon size — 20px. */\\n@recipe icon-md {\\n h-5 w-5 shrink-0\\n}\\n\\n/* Large icon — 24px. */\\n@recipe icon-lg {\\n h-6 w-6 shrink-0\\n}\\n\\n/* Icon with muted color. */\\n@recipe icon-muted {\\n text-muted-foreground\\n}\\n\",\n \"layout.css\": \"/* shortwind: layout@0.0.1 sha:000000 */\\n\\n/* @guide\\n Composition primitives. @stack-* stacks children vertically (flex-col);\\n @row* lays them out horizontally (flex-row). Choose the gap with the size\\n suffix (xs/sm/md/lg on stacks). Use @grid-2/3/4 only for true multi-column\\n grids, @center to center on both axes, @full to fill the parent. Common\\n slips: there is no @flex-row (use @row) or @flex-col (use a @stack-*), and\\n the grids are @grid-3, not @grid-cols-3.\\n*/\\n\\n/* Vertical stack with extra-small gap. */\\n@recipe stack-xs {\\n flex flex-col gap-1\\n}\\n\\n/* Vertical stack with small gap. */\\n@recipe stack-sm {\\n flex flex-col gap-2\\n}\\n\\n/* Vertical stack with medium gap. */\\n@recipe stack-md {\\n flex flex-col gap-4\\n}\\n\\n/* Vertical stack with large gap. */\\n@recipe stack-lg {\\n flex flex-col gap-8\\n}\\n\\n/* Horizontal row with default gap and centered items. */\\n@recipe row {\\n flex flex-row items-center gap-2\\n}\\n\\n/* Horizontal row with space between children. */\\n@recipe row-between {\\n flex flex-row items-center justify-between gap-2\\n}\\n\\n/* Horizontal row aligned to the end. */\\n@recipe row-end {\\n flex flex-row items-center justify-end gap-2\\n}\\n\\n/* Two-column responsive grid. */\\n@recipe grid-2 {\\n grid grid-cols-1 gap-4 sm:grid-cols-2\\n}\\n\\n/* Three-column responsive grid. */\\n@recipe grid-3 {\\n grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\\n}\\n\\n/* Four-column responsive grid. */\\n@recipe grid-4 {\\n grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\\n}\\n\\n/* Center content horizontally and vertically. */\\n@recipe center {\\n flex items-center justify-center\\n}\\n\\n/* Fill the available width and height. */\\n@recipe full {\\n h-full w-full\\n}\\n\",\n \"list.css\": \"/* shortwind: list@0.0.1 sha:000000 */\\n\\n/* @guide\\n @list wraps a stack of @list-item rows; use @list-bordered for divided rows.\\n Definition lists are separate: @dl with @dt (term) and @dd (description).\\n For site navigation reach for the navigation family (@nav), not @list.\\n*/\\n\\n/* Vertical list with default gap. */\\n@recipe list {\\n flex flex-col gap-1\\n}\\n\\n/* Single list item. */\\n@recipe list-item {\\n flex items-center gap-2 rounded-md px-3 py-2 text-sm text-foreground\\n}\\n\\n/* List with dividing borders between items. */\\n@recipe list-bordered {\\n divide-y divide-border rounded-md border border-border\\n}\\n\\n/* Definition list container. */\\n@recipe dl {\\n grid grid-cols-1 gap-2 sm:grid-cols-3 sm:gap-4\\n}\\n\\n/* Definition term. */\\n@recipe dt {\\n text-sm font-medium text-muted-foreground\\n}\\n\\n/* Definition description. */\\n@recipe dd {\\n text-sm text-foreground sm:col-span-2\\n}\\n\",\n \"media.css\": \"/* shortwind: media@0.0.1 sha:000000 */\\n\\n/* @guide\\n @avatar (with @avatar-sm/lg) is a round user image; @thumb is a small square\\n thumbnail. For responsive embeds use @aspect-square or @aspect-video. Avatars\\n are circular by default — don't restyle the radius.\\n*/\\n\\n/* User/profile avatar. */\\n@recipe avatar {\\n inline-flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-muted text-sm font-medium text-muted-foreground\\n}\\n\\n/* Small avatar. */\\n@recipe avatar-sm {\\n @avatar h-6 w-6 text-xs\\n}\\n\\n/* Large avatar. */\\n@recipe avatar-lg {\\n @avatar h-14 w-14 text-base\\n}\\n\\n/* Small image thumbnail. */\\n@recipe thumb {\\n h-16 w-16 rounded-md object-cover\\n}\\n\\n/* 1:1 aspect-ratio wrapper. */\\n@recipe aspect-square {\\n aspect-square w-full overflow-hidden rounded-md\\n}\\n\\n/* 16:9 aspect-ratio wrapper. */\\n@recipe aspect-video {\\n aspect-video w-full overflow-hidden rounded-md\\n}\\n\",\n \"navigation.css\": \"/* shortwind: navigation@0.0.1 sha:000000 */\\n\\n/* @guide\\n @nav is the container; links are @nav-link with @nav-link-active for the\\n current page. Tabs mirror that pair: @tab and @tab-active. Use @breadcrumb\\n for trail navigation. Active and inactive are separate recipes — swap the\\n whole class rather than combining them.\\n*/\\n\\n/* Top-level nav container. */\\n@recipe nav {\\n flex items-center gap-1\\n}\\n\\n/* Inactive nav link with hover/focus states. */\\n@recipe nav-link {\\n inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\\n}\\n\\n/* Active nav link. */\\n@recipe nav-link-active {\\n inline-flex items-center gap-2 rounded-md bg-muted px-3 py-1.5 text-sm font-medium text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\\n}\\n\\n/* Breadcrumb trail container. */\\n@recipe breadcrumb {\\n flex items-center gap-1.5 text-sm text-muted-foreground\\n}\\n\\n/* Inactive tab control. */\\n@recipe tab {\\n inline-flex items-center gap-2 border-b-2 border-transparent px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:border-border hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\\n}\\n\\n/* Active tab control. */\\n@recipe tab-active {\\n inline-flex items-center gap-2 border-b-2 border-primary px-3 py-2 text-sm font-medium text-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\\n}\\n\",\n \"progress.css\": \"/* shortwind: progress@0.0.1 sha:000000 */\\n\\n/* @guide\\n A bar is two pieces: @progress-track (the background) wrapping @progress-bar\\n (the fill). For an indeterminate state use @spinner instead — it's a\\n standalone loader, not a bar.\\n*/\\n\\n/* Progress bar track (background). */\\n@recipe progress-track {\\n h-2 w-full overflow-hidden rounded-full bg-muted\\n}\\n\\n/* Progress bar fill. */\\n@recipe progress-bar {\\n h-full rounded-full bg-primary transition-all\\n}\\n\\n/* Indeterminate loading spinner. */\\n@recipe spinner {\\n inline-block h-4 w-4 animate-spin rounded-full border-2 border-border border-t-primary\\n}\\n\",\n \"skeleton.css\": \"/* shortwind: skeleton@0.0.1 sha:000000 */\\n\\n/* @guide\\n Match the skeleton to the shape it stands in for: @skeleton (block),\\n @skeleton-text (a text line), @skeleton-circle (avatar/icon). Size block and\\n text skeletons with raw width/height utilities.\\n*/\\n\\n/* Default rectangular skeleton placeholder. */\\n@recipe skeleton {\\n animate-pulse rounded-md bg-muted\\n}\\n\\n/* Single-line text skeleton. */\\n@recipe skeleton-text {\\n h-4 w-full animate-pulse rounded bg-muted\\n}\\n\\n/* Circular skeleton (avatar/icon). */\\n@recipe skeleton-circle {\\n h-10 w-10 animate-pulse rounded-full bg-muted\\n}\\n\",\n \"surface.css\": \"/* shortwind: surface@0.0.1 sha:000000 */\\n\\n/* @guide\\n @surface / @surface-muted / @surface-accent set a background+foreground pair\\n for a region — one per section. @wrapper (or @wrapper-tight for prose)\\n centers and width-caps content; there is no @wrapper-lg, set a different cap\\n with max-w-* yourself. (Note: @container is reserved for Tailwind's\\n container-query utility, so the content wrapper is @wrapper.) @divider-h and\\n @divider-v are hairline rules.\\n*/\\n\\n/* Default page/section surface. */\\n@recipe surface {\\n bg-background text-foreground\\n}\\n\\n/* Muted surface — secondary background. */\\n@recipe surface-muted {\\n bg-muted text-foreground\\n}\\n\\n/* Accent surface — soft brand background. */\\n@recipe surface-accent {\\n bg-accent text-accent-foreground\\n}\\n\\n/* Centered content wrapper with a max width. */\\n@recipe wrapper {\\n mx-auto w-full max-w-6xl px-4 sm:px-6 lg:px-8\\n}\\n\\n/* Narrow content wrapper for prose. */\\n@recipe wrapper-tight {\\n mx-auto w-full max-w-3xl px-4 sm:px-6\\n}\\n\\n/* Horizontal divider line. */\\n@recipe divider-h {\\n shrink-0 h-px w-full bg-border\\n}\\n\\n/* Vertical divider line. */\\n@recipe divider-v {\\n shrink-0 h-full w-px bg-border\\n}\\n\",\n \"table.css\": \"/* shortwind: table@0.0.1 sha:000000 */\\n\\n/* @guide\\n Wrap the table in @table-container for horizontal overflow, then put @table\\n (or @table-zebra for striped rows) on the <table>. Cells are @th (header) and\\n @td (body); add @tr-hover to a <tr> for row highlighting.\\n*/\\n\\n/* Scroll container for a wide table — keeps overflow horizontal. */\\n@recipe table-container {\\n w-full overflow-x-auto rounded-md border border-border\\n}\\n\\n/* Data table base. */\\n@recipe table {\\n w-full border-collapse text-left text-sm text-foreground\\n}\\n\\n/* Table header cell. */\\n@recipe th {\\n border-b border-border px-3 py-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground\\n}\\n\\n/* Table body cell. */\\n@recipe td {\\n border-b border-border px-3 py-2\\n}\\n\\n/* Row hover state. */\\n@recipe tr-hover {\\n transition-colors hover:bg-muted\\n}\\n\\n/* Table with zebra striping on alternating rows. */\\n@recipe table-zebra {\\n w-full border-collapse text-left text-sm text-foreground [&_tbody_tr:nth-child(odd)]:bg-muted\\n}\\n\",\n \"text.css\": \"/* shortwind: text@0.0.1 sha:000000 */\\n\\n/* @guide\\n Headings are sized by weight, not HTML level: @heading-xl/lg/md/sm — there\\n is no @h1..@h6. Body copy: @body (default), @lead (intro paragraphs), @muted\\n (secondary), @caption (fine print). Use @label for form labels and @link for\\n inline links. Don't append a -text suffix: it's @body not @body-text, @muted\\n not @muted-text, @link not @link-text.\\n*/\\n\\n/* Top-level page heading. */\\n@recipe heading-xl {\\n text-4xl font-bold tracking-tight text-foreground\\n}\\n\\n/* Large section heading. */\\n@recipe heading-lg {\\n text-2xl font-semibold tracking-tight text-foreground\\n}\\n\\n/* Medium heading. */\\n@recipe heading-md {\\n text-xl font-semibold text-foreground\\n}\\n\\n/* Small heading. */\\n@recipe heading-sm {\\n text-base font-semibold text-foreground\\n}\\n\\n/* Default body text. */\\n@recipe body {\\n text-sm leading-6 text-foreground\\n}\\n\\n/* Lead paragraph — larger body copy for hero/intro sections. */\\n@recipe lead {\\n text-lg leading-relaxed text-muted-foreground\\n}\\n\\n/* Muted secondary text. */\\n@recipe muted {\\n text-sm text-muted-foreground\\n}\\n\\n/* Form label text. */\\n@recipe label {\\n text-sm font-medium text-foreground\\n}\\n\\n/* Caption — small supporting text. */\\n@recipe caption {\\n text-xs text-muted-foreground\\n}\\n\\n/* Inline link with hover/focus states. */\\n@recipe link {\\n text-primary underline-offset-2 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\\n}\\n\",\n \"tooltip.css\": \"/* shortwind: tooltip@0.0.1 sha:000000 */\\n\\n/* @guide\\n @tooltip is the floating label bubble — it styles appearance only, so pair it\\n with your own positioning.\\n*/\\n\\n/* Floating tooltip bubble. */\\n@recipe tooltip {\\n pointer-events-none z-50 rounded-md bg-foreground px-2 py-1 text-xs font-medium text-background shadow-md\\n}\\n\",\n};\n","// Representative agent-authored component files used by `shortwind bench`.\n// Every @recipe here resolves against the bundled catalog — the corpus doubles\n// as a check that the recipes an agent naturally reaches for actually exist.\nexport const CORPUS_FILES: Record<string, string> = {\n \"button.tsx\": `export function ButtonShowcase() {\n return (\n <div className=\"@row gap-4 p-6\">\n <button className=\"@btn-primary\">Primary Button</button>\n <button className=\"@btn-primary-sm\">Small Primary</button>\n <button className=\"@btn-primary-lg\">Large Primary</button>\n <button className=\"@btn-secondary\">Secondary Button</button>\n <button className=\"@btn-outline\">Outline Button</button>\n <button className=\"@btn-ghost\">Ghost Button</button>\n <button className=\"@btn-danger\">Danger Button</button>\n </div>\n );\n}`,\n\n \"card.tsx\": `export function ProductCard() {\n return (\n <div className=\"@card-interactive max-w-sm\">\n <div className=\"@card-header\">\n <h3 className=\"@heading-md\">Premium Product</h3>\n <span className=\"@badge-success\">In Stock</span>\n </div>\n <div className=\"@card-body\">\n <p className=\"@body\">\n This is a beautiful product description. It uses multiple utility classes and shortwind recipes to keep output clean and readable for agents.\n </p>\n <div className=\"@row-between mt-4\">\n <span className=\"text-2xl font-bold\">$99.99</span>\n <span className=\"@muted\">Save 20%</span>\n </div>\n </div>\n <div className=\"@card-footer\">\n <button className=\"@btn-secondary-sm\">View Details</button>\n <button className=\"@btn-primary-sm\">Buy Now</button>\n </div>\n </div>\n );\n}`,\n\n \"form.tsx\": `export function LoginForm() {\n return (\n <div className=\"@card max-w-md mx-auto p-8\">\n <h2 className=\"@heading-lg mb-6 text-center\">Welcome Back</h2>\n <form className=\"@stack-md\">\n <div className=\"@field\">\n <label className=\"@label\" htmlFor=\"email\">Email Address</label>\n <input className=\"@input\" id=\"email\" type=\"email\" placeholder=\"you@example.com\" required />\n <span className=\"@help\">We will never share your email.</span>\n </div>\n <div className=\"@field\">\n <label className=\"@label\" htmlFor=\"password\">Password</label>\n <input className=\"@input\" id=\"password\" type=\"password\" required />\n </div>\n <div className=\"@row gap-2\">\n <input className=\"@checkbox\" id=\"remember\" type=\"checkbox\" />\n <label className=\"@label\" htmlFor=\"remember\">Remember me</label>\n </div>\n <button className=\"@btn-primary w-full mt-4\" type=\"submit\">Sign In</button>\n </form>\n </div>\n );\n}`,\n\n \"table.tsx\": `export function UsersTable() {\n return (\n <div className=\"@table-container\">\n <table className=\"@table\">\n <thead>\n <tr className=\"bg-muted/50\">\n <th className=\"@th\">User</th>\n <th className=\"@th\">Status</th>\n <th className=\"@th\">Role</th>\n <th className=\"@th\">Actions</th>\n </tr>\n </thead>\n <tbody>\n <tr className=\"@tr-hover\">\n <td className=\"@td font-medium\">Alice Johnson</td>\n <td className=\"@td\"><span className=\"@badge-success\">Active</span></td>\n <td className=\"@td\">Administrator</td>\n <td className=\"@td\"><button className=\"@btn-ghost-sm\">Edit</button></td>\n </tr>\n <tr className=\"@tr-hover\">\n <td className=\"@td font-medium\">Bob Smith</td>\n <td className=\"@td\"><span className=\"@badge-warning\">Pending</span></td>\n <td className=\"@td\">Editor</td>\n <td className=\"@td\"><button className=\"@btn-ghost-sm\">Edit</button></td>\n </tr>\n <tr className=\"@tr-hover\">\n <td className=\"@td font-medium\">Charlie Brown</td>\n <td className=\"@td\"><span className=\"@badge-danger\">Suspended</span></td>\n <td className=\"@td\">Subscriber</td>\n <td className=\"@td\"><button className=\"@btn-ghost-sm\">Edit</button></td>\n </tr>\n </tbody>\n </table>\n </div>\n );\n}`,\n\n \"layout.tsx\": `export function DashboardLayout({ children }) {\n return (\n <div className=\"@row min-h-screen items-stretch bg-background\">\n <aside className=\"@stack-md w-64 border-r border-border bg-card p-4\">\n <div className=\"flex h-12 items-center px-2 font-bold text-lg\">\n Shortwind Console\n </div>\n <nav className=\"@nav flex-col items-stretch\">\n <a className=\"@nav-link-active\" href=\"/dashboard\">Dashboard</a>\n <a className=\"@nav-link\" href=\"/analytics\">Analytics</a>\n <a className=\"@nav-link\" href=\"/settings\">Settings</a>\n </nav>\n </aside>\n <div className=\"@stack-md flex-1\">\n <header className=\"@row-between h-16 border-b border-border bg-card px-6\">\n <h1 className=\"@heading-sm\">Overview</h1>\n <div className=\"@row gap-4\">\n <button className=\"@btn-icon\"><span className=\"sr-only\">Notifications</span></button>\n <div className=\"h-8 w-8 rounded-full bg-primary/20\" />\n </div>\n </header>\n <main className=\"@wrapper @stack-lg py-8\">\n <div className=\"@grid-3\">\n <div className=\"@card-elevated\">Card 1</div>\n <div className=\"@card-elevated\">Card 2</div>\n <div className=\"@card-elevated\">Card 3</div>\n </div>\n <div className=\"flex-1\">{children}</div>\n </main>\n </div>\n </div>\n );\n}`\n};\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { glob } from \"tinyglobby\";\nimport { buildRegistry, isReservedRecipeName, parseRecipeFile } from \"@shortwind/core\";\nimport type { Recipe, Registry } from \"@shortwind/core\";\nimport { installedFamilies, readConfig } from \"../project.js\";\n\nexport const ALL_RULES = [\n \"recipe/unknown\",\n \"recipe/cycle\",\n \"recipe/duplicate\",\n \"recipe/unused\",\n \"recipe/no-redundant-utility\",\n \"recipe/bad-suffix-order\",\n \"recipe/conflicting-intent\",\n \"recipe/dynamic-class\",\n \"recipe/no-sibling-overlap\",\n \"recipe/reserved-name\",\n] as const;\n\nexport type Rule = (typeof ALL_RULES)[number];\n\nexport type Severity = \"error\" | \"warning\" | \"info\";\n\nexport type Finding = {\n rule: Rule;\n severity: Severity;\n file: string;\n line: number;\n column: number;\n message: string;\n};\n\nexport type LintOptions = {\n cwd: string;\n rules?: Rule[];\n fix?: boolean;\n content?: string[];\n};\n\nexport type LintResult = {\n ok: boolean;\n findings: Finding[];\n filesFixed: string[];\n};\n\nconst DEFAULT_CONTENT = [\n \"src/**/*.{html,js,jsx,ts,tsx,vue,svelte,astro,md,mdx}\",\n];\n\nexport async function lint(options: LintOptions): Promise<LintResult> {\n const cwd = path.resolve(options.cwd);\n const config = await readConfig(cwd);\n const recipesDir = path.join(cwd, config.recipesDir);\n const enabledRules = new Set<Rule>(options.rules ?? ALL_RULES);\n const findings: Finding[] = [];\n\n const { registry, parseFindings } = loadRegistry(recipesDir, enabledRules);\n findings.push(...parseFindings);\n findings.push(...checkRecipeNames(registry, recipesDir, enabledRules));\n findings.push(...checkReservedNames(registry, recipesDir, enabledRules));\n\n const contentGlobs = options.content ?? DEFAULT_CONTENT;\n // tinyglobby evaluates ignore patterns relative to `cwd`; an absolute\n // recipesDir path passed verbatim would either fail to match or match by\n // accident on case-folded filesystems. Use the project-relative form.\n const recipesIgnore = path.posix.join(\n path.relative(cwd, recipesDir).split(path.sep).join(\"/\") || \".\",\n \"**\",\n );\n const files = await glob(contentGlobs, {\n cwd,\n absolute: true,\n onlyFiles: true,\n ignore: [\"**/node_modules/**\", \"**/dist/**\", \"**/.next/**\", recipesIgnore],\n });\n\n const usedRecipes = new Set<string>();\n const filesFixed: string[] = [];\n\n for (const file of files) {\n const source = await readFile(file, \"utf8\");\n const usages = extractClassUsages(source);\n for (const u of usages) {\n for (const token of u.tokens) {\n if (!token.value.startsWith(\"@\")) continue;\n const name = token.value.slice(1);\n // `Object.hasOwn`, never a truthy lookup: a token like `@constructor`\n // would otherwise resolve an inherited Object.prototype member and be\n // silently treated as a known recipe instead of flagged unknown.\n if (Object.hasOwn(registry.flattened, name)) usedRecipes.add(name);\n else if (enabledRules.has(\"recipe/unknown\")) {\n findings.push({\n rule: \"recipe/unknown\",\n severity: \"error\",\n file,\n line: token.line,\n column: token.column,\n message: `unknown recipe @${name}`,\n });\n }\n }\n if (enabledRules.has(\"recipe/bad-suffix-order\")) {\n findings.push(...checkUsageSuffixOrder(file, u.tokens, registry));\n }\n if (enabledRules.has(\"recipe/conflicting-intent\")) {\n findings.push(...checkConflictingIntent(file, u.tokens, registry));\n }\n if (enabledRules.has(\"recipe/no-sibling-overlap\")) {\n findings.push(...checkSiblingOverlap(file, u.tokens, registry));\n }\n if (enabledRules.has(\"recipe/dynamic-class\")) {\n findings.push(...checkDynamicClass(file, u.dynamicTokens));\n }\n }\n\n if (enabledRules.has(\"recipe/no-redundant-utility\")) {\n const result = checkRedundantUtility(file, source, registry, options.fix === true);\n findings.push(...result.findings);\n if (options.fix && result.fixed !== null && result.fixed !== source) {\n await writeFile(file, result.fixed);\n filesFixed.push(file);\n }\n }\n }\n\n if (enabledRules.has(\"recipe/unused\")) {\n const recipesByName = new Map<string, Recipe>();\n for (const recs of Object.values(registry.families)) {\n for (const r of recs) recipesByName.set(r.name, r);\n }\n for (const name of Object.keys(registry.flattened)) {\n if (usedRecipes.has(name)) continue;\n const recipe = recipesByName.get(name);\n if (!recipe) continue;\n findings.push({\n rule: \"recipe/unused\",\n severity: \"info\",\n file: path.join(recipesDir, recipe.sourceFile),\n line: recipe.sourceLine,\n column: 1,\n message: `recipe @${name} is defined but never referenced`,\n });\n }\n }\n\n findings.sort((a, b) => {\n if (a.file !== b.file) return a.file.localeCompare(b.file);\n if (a.line !== b.line) return a.line - b.line;\n return a.column - b.column;\n });\n\n const ok = !findings.some((f) => f.severity === \"error\");\n return { ok, findings, filesFixed };\n}\n\nfunction loadRegistry(\n recipesDir: string,\n rules: Set<Rule>,\n): { registry: Registry; parseFindings: Finding[] } {\n const families = installedFamilies(recipesDir);\n const allRecipes: Recipe[] = [];\n const parseFindings: Finding[] = [];\n\n for (const family of families) {\n const filePath = path.join(recipesDir, `${family}.css`);\n const source = readFileSync(filePath, \"utf8\");\n const parsed = parseRecipeFile(source, `${family}.css`);\n if (!parsed.ok) {\n for (const err of parsed.errors) {\n parseFindings.push({\n rule: \"recipe/unknown\",\n severity: \"error\",\n file: filePath,\n line: err.line,\n column: err.column ?? 1,\n message: err.message,\n });\n }\n continue;\n }\n for (const r of parsed.value.recipes) allRecipes.push(r);\n }\n\n const built = buildRegistry(allRecipes);\n if (!built.ok) {\n for (const err of built.errors) {\n const rule = mapErrorCodeToRule(err.code);\n if (!rules.has(rule)) continue;\n parseFindings.push({\n rule,\n severity: \"error\",\n file: path.join(recipesDir, err.file),\n line: err.line,\n column: err.column ?? 1,\n message: err.message,\n });\n }\n return { registry: { flattened: {}, families: {} }, parseFindings };\n }\n return { registry: built.value, parseFindings };\n}\n\n// Explicit table from core's diagnostic codes to lint rules. Keeps the\n// mapping debuggable and prevents a typo in `err.code` from silently being\n// classified as `recipe/unknown`.\nconst ERROR_CODE_RULE: Record<string, Rule> = {\n \"resolve/cycle\": \"recipe/cycle\",\n \"resolve/duplicate-name\": \"recipe/duplicate\",\n \"resolve/unknown-reference\": \"recipe/unknown\",\n};\n\nfunction mapErrorCodeToRule(code: string): Rule {\n return ERROR_CODE_RULE[code] ?? \"recipe/unknown\";\n}\n\nconst SIZE_SUFFIXES = new Set([\"xs\", \"sm\", \"md\", \"lg\", \"xl\"]);\nconst INTENT_SUFFIXES = new Set([\n \"primary\",\n \"secondary\",\n \"ghost\",\n \"danger\",\n \"warning\",\n \"success\",\n \"info\",\n]);\n\nfunction recipeMeta(name: string, familyHint?: string): {\n family: string;\n intent: string | null;\n badOrder: string | null;\n} {\n const family =\n familyHint && (name === familyHint || name.startsWith(`${familyHint}-`))\n ? familyHint\n : name.split(\"-\")[0] ?? name;\n const suffix =\n name === family ? [] : name.slice(family.length + 1).split(\"-\").filter(Boolean);\n let intent: string | null = null;\n let firstSizeIdx = -1;\n let laterIntentIdx = -1;\n\n for (let i = 0; i < suffix.length; i++) {\n const part = suffix[i] ?? \"\";\n if (SIZE_SUFFIXES.has(part)) {\n if (firstSizeIdx === -1) firstSizeIdx = i;\n }\n if (INTENT_SUFFIXES.has(part)) {\n intent ??= part;\n if (firstSizeIdx !== -1) laterIntentIdx = i;\n }\n }\n\n let badOrder: string | null = null;\n if (firstSizeIdx !== -1 && laterIntentIdx !== -1) {\n const reordered = [\n family,\n ...suffix.filter((p) => INTENT_SUFFIXES.has(p)),\n ...suffix.filter((p) => !INTENT_SUFFIXES.has(p) && !SIZE_SUFFIXES.has(p)),\n ...suffix.filter((p) => SIZE_SUFFIXES.has(p)),\n ];\n badOrder = reordered.join(\"-\");\n }\n\n return { family, intent, badOrder };\n}\n\nfunction checkRecipeNames(\n registry: Registry,\n recipesDir: string,\n enabledRules: Set<Rule>,\n): Finding[] {\n if (!enabledRules.has(\"recipe/bad-suffix-order\")) return [];\n const findings: Finding[] = [];\n for (const [family, recipes] of Object.entries(registry.families)) {\n for (const recipe of recipes) {\n const meta = recipeMeta(recipe.name, family);\n if (!meta.badOrder) continue;\n findings.push({\n rule: \"recipe/bad-suffix-order\",\n severity: \"warning\",\n file: path.join(recipesDir, recipe.sourceFile),\n line: recipe.sourceLine,\n column: 1,\n message: `recipe @${recipe.name} uses size before intent; prefer @${meta.badOrder}`,\n });\n }\n }\n return findings;\n}\n\nfunction checkReservedNames(\n registry: Registry,\n recipesDir: string,\n enabledRules: Set<Rule>,\n): Finding[] {\n if (!enabledRules.has(\"recipe/reserved-name\")) return [];\n const findings: Finding[] = [];\n for (const recipes of Object.values(registry.families)) {\n for (const recipe of recipes) {\n if (!isReservedRecipeName(recipe.name)) continue;\n findings.push({\n rule: \"recipe/reserved-name\",\n severity: \"error\",\n file: path.join(recipesDir, recipe.sourceFile),\n line: recipe.sourceLine,\n column: 1,\n message: `recipe @${recipe.name} collides with a reserved Tailwind @-utility; rename it`,\n });\n }\n }\n return findings;\n}\n\nfunction checkUsageSuffixOrder(\n file: string,\n tokens: ClassUsage[\"tokens\"],\n registry: Registry,\n): Finding[] {\n const findings: Finding[] = [];\n for (const token of tokens) {\n if (!token.value.startsWith(\"@\")) continue;\n const name = token.value.slice(1);\n if (!Object.hasOwn(registry.flattened, name)) continue;\n const meta = recipeMeta(name, familyForRecipe(registry, name));\n if (!meta.badOrder) continue;\n findings.push({\n rule: \"recipe/bad-suffix-order\",\n severity: \"warning\",\n file,\n line: token.line,\n column: token.column,\n message: `@${name} uses size before intent; prefer @${meta.badOrder}`,\n });\n }\n return findings;\n}\n\nfunction checkConflictingIntent(\n file: string,\n tokens: ClassUsage[\"tokens\"],\n registry: Registry,\n): Finding[] {\n const byFamily = new Map<\n string,\n Map<string, { token: ClassUsage[\"tokens\"][number]; name: string }>\n >();\n for (const token of tokens) {\n if (!token.value.startsWith(\"@\")) continue;\n const name = token.value.slice(1);\n if (!Object.hasOwn(registry.flattened, name)) continue;\n const meta = recipeMeta(name, familyForRecipe(registry, name));\n if (!meta.intent) continue;\n const familyIntents =\n byFamily.get(meta.family) ??\n new Map<string, { token: ClassUsage[\"tokens\"][number]; name: string }>();\n familyIntents.set(meta.intent, { token, name });\n byFamily.set(meta.family, familyIntents);\n }\n\n const findings: Finding[] = [];\n for (const [family, intents] of byFamily) {\n if (intents.size < 2) continue;\n const intentNames = Array.from(intents.values())\n .map((entry) => `@${entry.name}`)\n .sort();\n const first = Array.from(intents.values())\n .map((entry) => entry.token)\n .sort((a, b) => a.column - b.column)[0]!;\n findings.push({\n rule: \"recipe/conflicting-intent\",\n severity: \"warning\",\n file,\n line: first.line,\n column: first.column,\n message: `multiple ${family} intents on one element: ${intentNames.join(\", \")}`,\n });\n }\n return findings;\n}\n\nfunction familyForRecipe(registry: Registry, name: string): string | undefined {\n for (const [family, recipes] of Object.entries(registry.families)) {\n if (recipes.some((recipe) => recipe.name === name)) return family;\n }\n return undefined;\n}\n\nfunction checkSiblingOverlap(\n file: string,\n tokens: ClassUsage[\"tokens\"],\n registry: Registry,\n): Finding[] {\n const byFamily = new Map<string, Array<{ token: ClassUsage[\"tokens\"][number]; name: string }>>();\n for (const token of tokens) {\n if (!token.value.startsWith(\"@\")) continue;\n const name = token.value.slice(1);\n if (!Object.hasOwn(registry.flattened, name)) continue;\n const family = familyForRecipe(registry, name) ?? name.split(\"-\")[0] ?? name;\n const arr = byFamily.get(family) ?? [];\n arr.push({ token, name });\n byFamily.set(family, arr);\n }\n\n const findings: Finding[] = [];\n for (const [family, entries] of byFamily) {\n const unique = new Set(entries.map((e) => e.name));\n if (unique.size < 2) continue;\n const first = entries.map((e) => e.token).sort((a, b) => a.column - b.column)[0]!;\n const names = Array.from(unique).map((n) => `@${n}`).sort();\n findings.push({\n rule: \"recipe/no-sibling-overlap\",\n severity: \"warning\",\n file,\n line: first.line,\n column: first.column,\n message: `multiple ${family} recipes on one element: ${names.join(\", \")}`,\n });\n }\n return findings;\n}\n\n// Dynamic recipe names defeat unknown-reference checking and the safelist\n// pass — Tailwind never sees the computed token, so the recipe's expanded\n// utilities won't appear in the bundle unless they're already in another file.\nfunction checkDynamicClass(\n file: string,\n dynamicTokens: ClassUsage[\"dynamicTokens\"],\n): Finding[] {\n const findings: Finding[] = [];\n for (const token of dynamicTokens) {\n if (!token.value.includes(\"@\")) continue;\n findings.push({\n rule: \"recipe/dynamic-class\",\n severity: \"warning\",\n file,\n line: token.line,\n column: token.column,\n message: `dynamic recipe name ${token.value} — Tailwind cannot statically resolve this`,\n });\n }\n return findings;\n}\n\ntype ClassUsage = {\n fileOffset: number;\n // Exact source offset of the first character inside the attribute value\n // (just past the opening quote). raw.length characters from here is the\n // closing quote. Used by the auto-fix writer; indexOf-based location\n // hunting is unsafe because two attributes can share the same raw text.\n valueStart: number;\n raw: string;\n tokens: Array<{ value: string; line: number; column: number }>;\n // Tokens that contain a `${...}` interpolation. Surfaced separately so the\n // dynamic-class rule can flag computed recipe names while the normal token\n // pipeline still treats them as opaque.\n dynamicTokens: Array<{ value: string; line: number; column: number }>;\n // Only string-literal attribute values can be auto-fixed in place;\n // JSX expression containers (className={...}) may wrap clsx() / template\n // literals where blind substring writes would be unsafe.\n fixable: boolean;\n};\n\nconst CLASS_ATTR_STR_RE = /\\b(?:class|className)\\s*=\\s*([\"'])([^\"']*)\\1/g;\nconst CLASS_ATTR_BRACE_RE = /\\b(?:class|className)\\s*=\\s*\\{/g;\nconst STRING_LITERAL_RE = /([\"'`])((?:\\\\.|(?!\\1)[^\\\\])*)\\1/g;\n// The build's jsx-transform expands recipes inside variant-authoring calls\n// (cva/tv), not just className attributes. Lint must see those too, or recipes\n// referenced only from a `cva(...)` get falsely reported as recipe/unused.\nconst CALL_EXPANDER_RE = /\\b(?:cva|tv)\\s*\\(/g;\n\nexport function extractClassUsages(source: string): ClassUsage[] {\n const usages: ClassUsage[] = [];\n for (const m of source.matchAll(CLASS_ATTR_STR_RE)) {\n const value = m[2] ?? \"\";\n const attrStart = m.index ?? 0;\n const valueStart = attrStart + m[0]!.length - 1 - value.length;\n const { tokens, dynamicTokens } = tokenizeClassString(source, value, valueStart);\n usages.push({\n fileOffset: attrStart,\n valueStart,\n raw: value,\n tokens,\n dynamicTokens,\n fixable: true,\n });\n }\n\n for (const m of source.matchAll(CLASS_ATTR_BRACE_RE)) {\n const openBrace = (m.index ?? 0) + m[0]!.length - 1;\n const close = findMatchingBrace(source, openBrace);\n if (close === -1) continue;\n const inner = source.slice(openBrace + 1, close);\n for (const sm of inner.matchAll(STRING_LITERAL_RE)) {\n const value = sm[2] ?? \"\";\n if (value.length === 0) continue;\n const literalStart = openBrace + 1 + (sm.index ?? 0);\n const valueStart = literalStart + 1;\n const { tokens, dynamicTokens } = tokenizeClassString(source, value, valueStart);\n if (tokens.length === 0 && dynamicTokens.length === 0) continue;\n usages.push({\n fileOffset: literalStart,\n valueStart,\n raw: value,\n tokens,\n dynamicTokens,\n fixable: false,\n });\n }\n }\n\n for (const m of source.matchAll(CALL_EXPANDER_RE)) {\n const openParen = (m.index ?? 0) + m[0]!.length - 1;\n const close = findMatchingDelimiter(source, openParen, \"(\", \")\");\n if (close === -1) continue;\n const inner = source.slice(openParen + 1, close);\n for (const sm of inner.matchAll(STRING_LITERAL_RE)) {\n const value = sm[2] ?? \"\";\n if (value.length === 0) continue;\n const literalStart = openParen + 1 + (sm.index ?? 0);\n const valueStart = literalStart + 1;\n const { tokens, dynamicTokens } = tokenizeClassString(source, value, valueStart);\n // Only string args that actually reference a recipe matter here; plain\n // utility strings in cva (the common case) carry no @-tokens and are\n // ignored so we don't widen unrelated rules.\n if (!tokens.some((t) => t.value.startsWith(\"@\")) && dynamicTokens.length === 0) continue;\n usages.push({\n fileOffset: literalStart,\n valueStart,\n raw: value,\n tokens,\n dynamicTokens,\n fixable: false,\n });\n }\n }\n\n return usages;\n}\n\nfunction tokenizeClassString(\n source: string,\n value: string,\n valueStart: number,\n): {\n tokens: Array<{ value: string; line: number; column: number }>;\n dynamicTokens: Array<{ value: string; line: number; column: number }>;\n} {\n const tokens: Array<{ value: string; line: number; column: number }> = [];\n const dynamicTokens: Array<{ value: string; line: number; column: number }> = [];\n let offset = 0;\n for (const piece of value.split(/(\\s+)/)) {\n if (/^\\s+$/.test(piece) || piece.length === 0) {\n offset += piece.length;\n continue;\n }\n const abs = valueStart + offset;\n const { line, column } = offsetToLineCol(source, abs);\n // Tokens containing `${...}` are opaque to the normal pipeline (no merge,\n // no unknown-reference check) but still surfaced separately so the\n // dynamic-class rule can warn on computed recipe names.\n if (piece.includes(\"${\")) {\n dynamicTokens.push({ value: piece, line, column });\n offset += piece.length;\n continue;\n }\n tokens.push({ value: piece, line, column });\n offset += piece.length;\n }\n return { tokens, dynamicTokens };\n}\n\nfunction findMatchingBrace(source: string, openIdx: number): number {\n return findMatchingDelimiter(source, openIdx, \"{\", \"}\");\n}\n\n// Remove only the redundant tokens from the original attribute value, leaving\n// every other character — interior whitespace/newlines AND `${...}` dynamic\n// tokens — exactly as written. Rebuilding via `tokens.join(\" \")` instead would\n// delete dynamic tokens (they're tracked separately) and collapse whitespace in\n// attributes that had nothing to fix.\nfunction spliceRedundantTokens(raw: string, isRedundant: (token: string) => boolean): string {\n const pieces = raw.split(/(\\s+)/); // alternating content / whitespace runs\n const drop = new Array<boolean>(pieces.length).fill(false);\n for (let i = 0; i < pieces.length; i++) {\n const piece = pieces[i] ?? \"\";\n if (piece.length === 0 || /^\\s+$/.test(piece)) continue;\n if (piece.startsWith(\"@\") || piece.includes(\"${\")) continue; // never touch recipes/dynamic\n if (!isRedundant(piece)) continue;\n drop[i] = true;\n // Also drop one adjacent whitespace run so removing a token doesn't leave a\n // double gap; prefer the leading run (collapses cleanly against the\n // preceding token), fall back to the trailing one for a leading token.\n if (i > 0 && /^\\s+$/.test(pieces[i - 1] ?? \"\")) drop[i - 1] = true;\n else if (/^\\s+$/.test(pieces[i + 1] ?? \"\")) drop[i + 1] = true;\n }\n return pieces.filter((_, i) => !drop[i]).join(\"\");\n}\n\n// Walks past the opening delimiter at openIdx to its match, skipping over\n// string and template-literal contents (and nested `${...}` interpolations,\n// which always use braces regardless of the outer delimiter).\nfunction findMatchingDelimiter(\n source: string,\n openIdx: number,\n open: string,\n close: string,\n): number {\n let depth = 1;\n let i = openIdx + 1;\n while (i < source.length && depth > 0) {\n const ch = source[i];\n if (ch === '\"' || ch === \"'\") {\n const quote = ch;\n i++;\n while (i < source.length) {\n if (source[i] === \"\\\\\") {\n i += 2;\n continue;\n }\n if (source[i] === quote) {\n i++;\n break;\n }\n i++;\n }\n continue;\n }\n if (ch === \"`\") {\n i++;\n while (i < source.length) {\n if (source[i] === \"\\\\\") {\n i += 2;\n continue;\n }\n if (source[i] === \"`\") {\n i++;\n break;\n }\n if (source[i] === \"$\" && source[i + 1] === \"{\") {\n i += 2;\n let exprDepth = 1;\n while (i < source.length && exprDepth > 0) {\n if (source[i] === \"{\") exprDepth++;\n else if (source[i] === \"}\") exprDepth--;\n i++;\n }\n continue;\n }\n i++;\n }\n continue;\n }\n if (ch === open) depth++;\n else if (ch === close) depth--;\n i++;\n }\n return depth === 0 ? i - 1 : -1;\n}\n\nfunction offsetToLineCol(source: string, offset: number): { line: number; column: number } {\n const limit = Math.min(offset, source.length);\n let line = 1;\n let lastNl = -1;\n for (let i = 0; i < limit; i++) {\n if (source[i] === \"\\n\") {\n line++;\n lastNl = i;\n }\n }\n return { line, column: offset - lastNl };\n}\n\nfunction checkRedundantUtility(\n file: string,\n source: string,\n registry: Registry,\n applyFix: boolean,\n): { findings: Finding[]; fixed: string | null } {\n const findings: Finding[] = [];\n let fixed: string | null = applyFix ? \"\" : null;\n let cursor = 0;\n const usages = extractClassUsages(source).sort((a, b) => a.fileOffset - b.fileOffset);\n for (const usage of usages) {\n const expansions = new Set<string>();\n for (const tok of usage.tokens) {\n if (!tok.value.startsWith(\"@\")) continue;\n const exp = registry.flattened[tok.value.slice(1)];\n if (!exp) continue;\n for (const t of exp) expansions.add(t);\n }\n if (expansions.size === 0) continue;\n\n for (const tok of usage.tokens) {\n if (!tok.value.startsWith(\"@\") && expansions.has(tok.value)) {\n findings.push({\n rule: \"recipe/no-redundant-utility\",\n severity: \"info\",\n file,\n line: tok.line,\n column: tok.column,\n message: `${tok.value} is already included by a recipe on this element`,\n });\n }\n }\n\n if (fixed !== null && usage.fixable) {\n // valueStart is the exact offset of the first content char (just past\n // the opening quote); raw.length is the content length. Splice out only\n // the redundant tokens so dynamic tokens and whitespace survive verbatim.\n if (usage.valueStart < cursor) continue;\n fixed += source.slice(cursor, usage.valueStart);\n fixed += spliceRedundantTokens(usage.raw, (t) => expansions.has(t));\n cursor = usage.valueStart + usage.raw.length;\n }\n }\n if (fixed !== null) fixed += source.slice(cursor);\n return { findings, fixed };\n}\n\nexport function formatFindingsText(findings: Finding[]): string {\n if (findings.length === 0) return \"\";\n return findings\n .map(\n (f) =>\n `${f.file}:${f.line}:${f.column} ${f.severity} ${f.message} [${f.rule}]`,\n )\n .join(\"\\n\");\n}\n","import { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { glob } from \"tinyglobby\";\nimport { Tiktoken } from \"js-tiktoken/lite\";\nimport cl100k_base from \"js-tiktoken/ranks/cl100k_base\";\nimport { buildRegistry, parseRecipeFile, type Registry, type Recipe } from \"@shortwind/core\";\nimport { transformContent, loadRegistryFromDir, modeForFile } from \"@shortwind/tailwind\";\nimport { readConfig } from \"../project.js\";\nimport { DEFAULT_RECIPES_CSS } from \"../bench-corpus/default-recipes.js\";\nimport { CORPUS_FILES } from \"../bench-corpus/corpus.js\";\nimport { extractClassUsages } from \"./lint.js\";\n\nexport type BenchOptions = {\n cwd: string;\n corpus?: boolean;\n path?: string;\n};\n\nexport type FileBenchResult = {\n filename: string;\n compactClassTokens: number;\n expandedClassTokens: number;\n compactClassBytes: number;\n expandedClassBytes: number;\n compactFileBytes: number;\n expandedFileBytes: number;\n compactLlmTokens: number;\n expandedLlmTokens: number;\n};\n\nexport type BenchTotals = Omit<FileBenchResult, \"filename\">;\n\nexport type BenchResult = {\n files: FileBenchResult[];\n totals: BenchTotals;\n};\n\nconst DEFAULT_CONTENT_GLOBS = [\n \"src/**/*.{html,js,jsx,ts,tsx,vue,svelte,astro,md,mdx}\",\n];\n\nexport async function bench(options: BenchOptions): Promise<BenchResult> {\n const cwd = path.resolve(options.cwd);\n let registry: Registry;\n\n const runOnCorpus = options.corpus || !hasShortwindConfig(cwd);\n\n if (runOnCorpus) {\n registry = loadDefaultRegistry();\n } else {\n const config = await readConfig(cwd);\n const recipesDir = path.join(cwd, config.recipesDir);\n registry = loadRegistryFromDir(recipesDir);\n }\n\n const filesToBench: Array<{ filename: string; content: string }> = [];\n\n if (runOnCorpus) {\n for (const [filename, content] of Object.entries(CORPUS_FILES)) {\n filesToBench.push({ filename, content });\n }\n } else {\n const config = await readConfig(cwd);\n const recipesDir = path.join(cwd, config.recipesDir);\n const recipesIgnore = path.posix.join(\n path.relative(cwd, recipesDir).split(path.sep).join(\"/\") || \".\",\n \"**\",\n );\n const contentGlobs = options.path ? [options.path] : DEFAULT_CONTENT_GLOBS;\n const matchedFiles = await glob(contentGlobs, {\n cwd,\n absolute: true,\n onlyFiles: true,\n ignore: [\"**/node_modules/**\", \"**/dist/**\", \"**/.next/**\", recipesIgnore],\n });\n\n for (const file of matchedFiles) {\n const content = await readFile(file, \"utf8\");\n const relative = path.relative(cwd, file);\n filesToBench.push({ filename: relative, content });\n }\n }\n\n const results: FileBenchResult[] = [];\n const totals: BenchTotals = {\n compactClassTokens: 0,\n expandedClassTokens: 0,\n compactClassBytes: 0,\n expandedClassBytes: 0,\n compactFileBytes: 0,\n expandedFileBytes: 0,\n compactLlmTokens: 0,\n expandedLlmTokens: 0,\n };\n\n for (const { filename, content } of filesToBench) {\n const expanded = transformContent(content, registry, { mode: modeForFile(filename) });\n\n const compactUsages = extractClassUsages(content);\n const expandedUsages = extractClassUsages(expanded);\n\n const fileResult: FileBenchResult = {\n filename,\n compactClassTokens: sumTokens(compactUsages),\n expandedClassTokens: sumTokens(expandedUsages),\n compactClassBytes: sumBytes(compactUsages),\n expandedClassBytes: sumBytes(expandedUsages),\n compactFileBytes: Buffer.byteLength(content, \"utf8\"),\n expandedFileBytes: Buffer.byteLength(expanded, \"utf8\"),\n compactLlmTokens: countLlmTokens(content),\n expandedLlmTokens: countLlmTokens(expanded),\n };\n\n results.push(fileResult);\n\n totals.compactClassTokens += fileResult.compactClassTokens;\n totals.expandedClassTokens += fileResult.expandedClassTokens;\n totals.compactClassBytes += fileResult.compactClassBytes;\n totals.expandedClassBytes += fileResult.expandedClassBytes;\n totals.compactFileBytes += fileResult.compactFileBytes;\n totals.expandedFileBytes += fileResult.expandedFileBytes;\n totals.compactLlmTokens += fileResult.compactLlmTokens;\n totals.expandedLlmTokens += fileResult.expandedLlmTokens;\n }\n\n return { files: results, totals };\n}\n\nfunction hasShortwindConfig(cwd: string): boolean {\n return existsSync(path.join(cwd, \"shortwind.config.json\"));\n}\n\nfunction loadDefaultRegistry(): Registry {\n const allRecipes: Recipe[] = [];\n for (const [filename, source] of Object.entries(DEFAULT_RECIPES_CSS)) {\n const parsed = parseRecipeFile(source, filename);\n if (parsed.ok) {\n allRecipes.push(...parsed.value.recipes);\n }\n }\n const resolved = buildRegistry(allRecipes);\n if (!resolved.ok) {\n throw new Error(\n `Failed to build default registry: ${resolved.errors.map((e) => e.message).join(\"; \")}`,\n );\n }\n return resolved.value;\n}\n\nfunction sumTokens(usages: ReturnType<typeof extractClassUsages>): number {\n let count = 0;\n for (const u of usages) {\n count += u.tokens.length;\n }\n return count;\n}\n\nfunction sumBytes(usages: ReturnType<typeof extractClassUsages>): number {\n let count = 0;\n for (const u of usages) {\n count += Buffer.byteLength(u.raw, \"utf8\");\n }\n return count;\n}\n\n// cl100k_base is the encoding used by GPT-4 and the closest broadly-available\n// proxy for modern frontier-model tokenization. Anthropic's tokenizer isn't\n// public, but the *ratio* between compact and expanded forms is what backs the\n// README claim, and BPE schemes agree on that ratio within a few percent.\nlet _encoder: Tiktoken | null = null;\nfunction getEncoder(): Tiktoken {\n if (!_encoder) _encoder = new Tiktoken(cl100k_base);\n return _encoder;\n}\n\nexport function countLlmTokens(str: string): number {\n return getEncoder().encode(str).length;\n}\n\nexport function formatBenchTable(result: BenchResult): string {\n const lines: string[] = [];\n const colWidths = {\n file: 20,\n metric: 12,\n shortwind: 12,\n expanded: 12,\n saved: 10,\n };\n\n for (const f of result.files) {\n colWidths.file = Math.max(colWidths.file, f.filename.length);\n }\n\n const padR = (str: string, width: number): string => str.padEnd(width);\n const padL = (str: string, width: number): string => str.padStart(width);\n\n // Percent the expanded form would grow over compact, framed as \"saved\" from\n // the compact side. exp == 0 short-circuits to avoid divide-by-zero on files\n // with no class usage.\n const formatPct = (compact: number, exp: number): string => {\n if (exp === 0) return \"0.0%\";\n return `${((1 - compact / exp) * 100).toFixed(1)}%`;\n };\n\n const row = (file: string, metric: string, compact: number, exp: number): string =>\n [\n padR(file, colWidths.file),\n padR(metric, colWidths.metric),\n padL(compact.toString(), colWidths.shortwind),\n padL(exp.toString(), colWidths.expanded),\n padL(formatPct(compact, exp), colWidths.saved),\n ].join(\" \");\n\n const header = [\n padR(\"File\", colWidths.file),\n padR(\"Metric\", colWidths.metric),\n padL(\"Shortwind\", colWidths.shortwind),\n padL(\"Expanded\", colWidths.expanded),\n padL(\"Saved\", colWidths.saved),\n ].join(\" \");\n\n lines.push(header);\n lines.push(\"-\".repeat(header.length));\n\n for (const f of result.files) {\n lines.push(row(f.filename, \"Class Words\", f.compactClassTokens, f.expandedClassTokens));\n lines.push(row(\"\", \"Class Bytes\", f.compactClassBytes, f.expandedClassBytes));\n lines.push(row(\"\", \"File Bytes\", f.compactFileBytes, f.expandedFileBytes));\n lines.push(row(\"\", \"File Tokens\", f.compactLlmTokens, f.expandedLlmTokens));\n lines.push(\"-\".repeat(header.length));\n }\n\n lines.push(row(\"TOTAL\", \"Class Words\", result.totals.compactClassTokens, result.totals.expandedClassTokens));\n lines.push(row(\"\", \"Class Bytes\", result.totals.compactClassBytes, result.totals.expandedClassBytes));\n lines.push(row(\"\", \"File Bytes\", result.totals.compactFileBytes, result.totals.expandedFileBytes));\n lines.push(row(\"\", \"File Tokens\", result.totals.compactLlmTokens, result.totals.expandedLlmTokens));\n lines.push(\"-\".repeat(header.length));\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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"}
|
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { S as
|
|
2
|
+
import { C as cliVersion, S as DEFAULT_REGISTRY, _ as NewFamilyError, a as formatFindingsText, b as add, c as UpgradeError, d as BuildError, f as build, g as reseal, h as preset, l as upgrade, m as ls, n as formatBenchTable, o as lint, p as formatLsText, r as ALL_RULES, s as verify, t as bench, u as dev, v as newFamily, w as init, y as remove } from "./bench-BGTQAha8.js";
|
|
3
3
|
import * as p from "@clack/prompts";
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
import { cac } from "cac";
|
|
@@ -155,6 +155,17 @@ async function run(argv = process.argv) {
|
|
|
155
155
|
throw err;
|
|
156
156
|
}
|
|
157
157
|
});
|
|
158
|
+
cli.command("reseal [...families]", "Re-sign recipes after intentional edits (updates header sha + lockfile)").option("--cwd <dir>", "Working directory").action(async (families, opts) => {
|
|
159
|
+
const result = await reseal({
|
|
160
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
161
|
+
families
|
|
162
|
+
});
|
|
163
|
+
for (const f of result.resealed) p.log.success(`resealed ${f}`);
|
|
164
|
+
for (const f of result.unchanged) p.log.info(`${f} already sealed`);
|
|
165
|
+
for (const f of result.notFound) p.log.warn(`${f} is not installed`);
|
|
166
|
+
for (const f of result.noHeader) p.log.warn(`${f} has no fingerprint header`);
|
|
167
|
+
if (result.resealed.length === 0 && result.unchanged.length > 0 && result.notFound.length === 0) p.log.success("all recipes already sealed");
|
|
168
|
+
});
|
|
158
169
|
cli.command("verify", "Check installed recipes against fingerprint headers and lockfile").option("--cwd <dir>", "Working directory").action(async (opts) => {
|
|
159
170
|
const result = await verify({ cwd: opts.cwd ?? process.cwd() });
|
|
160
171
|
if (result.ok) {
|
|
@@ -190,7 +201,6 @@ async function run(argv = process.argv) {
|
|
|
190
201
|
cli.command("bench [path]", "Benchmark token savings in a corpus or target directory").option("--corpus", "Run benchmark on the built-in corpus").option("--json", "Emit machine-readable JSON").option("--cwd <dir>", "Working directory").action(async (pathArg, opts) => {
|
|
191
202
|
const benchOptions = { cwd: opts.cwd ?? process.cwd() };
|
|
192
203
|
if (opts.corpus) benchOptions.corpus = true;
|
|
193
|
-
if (opts.json) benchOptions.json = true;
|
|
194
204
|
if (pathArg !== void 0) benchOptions.path = pathArg;
|
|
195
205
|
const result = await bench(benchOptions);
|
|
196
206
|
if (opts.json) process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
@@ -200,8 +210,9 @@ async function run(argv = process.argv) {
|
|
|
200
210
|
}
|
|
201
211
|
});
|
|
202
212
|
cli.help();
|
|
203
|
-
cli.version("0.0.0");
|
|
204
|
-
cli.parse(argv);
|
|
213
|
+
cli.version(cliVersion() ?? "0.0.0");
|
|
214
|
+
cli.parse(argv, { run: false });
|
|
215
|
+
await cli.runMatchedCommand();
|
|
205
216
|
}
|
|
206
217
|
function makeInteractiveResolver() {
|
|
207
218
|
return async (ctx) => {
|
|
@@ -238,6 +249,7 @@ function describeVerifyIssue(issue) {
|
|
|
238
249
|
switch (issue.kind) {
|
|
239
250
|
case "missing-header": return "no fingerprint header — recipe was hand-stripped";
|
|
240
251
|
case "header-tampered": return `header sha ${issue.recorded} but body hashes to ${issue.actual}`;
|
|
252
|
+
case "legacy-fingerprint": return `sealed with an older fingerprint format (${issue.recorded}) — run \`shortwind reseal\` to upgrade it (the recipe body is unchanged)`;
|
|
241
253
|
case "lockfile-mismatch": return `lockfile expects ${issue.locked} but body hashes to ${issue.actual}`;
|
|
242
254
|
case "missing-lock-entry": return "installed but not recorded in lockfile";
|
|
243
255
|
case "missing-file": return "in lockfile but file is missing";
|
|
@@ -257,6 +269,13 @@ async function promptForPreset() {
|
|
|
257
269
|
}
|
|
258
270
|
return choice;
|
|
259
271
|
}
|
|
272
|
+
function installCmd(pm) {
|
|
273
|
+
switch (pm) {
|
|
274
|
+
case "bun": return "bun add -d";
|
|
275
|
+
case "npm": return "npm install -D";
|
|
276
|
+
default: return `${pm} add -D`;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
260
279
|
function printInitSummary(result) {
|
|
261
280
|
p.note([
|
|
262
281
|
`preset: ${result.preset}`,
|
|
@@ -264,17 +283,34 @@ function printInitSummary(result) {
|
|
|
264
283
|
`package manager: ${result.packageManager}`,
|
|
265
284
|
`families copied: ${result.installedFamilies.length}`,
|
|
266
285
|
`families skipped: ${result.skippedFamilies.length}`,
|
|
286
|
+
`adapters: ${result.installOk ? result.installedPackages.join(", ") || "none" : "install failed — see below"}`,
|
|
267
287
|
``,
|
|
268
288
|
`config: ${result.configPath}`,
|
|
269
289
|
`vscode settings: ${result.vscodePath}`,
|
|
270
290
|
`pre-commit: ${result.huskyPath}`,
|
|
271
291
|
`SKILL.md: ${result.skillPath}`,
|
|
272
292
|
`theme: ${describeTheme(result)}`,
|
|
273
|
-
`bundler config: ${describeBundlerConfig(result)}
|
|
293
|
+
`bundler config: ${describeBundlerConfig(result)}`,
|
|
294
|
+
`agent guide: ${describeAgentsFile(result)}`
|
|
274
295
|
].join("\n"), "shortwind init");
|
|
296
|
+
if (!result.installOk) {
|
|
297
|
+
const v = cliVersion();
|
|
298
|
+
const specs = result.installedPackages.map((p) => v ? `${p}@${v}` : p).join(" ");
|
|
299
|
+
p.log.warn(`Couldn't auto-install adapters (your recipes and config were still scaffolded).
|
|
300
|
+
Finish by installing them yourself:
|
|
301
|
+
|
|
302
|
+
${installCmd(result.packageManager)} ${specs}\n\n(${result.installError ?? "unknown error"})`);
|
|
303
|
+
}
|
|
275
304
|
if (result.bundlerConfigAction === "manual" && result.bundlerConfigSnippet) p.log.warn(`Add the plugin to your bundler config:\n\n${result.bundlerConfigSnippet}`);
|
|
276
305
|
p.outro(`Next: run \`${result.packageManager} dev\` to start watching.`);
|
|
277
306
|
}
|
|
307
|
+
function describeAgentsFile(result) {
|
|
308
|
+
switch (result.agentsFileAction) {
|
|
309
|
+
case "created": return `wrote ${result.agentsFilePath}`;
|
|
310
|
+
case "appended": return `added recipe pointer to ${result.agentsFilePath}`;
|
|
311
|
+
case "skipped": return `pointer already in ${result.agentsFilePath}`;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
278
314
|
function describeBundlerConfig(result) {
|
|
279
315
|
switch (result.bundlerConfigAction) {
|
|
280
316
|
case "patched": return `plugin added to ${result.bundlerConfigPath}`;
|