From f3652a897bb5ab8c2e5b13d9fa124dc7044a4d86 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 15:52:27 -0700 Subject: [PATCH 01/28] Add UI/UX harness auto-research loop design spec Co-Authored-By: Claude Fable 5 --- ...26-07-18-ui-harness-autoresearch-design.md | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md diff --git a/docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md b/docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md new file mode 100644 index 0000000..746c940 --- /dev/null +++ b/docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md @@ -0,0 +1,199 @@ +# UI/UX Harness Auto-Research Loop — Design + +**Date:** 2026-07-18 +**Status:** Approved + +## Purpose + +An automated research loop that iteratively improves a Pi coding-harness configuration for building better-looking webapp UIs. Each iteration builds a batch of landing pages from a fixed prompt set, screenshots them, scores them with a vision evaluator, and mutates the harness config to raise the score. The end product is a harness config (system instructions, skills, tools, subagents, model settings) that demonstrably produces better UI/UX than the baseline. + +## Non-goals + +- No server, no dashboard, no client app. Pure scripts. A visualization layer may come later; this design does not include it. +- No optimization of the reference set, the rubric, or the evaluator itself — those are fixed inputs. +- No multi-page apps or interactivity testing. Single self-contained HTML landing pages, judged visually from static screenshots. + +## Repo reset + +The existing repo contents (bhvr monorepo, introspection recipe) are deleted. Only `.git/` and `prompts.json` are kept. The new codebase is Bun + TypeScript scripts built from scratch. + +## Architecture overview + +Two nested loops, driven entirely by scripts and files on disk: + +``` +reference (one-time): + for each prompt (train + holdout): + build with Opus, fixed high-effort harness → output.html → screenshot + cache at runs/reference/.{html,png} + +outer loop (per iteration): + config = current harness version (JSON) + resolve(config) → generated Pi agent/extension + skill files [deterministic] + inner loop, bounded concurrency, train prompts only: + build: pi --print in isolated scratch dir → output.html + screenshot: headless Chromium → candidate.png + evaluate: one Opus vision call — rubric + candidate.png + reference.png + → structured subscores + relative verdict vs reference + aggregate scores → iteration summary + mutate: one Opus text call, schema-constrained → next config version + rationale + persist everything under runs//iterations// + track best_version by mean train score (elitist anchor) + +periodically / at end: + evaluate best_version against holdout prompts — report only, never fed to mutation +``` + +## Components + +### 1. Harness config ("genome") — `src/config/schema.ts` + +A single JSON document is the entire mutable search space. Nothing else is hand- or model-edited. + +```json +{ + "version": 7, + "parent_version": 6, + "rationale": "why the mutator made this change", + "model": { "name": "anthropic/claude-sonnet-4-6", "thinking_level": "medium" }, + "tools": ["read", "write", "bash"], + "system_instructions": "design guidance text given to the builder agent", + "skills": [{ "id": "visual-hierarchy", "content": "" }], + "subagents": [ + { + "name": "critic", + "description": "reviews the page before finalizing", + "system_instructions": "…", + "tools": ["read"] + } + ] +} +``` + +- Validated with a zod schema. The mutator's output is forced through the same schema via a tool-call (structured output); freeform file edits are impossible by construction. +- Config versions are content-addressable by `version` integer and stored as flat JSON files, so the whole search trajectory is diffable in git. + +### 2. Resolver — `src/config/resolver.ts` + +Pure deterministic function: config JSON → materialized Pi harness on disk (generated agent/extension file, skill directories with `SKILL.md`, subagent definitions). Byte-identical output for identical input. Output goes into a per-iteration `resolved/` directory; nothing is installed globally (no `recipes install`, no `~/.pi` mutation), so parallel runs and iterations cannot collide. + +### 3. Builder — `src/inner/build.ts` + +Per prompt: + +- Create an isolated scratch dir `runs//iterations//prompts//workspace/`. +- Invoke `pi --print --mode json --no-session` with the resolved harness (`-e` extension/agent file, `--skill` dirs, `--model`, `--thinking`, `--tools` from config), cwd set to the scratch dir. +- Contract with the builder agent: write exactly one self-contained `output.html` (inline CSS/JS, no external network dependencies beyond what renders offline). +- Missing/invalid `output.html` after the run → recorded as a build failure with the captured Pi transcript; scored as 0, never silently dropped. + +### 4. Screenshotter — `src/inner/screenshot.ts` + +Playwright headless Chromium loads `output.html` via `file://` and captures: + +- Desktop viewport 1440×900, full-page PNG (primary eval input). +- Mobile viewport 390×844, full-page PNG (secondary eval input). + +Render errors (blank page, JS exception preventing paint) are recorded as failures. + +### 5. Evaluator — `src/inner/evaluate.ts` + +One Opus vision call per prompt per iteration. Inputs: + +- The original prompt text and its must-include requirements. +- The fixed rubric. +- The candidate screenshots (desktop + mobile). +- The cached reference screenshot for the same prompt. + +Output (forced tool-call schema): + +- Rubric subscores 0–10: visual hierarchy, typography, spacing/layout, color/contrast, requirement coverage, overall polish. +- `overall` score 0–100 (weighted; requirement coverage acts as a gate — a page missing required sections cannot score high on polish alone). +- Relative verdict vs reference: `behind | on_par | ahead`, with the dimensions where it differs. +- Short critique text (feeds the mutator). + +The rubric text lives in `src/eval/rubric.md` and is fixed for the lifetime of a run. + +### 6. Reference generator — `src/reference/build-reference.ts` + +One-time, pre-loop script. For every prompt (train and holdout), builds the page with Opus at high thinking effort using a fixed, hand-written "best effort" harness (not part of the search space), screenshots it identically to candidates, and caches `runs/reference/.{html,png}`. The loop refuses to start if reference images are missing. References are never regenerated mid-run. + +### 7. Mutator — `src/outer/mutate.ts` + +After a full train batch: + +- Input: current `best_version` config, this iteration's aggregate summary (mean/percentile scores, per-dimension breakdown, worst prompts, evaluator critiques), and a compact history of prior versions with their scores and rationales. +- One Opus text call, schema-constrained to emit a complete new config object plus a `rationale` string. +- Selection strategy: **hill-climbing with an elitist anchor.** The challenger is always derived from `best_version` (the highest mean-train-score version so far), not necessarily the latest — a bad mutation cannot compound. + +### 8. Orchestrator — `src/run.ts` + +CLI entry points (invoked via `bun run`): + +- `bun run reference` — build the reference set. +- `bun run loop --iterations N [--run-id X] [--concurrency 5]` — run the outer loop. +- `bun run holdout [--version V]` — evaluate a config (default `best_version`) against holdout prompts; report only. +- `bun run resume --run-id X` — continue an interrupted run from the last completed iteration. + +Bounded concurrency (default 5) across the inner-loop prompt pipeline. Each prompt's build→screenshot→evaluate chain runs independently (pipeline, not barriers). + +## Data layout + +``` +prompts.json # kept as-is; train/holdout split is authoritative +runs/ + reference/ + .html + .desktop.png + .mobile.png + / + run.json # run metadata: models, concurrency, started_at + history.jsonl # one line per iteration: version, mean score, best_version + configs/ + v.json # every config version ever proposed + iterations/ + / + config-version.txt # which config this iteration ran + resolved/ # materialized harness (agent, skills) + prompts/ + / + workspace/output.html + desktop.png + mobile.png + eval.json + build.log + summary.json # aggregates + mutator rationale + holdout/ + /… # same shape as an iteration, holdout prompts +``` + +`runs/` is gitignored except `configs/` and `history.jsonl` snapshots the user chooses to commit. + +## Train/holdout discipline + +The loop optimizes exclusively against `split=train` prompts. Holdout prompts are used only by the explicit `holdout` command and their results are never included in mutator inputs. This is enforced structurally: the mutator's input assembly reads from iteration summaries, which only ever contain train results. + +## Failure handling + +- Build failure (no/invalid HTML): score 0, transcript saved, iteration continues. +- Screenshot failure: score 0, error saved. +- Evaluator API error: retry ×2 with backoff; then mark the prompt `eval_failed` and exclude it from the mean (recorded in summary so the mutator knows coverage was partial). +- Mutator schema-validation failure: retry ×2; then re-run the iteration's mutation with the error appended; if still failing, the run halts with state fully persisted (resumable). +- Interrupt/crash: `resume` picks up from the last fully persisted iteration. + +## Tech stack + +- **Runtime/scripts:** Bun + TypeScript, no framework. +- **Screenshots:** Playwright (chromium). +- **LLM calls:** `@anthropic-ai/sdk` — Opus for reference generation, evaluation, and mutation; builder model is part of the config genome (starts at Sonnet). +- **Builder harness:** `pi` CLI invoked as a subprocess, ephemeral per-prompt sessions, project-local resolved extensions only. +- **Validation:** zod for config schema and evaluator/mutator structured outputs. + +## Cost/scale envelope + +Per iteration: 15 train prompts × (1 Pi build + 1 Opus eval call). Mutation adds 1 Opus call. A 20-iteration run ≈ 300 builds + 300 eval calls + 20 mutation calls, plus one-time 20 reference builds. Concurrency 5 keeps wall-clock per iteration roughly at 3–4× a single build+eval chain. + +## Testing + +- Unit: config schema validation round-trips; resolver determinism (same config → identical bytes); mutator output validation and retry path. +- Integration (mocked LLM/pi): one full iteration over 2 fake prompts with a stub builder that writes canned HTML and a stub evaluator returning fixed scores — asserts directory layout, history.jsonl, best_version tracking, and holdout isolation. +- Smoke (real, manual): `bun run loop --iterations 1` against 2 train prompts with real Pi + API before any long run. From 53aa1ebc0354c222a7825fd80ec3871fb8629b9c Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 15:58:41 -0700 Subject: [PATCH 02/28] Add implementation plan for UI/UX harness auto-research loop Co-Authored-By: Claude Fable 5 --- .../2026-07-18-ui-harness-autoresearch.md | 2005 +++++++++++++++++ 1 file changed, 2005 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-18-ui-harness-autoresearch.md diff --git a/docs/superpowers/plans/2026-07-18-ui-harness-autoresearch.md b/docs/superpowers/plans/2026-07-18-ui-harness-autoresearch.md new file mode 100644 index 0000000..a27488f --- /dev/null +++ b/docs/superpowers/plans/2026-07-18-ui-harness-autoresearch.md @@ -0,0 +1,2005 @@ +# UI/UX Harness Auto-Research Loop Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Script-driven research loop that iteratively mutates a Pi harness config (system instructions, skills, tools, model, subagent roles) to maximize a vision-model rubric score on generated landing pages. + +**Architecture:** An inner loop builds one self-contained `output.html` per train prompt via the `pi` CLI, screenshots it with Playwright, and scores it with one Opus vision call against a fixed rubric plus a cached reference screenshot. An outer loop aggregates the batch and asks Opus (schema-constrained) for the next config version, hill-climbing from the best-scoring version so far. Everything persists as files under `runs/`. + +**Tech Stack:** Bun + TypeScript, zod (v4, for schemas + `z.toJSONSchema`), `@anthropic-ai/sdk`, Playwright (chromium), `pi` CLI as subprocess. + +**Spec:** `docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md` — read it before starting. + +## Global Constraints + +- Repo is wiped first: everything deleted except `.git/`, `prompts.json`, `docs/`. +- Optimization uses ONLY prompts with `"split": "train"` from `prompts.json`. Holdout results must never reach the mutator. +- The mutator and evaluator outputs are forced through zod schemas via tool-calls — no freeform edits to harness files, ever. +- Resolver must be deterministic: identical config → byte-identical files. +- Default models: evaluator/mutator/reference = `claude-opus-4-8` (env `EVAL_MODEL` overrides); builder model lives inside the config genome (baseline `anthropic/claude-sonnet-4-6`). +- No global installs or `~/.pi` mutation: `pi` runs with `--no-session --no-extensions --no-context-files --no-prompt-templates` and explicit `--skill`/`--append-system-prompt` paths, cwd = isolated scratch dir. +- Failures score 0 and are recorded, never silently dropped; `eval_failed` prompts are excluded from means but recorded in the summary. +- Tests use `bun test`. LLM client is injected via a minimal interface; `pi` is stubbed via env `PI_BIN`. +- All commits end with `Co-Authored-By: Claude Fable 5 `. + +--- + +### Task 1: Repo wipe + project scaffold + +**Files:** +- Delete: `client/`, `server/`, `shared/`, `introspection/`, `.introspection/`, `.turbo/`, `BHVR.md`, `CONTRIBUTING.md`, `LICENSE`, `README.md`, `bun.lock`, `package.json`, `tsconfig.json`, `turbo.json`, `.github/`, `node_modules/` +- Keep: `.git/`, `prompts.json`, `docs/` +- Create: `package.json`, `tsconfig.json`, `.gitignore`, `README.md` + +**Interfaces:** +- Produces: a Bun workspace where `bun test` runs and `zod`, `@anthropic-ai/sdk`, `playwright` are importable. + +- [ ] **Step 1: Wipe old contents** + +```bash +cd /Users/bukacdan/projects/agi_house_autoresearch +git rm -rq client server shared introspection .introspection BHVR.md CONTRIBUTING.md LICENSE README.md bun.lock package.json tsconfig.json turbo.json .github .gitignore +rm -rf node_modules .turbo client server shared introspection +git status --short # expect only deletions + untracked docs/ if not yet tracked +``` + +- [ ] **Step 2: Write new root files** + +`package.json`: +```json +{ + "name": "autodesign", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "reference": "bun src/cli.ts reference", + "loop": "bun src/cli.ts loop", + "holdout": "bun src/cli.ts holdout", + "resume": "bun src/cli.ts resume", + "test": "bun test" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.60.0", + "playwright": "^1.54.0", + "zod": "^4.0.0" + }, + "devDependencies": { + "@types/bun": "latest", + "typescript": "^5.8.0" + } +} +``` + +`tsconfig.json`: +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "skipLibCheck": true, + "types": ["bun"], + "noEmit": true + }, + "include": ["src", "tests"] +} +``` + +`.gitignore`: +``` +node_modules/ +runs/ +!runs/reference/.gitkeep +*.log +.env +``` + +`README.md`: +```markdown +# autodesign + +Automated research loop that evolves a Pi harness config for better UI/UX output. +See `docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md`. + +## Commands + +- `bun run reference` — build the fixed reference pages (one-time, needs ANTHROPIC_API_KEY + pi). +- `bun run loop --iterations N [--run-id X] [--concurrency 5]` — run the research loop. +- `bun run holdout --run-id X [--version V]` — score a config on holdout prompts (report only). +- `bun run resume --run-id X` — continue an interrupted run. +``` + +- [ ] **Step 3: Install and verify** + +```bash +bun install +bunx playwright install chromium +echo 'import { expect, test } from "bun:test"; test("smoke", () => expect(1).toBe(1));' > tests/smoke.test.ts +mkdir -p src tests && bun test +``` +Expected: 1 pass. Delete `tests/smoke.test.ts` afterwards. + +- [ ] **Step 4: Commit** + +```bash +git add -A +git commit -m "Wipe starter repo; scaffold autodesign Bun project" +``` + +--- + +### Task 2: Config schema (the genome) + +**Files:** +- Create: `src/config/schema.ts` +- Test: `tests/config-schema.test.ts` + +**Interfaces:** +- Produces: `HarnessConfigSchema` (zod), `type HarnessConfig`, `BASELINE_CONFIG: HarnessConfig` (version 0), `THINKING_LEVELS` tuple. + +- [ ] **Step 1: Write the failing test** + +`tests/config-schema.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { HarnessConfigSchema, BASELINE_CONFIG } from "../src/config/schema"; + +test("baseline config validates", () => { + expect(() => HarnessConfigSchema.parse(BASELINE_CONFIG)).not.toThrow(); + expect(BASELINE_CONFIG.version).toBe(0); + expect(BASELINE_CONFIG.parent_version).toBeNull(); +}); + +test("rejects unknown tools and bad skill ids", () => { + const bad = { ...BASELINE_CONFIG, tools: ["read", "browser"] }; + expect(() => HarnessConfigSchema.parse(bad)).toThrow(); + const badSkill = { ...BASELINE_CONFIG, skills: [{ id: "Bad Id!", description: "x", content: "x" }] }; + expect(() => HarnessConfigSchema.parse(badSkill)).toThrow(); +}); + +test("round-trips through JSON", () => { + const parsed = HarnessConfigSchema.parse(JSON.parse(JSON.stringify(BASELINE_CONFIG))); + expect(parsed).toEqual(BASELINE_CONFIG); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `bun test tests/config-schema.test.ts` — Expected: FAIL (module not found). + +- [ ] **Step 3: Implement** + +`src/config/schema.ts`: +```ts +import { z } from "zod"; + +export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const; +export const ALLOWED_TOOLS = ["read", "write", "edit", "bash"] as const; + +export const SkillSchema = z.object({ + id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/), + description: z.string().min(1), + content: z.string().min(1), +}); + +export const SubagentSchema = z.object({ + name: z.string().regex(/^[a-z0-9][a-z0-9-]*$/), + description: z.string().min(1), + system_instructions: z.string().min(1), + tools: z.array(z.enum(ALLOWED_TOOLS)), +}); + +export const HarnessConfigSchema = z.object({ + version: z.number().int().nonnegative(), + parent_version: z.number().int().nonnegative().nullable(), + rationale: z.string(), + model: z.object({ + name: z.string().min(1), + thinking_level: z.enum(THINKING_LEVELS), + }), + tools: z.array(z.enum(ALLOWED_TOOLS)).min(1), + system_instructions: z.string().min(1), + skills: z.array(SkillSchema).max(8), + subagents: z.array(SubagentSchema).max(4), +}); + +export type HarnessConfig = z.infer; + +export const BASELINE_CONFIG: HarnessConfig = { + version: 0, + parent_version: null, + rationale: "Hand-written baseline.", + model: { name: "anthropic/claude-sonnet-4-6", thinking_level: "medium" }, + tools: ["read", "write", "bash"], + system_instructions: [ + "You are building a single marketing landing page as one self-contained HTML file.", + "Write exactly one file named output.html in the current directory.", + "All CSS and JS must be inline; no external network requests. Use system font stacks or embedded styles.", + "Cover every requirement in the brief. Aim for a clean, modern, visually polished design.", + ].join("\n"), + skills: [], + subagents: [], +}; +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `bun test tests/config-schema.test.ts` — Expected: 3 pass. + +- [ ] **Step 5: Commit** + +```bash +git add src/config/schema.ts tests/config-schema.test.ts +git commit -m "feat: harness config schema and baseline genome" +``` + +--- + +### Task 3: Prompts loader + +**Files:** +- Create: `src/prompts.ts` +- Test: `tests/prompts.test.ts` + +**Interfaces:** +- Produces: `type PromptSpec = { id: string; category: string; split: "train" | "holdout"; prompt: string }`, `loadPrompts(path?: string): PromptSpec[]`, `trainPrompts(all): PromptSpec[]`, `holdoutPrompts(all): PromptSpec[]`. + +- [ ] **Step 1: Write the failing test** + +`tests/prompts.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { loadPrompts, trainPrompts, holdoutPrompts } from "../src/prompts"; + +test("loads real prompts.json with valid splits", () => { + const all = loadPrompts(); + expect(all.length).toBeGreaterThan(10); + const train = trainPrompts(all); + const holdout = holdoutPrompts(all); + expect(train.length + holdout.length).toBe(all.length); + expect(holdout.every((p) => p.split === "holdout")).toBe(true); + expect(new Set(all.map((p) => p.id)).size).toBe(all.length); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** — `bun test tests/prompts.test.ts` + +- [ ] **Step 3: Implement** + +`src/prompts.ts`: +```ts +import { z } from "zod"; +import { readFileSync } from "node:fs"; + +const PromptSpecSchema = z.object({ + id: z.string().min(1), + category: z.string().min(1), + split: z.enum(["train", "holdout"]), + prompt: z.string().min(1), +}); +const PromptFileSchema = z.object({ + version: z.number(), + description: z.string(), + prompts: z.array(PromptSpecSchema).min(1), +}); + +export type PromptSpec = z.infer; + +export function loadPrompts(path = "prompts.json"): PromptSpec[] { + const file = PromptFileSchema.parse(JSON.parse(readFileSync(path, "utf8"))); + const ids = new Set(); + for (const p of file.prompts) { + if (ids.has(p.id)) throw new Error(`duplicate prompt id: ${p.id}`); + ids.add(p.id); + } + return file.prompts; +} + +export const trainPrompts = (all: PromptSpec[]) => all.filter((p) => p.split === "train"); +export const holdoutPrompts = (all: PromptSpec[]) => all.filter((p) => p.split === "holdout"); +``` + +- [ ] **Step 4: Run to verify PASS** — `bun test tests/prompts.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/prompts.ts tests/prompts.test.ts +git commit -m "feat: prompts loader with train/holdout split" +``` + +--- + +### Task 4: Resolver (config → materialized Pi harness) + +**Files:** +- Create: `src/config/resolver.ts` +- Test: `tests/resolver.test.ts` + +**Interfaces:** +- Consumes: `HarnessConfig` from Task 2. +- Produces: + ```ts + type ResolvedHarness = { dir: string; systemPromptPath: string; skillDirs: string[]; piArgs: string[] }; + function resolveHarness(config: HarnessConfig, outDir: string): ResolvedHarness; + ``` + `piArgs` contains every flag except the user prompt itself: `--print --no-session --no-extensions --no-context-files --no-prompt-templates --model --thinking --tools --append-system-prompt ` plus one `--skill ` per skill. + +Design note (locked in spec): **subagents are rendered into the system prompt as mandatory internal passes** (e.g. a `critic` pass the builder must perform before finishing). Plain `pi` has no native subagent spawning without the recipes extension; this keeps runs hermetic while still letting the mutator experiment with multi-role workflows. + +- [ ] **Step 1: Write the failing test** + +`tests/resolver.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, readFileSync, readdirSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { resolveHarness } from "../src/config/resolver"; +import { BASELINE_CONFIG, type HarnessConfig } from "../src/config/schema"; + +const cfg: HarnessConfig = { + ...BASELINE_CONFIG, + skills: [{ id: "visual-hierarchy", description: "Layout guidance", content: "Use a clear grid." }], + subagents: [ + { name: "critic", description: "Design critic", system_instructions: "List 3 flaws, then fix them.", tools: ["read"] }, + ], +}; + +test("materializes system prompt, skills, and pi args", () => { + const dir = mkdtempSync(join(tmpdir(), "resolve-")); + const r = resolveHarness(cfg, dir); + const sys = readFileSync(r.systemPromptPath, "utf8"); + expect(sys).toContain("self-contained HTML"); + expect(sys).toContain("## Internal pass: critic"); + expect(sys).toContain("List 3 flaws"); + const skill = readFileSync(join(dir, "skills", "visual-hierarchy", "SKILL.md"), "utf8"); + expect(skill).toContain("name: visual-hierarchy"); + expect(skill).toContain("Use a clear grid."); + expect(r.piArgs).toContain("--print"); + expect(r.piArgs).toContain("anthropic/claude-sonnet-4-6"); + expect(r.piArgs.join(" ")).toContain("--skill"); +}); + +test("deterministic: same config twice → identical bytes", () => { + const a = mkdtempSync(join(tmpdir(), "ra-")); + const b = mkdtempSync(join(tmpdir(), "rb-")); + resolveHarness(cfg, a); + resolveHarness(cfg, b); + expect(readFileSync(join(a, "system-prompt.md"), "utf8")).toBe(readFileSync(join(b, "system-prompt.md"), "utf8")); + expect(readdirSync(join(a, "skills")).sort()).toEqual(readdirSync(join(b, "skills")).sort()); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** — `bun test tests/resolver.test.ts` + +- [ ] **Step 3: Implement** + +`src/config/resolver.ts`: +```ts +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import type { HarnessConfig } from "./schema"; + +export type ResolvedHarness = { + dir: string; + systemPromptPath: string; + skillDirs: string[]; + piArgs: string[]; +}; + +export function resolveHarness(config: HarnessConfig, outDir: string): ResolvedHarness { + mkdirSync(outDir, { recursive: true }); + + const parts: string[] = [config.system_instructions]; + for (const sub of config.subagents) { + parts.push( + [ + `## Internal pass: ${sub.name}`, + `Before finishing, perform this pass as "${sub.name}" (${sub.description}):`, + sub.system_instructions, + ].join("\n"), + ); + } + const systemPromptPath = join(outDir, "system-prompt.md"); + writeFileSync(systemPromptPath, parts.join("\n\n") + "\n"); + + const skillDirs: string[] = []; + const sortedSkills = [...config.skills].sort((a, b) => a.id.localeCompare(b.id)); + for (const skill of sortedSkills) { + const dir = join(outDir, "skills", skill.id); + mkdirSync(dir, { recursive: true }); + const frontmatter = `---\nname: ${skill.id}\ndescription: ${skill.description.replaceAll("\n", " ")}\n---\n\n`; + writeFileSync(join(dir, "SKILL.md"), frontmatter + skill.content + "\n"); + skillDirs.push(dir); + } + + const piArgs = [ + "--print", + "--no-session", + "--no-extensions", + "--no-context-files", + "--no-prompt-templates", + "--model", config.model.name, + "--thinking", config.model.thinking_level, + "--tools", config.tools.join(","), + "--append-system-prompt", systemPromptPath, + ...skillDirs.flatMap((d) => ["--skill", d]), + ]; + + return { dir: outDir, systemPromptPath, skillDirs, piArgs }; +} +``` + +- [ ] **Step 4: Run to verify PASS** — `bun test tests/resolver.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/config/resolver.ts tests/resolver.test.ts +git commit -m "feat: deterministic resolver from config genome to pi harness" +``` + +--- + +### Task 5: Builder (pi subprocess) + +**Files:** +- Create: `src/inner/build.ts` +- Test: `tests/build.test.ts` (uses a stub `pi` shell script via env `PI_BIN`) + +**Interfaces:** +- Consumes: `ResolvedHarness` from Task 4, `PromptSpec` from Task 3. +- Produces: + ```ts + type BuildResult = + | { ok: true; htmlPath: string; logPath: string } + | { ok: false; error: string; logPath: string }; + function buildPage(opts: { + resolved: ResolvedHarness; prompt: PromptSpec; workspaceDir: string; timeoutMs?: number; + }): Promise; + ``` + Uses `process.env.PI_BIN ?? "pi"`. Stdout+stderr are written to `/../build.log`. Success = exit 0 AND `output.html` exists, non-empty, contains ` { + const base = mkdtempSync(join(tmpdir(), "build-")); + const ws = join(base, "workspace"); + mkdirSync(ws, { recursive: true }); + process.env.PI_BIN = stubPi(base, `echo 'hi' > output.html`); + const resolved = resolveHarness(BASELINE_CONFIG, join(base, "resolved")); + const r = await buildPage({ resolved, prompt, workspaceDir: ws }); + expect(r.ok).toBe(true); +}); + +test("failure when no output.html", async () => { + const base = mkdtempSync(join(tmpdir(), "build2-")); + const ws = join(base, "workspace"); + mkdirSync(ws, { recursive: true }); + process.env.PI_BIN = stubPi(base, `echo did nothing`); + const resolved = resolveHarness(BASELINE_CONFIG, join(base, "resolved")); + const r = await buildPage({ resolved, prompt, workspaceDir: ws }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.error).toContain("output.html"); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** — `bun test tests/build.test.ts` + +- [ ] **Step 3: Implement** + +`src/inner/build.ts`: +```ts +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import type { ResolvedHarness } from "../config/resolver"; +import type { PromptSpec } from "../prompts"; + +export type BuildResult = + | { ok: true; htmlPath: string; logPath: string } + | { ok: false; error: string; logPath: string }; + +const BUILD_INSTRUCTION = + "Build the landing page described below. Write exactly one self-contained file named output.html " + + "in the current directory (inline CSS/JS, no external requests). Brief:\n\n"; + +export async function buildPage(opts: { + resolved: ResolvedHarness; + prompt: PromptSpec; + workspaceDir: string; + timeoutMs?: number; +}): Promise { + const { resolved, prompt, workspaceDir, timeoutMs = 10 * 60 * 1000 } = opts; + const logPath = join(dirname(workspaceDir), "build.log"); + const bin = process.env.PI_BIN ?? "pi"; + + const proc = Bun.spawn([bin, ...resolved.piArgs, BUILD_INSTRUCTION + prompt.prompt], { + cwd: workspaceDir, + stdout: "pipe", + stderr: "pipe", + env: { ...process.env }, + }); + const timer = setTimeout(() => proc.kill(), timeoutMs); + const [stdout, stderr, exitCode] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + proc.exited, + ]); + clearTimeout(timer); + writeFileSync(logPath, `# exit ${exitCode}\n## stdout\n${stdout}\n## stderr\n${stderr}\n`); + + const htmlPath = join(workspaceDir, "output.html"); + if (exitCode !== 0) return { ok: false, error: `pi exited ${exitCode}`, logPath }; + if (!existsSync(htmlPath)) return { ok: false, error: "no output.html produced", logPath }; + const html = readFileSync(htmlPath, "utf8"); + if (html.length < 100 || !/100 chars? `'hi'` is 29 chars — adjust the threshold check to `html.length < 20` OR make the stub write a longer body. Use the stub fix: in the test's success stub write `` + 200 chars of text. Keep `html.length < 100` in the implementation (real pages are always larger). + +- [ ] **Step 4: Run to verify PASS** — `bun test tests/build.test.ts` (after making the success stub emit ≥100 chars). + +- [ ] **Step 5: Commit** + +```bash +git add src/inner/build.ts tests/build.test.ts +git commit -m "feat: pi subprocess builder with hermetic flags and failure capture" +``` + +--- + +### Task 6: Screenshotter + +**Files:** +- Create: `src/inner/screenshot.ts` +- Test: `tests/screenshot.test.ts` + +**Interfaces:** +- Produces: + ```ts + type Screenshots = { desktopPng: string; mobilePng: string }; + function screenshotPage(htmlPath: string, outDir: string, baseName?: string): Promise; + ``` + Desktop 1440×900, mobile 390×844, both full-page PNG, written as `/.desktop.png` / `.mobile.png` (baseName defaults to `"candidate"`). Throws on render failure (caller records it). + +- [ ] **Step 1: Write the failing test** + +`tests/screenshot.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, statSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { screenshotPage } from "../src/inner/screenshot"; + +test("captures desktop and mobile pngs", async () => { + const dir = mkdtempSync(join(tmpdir(), "shot-")); + const html = join(dir, "output.html"); + writeFileSync(html, `

Hello

${"

x

".repeat(50)}`); + const shots = await screenshotPage(html, dir); + expect(statSync(shots.desktopPng).size).toBeGreaterThan(1000); + expect(statSync(shots.mobilePng).size).toBeGreaterThan(1000); +}, 60000); +``` + +- [ ] **Step 2: Run to verify FAIL** — `bun test tests/screenshot.test.ts` + +- [ ] **Step 3: Implement** + +`src/inner/screenshot.ts`: +```ts +import { chromium } from "playwright"; +import { pathToFileURL } from "node:url"; +import { join } from "node:path"; + +export type Screenshots = { desktopPng: string; mobilePng: string }; + +const VIEWPORTS = [ + { key: "desktop", width: 1440, height: 900 }, + { key: "mobile", width: 390, height: 844 }, +] as const; + +export async function screenshotPage(htmlPath: string, outDir: string, baseName = "candidate"): Promise { + const browser = await chromium.launch(); + try { + const out: Record = {}; + for (const vp of VIEWPORTS) { + const page = await browser.newPage({ viewport: { width: vp.width, height: vp.height } }); + await page.goto(pathToFileURL(htmlPath).href, { waitUntil: "load", timeout: 30000 }); + await page.waitForTimeout(500); // settle fonts/animations + const path = join(outDir, `${baseName}.${vp.key}.png`); + await page.screenshot({ path, fullPage: true }); + await page.close(); + out[vp.key] = path; + } + return { desktopPng: out.desktop, mobilePng: out.mobile }; + } finally { + await browser.close(); + } +} +``` + +- [ ] **Step 4: Run to verify PASS** — `bun test tests/screenshot.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/inner/screenshot.ts tests/screenshot.test.ts +git commit -m "feat: playwright screenshotter, desktop + mobile full-page" +``` + +--- + +### Task 7: LLM helper (forced tool call with retries) + +**Files:** +- Create: `src/llm.ts` +- Test: `tests/llm.test.ts` + +**Interfaces:** +- Produces: + ```ts + interface LlmClient { messages: { create(params: Record): Promise<{ content: Array<{ type: string; name?: string; input?: unknown }> }> } } + function realClient(): LlmClient; // wraps new Anthropic() + function forcedToolCall(client: LlmClient, opts: { + model: string; system?: string; content: unknown[]; // Anthropic content blocks + toolName: string; description: string; zodSchema: z.ZodType; maxRetries?: number; maxTokens?: number; + }): Promise; + function imageBlock(pngPath: string): unknown; // base64 image content block + ``` + On zod-parse failure or missing tool_use block: retry up to `maxRetries` (default 2), appending the validation error as an extra user turn. Exhausted → throw. + +- [ ] **Step 1: Write the failing test** + +`tests/llm.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { z } from "zod"; +import { forcedToolCall, type LlmClient } from "../src/llm"; + +const schema = z.object({ score: z.number().min(0).max(10) }); + +function fakeClient(responses: unknown[]): LlmClient { + let i = 0; + return { messages: { create: async () => ({ content: responses[i++] as any }) } }; +} + +test("parses a valid tool call", async () => { + const client = fakeClient([[{ type: "tool_use", name: "grade", input: { score: 7 } }]]); + const r = await forcedToolCall(client, { + model: "m", content: [{ type: "text", text: "grade it" }], + toolName: "grade", description: "d", zodSchema: schema, + }); + expect(r.score).toBe(7); +}); + +test("retries on invalid then succeeds", async () => { + const client = fakeClient([ + [{ type: "tool_use", name: "grade", input: { score: 99 } }], + [{ type: "tool_use", name: "grade", input: { score: 5 } }], + ]); + const r = await forcedToolCall(client, { + model: "m", content: [{ type: "text", text: "grade it" }], + toolName: "grade", description: "d", zodSchema: schema, + }); + expect(r.score).toBe(5); +}); + +test("throws after retries exhausted", async () => { + const bad = [{ type: "tool_use", name: "grade", input: { score: 99 } }]; + const client = fakeClient([bad, bad, bad]); + await expect( + forcedToolCall(client, { + model: "m", content: [{ type: "text", text: "x" }], + toolName: "grade", description: "d", zodSchema: schema, maxRetries: 2, + }), + ).rejects.toThrow(); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** — `bun test tests/llm.test.ts` + +- [ ] **Step 3: Implement** + +`src/llm.ts`: +```ts +import Anthropic from "@anthropic-ai/sdk"; +import { readFileSync } from "node:fs"; +import { z } from "zod"; + +export interface LlmClient { + messages: { + create(params: Record): Promise<{ content: Array<{ type: string; name?: string; input?: unknown }> }>; + }; +} + +export function realClient(): LlmClient { + return new Anthropic() as unknown as LlmClient; +} + +export function imageBlock(pngPath: string): unknown { + return { + type: "image", + source: { type: "base64", media_type: "image/png", data: readFileSync(pngPath).toString("base64") }, + }; +} + +export async function forcedToolCall( + client: LlmClient, + opts: { + model: string; + system?: string; + content: unknown[]; + toolName: string; + description: string; + zodSchema: z.ZodType; + maxRetries?: number; + maxTokens?: number; + }, +): Promise { + const { maxRetries = 2, maxTokens = 8192 } = opts; + const jsonSchema = z.toJSONSchema(opts.zodSchema); + const messages: Array<{ role: string; content: unknown }> = [{ role: "user", content: opts.content }]; + let lastError = ""; + + for (let attempt = 0; attempt <= maxRetries; attempt++) { + if (attempt > 0) { + messages.push({ + role: "user", + content: [{ type: "text", text: `Previous tool input was invalid: ${lastError}. Call ${opts.toolName} again with corrected input.` }], + }); + } + const res = await client.messages.create({ + model: opts.model, + max_tokens: maxTokens, + ...(opts.system ? { system: opts.system } : {}), + messages, + tools: [{ name: opts.toolName, description: opts.description, input_schema: jsonSchema }], + tool_choice: { type: "tool", name: opts.toolName }, + }); + const block = res.content.find((b) => b.type === "tool_use" && b.name === opts.toolName); + if (!block) { lastError = "no tool_use block returned"; continue; } + const parsed = opts.zodSchema.safeParse(block.input); + if (parsed.success) return parsed.data; + lastError = parsed.error.message; + } + throw new Error(`forcedToolCall(${opts.toolName}) failed after ${maxRetries + 1} attempts: ${lastError}`); +} +``` + +- [ ] **Step 4: Run to verify PASS** — `bun test tests/llm.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/llm.ts tests/llm.test.ts +git commit -m "feat: schema-forced tool call helper with retry" +``` + +--- + +### Task 8: Evaluator + rubric + +**Files:** +- Create: `src/inner/evaluate.ts`, `src/eval/rubric.md` +- Test: `tests/evaluate.test.ts` + +**Interfaces:** +- Consumes: `forcedToolCall`, `imageBlock`, `LlmClient` (Task 7); `PromptSpec` (Task 3); `Screenshots` (Task 6). +- Produces: + ```ts + const EvalResultSchema: z.ZodType; + type EvalResult = { + subscores: { hierarchy: number; typography: number; spacing: number; color_contrast: number; requirement_coverage: number; polish: number }; // each 0-10 + overall: number; // 0-100 + vs_reference: "behind" | "on_par" | "ahead"; + diff_dimensions: string[]; + critique: string; + }; + function evaluatePage(opts: { + client: LlmClient; model: string; prompt: PromptSpec; + candidate: Screenshots; referenceDesktopPng: string; + }): Promise; + ``` + Default model resolution happens in callers: `process.env.EVAL_MODEL ?? "claude-opus-4-8"`. + +- [ ] **Step 1: Write rubric** + +`src/eval/rubric.md`: +```markdown +# Landing Page UI/UX Rubric + +Score each dimension 0-10: + +- **hierarchy** — Clear visual hierarchy: obvious primary message, scannable sections, sensible reading order. +- **typography** — Type scale, pairing, line length, weight contrast; no default-browser look. +- **spacing** — Consistent rhythm, breathing room, aligned grid; no cramped or floaty regions. +- **color_contrast** — Cohesive palette, sufficient text contrast (WCAG-ish), intentional accent usage. +- **requirement_coverage** — Every must-include item from the brief is present and functional-looking. +- **polish** — Overall craft: imagery/placeholder quality, component consistency, micro-details (buttons, cards, footer). + +**overall (0-100):** weighted judgment, NOT a straight sum. requirement_coverage is a gate: if any required +section is missing, overall must not exceed 50 regardless of visual quality. A generic bootstrap-looking page +that covers everything sits around 50-60. Reserve 85+ for pages that would pass as a real product's site. + +**vs_reference:** compare the candidate against the provided reference screenshot for the same brief: +`behind`, `on_par`, or `ahead`, and list the dimensions where they differ in diff_dimensions. + +**critique:** 2-4 sentences: the single biggest weakness and the most impactful concrete improvement. +``` + +- [ ] **Step 2: Write the failing test** + +`tests/evaluate.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { evaluatePage, EvalResultSchema } from "../src/inner/evaluate"; +import type { LlmClient } from "../src/llm"; + +const png = (dir: string, name: string) => { + const p = join(dir, name); + // 1x1 png + writeFileSync(p, Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64")); + return p; +}; + +const valid = { + subscores: { hierarchy: 7, typography: 6, spacing: 7, color_contrast: 8, requirement_coverage: 9, polish: 6 }, + overall: 68, vs_reference: "behind", diff_dimensions: ["typography"], critique: "Weak type scale.", +}; + +test("returns parsed eval and sends images + rubric", async () => { + const dir = mkdtempSync(join(tmpdir(), "eval-")); + let captured: any; + const client: LlmClient = { + messages: { create: async (params) => { captured = params; return { content: [{ type: "tool_use", name: "submit_evaluation", input: valid }] }; } }, + }; + const r = await evaluatePage({ + client, model: "test-model", + prompt: { id: "p", category: "c", split: "train", prompt: "Landing page for X. Must include: hero." }, + candidate: { desktopPng: png(dir, "d.png"), mobilePng: png(dir, "m.png") }, + referenceDesktopPng: png(dir, "ref.png"), + }); + expect(r.overall).toBe(68); + expect(EvalResultSchema.parse(r)).toEqual(valid as any); + const text = JSON.stringify(captured.messages); + expect(text).toContain("requirement_coverage"); // rubric included + const images = captured.messages[0].content.filter((b: any) => b.type === "image"); + expect(images.length).toBe(3); // desktop, mobile, reference +}); +``` + +- [ ] **Step 3: Run to verify FAIL** — `bun test tests/evaluate.test.ts` + +- [ ] **Step 4: Implement** + +`src/inner/evaluate.ts`: +```ts +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { z } from "zod"; +import { forcedToolCall, imageBlock, type LlmClient } from "../llm"; +import type { PromptSpec } from "../prompts"; +import type { Screenshots } from "./screenshot"; + +const sub = z.number().min(0).max(10); +export const EvalResultSchema = z.object({ + subscores: z.object({ + hierarchy: sub, typography: sub, spacing: sub, + color_contrast: sub, requirement_coverage: sub, polish: sub, + }), + overall: z.number().min(0).max(100), + vs_reference: z.enum(["behind", "on_par", "ahead"]), + diff_dimensions: z.array(z.string()), + critique: z.string().min(1), +}); +export type EvalResult = z.infer; + +const RUBRIC = readFileSync(join(import.meta.dir, "../eval/rubric.md"), "utf8"); + +export async function evaluatePage(opts: { + client: LlmClient; + model: string; + prompt: PromptSpec; + candidate: Screenshots; + referenceDesktopPng: string; +}): Promise { + return forcedToolCall(opts.client, { + model: opts.model, + toolName: "submit_evaluation", + description: "Submit the structured rubric evaluation of the candidate landing page.", + zodSchema: EvalResultSchema, + content: [ + { type: "text", text: `You are a strict design reviewer.\n\n${RUBRIC}\n\n## Brief\n${opts.prompt.prompt}` }, + { type: "text", text: "Candidate — desktop:" }, + imageBlock(opts.candidate.desktopPng), + { type: "text", text: "Candidate — mobile:" }, + imageBlock(opts.candidate.mobilePng), + { type: "text", text: "Reference page for the same brief (desktop):" }, + imageBlock(opts.referenceDesktopPng), + { type: "text", text: "Evaluate the candidate per the rubric and call submit_evaluation." }, + ], + }); +} +``` + +- [ ] **Step 5: Run to verify PASS** — `bun test tests/evaluate.test.ts` + +- [ ] **Step 6: Commit** + +```bash +git add src/inner/evaluate.ts src/eval/rubric.md tests/evaluate.test.ts +git commit -m "feat: opus vision evaluator with fixed rubric and reference comparison" +``` + +--- + +### Task 9: Run store (persistence layout) + +**Files:** +- Create: `src/store/run-store.ts` +- Test: `tests/run-store.test.ts` + +**Interfaces:** +- Consumes: `HarnessConfig` (Task 2), `EvalResult` (Task 8). +- Produces: + ```ts + type HistoryEntry = { iteration: number; config_version: number; mean_overall: number; best_version: number; best_score: number }; + type PromptOutcome = { prompt_id: string; status: "ok" | "build_failed" | "screenshot_failed" | "eval_failed"; overall: number; eval?: EvalResult; error?: string }; + type IterationSummary = { + iteration: number; config_version: number; mean_overall: number; + outcomes: PromptOutcome[]; dimension_means: Record; mutator_rationale?: string; + }; + class RunStore { + constructor(runsDir: string, runId: string); + initRun(meta: Record): void; // writes run.json, mkdir -p + saveConfig(cfg: HarnessConfig): void; // configs/v.json + loadConfig(version: number): HarnessConfig; + listConfigVersions(): number[]; + nextConfigVersion(): number; // max + 1 + iterationDir(n: number): string; // …/iterations/, mkdir -p + promptDir(n: number, promptId: string): string; // …/prompts//workspace created + saveSummary(s: IterationSummary): void; + loadSummaries(): IterationSummary[]; + appendHistory(e: HistoryEntry): void; // history.jsonl + readHistory(): HistoryEntry[]; + bestVersion(): { version: number; score: number }; // from history; falls back to {0, -1} + completedIterations(): number[]; // iterations with summary.json + } + ``` + +- [ ] **Step 1: Write the failing test** + +`tests/run-store.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { RunStore } from "../src/store/run-store"; +import { BASELINE_CONFIG } from "../src/config/schema"; + +test("full store lifecycle", () => { + const dir = mkdtempSync(join(tmpdir(), "store-")); + const s = new RunStore(dir, "run1"); + s.initRun({ note: "test" }); + expect(existsSync(join(dir, "run1", "run.json"))).toBe(true); + + s.saveConfig(BASELINE_CONFIG); + expect(s.loadConfig(0)).toEqual(BASELINE_CONFIG); + expect(s.nextConfigVersion()).toBe(1); + + const pd = s.promptDir(1, "saas-crm"); + expect(existsSync(join(pd, "workspace"))).toBe(true); + + s.saveSummary({ iteration: 1, config_version: 0, mean_overall: 55, outcomes: [], dimension_means: {} }); + s.appendHistory({ iteration: 1, config_version: 0, mean_overall: 55, best_version: 0, best_score: 55 }); + s.appendHistory({ iteration: 2, config_version: 1, mean_overall: 61, best_version: 1, best_score: 61 }); + expect(s.bestVersion()).toEqual({ version: 1, score: 61 }); + expect(s.completedIterations()).toEqual([1]); + expect(s.readHistory().length).toBe(2); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** — `bun test tests/run-store.test.ts` + +- [ ] **Step 3: Implement** + +`src/store/run-store.ts`: +```ts +import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { HarnessConfigSchema, type HarnessConfig } from "../config/schema"; +import type { EvalResult } from "../inner/evaluate"; + +export type HistoryEntry = { + iteration: number; config_version: number; mean_overall: number; best_version: number; best_score: number; +}; +export type PromptOutcome = { + prompt_id: string; + status: "ok" | "build_failed" | "screenshot_failed" | "eval_failed"; + overall: number; + eval?: EvalResult; + error?: string; +}; +export type IterationSummary = { + iteration: number; config_version: number; mean_overall: number; + outcomes: PromptOutcome[]; dimension_means: Record; mutator_rationale?: string; +}; + +export class RunStore { + readonly root: string; + constructor(runsDir: string, runId: string) { + this.root = join(runsDir, runId); + } + initRun(meta: Record): void { + mkdirSync(join(this.root, "configs"), { recursive: true }); + mkdirSync(join(this.root, "iterations"), { recursive: true }); + if (!existsSync(join(this.root, "run.json"))) { + writeFileSync(join(this.root, "run.json"), JSON.stringify({ started_at: new Date().toISOString(), ...meta }, null, 2)); + } + } + saveConfig(cfg: HarnessConfig): void { + writeFileSync(join(this.root, "configs", `v${cfg.version}.json`), JSON.stringify(cfg, null, 2)); + } + loadConfig(version: number): HarnessConfig { + return HarnessConfigSchema.parse(JSON.parse(readFileSync(join(this.root, "configs", `v${version}.json`), "utf8"))); + } + listConfigVersions(): number[] { + return readdirSync(join(this.root, "configs")) + .map((f) => Number(f.match(/^v(\d+)\.json$/)?.[1])) + .filter((n) => Number.isFinite(n)) + .sort((a, b) => a - b); + } + nextConfigVersion(): number { + const vs = this.listConfigVersions(); + return vs.length ? Math.max(...vs) + 1 : 0; + } + iterationDir(n: number): string { + const d = join(this.root, "iterations", String(n)); + mkdirSync(d, { recursive: true }); + return d; + } + promptDir(n: number, promptId: string): string { + const d = join(this.iterationDir(n), "prompts", promptId); + mkdirSync(join(d, "workspace"), { recursive: true }); + return d; + } + saveSummary(s: IterationSummary): void { + writeFileSync(join(this.iterationDir(s.iteration), "summary.json"), JSON.stringify(s, null, 2)); + } + loadSummaries(): IterationSummary[] { + return this.completedIterations().map((n) => + JSON.parse(readFileSync(join(this.root, "iterations", String(n), "summary.json"), "utf8")), + ); + } + appendHistory(e: HistoryEntry): void { + appendFileSync(join(this.root, "history.jsonl"), JSON.stringify(e) + "\n"); + } + readHistory(): HistoryEntry[] { + const p = join(this.root, "history.jsonl"); + if (!existsSync(p)) return []; + return readFileSync(p, "utf8").trim().split("\n").filter(Boolean).map((l) => JSON.parse(l)); + } + bestVersion(): { version: number; score: number } { + const h = this.readHistory(); + if (!h.length) return { version: 0, score: -1 }; + const best = h.reduce((a, b) => (b.mean_overall > a.mean_overall ? b : a)); + return { version: best.config_version, score: best.mean_overall }; + } + completedIterations(): number[] { + const dir = join(this.root, "iterations"); + if (!existsSync(dir)) return []; + return readdirSync(dir) + .map(Number) + .filter((n) => Number.isFinite(n) && existsSync(join(dir, String(n), "summary.json"))) + .sort((a, b) => a - b); + } +} +``` + +- [ ] **Step 4: Run to verify PASS** — `bun test tests/run-store.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/store/run-store.ts tests/run-store.test.ts +git commit -m "feat: run store for configs, iterations, history" +``` + +--- + +### Task 10: Aggregation + +**Files:** +- Create: `src/outer/aggregate.ts` +- Test: `tests/aggregate.test.ts` + +**Interfaces:** +- Consumes: `PromptOutcome`, `IterationSummary` (Task 9). +- Produces: + ```ts + function aggregate(iteration: number, configVersion: number, outcomes: PromptOutcome[]): IterationSummary; + ``` + Rules: `mean_overall` averages `overall` across outcomes EXCLUDING `eval_failed` (build/screenshot failures count as 0 and are included); `dimension_means` averages subscores across `status === "ok"` outcomes only; empty ok-set → `dimension_means = {}`. + +- [ ] **Step 1: Write the failing test** + +`tests/aggregate.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { aggregate } from "../src/outer/aggregate"; +import type { PromptOutcome } from "../src/store/run-store"; + +const ok = (id: string, overall: number, hierarchy: number): PromptOutcome => ({ + prompt_id: id, status: "ok", overall, + eval: { + subscores: { hierarchy, typography: 5, spacing: 5, color_contrast: 5, requirement_coverage: 5, polish: 5 }, + overall, vs_reference: "on_par", diff_dimensions: [], critique: "c", + }, +}); + +test("means: failures are 0, eval_failed excluded", () => { + const s = aggregate(3, 7, [ + ok("a", 80, 8), + ok("b", 60, 6), + { prompt_id: "c", status: "build_failed", overall: 0, error: "no html" }, + { prompt_id: "d", status: "eval_failed", overall: 0, error: "api" }, + ]); + expect(s.iteration).toBe(3); + expect(s.config_version).toBe(7); + expect(s.mean_overall).toBeCloseTo((80 + 60 + 0) / 3); + expect(s.dimension_means.hierarchy).toBeCloseTo(7); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** — `bun test tests/aggregate.test.ts` + +- [ ] **Step 3: Implement** + +`src/outer/aggregate.ts`: +```ts +import type { IterationSummary, PromptOutcome } from "../store/run-store"; + +export function aggregate(iteration: number, configVersion: number, outcomes: PromptOutcome[]): IterationSummary { + const scored = outcomes.filter((o) => o.status !== "eval_failed"); + const mean_overall = scored.length ? scored.reduce((s, o) => s + o.overall, 0) / scored.length : 0; + + const oks = outcomes.filter((o) => o.status === "ok" && o.eval); + const dimension_means: Record = {}; + if (oks.length) { + const keys = Object.keys(oks[0]!.eval!.subscores) as Array; + for (const k of keys) { + dimension_means[k] = oks.reduce((s, o) => s + (o.eval!.subscores as any)[k], 0) / oks.length; + } + } + return { iteration, config_version: configVersion, mean_overall, outcomes, dimension_means }; +} +``` + +- [ ] **Step 4: Run to verify PASS** — `bun test tests/aggregate.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/outer/aggregate.ts tests/aggregate.test.ts +git commit -m "feat: iteration aggregation with failure semantics" +``` + +--- + +### Task 11: Mutator + +**Files:** +- Create: `src/outer/mutate.ts` +- Test: `tests/mutate.test.ts` + +**Interfaces:** +- Consumes: `forcedToolCall`, `LlmClient` (Task 7); `HarnessConfig`, `HarnessConfigSchema` (Task 2); `IterationSummary`, `HistoryEntry` (Task 9). +- Produces: + ```ts + function mutateConfig(opts: { + client: LlmClient; model: string; + bestConfig: HarnessConfig; // elitist anchor + latestSummary: IterationSummary; + history: HistoryEntry[]; + pastRationales: Array<{ version: number; rationale: string; mean_overall: number | null }>; + nextVersion: number; + }): Promise; + ``` + The returned config MUST have `version === nextVersion` and `parent_version === bestConfig.version` — enforced after the LLM call by overwriting those two fields, then re-validating with `HarnessConfigSchema`. + +- [ ] **Step 1: Write the failing test** + +`tests/mutate.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mutateConfig } from "../src/outer/mutate"; +import { BASELINE_CONFIG } from "../src/config/schema"; +import type { LlmClient } from "../src/llm"; + +test("returns validated config with pinned version fields", async () => { + const proposal = { ...BASELINE_CONFIG, version: 999, parent_version: 42, rationale: "Add typography skill", system_instructions: BASELINE_CONFIG.system_instructions + "\nUse a modular type scale." }; + let captured: any; + const client: LlmClient = { + messages: { create: async (p) => { captured = p; return { content: [{ type: "tool_use", name: "propose_config", input: proposal }] }; } }, + }; + const next = await mutateConfig({ + client, model: "m", bestConfig: BASELINE_CONFIG, + latestSummary: { iteration: 1, config_version: 0, mean_overall: 52, outcomes: [], dimension_means: { typography: 4.1 } }, + history: [{ iteration: 1, config_version: 0, mean_overall: 52, best_version: 0, best_score: 52 }], + pastRationales: [{ version: 0, rationale: "baseline", mean_overall: 52 }], + nextVersion: 5, + }); + expect(next.version).toBe(5); + expect(next.parent_version).toBe(0); + const sent = JSON.stringify(captured.messages); + expect(sent).toContain("typography"); // summary reached the prompt + expect(sent).toContain("baseline"); // history reached the prompt +}); +``` + +- [ ] **Step 2: Run to verify FAIL** — `bun test tests/mutate.test.ts` + +- [ ] **Step 3: Implement** + +`src/outer/mutate.ts`: +```ts +import { forcedToolCall, type LlmClient } from "../llm"; +import { HarnessConfigSchema, type HarnessConfig } from "../config/schema"; +import type { HistoryEntry, IterationSummary } from "../store/run-store"; + +const MUTATOR_SYSTEM = `You are a harness engineer optimizing an AI coding agent's configuration so it produces +better-designed landing pages. You may change: system_instructions, skills (add/edit/remove markdown skill +documents), subagents (internal review passes), tools, model thinking_level. Make ONE focused, well-motivated +change per proposal — a targeted hypothesis, not a rewrite. Ground it in the evaluation critiques and the +weakest rubric dimensions. Avoid repeating past changes that did not improve the score.`; + +export async function mutateConfig(opts: { + client: LlmClient; + model: string; + bestConfig: HarnessConfig; + latestSummary: IterationSummary; + history: HistoryEntry[]; + pastRationales: Array<{ version: number; rationale: string; mean_overall: number | null }>; + nextVersion: number; +}): Promise { + const critiques = opts.latestSummary.outcomes + .filter((o) => o.eval) + .map((o) => `- ${o.prompt_id} (${o.overall}, ${o.eval!.vs_reference}): ${o.eval!.critique}`) + .join("\n"); + const failures = opts.latestSummary.outcomes + .filter((o) => o.status !== "ok") + .map((o) => `- ${o.prompt_id}: ${o.status} ${o.error ?? ""}`) + .join("\n"); + + const proposal = await forcedToolCall(opts.client, { + model: opts.model, + system: MUTATOR_SYSTEM, + toolName: "propose_config", + description: "Propose the complete next harness configuration.", + zodSchema: HarnessConfigSchema, + maxTokens: 16384, + content: [ + { + type: "text", + text: [ + `## Current best config (version ${opts.bestConfig.version}, derive your proposal from this)`, + JSON.stringify(opts.bestConfig, null, 2), + `## Latest iteration summary (config v${opts.latestSummary.config_version}, mean ${opts.latestSummary.mean_overall.toFixed(1)})`, + `Dimension means: ${JSON.stringify(opts.latestSummary.dimension_means)}`, + `Per-prompt critiques:\n${critiques || "(none)"}`, + failures ? `Failures:\n${failures}` : "", + `## Score history`, + opts.history.map((h) => `iter ${h.iteration}: v${h.config_version} → ${h.mean_overall.toFixed(1)} (best v${h.best_version}=${h.best_score.toFixed(1)})`).join("\n"), + `## Past change rationales`, + opts.pastRationales.map((r) => `v${r.version} (${r.mean_overall ?? "unscored"}): ${r.rationale}`).join("\n"), + `Propose the next config now. Include a specific rationale explaining the hypothesis.`, + ].filter(Boolean).join("\n\n"), + }, + ], + }); + + return HarnessConfigSchema.parse({ + ...proposal, + version: opts.nextVersion, + parent_version: opts.bestConfig.version, + }); +} +``` + +- [ ] **Step 4: Run to verify PASS** — `bun test tests/mutate.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/outer/mutate.ts tests/mutate.test.ts +git commit -m "feat: schema-constrained config mutator with elitist anchoring" +``` + +--- + +### Task 12: Inner-loop pipeline (build → screenshot → evaluate per prompt) + +**Files:** +- Create: `src/inner/pipeline.ts`, `src/util/concurrency.ts` +- Test: `tests/pipeline.test.ts` + +**Interfaces:** +- Consumes: everything from Tasks 3–9. +- Produces: + ```ts + function pLimit(n: number): (fn: () => Promise) => Promise; // src/util/concurrency.ts + function runPromptPipeline(opts: { + resolved: ResolvedHarness; prompt: PromptSpec; promptDir: string; // from RunStore.promptDir + client: LlmClient; evalModel: string; referenceDir: string; // runs/reference + }): Promise; + ``` + Steps: `buildPage` (workspace = `/workspace`) → on failure return `build_failed`; `screenshotPage(htmlPath, promptDir)` → on throw return `screenshot_failed`; `evaluatePage` with reference `/.desktop.png`, retried internally by `forcedToolCall` → on throw return `eval_failed`; success writes `/eval.json` and returns `ok` with `overall`. + +- [ ] **Step 1: Write the failing test** + +`tests/pipeline.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { runPromptPipeline } from "../src/inner/pipeline"; +import { pLimit } from "../src/util/concurrency"; +import { resolveHarness } from "../src/config/resolver"; +import { BASELINE_CONFIG } from "../src/config/schema"; +import type { LlmClient } from "../src/llm"; + +const PAGE = `

Test page

${"

content

".repeat(30)}`; +const EVAL = { + subscores: { hierarchy: 6, typography: 6, spacing: 6, color_contrast: 6, requirement_coverage: 8, polish: 5 }, + overall: 62, vs_reference: "behind", diff_dimensions: [], critique: "fine", +}; + +function setup() { + const base = mkdtempSync(join(tmpdir(), "pipe-")); + const stub = join(base, "pi.sh"); + writeFileSync(stub, `#!/bin/bash\ncat > /dev/null <<'EOF'\nEOF\nprintf '%s' '${PAGE}' > output.html\n`); + chmodSync(stub, 0o755); + process.env.PI_BIN = stub; + const refDir = join(base, "reference"); + mkdirSync(refDir, { recursive: true }); + writeFileSync(join(refDir, "t1.desktop.png"), + Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64")); + const promptDir = join(base, "p", "t1"); + mkdirSync(join(promptDir, "workspace"), { recursive: true }); + const client: LlmClient = { messages: { create: async () => ({ content: [{ type: "tool_use", name: "submit_evaluation", input: EVAL }] }) } }; + return { base, refDir, promptDir, client }; +} + +test("happy path produces ok outcome with eval.json", async () => { + const { refDir, promptDir, client } = setup(); + const resolved = resolveHarness(BASELINE_CONFIG, join(promptDir, "resolved")); + const out = await runPromptPipeline({ + resolved, prompt: { id: "t1", category: "c", split: "train", prompt: "x" }, + promptDir, client, evalModel: "m", referenceDir: refDir, + }); + expect(out.status).toBe("ok"); + expect(out.overall).toBe(62); +}, 60000); + +test("pLimit caps concurrency", async () => { + const limit = pLimit(2); + let active = 0, peak = 0; + await Promise.all( + Array.from({ length: 6 }, () => + limit(async () => { + active++; peak = Math.max(peak, active); + await new Promise((r) => setTimeout(r, 20)); + active--; + }), + ), + ); + expect(peak).toBeLessThanOrEqual(2); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** — `bun test tests/pipeline.test.ts` + +- [ ] **Step 3: Implement** + +`src/util/concurrency.ts`: +```ts +export function pLimit(n: number): (fn: () => Promise) => Promise { + let active = 0; + const queue: Array<() => void> = []; + const next = () => { + if (active >= n || queue.length === 0) return; + active++; + queue.shift()!(); + }; + return (fn: () => Promise) => + new Promise((resolve, reject) => { + queue.push(() => fn().then(resolve, reject).finally(() => { active--; next(); })); + next(); + }); +} +``` + +`src/inner/pipeline.ts`: +```ts +import { writeFileSync } from "node:fs"; +import { join } from "node:path"; +import type { ResolvedHarness } from "../config/resolver"; +import type { PromptSpec } from "../prompts"; +import type { LlmClient } from "../llm"; +import type { PromptOutcome } from "../store/run-store"; +import { buildPage } from "./build"; +import { screenshotPage } from "./screenshot"; +import { evaluatePage } from "./evaluate"; + +export async function runPromptPipeline(opts: { + resolved: ResolvedHarness; + prompt: PromptSpec; + promptDir: string; + client: LlmClient; + evalModel: string; + referenceDir: string; +}): Promise { + const { prompt, promptDir } = opts; + + const build = await buildPage({ resolved: opts.resolved, prompt, workspaceDir: join(promptDir, "workspace") }); + if (!build.ok) return { prompt_id: prompt.id, status: "build_failed", overall: 0, error: build.error }; + + let shots; + try { + shots = await screenshotPage(build.htmlPath, promptDir); + } catch (e) { + return { prompt_id: prompt.id, status: "screenshot_failed", overall: 0, error: String(e) }; + } + + try { + const evalResult = await evaluatePage({ + client: opts.client, + model: opts.evalModel, + prompt, + candidate: shots, + referenceDesktopPng: join(opts.referenceDir, `${prompt.id}.desktop.png`), + }); + writeFileSync(join(promptDir, "eval.json"), JSON.stringify(evalResult, null, 2)); + return { prompt_id: prompt.id, status: "ok", overall: evalResult.overall, eval: evalResult }; + } catch (e) { + return { prompt_id: prompt.id, status: "eval_failed", overall: 0, error: String(e) }; + } +} +``` + +- [ ] **Step 4: Run to verify PASS** — `bun test tests/pipeline.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/inner/pipeline.ts src/util/concurrency.ts tests/pipeline.test.ts +git commit -m "feat: per-prompt build/screenshot/evaluate pipeline with pLimit" +``` + +--- + +### Task 13: Reference generator + +**Files:** +- Create: `src/reference/build-reference.ts` +- Test: `tests/reference.test.ts` (stubbed PI_BIN, real Playwright) + +**Interfaces:** +- Consumes: Tasks 2–6. +- Produces: + ```ts + const REFERENCE_CONFIG: HarnessConfig; // Opus, thinking high, richer fixed design instructions; NOT part of the search + function buildReferenceSet(opts: { + prompts: PromptSpec[]; referenceDir: string; concurrency?: number; force?: boolean; + }): Promise<{ built: string[]; skipped: string[]; failed: Array<{ id: string; error: string }> }>; + function assertReferencesExist(prompts: PromptSpec[], referenceDir: string): void; // throws listing missing ids + ``` + Per prompt: skip if `.desktop.png` exists (unless `force`); otherwise build into `/.work//workspace`, screenshot with `baseName = prompt.id` directly into `referenceDir`, and copy `output.html` to `/.html`. + +- [ ] **Step 1: Write the failing test** + +`tests/reference.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { buildReferenceSet, assertReferencesExist } from "../src/reference/build-reference"; + +const PAGE = `

Ref

${"

content

".repeat(30)}`; + +test("builds and caches reference pages, skips existing", async () => { + const base = mkdtempSync(join(tmpdir(), "ref-")); + const stub = join(base, "pi.sh"); + writeFileSync(stub, `#!/bin/bash\nprintf '%s' '${PAGE}' > output.html\n`); + chmodSync(stub, 0o755); + process.env.PI_BIN = stub; + const refDir = join(base, "reference"); + const prompts = [{ id: "r1", category: "c", split: "train" as const, prompt: "x" }]; + + const first = await buildReferenceSet({ prompts, referenceDir: refDir }); + expect(first.built).toEqual(["r1"]); + expect(existsSync(join(refDir, "r1.desktop.png"))).toBe(true); + expect(existsSync(join(refDir, "r1.html"))).toBe(true); + + const second = await buildReferenceSet({ prompts, referenceDir: refDir }); + expect(second.skipped).toEqual(["r1"]); + + expect(() => assertReferencesExist(prompts, refDir)).not.toThrow(); + expect(() => assertReferencesExist([{ id: "missing", category: "c", split: "train", prompt: "x" }], refDir)).toThrow("missing"); +}, 120000); +``` + +- [ ] **Step 2: Run to verify FAIL** — `bun test tests/reference.test.ts` + +- [ ] **Step 3: Implement** + +`src/reference/build-reference.ts`: +```ts +import { copyFileSync, existsSync, mkdirSync } from "node:fs"; +import { join } from "node:path"; +import { BASELINE_CONFIG, type HarnessConfig } from "../config/schema"; +import { resolveHarness } from "../config/resolver"; +import { buildPage } from "../inner/build"; +import { screenshotPage } from "../inner/screenshot"; +import { pLimit } from "../util/concurrency"; +import type { PromptSpec } from "../prompts"; + +export const REFERENCE_CONFIG: HarnessConfig = { + ...BASELINE_CONFIG, + rationale: "Fixed reference harness — not part of the search space.", + model: { name: "anthropic/claude-opus-4-8", thinking_level: "high" }, + system_instructions: [ + BASELINE_CONFIG.system_instructions, + "You are the reference standard: produce the best landing page you possibly can.", + "Invest in typography (real type scale), a cohesive palette, generous consistent spacing,", + "distinctive hero treatment, and polished components. Take your time; quality over speed.", + ].join("\n"), +}; + +export async function buildReferenceSet(opts: { + prompts: PromptSpec[]; + referenceDir: string; + concurrency?: number; + force?: boolean; +}): Promise<{ built: string[]; skipped: string[]; failed: Array<{ id: string; error: string }> }> { + const { prompts, referenceDir, concurrency = 3, force = false } = opts; + mkdirSync(referenceDir, { recursive: true }); + const resolved = resolveHarness(REFERENCE_CONFIG, join(referenceDir, ".work", "resolved")); + const limit = pLimit(concurrency); + const built: string[] = [], skipped: string[] = [], failed: Array<{ id: string; error: string }> = []; + + await Promise.all( + prompts.map((prompt) => + limit(async () => { + if (!force && existsSync(join(referenceDir, `${prompt.id}.desktop.png`))) { + skipped.push(prompt.id); + return; + } + const ws = join(referenceDir, ".work", prompt.id, "workspace"); + mkdirSync(ws, { recursive: true }); + const r = await buildPage({ resolved, prompt, workspaceDir: ws }); + if (!r.ok) { failed.push({ id: prompt.id, error: r.error }); return; } + try { + await screenshotPage(r.htmlPath, referenceDir, prompt.id); + copyFileSync(r.htmlPath, join(referenceDir, `${prompt.id}.html`)); + built.push(prompt.id); + } catch (e) { + failed.push({ id: prompt.id, error: String(e) }); + } + }), + ), + ); + return { built, skipped, failed }; +} + +export function assertReferencesExist(prompts: PromptSpec[], referenceDir: string): void { + const missing = prompts.filter((p) => !existsSync(join(referenceDir, `${p.id}.desktop.png`))).map((p) => p.id); + if (missing.length) throw new Error(`missing reference screenshots: ${missing.join(", ")} — run \`bun run reference\` first`); +} +``` + +- [ ] **Step 4: Run to verify PASS** — `bun test tests/reference.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/reference/build-reference.ts tests/reference.test.ts +git commit -m "feat: one-time reference set generator with fixed opus harness" +``` + +--- + +### Task 14: Orchestrator + CLI + +**Files:** +- Create: `src/orchestrator.ts`, `src/cli.ts` +- Test: `tests/orchestrator.test.ts` (full fake iteration: stub PI_BIN + fake LlmClient, real Playwright/store) + +**Interfaces:** +- Consumes: everything above. +- Produces: + ```ts + // src/orchestrator.ts + function runLoop(opts: { + store: RunStore; prompts: PromptSpec[]; // TRAIN prompts only — caller filters + iterations: number; concurrency: number; + client: LlmClient; evalModel: string; referenceDir: string; + startIteration?: number; // resume support, default = last completed + 1 + }): Promise; + function runHoldout(opts: { + store: RunStore; prompts: PromptSpec[]; // HOLDOUT prompts + configVersion: number; concurrency: number; + client: LlmClient; evalModel: string; referenceDir: string; outDir: string; + }): Promise; + ``` + `runLoop` per iteration N: ensure config v exists (iteration 1 seeds `BASELINE_CONFIG` if `configs/` empty; otherwise use the version proposed by the previous mutation, recorded as the latest saved config); resolve into `iterations//resolved/`; write `config-version.txt`; run all prompts via `runPromptPipeline` under `pLimit(concurrency)`; `aggregate`; mutate from `bestVersion()`s config (after updating history with this iteration); save proposal via `saveConfig`; `saveSummary` (with `mutator_rationale`); `appendHistory`. Mutation failure after retries: persist everything done so far, then throw (run is resumable). + +- [ ] **Step 1: Write the failing test** + +`tests/orchestrator.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { runLoop, runHoldout } from "../src/orchestrator"; +import { RunStore } from "../src/store/run-store"; +import { BASELINE_CONFIG } from "../src/config/schema"; +import type { LlmClient } from "../src/llm"; + +const PAGE = `

P

${"

c

".repeat(40)}`; +const EVAL = { + subscores: { hierarchy: 6, typography: 6, spacing: 6, color_contrast: 6, requirement_coverage: 8, polish: 5 }, + overall: 62, vs_reference: "behind", diff_dimensions: [], critique: "ok", +}; +const PNG1 = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64"); + +function fakeClient(): LlmClient { + return { + messages: { + create: async (params: any) => { + const isMutate = JSON.stringify(params.tools).includes("propose_config"); + if (isMutate) { + return { content: [{ type: "tool_use", name: "propose_config", input: { ...BASELINE_CONFIG, rationale: "tweak", version: 0, parent_version: null } }] }; + } + return { content: [{ type: "tool_use", name: "submit_evaluation", input: EVAL }] }; + }, + }, + }; +} + +function setup() { + const base = mkdtempSync(join(tmpdir(), "orch-")); + const stub = join(base, "pi.sh"); + writeFileSync(stub, `#!/bin/bash\nprintf '%s' '${PAGE}' > output.html\n`); + chmodSync(stub, 0o755); + process.env.PI_BIN = stub; + const refDir = join(base, "reference"); + mkdirSync(refDir, { recursive: true }); + for (const id of ["a", "b", "h1"]) writeFileSync(join(refDir, `${id}.desktop.png`), PNG1); + const store = new RunStore(join(base, "runs"), "test-run"); + store.initRun({}); + return { base, refDir, store }; +} +const P = (id: string, split: "train" | "holdout") => ({ id, category: "c", split, prompt: "make page " + id }); + +test("two iterations: configs, summaries, history, best tracking", async () => { + const { refDir, store } = setup(); + await runLoop({ + store, prompts: [P("a", "train"), P("b", "train")], iterations: 2, concurrency: 2, + client: fakeClient(), evalModel: "m", referenceDir: refDir, + }); + expect(store.completedIterations()).toEqual([1, 2]); + expect(store.readHistory().length).toBe(2); + expect(store.listConfigVersions()).toEqual([0, 1, 2]); // baseline + 2 proposals + expect(store.bestVersion().score).toBeCloseTo(62); + const s = store.loadSummaries()[0]; + expect(s.mutator_rationale).toBe("tweak"); + expect(existsSync(join(store.root, "iterations", "1", "config-version.txt"))).toBe(true); +}, 120000); + +test("holdout writes report without touching history", async () => { + const { refDir, store, base } = setup(); + await runLoop({ + store, prompts: [P("a", "train")], iterations: 1, concurrency: 1, + client: fakeClient(), evalModel: "m", referenceDir: refDir, + }); + const before = store.readHistory().length; + const summary = await runHoldout({ + store, prompts: [P("h1", "holdout")], configVersion: 0, concurrency: 1, + client: fakeClient(), evalModel: "m", referenceDir: refDir, outDir: join(base, "runs", "test-run", "holdout", "t1"), + }); + expect(summary.mean_overall).toBeCloseTo(62); + expect(store.readHistory().length).toBe(before); // unchanged +}, 120000); +``` + +- [ ] **Step 2: Run to verify FAIL** — `bun test tests/orchestrator.test.ts` + +- [ ] **Step 3: Implement** + +`src/orchestrator.ts`: +```ts +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { BASELINE_CONFIG } from "./config/schema"; +import { resolveHarness } from "./config/resolver"; +import { runPromptPipeline } from "./inner/pipeline"; +import { aggregate } from "./outer/aggregate"; +import { mutateConfig } from "./outer/mutate"; +import { pLimit } from "./util/concurrency"; +import type { LlmClient } from "./llm"; +import type { PromptSpec } from "./prompts"; +import type { RunStore, IterationSummary } from "./store/run-store"; + +export async function runLoop(opts: { + store: RunStore; + prompts: PromptSpec[]; + iterations: number; + concurrency: number; + client: LlmClient; + evalModel: string; + referenceDir: string; + startIteration?: number; +}): Promise { + const { store, prompts, client, evalModel, referenceDir, concurrency } = opts; + if (prompts.some((p) => p.split !== "train")) throw new Error("runLoop accepts train prompts only"); + + if (store.listConfigVersions().length === 0) store.saveConfig(BASELINE_CONFIG); + const completed = store.completedIterations(); + const start = opts.startIteration ?? (completed.length ? Math.max(...completed) + 1 : 1); + + for (let iter = start; iter < start + opts.iterations; iter++) { + // The config to evaluate this iteration: the newest saved config (last mutation's proposal, or baseline). + const versions = store.listConfigVersions(); + const configVersion = Math.max(...versions); + const config = store.loadConfig(configVersion); + + const iterDir = store.iterationDir(iter); + writeFileSync(join(iterDir, "config-version.txt"), String(configVersion)); + const resolved = resolveHarness(config, join(iterDir, "resolved")); + + console.log(`[iter ${iter}] config v${configVersion} — building ${prompts.length} prompts…`); + const limit = pLimit(concurrency); + const outcomes = await Promise.all( + prompts.map((prompt) => + limit(() => + runPromptPipeline({ + resolved, prompt, promptDir: store.promptDir(iter, prompt.id), + client, evalModel, referenceDir, + }).then((o) => { console.log(`[iter ${iter}] ${prompt.id}: ${o.status} ${o.overall}`); return o; }), + ), + ), + ); + + const summary = aggregate(iter, configVersion, outcomes); + const prevBest = store.bestVersion(); + store.appendHistory({ + iteration: iter, + config_version: configVersion, + mean_overall: summary.mean_overall, + best_version: summary.mean_overall > prevBest.score ? configVersion : prevBest.version, + best_score: Math.max(summary.mean_overall, prevBest.score), + }); + + const best = store.bestVersion(); + const bestConfig = store.loadConfig(best.version); + const historyEntries = store.readHistory(); + const pastRationales = store.listConfigVersions().map((v) => { + const c = store.loadConfig(v); + const scored = historyEntries.find((h) => h.config_version === v); + return { version: v, rationale: c.rationale, mean_overall: scored ? scored.mean_overall : null }; + }); + + console.log(`[iter ${iter}] mean ${summary.mean_overall.toFixed(1)} (best v${best.version}=${best.score.toFixed(1)}) — mutating…`); + const next = await mutateConfig({ + client, model: evalModel, bestConfig, latestSummary: summary, + history: historyEntries, pastRationales, nextVersion: store.nextConfigVersion(), + }); + store.saveConfig(next); + summary.mutator_rationale = next.rationale; + store.saveSummary(summary); + } +} + +export async function runHoldout(opts: { + store: RunStore; + prompts: PromptSpec[]; + configVersion: number; + concurrency: number; + client: LlmClient; + evalModel: string; + referenceDir: string; + outDir: string; +}): Promise { + const config = opts.store.loadConfig(opts.configVersion); + mkdirSync(opts.outDir, { recursive: true }); + const resolved = resolveHarness(config, join(opts.outDir, "resolved")); + const limit = pLimit(opts.concurrency); + const outcomes = await Promise.all( + opts.prompts.map((prompt) => + limit(() => { + const promptDir = join(opts.outDir, "prompts", prompt.id); + mkdirSync(join(promptDir, "workspace"), { recursive: true }); + return runPromptPipeline({ + resolved, prompt, promptDir, + client: opts.client, evalModel: opts.evalModel, referenceDir: opts.referenceDir, + }); + }), + ), + ); + const summary = aggregate(0, opts.configVersion, outcomes); + writeFileSync(join(opts.outDir, "summary.json"), JSON.stringify(summary, null, 2)); + return summary; +} +``` + +`src/cli.ts`: +```ts +import { parseArgs } from "node:util"; +import { join } from "node:path"; +import { loadPrompts, trainPrompts, holdoutPrompts } from "./prompts"; +import { RunStore } from "./store/run-store"; +import { realClient } from "./llm"; +import { runLoop, runHoldout } from "./orchestrator"; +import { buildReferenceSet, assertReferencesExist } from "./reference/build-reference"; + +const RUNS_DIR = "runs"; +const REFERENCE_DIR = join(RUNS_DIR, "reference"); +const EVAL_MODEL = process.env.EVAL_MODEL ?? "claude-opus-4-8"; + +const [command] = Bun.argv.slice(2); +const { values } = parseArgs({ + args: Bun.argv.slice(3), + options: { + iterations: { type: "string", default: "5" }, + "run-id": { type: "string" }, + concurrency: { type: "string", default: "5" }, + version: { type: "string" }, + force: { type: "boolean", default: false }, + }, +}); + +const all = loadPrompts(); +const concurrency = Number(values.concurrency); + +async function main() { + switch (command) { + case "reference": { + const r = await buildReferenceSet({ prompts: all, referenceDir: REFERENCE_DIR, concurrency, force: values.force }); + console.log(`built: ${r.built.length}, skipped: ${r.skipped.length}, failed: ${r.failed.length}`); + for (const f of r.failed) console.error(` FAILED ${f.id}: ${f.error}`); + if (r.failed.length) process.exit(1); + break; + } + case "loop": + case "resume": { + const runId = values["run-id"] ?? `run-${new Date().toISOString().slice(0, 16).replace(/[:T]/g, "-")}`; + if (command === "resume" && !values["run-id"]) throw new Error("resume requires --run-id"); + const train = trainPrompts(all); + assertReferencesExist(train, REFERENCE_DIR); + const store = new RunStore(RUNS_DIR, runId); + store.initRun({ eval_model: EVAL_MODEL, concurrency }); + console.log(`run: ${runId}`); + await runLoop({ + store, prompts: train, iterations: Number(values.iterations), concurrency, + client: realClient(), evalModel: EVAL_MODEL, referenceDir: REFERENCE_DIR, + }); + const best = store.bestVersion(); + console.log(`done. best config: v${best.version} (mean ${best.score.toFixed(1)})`); + break; + } + case "holdout": { + if (!values["run-id"]) throw new Error("holdout requires --run-id"); + const store = new RunStore(RUNS_DIR, values["run-id"]); + const holdout = holdoutPrompts(all); + assertReferencesExist(holdout, REFERENCE_DIR); + const version = values.version ? Number(values.version) : store.bestVersion().version; + const stamp = new Date().toISOString().replace(/[:.]/g, "-"); + const summary = await runHoldout({ + store, prompts: holdout, configVersion: version, concurrency, + client: realClient(), evalModel: EVAL_MODEL, referenceDir: REFERENCE_DIR, + outDir: join(store.root, "holdout", stamp), + }); + console.log(`holdout mean for v${version}: ${summary.mean_overall.toFixed(1)}`); + break; + } + default: + console.error("usage: bun src/cli.ts [options]"); + process.exit(1); + } +} +main(); +``` + +- [ ] **Step 4: Run to verify PASS** — `bun test tests/orchestrator.test.ts`, then full suite `bun test`. + +- [ ] **Step 5: Commit** + +```bash +git add src/orchestrator.ts src/cli.ts tests/orchestrator.test.ts +git commit -m "feat: outer loop orchestrator, resume, holdout, CLI" +``` + +--- + +### Task 15: Real smoke run + docs polish + +**Files:** +- Modify: `README.md` (fill in anything learned) +- No new source files. + +Manual verification (needs `ANTHROPIC_API_KEY` exported and real `pi` on PATH): + +- [ ] **Step 1: Type-check everything** + +Run: `bunx tsc --noEmit` — Expected: no errors. + +- [ ] **Step 2: Reference smoke (2 prompts)** + +Temporarily verify with a trimmed prompt file: copy `prompts.json` to `runs/smoke-prompts.json` with only `saas-crm` + `holdout-proptech`, then run `bun src/cli.ts reference` pointed at it by editing nothing — instead just run the real thing for all prompts if cost is acceptable, or run: +```bash +PI_BIN=pi bun run reference +``` +Expected: `runs/reference/.desktop.png` for every prompt id; inspect 2-3 PNGs by eye. + +- [ ] **Step 3: One-iteration loop smoke** + +```bash +bun run loop --iterations 1 --run-id smoke --concurrency 3 +``` +Expected: `runs/smoke/iterations/1/` populated (html, pngs, eval.json per prompt), `history.jsonl` with 1 line, `configs/v0.json` + `configs/v1.json`, console shows mean score and mutation rationale. + +- [ ] **Step 4: Holdout smoke** + +```bash +bun run holdout --run-id smoke +``` +Expected: holdout mean printed; `runs/smoke/holdout//summary.json` exists; `history.jsonl` unchanged. + +- [ ] **Step 5: Update README with any corrected usage, commit** + +```bash +git add README.md +git commit -m "docs: verified usage after smoke run" +``` + +--- + +## Self-review notes + +- **Spec coverage:** repo reset (T1), genome schema (T2), prompts+split discipline (T3, enforced again in `runLoop`'s train-only guard and CLI wiring), resolver determinism (T4), builder contract+hermetic flags (T5), dual-viewport screenshots (T6), forced-tool-call plumbing (T7), rubric+evaluator with reference image (T8), persistence layout (T9), failure semantics in means (T10), elitist-anchor mutator (T11), pipeline-not-barrier inner loop with bounded concurrency (T12), one-time cached reference set with startup assertion (T13), orchestrator/resume/holdout/CLI (T14), real smoke (T15). +- **Known deviation from spec, locked here:** subagents materialize as mandatory internal passes in the system prompt (plain `pi` has no hermetic subagent spawning); spec's intent (mutator can experiment with multi-role workflows) is preserved. +- **Type consistency:** `PromptOutcome`/`IterationSummary`/`HistoryEntry` defined once in Task 9 and imported everywhere; `Screenshots` from Task 6; `ResolvedHarness` from Task 4. From aa49e365f61e2e6ac87f56983e8d5258f5a85d7c Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:07:52 -0700 Subject: [PATCH 03/28] Switch screenshots to per-viewport scroll segments in spec and plan Co-Authored-By: Claude Fable 5 --- .../2026-07-18-ui-harness-autoresearch.md | 138 ++++++++++++------ ...26-07-18-ui-harness-autoresearch-design.md | 19 +-- 2 files changed, 106 insertions(+), 51 deletions(-) diff --git a/docs/superpowers/plans/2026-07-18-ui-harness-autoresearch.md b/docs/superpowers/plans/2026-07-18-ui-harness-autoresearch.md index a27488f..f7f9c5f 100644 --- a/docs/superpowers/plans/2026-07-18-ui-harness-autoresearch.md +++ b/docs/superpowers/plans/2026-07-18-ui-harness-autoresearch.md @@ -582,10 +582,11 @@ git commit -m "feat: pi subprocess builder with hermetic flags and failure captu **Interfaces:** - Produces: ```ts - type Screenshots = { desktopPng: string; mobilePng: string }; + type Screenshots = { desktop: string[]; mobile: string[] }; // scroll-ordered segment paths + const MAX_SEGMENTS = 8; function screenshotPage(htmlPath: string, outDir: string, baseName?: string): Promise; ``` - Desktop 1440×900, mobile 390×844, both full-page PNG, written as `/.desktop.png` / `.mobile.png` (baseName defaults to `"candidate"`). Throws on render failure (caller records it). + Desktop 1440×900, mobile 390×844. Instead of one full-page shot, scroll one viewport height per step and capture a viewport-sized PNG per screen: `/.desktop..png` / `.mobile..png`, `i` starting at 0, at most `MAX_SEGMENTS` per viewport. The last segment is bottom-aligned (scroll position clamped to `scrollHeight - viewportHeight`) so there is no blank overshoot. A page shorter than one viewport yields exactly one segment. `baseName` defaults to `"candidate"`. Throws on render failure (caller records it). - [ ] **Step 1: Write the failing test** @@ -595,15 +596,28 @@ import { expect, test } from "bun:test"; import { mkdtempSync, writeFileSync, statSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { screenshotPage } from "../src/inner/screenshot"; +import { screenshotPage, MAX_SEGMENTS } from "../src/inner/screenshot"; -test("captures desktop and mobile pngs", async () => { +test("long page yields multiple scroll segments per viewport", async () => { const dir = mkdtempSync(join(tmpdir(), "shot-")); const html = join(dir, "output.html"); - writeFileSync(html, `

Hello

${"

x

".repeat(50)}`); + // ~4 desktop screens tall + writeFileSync(html, `

Hello

`); const shots = await screenshotPage(html, dir); - expect(statSync(shots.desktopPng).size).toBeGreaterThan(1000); - expect(statSync(shots.mobilePng).size).toBeGreaterThan(1000); + expect(shots.desktop.length).toBeGreaterThanOrEqual(3); + expect(shots.desktop.length).toBeLessThanOrEqual(MAX_SEGMENTS); + expect(shots.mobile.length).toBeGreaterThanOrEqual(4); + expect(shots.desktop[0]).toContain("candidate.desktop.0.png"); + for (const p of [...shots.desktop, ...shots.mobile]) expect(statSync(p).size).toBeGreaterThan(1000); +}, 60000); + +test("short page yields exactly one segment per viewport", async () => { + const dir = mkdtempSync(join(tmpdir(), "shot2-")); + const html = join(dir, "output.html"); + writeFileSync(html, `

Tiny

`); + const shots = await screenshotPage(html, dir); + expect(shots.desktop.length).toBe(1); + expect(shots.mobile.length).toBe(1); }, 60000); ``` @@ -617,7 +631,9 @@ import { chromium } from "playwright"; import { pathToFileURL } from "node:url"; import { join } from "node:path"; -export type Screenshots = { desktopPng: string; mobilePng: string }; +export type Screenshots = { desktop: string[]; mobile: string[] }; + +export const MAX_SEGMENTS = 8; const VIEWPORTS = [ { key: "desktop", width: 1440, height: 900 }, @@ -627,17 +643,24 @@ const VIEWPORTS = [ export async function screenshotPage(htmlPath: string, outDir: string, baseName = "candidate"): Promise { const browser = await chromium.launch(); try { - const out: Record = {}; + const out: Record = { desktop: [], mobile: [] }; for (const vp of VIEWPORTS) { const page = await browser.newPage({ viewport: { width: vp.width, height: vp.height } }); await page.goto(pathToFileURL(htmlPath).href, { waitUntil: "load", timeout: 30000 }); await page.waitForTimeout(500); // settle fonts/animations - const path = join(outDir, `${baseName}.${vp.key}.png`); - await page.screenshot({ path, fullPage: true }); + const scrollHeight: number = await page.evaluate(() => document.documentElement.scrollHeight); + const segments = Math.min(MAX_SEGMENTS, Math.max(1, Math.ceil(scrollHeight / vp.height))); + for (let i = 0; i < segments; i++) { + const y = Math.min(i * vp.height, Math.max(0, scrollHeight - vp.height)); // bottom-align last segment + await page.evaluate((top) => window.scrollTo(0, top), y); + await page.waitForTimeout(150); // let scroll-triggered rendering settle + const path = join(outDir, `${baseName}.${vp.key}.${i}.png`); + await page.screenshot({ path }); // viewport-sized, NOT fullPage + out[vp.key].push(path); + } await page.close(); - out[vp.key] = path; } - return { desktopPng: out.desktop, mobilePng: out.mobile }; + return { desktop: out.desktop, mobile: out.mobile }; } finally { await browser.close(); } @@ -650,7 +673,7 @@ export async function screenshotPage(htmlPath: string, outDir: string, baseName ```bash git add src/inner/screenshot.ts tests/screenshot.test.ts -git commit -m "feat: playwright screenshotter, desktop + mobile full-page" +git commit -m "feat: playwright screenshotter with per-viewport scroll segments" ``` --- @@ -823,10 +846,10 @@ git commit -m "feat: schema-forced tool call helper with retry" }; function evaluatePage(opts: { client: LlmClient; model: string; prompt: PromptSpec; - candidate: Screenshots; referenceDesktopPng: string; + candidate: Screenshots; referenceDesktopPngs: string[]; }): Promise; ``` - Default model resolution happens in callers: `process.env.EVAL_MODEL ?? "claude-opus-4-8"`. + Image budget per call: all candidate desktop segments (≤8, scroll order, each labeled "Candidate desktop — screen i+1/N"), the first 3 candidate mobile segments, and the first 4 reference desktop segments (labeled as reference). Default model resolution happens in callers: `process.env.EVAL_MODEL ?? "claude-opus-4-8"`. - [ ] **Step 1: Write rubric** @@ -876,7 +899,7 @@ const valid = { overall: 68, vs_reference: "behind", diff_dimensions: ["typography"], critique: "Weak type scale.", }; -test("returns parsed eval and sends images + rubric", async () => { +test("returns parsed eval and sends capped image segments + rubric", async () => { const dir = mkdtempSync(join(tmpdir(), "eval-")); let captured: any; const client: LlmClient = { @@ -885,15 +908,19 @@ test("returns parsed eval and sends images + rubric", async () => { const r = await evaluatePage({ client, model: "test-model", prompt: { id: "p", category: "c", split: "train", prompt: "Landing page for X. Must include: hero." }, - candidate: { desktopPng: png(dir, "d.png"), mobilePng: png(dir, "m.png") }, - referenceDesktopPng: png(dir, "ref.png"), + candidate: { + desktop: [png(dir, "d0.png"), png(dir, "d1.png")], + mobile: [png(dir, "m0.png"), png(dir, "m1.png"), png(dir, "m2.png"), png(dir, "m3.png")], // 4 → capped to 3 + }, + referenceDesktopPngs: [png(dir, "r0.png"), png(dir, "r1.png"), png(dir, "r2.png"), png(dir, "r3.png"), png(dir, "r4.png")], // 5 → capped to 4 }); expect(r.overall).toBe(68); expect(EvalResultSchema.parse(r)).toEqual(valid as any); const text = JSON.stringify(captured.messages); expect(text).toContain("requirement_coverage"); // rubric included + expect(text).toContain("screen 1/2"); // segment labeling const images = captured.messages[0].content.filter((b: any) => b.type === "image"); - expect(images.length).toBe(3); // desktop, mobile, reference + expect(images.length).toBe(2 + 3 + 4); // desktop + capped mobile + capped reference }); ``` @@ -925,28 +952,40 @@ export type EvalResult = z.infer; const RUBRIC = readFileSync(join(import.meta.dir, "../eval/rubric.md"), "utf8"); +const MAX_MOBILE = 3; +const MAX_REFERENCE = 4; + export async function evaluatePage(opts: { client: LlmClient; model: string; prompt: PromptSpec; candidate: Screenshots; - referenceDesktopPng: string; + referenceDesktopPngs: string[]; }): Promise { + const content: unknown[] = [ + { type: "text", text: `You are a strict design reviewer.\n\n${RUBRIC}\n\n## Brief\n${opts.prompt.prompt}` }, + { type: "text", text: "Screenshots are scrolled viewport segments in top-to-bottom order." }, + ]; + const desktop = opts.candidate.desktop; + desktop.forEach((p, i) => { + content.push({ type: "text", text: `Candidate desktop — screen ${i + 1}/${desktop.length}:` }, imageBlock(p)); + }); + const mobile = opts.candidate.mobile.slice(0, MAX_MOBILE); + mobile.forEach((p, i) => { + content.push({ type: "text", text: `Candidate mobile — screen ${i + 1}/${mobile.length}:` }, imageBlock(p)); + }); + const refs = opts.referenceDesktopPngs.slice(0, MAX_REFERENCE); + refs.forEach((p, i) => { + content.push({ type: "text", text: `Reference page for the same brief, desktop — screen ${i + 1}/${refs.length}:` }, imageBlock(p)); + }); + content.push({ type: "text", text: "Evaluate the candidate per the rubric and call submit_evaluation." }); + return forcedToolCall(opts.client, { model: opts.model, toolName: "submit_evaluation", description: "Submit the structured rubric evaluation of the candidate landing page.", zodSchema: EvalResultSchema, - content: [ - { type: "text", text: `You are a strict design reviewer.\n\n${RUBRIC}\n\n## Brief\n${opts.prompt.prompt}` }, - { type: "text", text: "Candidate — desktop:" }, - imageBlock(opts.candidate.desktopPng), - { type: "text", text: "Candidate — mobile:" }, - imageBlock(opts.candidate.mobilePng), - { type: "text", text: "Reference page for the same brief (desktop):" }, - imageBlock(opts.referenceDesktopPng), - { type: "text", text: "Evaluate the candidate per the rubric and call submit_evaluation." }, - ], + content, }); } ``` @@ -1363,7 +1402,8 @@ git commit -m "feat: schema-constrained config mutator with elitist anchoring" client: LlmClient; evalModel: string; referenceDir: string; // runs/reference }): Promise; ``` - Steps: `buildPage` (workspace = `/workspace`) → on failure return `build_failed`; `screenshotPage(htmlPath, promptDir)` → on throw return `screenshot_failed`; `evaluatePage` with reference `/.desktop.png`, retried internally by `forcedToolCall` → on throw return `eval_failed`; success writes `/eval.json` and returns `ok` with `overall`. + Also produces `referenceSegments(referenceDir: string, promptId: string): string[]` — all `/.desktop..png` paths sorted by segment index (exported for reuse by holdout/orchestrator). + Steps: `buildPage` (workspace = `/workspace`) → on failure return `build_failed`; `screenshotPage(htmlPath, promptDir)` → on throw return `screenshot_failed`; `evaluatePage` with `referenceDesktopPngs = referenceSegments(...)`, retried internally by `forcedToolCall` → on throw return `eval_failed`; success writes `/eval.json` and returns `ok` with `overall`. - [ ] **Step 1: Write the failing test** @@ -1393,8 +1433,9 @@ function setup() { process.env.PI_BIN = stub; const refDir = join(base, "reference"); mkdirSync(refDir, { recursive: true }); - writeFileSync(join(refDir, "t1.desktop.png"), - Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64")); + const png1 = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64"); + writeFileSync(join(refDir, "t1.desktop.0.png"), png1); + writeFileSync(join(refDir, "t1.desktop.1.png"), png1); const promptDir = join(base, "p", "t1"); mkdirSync(join(promptDir, "workspace"), { recursive: true }); const client: LlmClient = { messages: { create: async () => ({ content: [{ type: "tool_use", name: "submit_evaluation", input: EVAL }] }) } }; @@ -1452,7 +1493,7 @@ export function pLimit(n: number): (fn: () => Promise) => Promise { `src/inner/pipeline.ts`: ```ts -import { writeFileSync } from "node:fs"; +import { readdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import type { ResolvedHarness } from "../config/resolver"; import type { PromptSpec } from "../prompts"; @@ -1462,6 +1503,15 @@ import { buildPage } from "./build"; import { screenshotPage } from "./screenshot"; import { evaluatePage } from "./evaluate"; +export function referenceSegments(referenceDir: string, promptId: string): string[] { + const re = new RegExp(`^${promptId}\\.desktop\\.(\\d+)\\.png$`); + return readdirSync(referenceDir) + .map((f) => ({ f, m: f.match(re) })) + .filter((x) => x.m) + .sort((a, b) => Number(a.m![1]) - Number(b.m![1])) + .map((x) => join(referenceDir, x.f)); +} + export async function runPromptPipeline(opts: { resolved: ResolvedHarness; prompt: PromptSpec; @@ -1483,12 +1533,16 @@ export async function runPromptPipeline(opts: { } try { + const refs = referenceSegments(opts.referenceDir, prompt.id); + if (!refs.length) { + return { prompt_id: prompt.id, status: "eval_failed", overall: 0, error: "no reference segments found" }; + } const evalResult = await evaluatePage({ client: opts.client, model: opts.evalModel, prompt, candidate: shots, - referenceDesktopPng: join(opts.referenceDir, `${prompt.id}.desktop.png`), + referenceDesktopPngs: refs, }); writeFileSync(join(promptDir, "eval.json"), JSON.stringify(evalResult, null, 2)); return { prompt_id: prompt.id, status: "ok", overall: evalResult.overall, eval: evalResult }; @@ -1525,7 +1579,7 @@ git commit -m "feat: per-prompt build/screenshot/evaluate pipeline with pLimit" }): Promise<{ built: string[]; skipped: string[]; failed: Array<{ id: string; error: string }> }>; function assertReferencesExist(prompts: PromptSpec[], referenceDir: string): void; // throws listing missing ids ``` - Per prompt: skip if `.desktop.png` exists (unless `force`); otherwise build into `/.work//workspace`, screenshot with `baseName = prompt.id` directly into `referenceDir`, and copy `output.html` to `/.html`. + Per prompt: skip if `.desktop.0.png` exists (unless `force`); otherwise build into `/.work//workspace`, screenshot with `baseName = prompt.id` directly into `referenceDir` (yielding `.desktop..png` / `.mobile..png` segments), and copy `output.html` to `/.html`. - [ ] **Step 1: Write the failing test** @@ -1550,7 +1604,7 @@ test("builds and caches reference pages, skips existing", async () => { const first = await buildReferenceSet({ prompts, referenceDir: refDir }); expect(first.built).toEqual(["r1"]); - expect(existsSync(join(refDir, "r1.desktop.png"))).toBe(true); + expect(existsSync(join(refDir, "r1.desktop.0.png"))).toBe(true); expect(existsSync(join(refDir, "r1.html"))).toBe(true); const second = await buildReferenceSet({ prompts, referenceDir: refDir }); @@ -1603,7 +1657,7 @@ export async function buildReferenceSet(opts: { await Promise.all( prompts.map((prompt) => limit(async () => { - if (!force && existsSync(join(referenceDir, `${prompt.id}.desktop.png`))) { + if (!force && existsSync(join(referenceDir, `${prompt.id}.desktop.0.png`))) { skipped.push(prompt.id); return; } @@ -1625,7 +1679,7 @@ export async function buildReferenceSet(opts: { } export function assertReferencesExist(prompts: PromptSpec[], referenceDir: string): void { - const missing = prompts.filter((p) => !existsSync(join(referenceDir, `${p.id}.desktop.png`))).map((p) => p.id); + const missing = prompts.filter((p) => !existsSync(join(referenceDir, `${p.id}.desktop.0.png`))).map((p) => p.id); if (missing.length) throw new Error(`missing reference screenshots: ${missing.join(", ")} — run \`bun run reference\` first`); } ``` @@ -1708,7 +1762,7 @@ function setup() { process.env.PI_BIN = stub; const refDir = join(base, "reference"); mkdirSync(refDir, { recursive: true }); - for (const id of ["a", "b", "h1"]) writeFileSync(join(refDir, `${id}.desktop.png`), PNG1); + for (const id of ["a", "b", "h1"]) writeFileSync(join(refDir, `${id}.desktop.0.png`), PNG1); const store = new RunStore(join(base, "runs"), "test-run"); store.initRun({}); return { base, refDir, store }; @@ -1973,7 +2027,7 @@ Temporarily verify with a trimmed prompt file: copy `prompts.json` to `runs/smok ```bash PI_BIN=pi bun run reference ``` -Expected: `runs/reference/.desktop.png` for every prompt id; inspect 2-3 PNGs by eye. +Expected: `runs/reference/.desktop.0.png` for every prompt id; inspect 2-3 PNGs by eye. - [ ] **Step 3: One-iteration loop smoke** diff --git a/docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md b/docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md index 746c940..a6f04c3 100644 --- a/docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md +++ b/docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md @@ -88,10 +88,11 @@ Per prompt: ### 4. Screenshotter — `src/inner/screenshot.ts` -Playwright headless Chromium loads `output.html` via `file://` and captures: +Playwright headless Chromium loads `output.html` via `file://` and captures **scrolled viewport segments**, not a single full-page shot (full-page PNGs of long pages get downscaled into illegibility by the vision model): -- Desktop viewport 1440×900, full-page PNG (primary eval input). -- Mobile viewport 390×844, full-page PNG (secondary eval input). +- Desktop viewport 1440×900: scroll from the top one viewport height at a time, one PNG per screen (`.desktop..png`), capped at 8 segments. +- Mobile viewport 390×844: same procedure (`.mobile..png`), capped at 8 segments. +- The final segment aligns to the bottom of the page (no blank overshoot); trivially short pages produce a single segment per viewport. Render errors (blank page, JS exception preventing paint) are recorded as failures. @@ -101,8 +102,8 @@ One Opus vision call per prompt per iteration. Inputs: - The original prompt text and its must-include requirements. - The fixed rubric. -- The candidate screenshots (desktop + mobile). -- The cached reference screenshot for the same prompt. +- The candidate screenshot segments: all desktop segments (≤8) in scroll order, plus the first 3 mobile segments. +- The cached reference desktop segments (≤4) for the same prompt, clearly labeled as the reference. Output (forced tool-call schema): @@ -143,8 +144,8 @@ prompts.json # kept as-is; train/holdout split is autho runs/ reference/ .html - .desktop.png - .mobile.png + .desktop..png # scrolled segments, i = 0..N-1 + .mobile..png / run.json # run metadata: models, concurrency, started_at history.jsonl # one line per iteration: version, mean score, best_version @@ -157,8 +158,8 @@ runs/ prompts/ / workspace/output.html - desktop.png - mobile.png + candidate.desktop..png # scrolled segments + candidate.mobile..png eval.json build.log summary.json # aggregates + mutator rationale From 0f678d4f1aa3f62d3748cd2dde66f120bd37cbd0 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:19:25 -0700 Subject: [PATCH 04/28] Wipe starter repo; scaffold autodesign Bun project Co-Authored-By: Claude Fable 5 --- .github/FUNDING.yml | 1 - .gitignore | 47 +- .introspection/customer-support-agent.yaml | 6 - BHVR.md | 290 - CONTRIBUTING.md | 25 - LICENSE | 21 - README.md | 21 +- bun.lock | 516 +- client/.gitignore | 24 - client/README.md | 54 - client/eslint.config.js | 28 - client/index.html | 13 - client/package.json | 32 - client/public/vite.svg | 1 - client/src/App.css | 80 - client/src/App.tsx | 51 - client/src/assets/beaver.svg | 1 - client/src/index.css | 68 - client/src/main.tsx | 10 - client/src/vite-env.d.ts | 1 - client/tsconfig.app.json | 22 - client/tsconfig.json | 5 - client/tsconfig.node.json | 24 - client/vite.config.ts | 6 - introspection/.env.example | 5 - introspection/.githooks/pre-commit | 4 - .../.github/workflows/recipe-validation.yml | 34 - introspection/.gitignore | 6 - introspection/.pi/mcp.local.example.json | 20 - introspection/README.md | 55 - introspection/SYSTEM.md | 5 - introspection/agents/agent.yaml | 36 - introspection/agents/escalation.yaml | 20 - introspection/agents/responder.yaml | 21 - introspection/agents/triage.yaml | 21 - introspection/package-lock.json | 5542 ----------------- introspection/package.json | 48 - introspection/skills/ticket-triage/SKILL.md | 19 - package.json | 51 +- server/.gitignore | 2 - server/README.md | 11 - server/package.json | 23 - server/src/index.ts | 23 - server/tsconfig.json | 26 - shared/package.json | 19 - shared/src/index.ts | 1 - shared/src/types/index.ts | 4 - shared/tsconfig.json | 24 - tsconfig.json | 30 +- turbo.json | 148 - 50 files changed, 47 insertions(+), 7498 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100644 .introspection/customer-support-agent.yaml delete mode 100644 BHVR.md delete mode 100644 CONTRIBUTING.md delete mode 100644 LICENSE delete mode 100644 client/.gitignore delete mode 100644 client/README.md delete mode 100644 client/eslint.config.js delete mode 100644 client/index.html delete mode 100644 client/package.json delete mode 100644 client/public/vite.svg delete mode 100644 client/src/App.css delete mode 100644 client/src/App.tsx delete mode 100644 client/src/assets/beaver.svg delete mode 100644 client/src/index.css delete mode 100644 client/src/main.tsx delete mode 100644 client/src/vite-env.d.ts delete mode 100644 client/tsconfig.app.json delete mode 100644 client/tsconfig.json delete mode 100644 client/tsconfig.node.json delete mode 100644 client/vite.config.ts delete mode 100644 introspection/.env.example delete mode 100755 introspection/.githooks/pre-commit delete mode 100644 introspection/.github/workflows/recipe-validation.yml delete mode 100644 introspection/.gitignore delete mode 100644 introspection/.pi/mcp.local.example.json delete mode 100644 introspection/README.md delete mode 100644 introspection/SYSTEM.md delete mode 100644 introspection/agents/agent.yaml delete mode 100644 introspection/agents/escalation.yaml delete mode 100644 introspection/agents/responder.yaml delete mode 100644 introspection/agents/triage.yaml delete mode 100644 introspection/package-lock.json delete mode 100644 introspection/package.json delete mode 100644 introspection/skills/ticket-triage/SKILL.md delete mode 100644 server/.gitignore delete mode 100644 server/README.md delete mode 100644 server/package.json delete mode 100644 server/src/index.ts delete mode 100644 server/tsconfig.json delete mode 100644 shared/package.json delete mode 100644 shared/src/index.ts delete mode 100644 shared/src/types/index.ts delete mode 100644 shared/tsconfig.json delete mode 100644 turbo.json diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index b144148..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -ko_fi: stevedylandev diff --git a/.gitignore b/.gitignore index 83c044e..f47607e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,44 +1,5 @@ -# bhvr/.gitignore -# dependencies -node_modules -.pnp -.pnp.js - -# testing -coverage - -# production -dist -build - -# misc -.DS_Store +node_modules/ +runs/ +!runs/reference/.gitkeep +*.log .env -.env.local -.env.development.local -.env.test.local -.env.production.local -.env*.local - -# logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - -# editor directories and files -.idea -.vscode/* -!.vscode/extensions.json -!.vscode/settings.json -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# Bun -bun.lockb - -# Turbo -.turbo \ No newline at end of file diff --git a/.introspection/customer-support-agent.yaml b/.introspection/customer-support-agent.yaml deleted file mode 100644 index d0c7631..0000000 --- a/.introspection/customer-support-agent.yaml +++ /dev/null @@ -1,6 +0,0 @@ -name: customer-support-agent -runtime_name: customer-support-agent -path: ./introspection -description: Customer support recipe for Pi and Introspection Cloud. -runtime: - llm_mode: managed diff --git a/BHVR.md b/BHVR.md deleted file mode 100644 index 1f28f52..0000000 --- a/BHVR.md +++ /dev/null @@ -1,290 +0,0 @@ -# bhvr 🦫 - -![cover](https://cdn.stevedylan.dev/ipfs/bafybeievx27ar5qfqyqyud7kemnb5n2p4rzt2matogi6qttwkpxonqhra4) - -A full-stack TypeScript monorepo starter with shared types, using Bun, Hono, Vite, and React. - -## Why bhvr? - -While there are plenty of existing app building stacks out there, many of them are either bloated, outdated, or have too much of a vendor lock-in. bhvr is built with the opinion that you should be able to deploy your client or server in any environment while also keeping type safety. - -## Quickstart - -Make sure [bun](https://bun.sh) is installed - -```bash -bun --version -``` - -Run the command below to make a new bhvr project - -```bash -bun create bhvr@latest my-app -``` - -Once complete run the dev server - -```bash -cd my-app -bun dev -``` - -> [!NOTE] -> Visit [bhvr.dev](https://bhvr.dev) for the full documentation! - -## Features - -- **Full-Stack TypeScript**: End-to-end type safety between client and server -- **Shared Types**: Common type definitions shared between client and server -- **Monorepo Structure**: Organized as a workspaces-based monorepo with Turbo for build orchestration -- **Modern Stack**: - - [Bun](https://bun.sh) as the JavaScript runtime and package manager - - [Hono](https://hono.dev) as the backend framework - - [Vite](https://vitejs.dev) for frontend bundling - - [React](https://react.dev) for the frontend UI - - [Turbo](https://turbo.build) for monorepo build orchestration and caching - -## Project Structure - -``` -. -├── client/ # React frontend -├── server/ # Hono backend -├── shared/ # Shared TypeScript definitions -│ └── src/types/ # Type definitions used by both client and server -├── package.json # Root package.json with workspaces -└── turbo.json # Turbo configuration for build orchestration -``` - -### Server - -bhvr uses Hono as a backend API for its simplicity and massive ecosystem of plugins. If you have ever used Express then it might feel familiar. Declaring routes and returning data is easy. - -``` -server -├── bun.lock -├── package.json -├── README.md -├── src -│   └── index.ts -└── tsconfig.json -``` - -```typescript src/index.ts -import { Hono } from 'hono' -import { cors } from 'hono/cors' -import type { ApiResponse } from 'shared' - -const app = new Hono() - -app.use(cors()) - -app.get('/', (c) => { - return c.text('Hello Hono!') -}) - -app.get('/hello', async (c) => { - - const data: ApiResponse = { - message: "Hello BHVR!", - success: true - } - - return c.json(data, { status: 200 }) -}) - -export default app -``` - -If you wanted to add a database to Hono you can do so with a multitude of Typescript libraries like [Supabase](https://supabase.com), or ORMs like [Drizzle](https://orm.drizzle.team/docs/get-started) or [Prisma](https://www.prisma.io/orm) - -### Client - -bhvr uses Vite + React Typescript template, which means you can build your frontend just as you would with any other React app. This makes it flexible to add UI components like [shadcn/ui](https://ui.shadcn.com) or routing using [React Router](https://reactrouter.com/start/declarative/installation). - -``` -client -├── eslint.config.js -├── index.html -├── package.json -├── public -│   └── vite.svg -├── README.md -├── src -│   ├── App.css -│   ├── App.tsx -│   ├── assets -│   ├── index.css -│   ├── main.tsx -│   └── vite-env.d.ts -├── tsconfig.app.json -├── tsconfig.json -├── tsconfig.node.json -└── vite.config.ts -``` - -```typescript src/App.tsx -import { useState } from 'react' -import beaver from './assets/beaver.svg' -import { ApiResponse } from 'shared' -import './App.css' - -const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000" - -function App() { - const [data, setData] = useState() - - async function sendRequest() { - try { - const req = await fetch(`${SERVER_URL}/hello`) - const res: ApiResponse = await req.json() - setData(res) - } catch (error) { - console.log(error) - } - } - - return ( - <> -
- - beaver logo - -
-

bhvr

-

Bun + Hono + Vite + React

-

A typesafe fullstack monorepo

-
- - {data && ( -
-            
-            Message: {data.message} 
- Success: {data.success.toString()} -
-
- )} -
-

- Click the beaver to learn more -

- - ) -} - -export default App -``` - -### Shared - -The Shared package is used for anything you want to share between the Server and Client. This could be types or libraries that you use in both environments. - -``` -shared -├── package.json -├── src -│   ├── index.ts -│   └── types -│   └── index.ts -└── tsconfig.json -``` - -Inside the `src/index.ts` we export any of our code from the folders so it's usable in other parts of the monorepo - -```typescript -export * from "./types" -``` - -By running `bun run dev` or `bun run build` it will compile and export the packages from `shared` so it can be used in either `client` or `server` - -```typescript -import { ApiResponse } from 'shared' -``` - -## Getting Started - -### Quick Start - -You can start a new bhvr project using the [CLI](https://github.com/stevedylandev/create-bhvr) - -```bash -bun create bhvr -``` - -### Installation - -```bash -# Install dependencies for all workspaces -bun install -``` - -### Development - -```bash -# Run all workspaces in development mode with Turbo -bun run dev - -# Or run individual workspaces directly -bun run dev:client # Run the Vite dev server for React -bun run dev:server # Run the Hono backend -``` - -### Building - -```bash -# Build all workspaces with Turbo -bun run build - -# Or build individual workspaces directly -bun run build:client # Build the React frontend -bun run build:server # Build the Hono backend -``` - -### Additional Commands - -```bash -# Lint all workspaces -bun run lint - -# Type check all workspaces -bun run type-check - -# Run tests across all workspaces -bun run test -``` - -### Deployment - -Deplying each piece is very versatile and can be done numerous ways, and exploration into automating these will happen at a later date. Here are some references in the meantime. - -**Client** -- [Orbiter](https://bhvr.dev/deployment/client/orbiter) -- [GitHub Pages](https://bhvr.dev/deployment/client/github-pages) -- [Netlify](https://bhvr.dev/deployment/client/netlify) -- [Cloudflare Pages](https://bhvr.dev/deployment/client/cloudflare-pages) - -**Server** -- [Orbiter](https://bhvr.dev/deployment/server/orbiter) -- [Cloudflare Worker](https://bhvr.dev/deployment/server/cloudflare-workers) -- [Bun](https://bhvr.dev/deployment/server/railway) -- [Node.js](https://bhvr.dev/deployment/server/railway) - -## Type Sharing - -Types are automatically shared between the client and server thanks to the shared package and TypeScript path aliases. You can import them in your code using: - -```typescript -import { ApiResponse } from 'shared/types'; -``` - -## Learn More - -- [bhvr Documentation](https://bhvr.dev) -- [Bun Documentation](https://bun.sh/docs) -- [Vite Documentation](https://vitejs.dev/guide/) -- [React Documentation](https://react.dev/learn) -- [Hono Documentation](https://hono.dev/docs) -- [Turbo Documentation](https://turbo.build/docs) -- [TypeScript Documentation](https://www.typescriptlang.org/docs/) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index fb8d6f9..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,25 +0,0 @@ -# Contributing to create-bhvr - -First off, thank you for considering contributing to `bhvr`! This is a true open source project and we welcome community ideas and contributions. In order to keep things clean please follow the contributing guidelines below. - -## How Can I Contribute? - -### Reporting Bugs - -If you find a bug, please make sure the bug has not already been reported by searching on GitHub under [Issues](https://github.com/stevedylandev/bhvr/issues). If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/stevedylandev/bhvr/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. - -### Suggesting Enhancements - -If you have an idea for an enhancement, please make sure the enhancement has not already been suggested by searching on GitHub under [Issues](https://github.com/stevedylandev/bhvr/issues). If you're unable to find an open issue addressing the suggestion, [open a new one](https://github.com/stevedylandev/bhvr/issues/new). Be sure to include a **title and clear description** of the enhancement you're suggesting. - -### Submitting a Pull Request - -1. Fork the repository and create your branch from `main`. -2. Run `bun install` to install the dependencies. -3. Make your changes. -4. Run `bun run build` to make sure your changes build correctly. -5. Issue that pull request! - -## License - -By contributing, you agree that your contributions will be licensed under its MIT License. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a0aa89b..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Steve Simkins - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index c1ce60d..bbe0073 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,11 @@ # autodesign -Autoresearch loop for AI-generated landing pages: propose a page, render it, score it against a frozen verifier, keep it if it beats the best so far, revert if not. +Automated research loop that evolves a Pi harness config for better UI/UX output. +See `docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md`. -Built at the AGI House [autoresearch hackathon](https://blog.agihouse.org/posts/autoresearch-research-brief). +## Commands -## Prompts - -[prompts.json](prompts.json) holds the landing page generation tasks the loop runs against. - -- Each prompt has an `id`, `category`, `split`, and the generation `prompt` itself. -- Prompts span 15+ categories (SaaS, dev tools, fintech, ecommerce, nonprofit, gaming, local business, ...) so the loop can't overfit to one page archetype. -- Every prompt lists required sections (hero, pricing, CTA, ...) so a degenerate output (blank page, single centered div) fails on content grounds, not just aesthetics. - -### Train / holdout split - -- `split: "train"` (15 prompts): the only prompts the optimization loop may score against. -- `split: "holdout"` (5 prompts): reserved for final validation. Never used during optimization, so improvements that transfer to them are evidence of genuine gains rather than verifier overfitting. +- `bun run reference` — build the fixed reference pages (one-time, needs ANTHROPIC_API_KEY + pi). +- `bun run loop --iterations N [--run-id X] [--concurrency 5]` — run the research loop. +- `bun run holdout --run-id X [--version V]` — score a config on holdout prompts (report only). +- `bun run resume --run-id X` — continue an interrupted run. diff --git a/bun.lock b/bun.lock index d14e5b8..44f3301 100644 --- a/bun.lock +++ b/bun.lock @@ -1,525 +1,39 @@ { "lockfileVersion": 1, - "configVersion": 0, + "configVersion": 1, "workspaces": { "": { - "name": "bhvr", - "devDependencies": { - "bun-types": "latest", - "turbo": "^2.6.3", - }, - "peerDependencies": { - "typescript": "^5.8.3", - }, - }, - "client": { - "name": "client", - "version": "0.0.1", + "name": "autodesign", "dependencies": { - "react": "^19.2.3", - "react-dom": "^19.2.3", - "server": "workspace:*", - "shared": "workspace:*", - }, - "devDependencies": { - "@eslint/js": "^9.39.2", - "@types/node": "^22.19.2", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "eslint": "^9.39.2", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.24", - "globals": "^16.5.0", - "typescript": "~5.7.3", - "typescript-eslint": "^8.49.0", - "vite": "^6.4.1", - }, - }, - "server": { - "name": "server", - "version": "0.0.1", - "dependencies": { - "hono": "^4.10.8", - "shared": "workspace:*", + "@anthropic-ai/sdk": "^0.60.0", + "playwright": "^1.54.0", + "zod": "^4.0.0", }, "devDependencies": { "@types/bun": "latest", - }, - }, - "shared": { - "name": "shared", - "version": "0.0.1", - "devDependencies": { - "typescript": "^5.9.3", + "typescript": "^5.8.0", }, }, }, "packages": { - "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], - - "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="], - - "@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], - - "@babel/generator": ["@babel/generator@7.28.0", "", { "dependencies": { "@babel/parser": "^7.28.0", "@babel/types": "^7.28.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg=="], - - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], - - "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], - - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.27.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.27.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg=="], - - "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], - - "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - - "@babel/helpers": ["@babel/helpers@7.27.6", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" } }, "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug=="], - - "@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="], - - "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], - - "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], - - "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/traverse": ["@babel/traverse@7.28.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/types": "^7.28.0", "debug": "^4.3.1" } }, "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg=="], - - "@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="], - - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.8", "", { "os": "aix", "cpu": "ppc64" }, "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.8", "", { "os": "android", "cpu": "arm" }, "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.8", "", { "os": "android", "cpu": "arm64" }, "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.8", "", { "os": "android", "cpu": "x64" }, "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.8", "", { "os": "linux", "cpu": "arm" }, "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.8", "", { "os": "linux", "cpu": "ia32" }, "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.8", "", { "os": "linux", "cpu": "x64" }, "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.8", "", { "os": "none", "cpu": "x64" }, "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.8", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.8", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.8", "", { "os": "sunos", "cpu": "x64" }, "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="], - - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], - - "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], - - "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], - - "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], - - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], - - "@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="], - - "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], - - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], - - "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], - - "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], - - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], - - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.45.1", "", { "os": "android", "cpu": "arm" }, "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.45.1", "", { "os": "android", "cpu": "arm64" }, "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.45.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.45.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.45.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.45.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A=="], - - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q=="], - - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q=="], - - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw=="], - - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog=="], - - "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg=="], - - "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.45.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg=="], - - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw=="], - - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA=="], - - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.45.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw=="], - - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw=="], - - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.45.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.45.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.45.1", "", { "os": "win32", "cpu": "x64" }, "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA=="], - - "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], - - "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], - - "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], - - "@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="], - - "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="], - - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - - "@types/node": ["@types/node@22.19.2", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw=="], - - "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], - - "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.49.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/type-utils": "8.49.0", "@typescript-eslint/utils": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.49.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.49.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA=="], - - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.49.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.49.0", "@typescript-eslint/types": "^8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0" } }, "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg=="], - - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.49.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA=="], - - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/utils": "8.49.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.49.0", "", {}, "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.49.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.49.0", "@typescript-eslint/tsconfig-utils": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA=="], - - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.49.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA=="], - - "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], - - "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.60.0", "", { "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-9zu/TXaUy8BZhXedDtt1wT3H4LOlpKDO1/ftiFpeR3N1PCr3KJFKkxxlQWWt1NNp08xSwUNJ3JNY8yhl8av6eQ=="], - "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "playwright": ["playwright@1.61.1", "", { "dependencies": { "playwright-core": "1.61.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ=="], - "browserslist": ["browserslist@4.25.1", "", { "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw=="], - - "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="], - - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001727", "", {}, "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q=="], - - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "client": ["client@workspace:client"], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], - - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - - "electron-to-chromium": ["electron-to-chromium@1.5.189", "", {}, "sha512-y9D1ntS1ruO/pZ/V2FtLE+JXLQe28XoRpZ7QCCo0T8LdQladzdcOVQZH/IWLVJvCw12OGMb6hYOeOAjntCmJRQ=="], - - "esbuild": ["esbuild@0.25.8", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.8", "@esbuild/android-arm": "0.25.8", "@esbuild/android-arm64": "0.25.8", "@esbuild/android-x64": "0.25.8", "@esbuild/darwin-arm64": "0.25.8", "@esbuild/darwin-x64": "0.25.8", "@esbuild/freebsd-arm64": "0.25.8", "@esbuild/freebsd-x64": "0.25.8", "@esbuild/linux-arm": "0.25.8", "@esbuild/linux-arm64": "0.25.8", "@esbuild/linux-ia32": "0.25.8", "@esbuild/linux-loong64": "0.25.8", "@esbuild/linux-mips64el": "0.25.8", "@esbuild/linux-ppc64": "0.25.8", "@esbuild/linux-riscv64": "0.25.8", "@esbuild/linux-s390x": "0.25.8", "@esbuild/linux-x64": "0.25.8", "@esbuild/netbsd-arm64": "0.25.8", "@esbuild/netbsd-x64": "0.25.8", "@esbuild/openbsd-arm64": "0.25.8", "@esbuild/openbsd-x64": "0.25.8", "@esbuild/openharmony-arm64": "0.25.8", "@esbuild/sunos-x64": "0.25.8", "@esbuild/win32-arm64": "0.25.8", "@esbuild/win32-ia32": "0.25.8", "@esbuild/win32-x64": "0.25.8" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q=="], - - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], - - "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="], - - "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.24", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w=="], - - "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - - "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], - - "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], - - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - - "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "hono": ["hono@4.10.8", "", {}, "sha512-DDT0A0r6wzhe8zCGoYOmMeuGu3dyTAE40HHjwUsWFTEy5WxK1x2WDSsBPlEXgPbRIFY6miDualuUDbasPogIww=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - - "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], - - "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - - "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - - "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], - - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - - "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], - - "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], - - "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], - - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - - "rollup": ["rollup@4.45.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.45.1", "@rollup/rollup-android-arm64": "4.45.1", "@rollup/rollup-darwin-arm64": "4.45.1", "@rollup/rollup-darwin-x64": "4.45.1", "@rollup/rollup-freebsd-arm64": "4.45.1", "@rollup/rollup-freebsd-x64": "4.45.1", "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", "@rollup/rollup-linux-arm-musleabihf": "4.45.1", "@rollup/rollup-linux-arm64-gnu": "4.45.1", "@rollup/rollup-linux-arm64-musl": "4.45.1", "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-musl": "4.45.1", "@rollup/rollup-linux-s390x-gnu": "4.45.1", "@rollup/rollup-linux-x64-gnu": "4.45.1", "@rollup/rollup-linux-x64-musl": "4.45.1", "@rollup/rollup-win32-arm64-msvc": "4.45.1", "@rollup/rollup-win32-ia32-msvc": "4.45.1", "@rollup/rollup-win32-x64-msvc": "4.45.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw=="], - - "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "server": ["server@workspace:server"], - - "shared": ["shared@workspace:shared"], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], - - "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], - - "turbo": ["turbo@2.6.3", "", { "optionalDependencies": { "turbo-darwin-64": "2.6.3", "turbo-darwin-arm64": "2.6.3", "turbo-linux-64": "2.6.3", "turbo-linux-arm64": "2.6.3", "turbo-windows-64": "2.6.3", "turbo-windows-arm64": "2.6.3" }, "bin": { "turbo": "bin/turbo" } }, "sha512-bf6YKUv11l5Xfcmg76PyWoy/e2vbkkxFNBGJSnfdSXQC33ZiUfutYh6IXidc5MhsnrFkWfdNNLyaRk+kHMLlwA=="], - - "turbo-darwin-64": ["turbo-darwin-64@2.6.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlJJDc1CQ7SK5Y5qnl7AzpkvKSnpkfPmnA+HeU/sgny3oHZckPV2776ebO2M33CYDSor7+8HQwaodY++IINhYg=="], - - "turbo-darwin-arm64": ["turbo-darwin-arm64@2.6.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MwVt7rBKiOK7zdYerenfCRTypefw4kZCue35IJga9CH1+S50+KTiCkT6LBqo0hHeoH2iKuI0ldTF2a0aB72z3w=="], - - "turbo-linux-64": ["turbo-linux-64@2.6.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cqpcw+dXxbnPtNnzeeSyWprjmuFVpHJqKcs7Jym5oXlu/ZcovEASUIUZVN3OGEM6Y/OTyyw0z09tOHNt5yBAVg=="], - - "turbo-linux-arm64": ["turbo-linux-arm64@2.6.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-MterpZQmjXyr4uM7zOgFSFL3oRdNKeflY7nsjxJb2TklsYqiu3Z9pQ4zRVFFH8n0mLGna7MbQMZuKoWqqHb45w=="], - - "turbo-windows-64": ["turbo-windows-64@2.6.3", "", { "os": "win32", "cpu": "x64" }, "sha512-biDU70v9dLwnBdLf+daoDlNJVvqOOP8YEjqNipBHzgclbQlXbsi6Gqqelp5er81Qo3BiRgmTNx79oaZQTPb07Q=="], - - "turbo-windows-arm64": ["turbo-windows-arm64@2.6.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-dDHVKpSeukah3VsI/xMEKeTnV9V9cjlpFSUs4bmsUiLu3Yv2ENlgVEZv65wxbeE0bh0jjpmElDT+P1KaCxArQQ=="], - - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + "playwright-core": ["playwright-core@1.61.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "typescript-eslint": ["typescript-eslint@8.49.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.49.0", "@typescript-eslint/parser": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/utils": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], - - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - - "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - - "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], - - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], - - "@typescript-eslint/typescript-estree/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - - "bun-types/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="], - - "client/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], - "@typescript-eslint/typescript-estree/tinyglobby/fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], } } diff --git a/client/.gitignore b/client/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/client/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/client/README.md b/client/README.md deleted file mode 100644 index da98444..0000000 --- a/client/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default tseslint.config({ - extends: [ - // Remove ...tseslint.configs.recommended and replace with this - ...tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - ...tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - ...tseslint.configs.stylisticTypeChecked, - ], - languageOptions: { - // other options... - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - }, -}) -``` - -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' - -export default tseslint.config({ - plugins: { - // Add the react-x and react-dom plugins - 'react-x': reactX, - 'react-dom': reactDom, - }, - rules: { - // other rules... - // Enable its recommended typescript rules - ...reactX.configs['recommended-typescript'].rules, - ...reactDom.configs.recommended.rules, - }, -}) -``` diff --git a/client/eslint.config.js b/client/eslint.config.js deleted file mode 100644 index 092408a..0000000 --- a/client/eslint.config.js +++ /dev/null @@ -1,28 +0,0 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' - -export default tseslint.config( - { ignores: ['dist'] }, - { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, - }, - rules: { - ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, - }, -) diff --git a/client/index.html b/client/index.html deleted file mode 100644 index c37bf7e..0000000 --- a/client/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - BHVR - - -
- - - diff --git a/client/package.json b/client/package.json deleted file mode 100644 index 90e6a32..0000000 --- a/client/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "client", - "private": true, - "version": "0.0.1", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "react": "^19.2.3", - "react-dom": "^19.2.3", - "shared": "workspace:*", - "server": "workspace:*" - }, - "devDependencies": { - "@eslint/js": "^9.39.2", - "@types/node": "^22.19.2", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "eslint": "^9.39.2", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.24", - "globals": "^16.5.0", - "typescript": "~5.7.3", - "typescript-eslint": "^8.49.0", - "vite": "^6.4.1" - } -} diff --git a/client/public/vite.svg b/client/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/client/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/App.css b/client/src/App.css deleted file mode 100644 index 9c22114..0000000 --- a/client/src/App.css +++ /dev/null @@ -1,80 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(1) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - -.response { - background: #444444; - border-radius: 15px; - padding: 2rem; -} - -.code { - text-align: start; - max-width: 500px; - margin: auto; -} - -.button-container { - display: flex; - align-items: center; - gap: 2rem; -} - -.docs-link { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; - color: rgba(255, 255, 255, 0.87); -} -.docs-link:hover { - border-color: #646cff; -} -.docs-link:focus, -.docs-link:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} diff --git a/client/src/App.tsx b/client/src/App.tsx deleted file mode 100644 index b1d0881..0000000 --- a/client/src/App.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useState } from 'react' -import beaver from './assets/beaver.svg' -import type { ApiResponse } from 'shared' -import './App.css' - -const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000" - -function App() { - const [data, setData] = useState() - - async function sendRequest() { - try { - const req = await fetch(`${SERVER_URL}/hello`) - const res: ApiResponse = await req.json() - setData(res) - } catch (error) { - console.log(error) - } - } - - return ( - <> -
- - beaver logo - -
-

bhvr

-

Bun + Hono + Vite + React

-

A typesafe fullstack monorepo

-
-
- - Docs -
- {data && ( -
-            
-            Message: {data.message} 
- Success: {data.success.toString()} -
-
- )} -
- - ) -} - -export default App diff --git a/client/src/assets/beaver.svg b/client/src/assets/beaver.svg deleted file mode 100644 index 9c6f8f0..0000000 --- a/client/src/assets/beaver.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/client/src/index.css b/client/src/index.css deleted file mode 100644 index 08a3ac9..0000000 --- a/client/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/client/src/main.tsx b/client/src/main.tsx deleted file mode 100644 index bef5202..0000000 --- a/client/src/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' - -createRoot(document.getElementById('root')!).render( - - - , -) diff --git a/client/src/vite-env.d.ts b/client/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/client/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json deleted file mode 100644 index 1903283..0000000 --- a/client/tsconfig.app.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["src"] -} diff --git a/client/tsconfig.json b/client/tsconfig.json deleted file mode 100644 index ff58696..0000000 --- a/client/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../tsconfig.json", - "files": [], - "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.app.json"}] -} diff --git a/client/tsconfig.node.json b/client/tsconfig.node.json deleted file mode 100644 index db0becc..0000000 --- a/client/tsconfig.node.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2022", - "lib": ["ES2023"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/client/vite.config.ts b/client/vite.config.ts deleted file mode 100644 index 9ffcc67..0000000 --- a/client/vite.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -export default defineConfig({ - plugins: [react()], -}) diff --git a/introspection/.env.example b/introspection/.env.example deleted file mode 100644 index 8c587ea..0000000 --- a/introspection/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -PI_RECIPES_MCP_LOCAL_CONFIG=.pi/mcp.local.json -SLACK_MCP_URL=http://localhost:3201/mcp -SLACK_MCP_TOKEN= -NOTION_MCP_URL=http://localhost:3202/mcp -NOTION_MCP_TOKEN= diff --git a/introspection/.githooks/pre-commit b/introspection/.githooks/pre-commit deleted file mode 100755 index 939aac7..0000000 --- a/introspection/.githooks/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh -set -eu - -npm run check diff --git a/introspection/.github/workflows/recipe-validation.yml b/introspection/.github/workflows/recipe-validation.yml deleted file mode 100644 index bc41214..0000000 --- a/introspection/.github/workflows/recipe-validation.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: recipe validation - -on: - pull_request: - push: - -permissions: - contents: read - -jobs: - recipe-check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 24 - - - run: npm install --no-audit --no-fund - - - name: Validate recipe - run: npm run check - - - name: Write structured report - if: always() - run: npm run check:json > recipe-check-report.json || true - - - name: Upload recipe check report - if: always() - uses: actions/upload-artifact@v4 - with: - name: recipe-check-report - path: recipe-check-report.json diff --git a/introspection/.gitignore b/introspection/.gitignore deleted file mode 100644 index ce08e57..0000000 --- a/introspection/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules/ -.env -.pi/mcp.local.json -.DS_Store -dist/ -.env.* diff --git a/introspection/.pi/mcp.local.example.json b/introspection/.pi/mcp.local.example.json deleted file mode 100644 index f323477..0000000 --- a/introspection/.pi/mcp.local.example.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "servers": [ - { - "id": "slack", - "transport": "streamable_http", - "url": "${SLACK_MCP_URL}", - "headers": { - "Authorization": "Bearer ${SLACK_MCP_TOKEN}" - } - }, - { - "id": "notion", - "transport": "streamable_http", - "url": "${NOTION_MCP_URL}", - "headers": { - "Authorization": "Bearer ${NOTION_MCP_AUTH_TOKEN}" - } - } - ] -} diff --git a/introspection/README.md b/introspection/README.md deleted file mode 100644 index 5cb8933..0000000 --- a/introspection/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Customer Support - -Frontline support agent: triage Slack threads, search the Notion KB, draft replies, and escalate when policy requires a human. - -## Try It - -```bash -recipes install github:introspection-recipes/customer-support -pi --recipe customer-support -``` - -For local development: - -```bash -recipes doctor . -recipes install . -pi --recipe customer-support -``` - -## What It Includes - -- `agent`: support lead. -- `triage`: intent, urgency, and policy classifier. -- `responder`: KB-grounded reply drafter. -- `escalation`: human handoff and risk checker. -- `ticket-triage`: support workflow skill. - -## MCP Configuration - -The recipe uses MCP servers for Slack and Notion access. For local development, -`recipes install` writes `.pi/mcp.local.json` into the installed recipe if it -does not already exist. Fill in the environment variables printed by install -before launching Pi: - -```bash -export SLACK_MCP_URL=http://localhost:3201/mcp -export SLACK_MCP_TOKEN=... -export NOTION_MCP_URL=http://localhost:3202/mcp -export NOTION_MCP_AUTH_TOKEN=... -pi --recipe customer-support -``` - -For the local Notion MCP server, set `NOTION_TOKEN` in the shell that starts the -MCP server. Set `NOTION_MCP_AUTH_TOKEN` in both the MCP server command and the -shell that launches Pi; this is the bearer token for the local MCP endpoint, not -the Notion API token. - -If you want workspace-specific bindings instead, create `.pi/mcp.local.json` in -the workspace where you launch Pi and set `PI_RECIPES_MCP_LOCAL_CONFIG` to that -path. The selected recipe agent gets a session-local `mcp` command on `PATH`; -no global `mcp` binary is required. - -The Slack MCP server should expose `slack_read_channel`, `slack_read_thread`, -and `slack_send_message_draft`. The Notion MCP server should expose -`API-post-search` and `API-retrieve-page-markdown`. diff --git a/introspection/SYSTEM.md b/introspection/SYSTEM.md deleted file mode 100644 index fbaae9c..0000000 --- a/introspection/SYSTEM.md +++ /dev/null @@ -1,5 +0,0 @@ -# Recipe Workflow - -Handle support requests with accuracy, empathy, and policy awareness. Ground replies in approved documentation whenever possible. If the request touches billing, security, legal, account access, abuse, privacy, or customer-specific commitments, prepare a concise escalation instead of pretending to decide. - -Draft replies that are ready for a human to review. Include the evidence used, any missing context, and the proposed next step. diff --git a/introspection/agents/agent.yaml b/introspection/agents/agent.yaml deleted file mode 100644 index 5480c83..0000000 --- a/introspection/agents/agent.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: agent -description: Support orchestrator, classify requests, retrieve KB answers, draft replies. -model: - name: anthropic/claude-sonnet-4-6 - thinking_level: medium -tools: - - read - - bash -mcp: - slack: - include: - - slack_read_channel - - slack_read_thread - - slack_send_message_draft - notion: - include: - - API-post-search - - API-retrieve-page-markdown -subagents: - - triage - - responder - - escalation -skills: - - ticket-triage -system_instructions: - mode: append - content: | - # Role: Support lead - - Read the customer channel and thread, search Notion for approved answers, draft a reply, escalate billing and security issues to a human. Prefer Slack drafts over posting unless explicitly asked to send. - - Use the recipe subagents for the normal support workflow: - - Start `triage` first to classify the request, customer impact, missing context, and escalation need. - - Use `responder` to search the approved Notion KB and draft the customer-facing reply. - - Use `escalation` whenever the request touches billing, refunds, disputes, security, legal, account access, privacy, abuse, or customer-specific commitments. - - Synthesize the subagent outputs yourself before answering the user. Do not post or send messages unless explicitly asked. diff --git a/introspection/agents/escalation.yaml b/introspection/agents/escalation.yaml deleted file mode 100644 index d9e4b32..0000000 --- a/introspection/agents/escalation.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: escalation -from: agent -description: Prepares human handoff notes for sensitive or policy-bound support cases. -model: - name: anthropic/claude-sonnet-4-6 - thinking_level: medium -tools: - - read - - bash -mcp: - slack: - include: - - slack_send_message_draft -subagents: [] -skills: - - ticket-triage -system_instructions: - mode: append - content: | - Prepare a human escalation with summary, risk, customer ask, known facts, missing facts, and recommended owner. Keep the customer-facing text separate. diff --git a/introspection/agents/responder.yaml b/introspection/agents/responder.yaml deleted file mode 100644 index ad77056..0000000 --- a/introspection/agents/responder.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: responder -from: agent -description: Drafts customer replies grounded in approved knowledge base content. -model: - name: anthropic/claude-sonnet-4-6 - thinking_level: medium -tools: - - read - - bash -mcp: - notion: - include: - - API-post-search - - API-retrieve-page-markdown -subagents: [] -skills: - - ticket-triage -system_instructions: - mode: append - content: | - Search approved documentation, cite the relevant KB pages by title, and draft a concise reply. Do not invent product behavior or policy. diff --git a/introspection/agents/triage.yaml b/introspection/agents/triage.yaml deleted file mode 100644 index e90063e..0000000 --- a/introspection/agents/triage.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: triage -from: agent -description: Classifies support requests by intent, urgency, customer impact, and escalation need. -model: - name: anthropic/claude-sonnet-4-6 - thinking_level: medium -tools: - - read - - bash -mcp: - slack: - include: - - slack_read_channel - - slack_read_thread -subagents: [] -skills: - - ticket-triage -system_instructions: - mode: append - content: | - Classify the request, summarize customer impact, identify missing context, and decide whether it is safe for an AI-drafted response. diff --git a/introspection/package-lock.json b/introspection/package-lock.json deleted file mode 100644 index 7826f7c..0000000 --- a/introspection/package-lock.json +++ /dev/null @@ -1,5542 +0,0 @@ -{ - "name": "@introspection-recipes/customer-support", - "version": "0.1.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@introspection-recipes/customer-support", - "version": "0.1.1", - "license": "Apache-2.0", - "devDependencies": { - "@introspection-ai/pi-recipes": "^0.6.0" - } - }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.91.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", - "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "json-schema-to-ts": "^3.1.1" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-bedrock-runtime": { - "version": "3.1048.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz", - "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/credential-provider-node": "^3.972.42", - "@aws-sdk/eventstream-handler-node": "^3.972.16", - "@aws-sdk/middleware-eventstream": "^3.972.12", - "@aws-sdk/middleware-websocket": "^3.972.19", - "@aws-sdk/token-providers": "3.1048.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.975.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.975.1.tgz", - "integrity": "sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@aws-sdk/xml-builder": "^3.972.34", - "@aws/lambda-invoke-store": "^0.3.0", - "@smithy/core": "^3.29.2", - "@smithy/signature-v4": "^5.6.3", - "@smithy/types": "^4.16.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.57", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.57.tgz", - "integrity": "sha512-1RfJaF7SW1TOnvNGU7kaYjwUf5H3sfm+synGH1bHhRlqcnxCt3szebH3dmKEyY4tuGcbQ6ffzUT89cRitBV8OQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.59", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.59.tgz", - "integrity": "sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.4.tgz", - "integrity": "sha512-BNTop/fSOptmoVk8g+efwHCofFh37g70OWGAFES1TeAAJja1K5aAI8rTE26ETSc5k8IQuWY2kAIoPla01NgYrA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.1.tgz", - "integrity": "sha512-6d8H6ZAh3ZPKZ6fe1nG2OWeZEZPtt9ravoD1dezPdPtsSkJRoxGAnFSHwKT3E/Te6fHE30zRzjV6TD12rvF6yQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-env": "^3.972.57", - "@aws-sdk/credential-provider-http": "^3.972.59", - "@aws-sdk/credential-provider-login": "^3.972.63", - "@aws-sdk/credential-provider-process": "^3.972.57", - "@aws-sdk/credential-provider-sso": "^3.973.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.63", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/credential-provider-imds": "^4.4.7", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.63", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.63.tgz", - "integrity": "sha512-GREWRrMj0XnNKMaVa/Mauoaui26qBEHu71WWqXbwZOu/jFQOnPZjTf7u0KtGKC8VGa6VUs9kDWGgocrKNLS9vw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.66", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.66.tgz", - "integrity": "sha512-f+qjRXZpz7sgzbc4QB+6nLKfyKFgRRXzWdXbsKPv/VhVRyHsDyq4yBWC/B75BAJpFIcUeI2XR/3gdWJ677zB4A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.57", - "@aws-sdk/credential-provider-http": "^3.972.59", - "@aws-sdk/credential-provider-ini": "^3.973.1", - "@aws-sdk/credential-provider-process": "^3.972.57", - "@aws-sdk/credential-provider-sso": "^3.973.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.63", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/credential-provider-imds": "^4.4.7", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.57", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.57.tgz", - "integrity": "sha512-TiVQhuU0pbhIZAUZacbPHMyzrIdiH+lnx+PMY/Pu/b93dJrq3wdZwzUJ0TPpvNxaqbHsxJvQZW3/h/beLiKq7Q==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.1.tgz", - "integrity": "sha512-3foTZUJ4821Ij60X7K3NJroygiZLnbBmarN+T//O2cjkISan90zElN3NBmgSlDrTQ7Gs6z/yO8V7h60QNcDZHQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/token-providers": "3.1083.0", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1083.0.tgz", - "integrity": "sha512-s0woKnxuHrExLc5L2ArIH5BMkbonHPtt+5hSBM8oknp9M6QTuUmmAmJ2E0EdzCGONrO+8+ADPqvv6UX0nNcc7A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.63", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.63.tgz", - "integrity": "sha512-8qZLFhM69eKcS37m459ctPR05Qimycm/74OPVioe6wNZabMT54GYhwBju0+J656RkMasNSawWQu+c8CmBe3TUQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.26", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.26.tgz", - "integrity": "sha512-RE1fu7Nn05vG0EUJM+8Sde2GFecC658WGaC/asPzLF6K4x3H5ZaDBcQtHRE67Gdgb1VZpyUUliYejHFK1qt0Uw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.22", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.22.tgz", - "integrity": "sha512-jtkgmhevnpzC1WeS+Y/sgymYbaQ6qg7pVOUl5cUT/8MiLptqrtnXQlNV80m+j2WIx5MIL7kVHIZNxxcK2tfUEQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.39.tgz", - "integrity": "sha512-CS1spxRSezmTmI3PD+3Xrnp6KryTSEz0EefA8u6uGd0s2I0uXseWHALDI/03Wi0IUczXNWo2QrZEaHDuJNby/Q==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/signature-v4": "^5.6.3", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.31", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.31.tgz", - "integrity": "sha512-BDHTpwcsZHEBNEJzOg/B1BkFYJxAXY50dau/NyVWs3d51F0WgIUGSWZot/Os+N3KpDhXeaXnz37mWffAvduREw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/signature-v4-multi-region": "^3.996.39", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.4.tgz", - "integrity": "sha512-BNTop/fSOptmoVk8g+efwHCofFh37g70OWGAFES1TeAAJja1K5aAI8rTE26ETSc5k8IQuWY2kAIoPla01NgYrA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.39.tgz", - "integrity": "sha512-8+srXqYIF8KYMLC4FxMLEM5Ek7kUNibJu1R4m8/fUhhNYIZZz26oGtKkCr8I/HiG2fFQxBvaGgQZT4/mqRCSnA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@smithy/signature-v4": "^5.6.3", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.1048.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz", - "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.0.tgz", - "integrity": "sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.8.tgz", - "integrity": "sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.34.tgz", - "integrity": "sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", - "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", - "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@earendil-works/pi-agent-core": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.6.tgz", - "integrity": "sha512-Lvn89ko42h5ETUb6Z0Ku6ldskEqXaTdQBYvSa0+7bdG9V6rUEpXptv5e0OVZ1HDcvi8s6/2lGCQWsxKX+DFHNw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@earendil-works/pi-ai": "^0.80.6", - "ignore": "7.0.5", - "typebox": "1.1.38", - "yaml": "2.9.0" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-agent-core/node_modules/typebox": { - "version": "1.1.38", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", - "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-ai": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.6.tgz", - "integrity": "sha512-7xfLk8sANBp+bpPEbjoOZTbPxsa+++b1JXAoSJsNa3vbs9AHHEclmvg54XLQcxH+fuwaeti/g2jeIfJ+mVYLpA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@anthropic-ai/sdk": "0.91.1", - "@aws-sdk/client-bedrock-runtime": "3.1048.0", - "@google/genai": "1.52.0", - "@mistralai/mistralai": "2.2.6", - "@opentelemetry/api": "1.9.0", - "@smithy/node-http-handler": "4.7.3", - "http-proxy-agent": "7.0.2", - "https-proxy-agent": "7.0.6", - "openai": "6.26.0", - "partial-json": "0.1.7", - "typebox": "1.1.38" - }, - "bin": { - "pi-ai": "dist/cli.js" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-ai/node_modules/typebox": { - "version": "1.1.38", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", - "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.80.6.tgz", - "integrity": "sha512-vcfD6tOk402isLl3Cm/qbn2O10TvgroMp1+/fEGM24ZdvETFCdOYv5VZ7m59EI5fPsjfSJh+CpQ5bhBrhfOg7g==", - "dev": true, - "hasShrinkwrap": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@earendil-works/pi-agent-core": "^0.80.6", - "@earendil-works/pi-ai": "^0.80.6", - "@earendil-works/pi-tui": "^0.80.6", - "@silvia-odwyer/photon-node": "0.3.4", - "chalk": "5.6.2", - "cross-spawn": "7.0.6", - "diff": "8.0.4", - "glob": "13.0.6", - "highlight.js": "10.7.3", - "hosted-git-info": "9.0.3", - "ignore": "7.0.5", - "jiti": "2.7.0", - "minimatch": "10.2.5", - "proper-lockfile": "4.1.2", - "semver": "7.8.0", - "typebox": "1.1.38", - "undici": "8.5.0", - "yaml": "2.9.0" - }, - "bin": { - "pi": "dist/cli.js" - }, - "engines": { - "node": ">=22.19.0" - }, - "optionalDependencies": { - "@mariozechner/clipboard": "0.3.9" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@anthropic-ai/sdk": { - "version": "0.91.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", - "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "json-schema-to-ts": "^3.1.1" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/client-bedrock-runtime": { - "version": "3.1048.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz", - "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/credential-provider-node": "^3.972.42", - "@aws-sdk/eventstream-handler-node": "^3.972.16", - "@aws-sdk/middleware-eventstream": "^3.972.12", - "@aws-sdk/middleware-websocket": "^3.972.19", - "@aws-sdk/token-providers": "3.1048.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/core": { - "version": "3.974.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.11.tgz", - "integrity": "sha512-QpnINq5FZH6EOaDEkmHdT7eUunbvD27pDNQypaWjFyYz7Zl1q3UCMQErBZxpmfGfI7MvI2TlK8KTkgNpv8b1ug==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/xml-builder": "^3.972.24", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/core": "^3.24.2", - "@smithy/signature-v4": "^5.4.2", - "@smithy/types": "^4.14.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.37.tgz", - "integrity": "sha512-/jpPvEh6f7ntmIzf7dNxoNX6Q8vt8UpesCjbW6mFfk4V1NW6bIy9qxcQ6WbA8As5yQhsZOe+xeNd4xHX8kdY2Q==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.39.tgz", - "integrity": "sha512-pIgTpisWyWg7X1bUbzSjuUYosYTD0Ghz2M0hkSTmb3a6i3qV3uU+NYJPI/E2XSC0HcsZh5rsLPzeXrkb2DS0Cg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.41.tgz", - "integrity": "sha512-u2tyjaxJJzW8UtW4SM1ZcPMDwO6y+kV+llvou+Adts0FAKyzes5jG4izQN+KX3yE8ZROpS5y1LJ//xL2iSf76w==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/credential-provider-env": "^3.972.37", - "@aws-sdk/credential-provider-http": "^3.972.39", - "@aws-sdk/credential-provider-login": "^3.972.41", - "@aws-sdk/credential-provider-process": "^3.972.37", - "@aws-sdk/credential-provider-sso": "^3.972.41", - "@aws-sdk/credential-provider-web-identity": "^3.972.41", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/credential-provider-imds": "^4.3.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.41.tgz", - "integrity": "sha512-0LBitxXiAiaE5nlFPfpNIww/8FRY/I7WIndWsc9GmNFOM7cE1wNpVNQEGEk9Outg5l8xl+3vybxFyUy4l9q/LQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.42", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.42.tgz", - "integrity": "sha512-D4oon2zbqqsWOJUM99Gm3/ZyJ0IJvTXVN3PyloGb3kQEyI36fjCZheZj422lAgTWWd6TSHgiImLt3RIaLdv3dQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.37", - "@aws-sdk/credential-provider-http": "^3.972.39", - "@aws-sdk/credential-provider-ini": "^3.972.41", - "@aws-sdk/credential-provider-process": "^3.972.37", - "@aws-sdk/credential-provider-sso": "^3.972.41", - "@aws-sdk/credential-provider-web-identity": "^3.972.41", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/credential-provider-imds": "^4.3.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.37.tgz", - "integrity": "sha512-7nVaHBUaWIddASYfVaA9O4D5ZVjewU3sCol9WqZPGfW0nR+0WqE0xHZnD/U2L33PlOB8KNXGKZ6wOES/QijKzg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.41.tgz", - "integrity": "sha512-IOWAWEHe5LkjSKkkUUX9ciV6Y1scHTsnfEkdt5yyC4Slrc7AGbkLPrpntjqh18ksJAMOaVhoBsO8p2WyTcY2wQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/token-providers": "3.1048.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.41.tgz", - "integrity": "sha512-mbACk9Yypa8nm4iGZLs0PofOXEcTDOUw6wDnsPXNDNSd2WNXs1tSo+6nc/fh0jLYdfVZThhBL98PHW4aXFsG5A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.16", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.16.tgz", - "integrity": "sha512-yedpPgKftqjU5SlPFHfqWpOw6xSCRieWRG1euWOlXn4WJxt2VX92VprCa2PpSOXjVCAeK6dTjW9eJRXVig9yGA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.12", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.12.tgz", - "integrity": "sha512-tHTHHCHNrq6XklQvlzHBDJG4Iuhh7NVPRdtmvP+nHFA+5sxPlIDzlAHHgfoYHGvT3NXP1yVP/L5c3opUn6T3Qg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.19.tgz", - "integrity": "sha512-mkEhOGYozqKQkbFaVrjwr0faiwwZza1v5/jSY6Tucm3bD+uKTazIUH/4Yo6aMnQD2ua2W9cMP6s8mvwTcjtqHw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/signature-v4": "^5.4.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/nested-clients": { - "version": "3.997.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.9.tgz", - "integrity": "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/signature-v4-multi-region": "^3.996.27", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.27", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.27.tgz", - "integrity": "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/signature-v4": "^5.4.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/token-providers": { - "version": "3.1048.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz", - "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/types": { - "version": "3.973.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", - "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", - "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.24", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.24.tgz", - "integrity": "sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@nodable/entities": "2.1.0", - "@smithy/types": "^4.14.1", - "fast-xml-parser": "5.7.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws/lambda-invoke-store": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", - "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.6.tgz", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@earendil-works/pi-ai": "^0.80.6", - "ignore": "7.0.5", - "typebox": "1.1.38", - "yaml": "2.9.0" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.6.tgz", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@anthropic-ai/sdk": "0.91.1", - "@aws-sdk/client-bedrock-runtime": "3.1048.0", - "@google/genai": "1.52.0", - "@mistralai/mistralai": "2.2.6", - "@opentelemetry/api": "1.9.0", - "@smithy/node-http-handler": "4.7.3", - "http-proxy-agent": "7.0.2", - "https-proxy-agent": "7.0.6", - "openai": "6.26.0", - "partial-json": "0.1.7", - "typebox": "1.1.38" - }, - "bin": { - "pi-ai": "./dist/cli.js" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.6.tgz", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "get-east-asian-width": "1.6.0", - "marked": "18.0.5" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@google/genai": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", - "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "google-auth-library": "^10.3.0", - "p-retry": "^4.6.2", - "protobufjs": "^7.5.4", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.25.2" - }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.9.tgz", - "integrity": "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@mariozechner/clipboard-darwin-arm64": "0.3.9", - "@mariozechner/clipboard-darwin-universal": "0.3.9", - "@mariozechner/clipboard-darwin-x64": "0.3.9", - "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", - "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", - "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", - "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", - "@mariozechner/clipboard-linux-x64-musl": "0.3.9", - "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", - "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-arm64": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.9.tgz", - "integrity": "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-universal": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.9.tgz", - "integrity": "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-x64": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.9.tgz", - "integrity": "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-gnu": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.9.tgz", - "integrity": "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-musl": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.9.tgz", - "integrity": "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.9.tgz", - "integrity": "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-gnu": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.9.tgz", - "integrity": "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-musl": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.9.tgz", - "integrity": "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-arm64-msvc": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.9.tgz", - "integrity": "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-x64-msvc": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.9.tgz", - "integrity": "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mistralai/mistralai": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz", - "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.40.0", - "ws": "^8.18.0", - "zod": "^3.25.0 || ^4.0.0", - "zod-to-json-schema": "^3.25.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@nodable/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/nodable" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.41.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", - "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/eventemitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", - "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/fetch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", - "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@silvia-odwyer/photon-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", - "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", - "dev": true, - "license": "Apache-2.0", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/core": { - "version": "3.24.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz", - "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/credential-provider-imds": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.3.tgz", - "integrity": "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/fetch-http-handler": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.3.tgz", - "integrity": "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/node-http-handler": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", - "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/signature-v4": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.3.tgz", - "integrity": "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/types": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", - "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@types/node": { - "version": "22.19.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", - "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-builder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", - "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-parser": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", - "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.1.7", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.2.3" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/google-auth-library": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", - "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.1.4", - "gcp-metadata": "8.1.2", - "google-logging-utils": "1.1.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/hosted-git-info": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", - "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "dev": true, - "license": "Apache-2.0", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/lru-cache": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz", - "integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/marked": { - "version": "18.0.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", - "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/openai": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", - "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry/node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/partial-json": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", - "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/protobufjs": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", - "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", - "dev": true, - "hasInstallScript": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/strnum": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", - "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ts-algebra": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", - "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/typebox": { - "version": "1.1.38", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", - "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz", - "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "dev": true, - "license": "ISC", - "peer": true, - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - }, - "node_modules/@earendil-works/pi-tui": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.6.tgz", - "integrity": "sha512-bSuzS4EVSqEPj/Qr/p9eqCESfKsGuDNbl77EGci8Iaqqt/C/XCBZL1MjXaxSWW1NsT5afjp/Cb0NTPzOLv/aPA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "get-east-asian-width": "1.6.0", - "marked": "18.0.5" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@google/genai": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", - "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "google-auth-library": "^10.3.0", - "p-retry": "^4.6.2", - "protobufjs": "^7.5.4", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.25.2" - }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - } - } - }, - "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@iarna/toml": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", - "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", - "dev": true, - "license": "ISC" - }, - "node_modules/@introspection-ai/pi-recipes": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@introspection-ai/pi-recipes/-/pi-recipes-0.6.0.tgz", - "integrity": "sha512-mENvp9znUUwtwbE5JnVhv5igSfwtBvU6wDPB/s1U9iwXSLuyBGK9n2AA6/TK1lgeRIL+7fFOpN7ZD5NgfG4l3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "mcporter": "0.12.3", - "yaml": "^2.9.0" - }, - "bin": { - "recipes": "dist/cli.js" - }, - "peerDependencies": { - "@earendil-works/pi-agent-core": "*", - "@earendil-works/pi-ai": "*", - "@earendil-works/pi-coding-agent": "*", - "@earendil-works/pi-tui": "*", - "typebox": "*" - } - }, - "node_modules/@mistralai/mistralai": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz", - "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.40.0", - "ws": "^8.18.0", - "zod": "^3.25.0 || ^4.0.0", - "zod-to-json-schema": "^3.25.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - } - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", - "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", - "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", - "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", - "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.1" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", - "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", - "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", - "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", - "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", - "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", - "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", - "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", - "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", - "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", - "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", - "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", - "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", - "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", - "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", - "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", - "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@smithy/core": { - "version": "3.29.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.2.tgz", - "integrity": "sha512-DXUk6yU0C1Q1tYvJh1VCtl8QOBcSoZpKwjTPkxT6A4MUQYHvgeKGByL8mrEdxnvhdf9nq5GyzmRb5n/vPgu3Lw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.7.tgz", - "integrity": "sha512-UEMLOoA0Fl4uYBxh6l0uN0H6EJe/A89OGeDNTteQeXpJ20BcpfIr4wlCY9pel1jEAUHAxaYwuqrYlrKdXE1GKQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.6.4", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.4.tgz", - "integrity": "sha512-psnst7NZWdAEvJvyW8YZEE7xNVMyLrQFfHtyrVFrxNyy+dKWkQ+rqC6oI5ZhxThpUy9RSfEshgm34zqbOxzsRw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", - "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.3.tgz", - "integrity": "sha512-8qVKKzqh7naF27ePmx0SkUfnGP/wBI9dyaeAmhHvopnbIlItUAmB/e6PkPCU3rRb2v9BY8D4EZXSoydSibatvw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.0.tgz", - "integrity": "sha512-aVUabzlBBmY0PfvVgLKQSOGFIL5/7R54JE3uD9a5Ay/jSED61SkuAcCYENNXJzYUvJ1NPrWO0P+rAXHCkbBUKw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/node": { - "version": "26.1.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", - "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", - "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/commander": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", - "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=22.12.0" - } - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-toolkit": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.49.0.tgz", - "integrity": "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==", - "dev": true, - "license": "MIT", - "workspaces": [ - "docs", - "benchmarks" - ] - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", - "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", - "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gaxios": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.2.0.tgz", - "integrity": "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/google-auth-library": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz", - "integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.1.4", - "gcp-metadata": "8.1.2", - "google-logging-utils": "1.1.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hono": { - "version": "4.12.29", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.29.tgz", - "integrity": "sha512-1hNiRjawYrLq/4m3DQQjPGFg0VZkk4RjQJDff/excI6Dm9BiL75qxGrd7/c6YOxPdq6AscP3LiXhQ6fKFC1Waw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/log-symbols": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", - "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "dev": true, - "license": "Apache-2.0", - "peer": true - }, - "node_modules/marked": { - "version": "18.0.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", - "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mcporter": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/mcporter/-/mcporter-0.12.3.tgz", - "integrity": "sha512-FD6nV4AzrsJSYtIqkLE0emNNiVl0p9W2bJosORAhmI5HCfcz2fc0WjmaY26bfFRW+2aCNL3aCssoFRjcYcQjgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@iarna/toml": "^2.2.5", - "@modelcontextprotocol/sdk": "^1.29.0", - "acorn": "^8.17.0", - "commander": "^15.0.0", - "es-toolkit": "^1.49.0", - "jsonc-parser": "^3.3.1", - "ora": "^9.4.1", - "rolldown": "1.1.3", - "zod": "^4.4.3" - }, - "bin": { - "mcporter": "dist/cli.js" - }, - "engines": { - "node": ">=24" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/openai": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", - "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/ora": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-9.4.1.tgz", - "integrity": "sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.6.2", - "cli-cursor": "^5.0.0", - "cli-spinners": "^3.2.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.1.0", - "log-symbols": "^7.0.1", - "stdin-discarder": "^0.3.2", - "string-width": "^8.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/partial-json": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", - "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/protobufjs": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", - "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", - "dev": true, - "hasInstallScript": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", - "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/rolldown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", - "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.137.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.3", - "@rolldown/binding-darwin-arm64": "1.1.3", - "@rolldown/binding-darwin-x64": "1.1.3", - "@rolldown/binding-freebsd-x64": "1.1.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", - "@rolldown/binding-linux-arm64-gnu": "1.1.3", - "@rolldown/binding-linux-arm64-musl": "1.1.3", - "@rolldown/binding-linux-ppc64-gnu": "1.1.3", - "@rolldown/binding-linux-s390x-gnu": "1.1.3", - "@rolldown/binding-linux-x64-gnu": "1.1.3", - "@rolldown/binding-linux-x64-musl": "1.1.3", - "@rolldown/binding-openharmony-arm64": "1.1.3", - "@rolldown/binding-wasm32-wasi": "1.1.3", - "@rolldown/binding-win32-arm64-msvc": "1.1.3", - "@rolldown/binding-win32-x64-msvc": "1.1.3" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stdin-discarder": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.2.tgz", - "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", - "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/ts-algebra": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", - "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "dev": true, - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/typebox": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.6.tgz", - "integrity": "sha512-Sc8RA0NCMEFmApHNU9ZMzqcpQj46She44J8ffpLM/bdhLNUZKq7DJumcLcsFx1gRmDfQPgCgOmFFJ7rcnfWNyA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - } - } -} diff --git a/introspection/package.json b/introspection/package.json deleted file mode 100644 index 044f5f4..0000000 --- a/introspection/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@introspection-recipes/customer-support", - "version": "0.1.1", - "description": "Customer support recipe that triages Slack threads, searches Notion, drafts replies, and escalates sensitive requests.", - "type": "module", - "license": "Apache-2.0", - "scripts": { - "check": "recipes check . --profile ci", - "check:json": "recipes check . --profile ci --json", - "prepare": "git config core.hooksPath .githooks || true" - }, - "devDependencies": { - "@introspection-ai/pi-recipes": "^0.6.0" - }, - "pi": { - "agents": [ - "agents/*.yaml" - ], - "skills": [ - "skills/**/SKILL.md" - ], - "mcp": { - "servers": [ - { - "id": "slack", - "required": false, - "tools": { - "include": [ - "slack_read_channel", - "slack_read_thread", - "slack_send_message_draft" - ] - } - }, - { - "id": "notion", - "required": false, - "tools": { - "include": [ - "API-post-search", - "API-retrieve-page-markdown" - ] - } - } - ] - } - } -} diff --git a/introspection/skills/ticket-triage/SKILL.md b/introspection/skills/ticket-triage/SKILL.md deleted file mode 100644 index f461c64..0000000 --- a/introspection/skills/ticket-triage/SKILL.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -description: Use for support threads, ticket triage, customer reply drafting, and escalation notes. ---- - -# Ticket Triage - -Use this skill for support threads, tickets, and customer escalation drafts. - -## Workflow - -1. Identify the customer ask and urgency. -2. Check for escalation triggers: billing, security, legal, privacy, account access, abuse, enterprise commitments, or angry high-impact customer sentiment. -3. Search approved KB or policy sources. -4. Draft a reply or escalation note. -5. Include evidence and missing context for human review. - -## Response Style - -Be direct, warm, and specific. Do not promise timelines, discounts, security claims, or unsupported product behavior. diff --git a/package.json b/package.json index fcf0f56..d37f55e 100644 --- a/package.json +++ b/package.json @@ -1,41 +1,22 @@ { - "name": "my-app", - "version": "0.5.1", - "description": "A monorepo template built with Bun, Hono, Vite, and React", - "author": "Steve Simkins", - "license": "MIT", - "homepage": "https://github.com/stevedylandev/bhvr", - "packageManager": "bun@1.2.4", - "workspaces": [ - "./server", - "./client", - "./shared" - ], + "name": "autodesign", + "version": "0.1.0", + "private": true, + "type": "module", "scripts": { - "dev": "turbo dev", - "dev:client": "turbo dev --filter=client", - "dev:server": "turbo dev --filter=server", - "build": "turbo build", - "build:client": "turbo build --filter=client", - "build:server": "turbo build --filter=server", - "lint": "turbo lint", - "type-check": "turbo type-check", - "test": "turbo test", - "postinstall": "turbo build --filter=shared --filter=server" + "reference": "bun src/cli.ts reference", + "loop": "bun src/cli.ts loop", + "holdout": "bun src/cli.ts holdout", + "resume": "bun src/cli.ts resume", + "test": "bun test" }, - "keywords": [ - "bun", - "hono", - "react", - "vite", - "monorepo", - "turbo" - ], - "devDependencies": { - "bun-types": "latest", - "turbo": "^2.6.3" + "dependencies": { + "@anthropic-ai/sdk": "^0.60.0", + "playwright": "^1.54.0", + "zod": "^4.0.0" }, - "peerDependencies": { - "typescript": "^5.8.3" + "devDependencies": { + "@types/bun": "latest", + "typescript": "^5.8.0" } } diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index 506e4c3..0000000 --- a/server/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# deps -node_modules/ diff --git a/server/README.md b/server/README.md deleted file mode 100644 index 6dd13e7..0000000 --- a/server/README.md +++ /dev/null @@ -1,11 +0,0 @@ -To install dependencies: -```sh -bun install -``` - -To run: -```sh -bun run dev -``` - -open http://localhost:3000 diff --git a/server/package.json b/server/package.json deleted file mode 100644 index 875bcfd..0000000 --- a/server/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "server", - "version": "0.0.1", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "scripts": { - "build": "tsc", - "dev": "bun --watch run src/index.ts" - }, - "dependencies": { - "hono": "^4.10.8", - "shared": "workspace:*" - }, - "devDependencies": { - "@types/bun": "latest" - } -} diff --git a/server/src/index.ts b/server/src/index.ts deleted file mode 100644 index 603c2ba..0000000 --- a/server/src/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Hono } from 'hono' -import { cors } from 'hono/cors' -import type { ApiResponse } from 'shared' - -const app = new Hono() - -app.use(cors()) - -app.get('/', (c) => { - return c.text('Hello Hono!') -}) - -app.get('/hello', async (c) => { - - const data: ApiResponse = { - message: "Hello BHVR!", - success: true - } - - return c.json(data, { status: 200 }) -}) - -export default app diff --git a/server/tsconfig.json b/server/tsconfig.json deleted file mode 100644 index 5f51d10..0000000 --- a/server/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - // Environment settings - "lib": ["ESNext"], - "target": "ESNext", - "module": "ESNext", - "jsx": "react-jsx", - "jsxImportSource": "hono/jsx", - - // Types - "types": ["bun-types"], - - // Output settings - "declaration": true, - "outDir": "dist", - "noEmit": false, - "emitDecoratorMetadata": true, - - // Module resolution - "moduleResolution": "bundler", - "allowImportingTsExtensions": false - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/shared/package.json b/shared/package.json deleted file mode 100644 index d6e0c6b..0000000 --- a/shared/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "shared", - "version": "0.0.1", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "scripts": { - "build": "tsc", - "dev": "tsc --watch" - }, - "devDependencies": { - "typescript": "^5.9.3" - } -} diff --git a/shared/src/index.ts b/shared/src/index.ts deleted file mode 100644 index 51f739d..0000000 --- a/shared/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./types" diff --git a/shared/src/types/index.ts b/shared/src/types/index.ts deleted file mode 100644 index 898a45b..0000000 --- a/shared/src/types/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type ApiResponse = { - message: string; - success: true; -} diff --git a/shared/tsconfig.json b/shared/tsconfig.json deleted file mode 100644 index 2a9e8ce..0000000 --- a/shared/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - // Environment setup - "lib": ["ESNext"], - "target": "ESNext", - "module": "ESNext", - - // Output configuration - "declaration": true, - "outDir": "./dist", - "noEmit": false, - - // Type checking - "strict": true, - "skipLibCheck": true, - - // Additional checks - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "**/*.test.ts", "dist"] -} diff --git a/tsconfig.json b/tsconfig.json index f38bcbf..00e946d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,32 +1,12 @@ { "compilerOptions": { - // Environment setup & latest features - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "target": "ESNext", + "target": "ES2022", "module": "ESNext", - "moduleDetection": "force", - "jsx": "react-jsx", - "allowJs": true, - - // Module resolution "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "verbatimModuleSyntax": true, - - // Strictness and best practices "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true, - "experimentalDecorators": true, - - // Output control "skipLibCheck": true, - - // Optional strict flags (disabled by default) - "noUnusedLocals": false, - "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false - } + "types": ["bun"], + "noEmit": true + }, + "include": ["src", "tests"] } diff --git a/turbo.json b/turbo.json deleted file mode 100644 index 532e097..0000000 --- a/turbo.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "ui": "tui", - "tasks": { - "build": { - "dependsOn": [ - "^build" - ], - "inputs": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.js", - "src/**/*.jsx", - "src/**/*.json", - "tsconfig.json", - "tsconfig.*.json", - "vite.config.ts", - "package.json", - "bun.lock", - "eslint.config.js", - "index.html" - ], - "outputs": [ - "dist/**", - "build/**", - ".turbo/**" - ], - "env": [ - "NODE_ENV", - "VITE_*" - ] - }, - "dev": { - "cache": false, - "persistent": true, - "inputs": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.js", - "src/**/*.jsx", - "tsconfig.json", - "tsconfig.*.json", - "vite.config.ts", - "package.json" - ], - "env": [ - "NODE_ENV", - "PORT", - "VITE_*" - ] - }, - "lint": { - "dependsOn": [ - "^build" - ], - "inputs": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.js", - "src/**/*.jsx", - "eslint.config.js", - ".eslintrc*", - "package.json", - "tsconfig.json", - "tsconfig.*.json" - ], - "outputs": [ - ".eslintcache" - ], - "env": [ - "NODE_ENV" - ] - }, - "type-check": { - "dependsOn": [ - "^build" - ], - "inputs": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.d.ts", - "tsconfig.json", - "tsconfig.*.json", - "package.json" - ], - "outputs": [ - "dist/**/*.d.ts", - ".tsbuildinfo" - ], - "env": [ - "NODE_ENV" - ] - }, - "test": { - "dependsOn": [ - "^build" - ], - "inputs": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.js", - "src/**/*.jsx", - "test/**/*.ts", - "test/**/*.tsx", - "test/**/*.js", - "test/**/*.jsx", - "__tests__/**/*.ts", - "__tests__/**/*.tsx", - "**/*.test.ts", - "**/*.test.tsx", - "**/*.spec.ts", - "**/*.spec.tsx", - "jest.config.*", - "vitest.config.*", - "package.json", - "tsconfig.json", - "tsconfig.*.json" - ], - "outputs": [ - "coverage/**", - ".nyc_output/**", - "test-results/**" - ], - "env": [ - "NODE_ENV", - "CI" - ] - } - }, - "globalDependencies": [ - "**/.env", - "**/.env.*", - "**/.env.local", - "**/.env.*.local", - ".gitignore", - "turbo.json", - "package.json", - "bun.lock", - "tsconfig.json" - ], - "globalEnv": [ - "NODE_ENV", - "CI", - "TURBO_TOKEN", - "TURBO_TEAM", - "TURBO_REMOTE_ONLY" - ] -} \ No newline at end of file From c9c1af27d06df65f32a6a9f78a22073ca247ec99 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:22:07 -0700 Subject: [PATCH 05/28] feat: harness config schema and baseline genome Co-Authored-By: Claude Fable 5 --- src/config/schema.ts | 49 +++++++++++++++++++++++++++++++++++++ tests/config-schema.test.ts | 20 +++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/config/schema.ts create mode 100644 tests/config-schema.test.ts diff --git a/src/config/schema.ts b/src/config/schema.ts new file mode 100644 index 0000000..ddb2c6d --- /dev/null +++ b/src/config/schema.ts @@ -0,0 +1,49 @@ +import { z } from "zod"; + +export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const; +export const ALLOWED_TOOLS = ["read", "write", "edit", "bash"] as const; + +export const SkillSchema = z.object({ + id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/), + description: z.string().min(1), + content: z.string().min(1), +}); + +export const SubagentSchema = z.object({ + name: z.string().regex(/^[a-z0-9][a-z0-9-]*$/), + description: z.string().min(1), + system_instructions: z.string().min(1), + tools: z.array(z.enum(ALLOWED_TOOLS)), +}); + +export const HarnessConfigSchema = z.object({ + version: z.number().int().nonnegative(), + parent_version: z.number().int().nonnegative().nullable(), + rationale: z.string(), + model: z.object({ + name: z.string().min(1), + thinking_level: z.enum(THINKING_LEVELS), + }), + tools: z.array(z.enum(ALLOWED_TOOLS)).min(1), + system_instructions: z.string().min(1), + skills: z.array(SkillSchema).max(8), + subagents: z.array(SubagentSchema).max(4), +}); + +export type HarnessConfig = z.infer; + +export const BASELINE_CONFIG: HarnessConfig = { + version: 0, + parent_version: null, + rationale: "Hand-written baseline.", + model: { name: "anthropic/claude-sonnet-4-6", thinking_level: "medium" }, + tools: ["read", "write", "bash"], + system_instructions: [ + "You are building a single marketing landing page as one self-contained HTML file.", + "Write exactly one file named output.html in the current directory.", + "All CSS and JS must be inline; no external network requests. Use system font stacks or embedded styles.", + "Cover every requirement in the brief. Aim for a clean, modern, visually polished design.", + ].join("\n"), + skills: [], + subagents: [], +}; diff --git a/tests/config-schema.test.ts b/tests/config-schema.test.ts new file mode 100644 index 0000000..b7f04a4 --- /dev/null +++ b/tests/config-schema.test.ts @@ -0,0 +1,20 @@ +import { expect, test } from "bun:test"; +import { HarnessConfigSchema, BASELINE_CONFIG } from "../src/config/schema"; + +test("baseline config validates", () => { + expect(() => HarnessConfigSchema.parse(BASELINE_CONFIG)).not.toThrow(); + expect(BASELINE_CONFIG.version).toBe(0); + expect(BASELINE_CONFIG.parent_version).toBeNull(); +}); + +test("rejects unknown tools and bad skill ids", () => { + const bad = { ...BASELINE_CONFIG, tools: ["read", "browser"] }; + expect(() => HarnessConfigSchema.parse(bad)).toThrow(); + const badSkill = { ...BASELINE_CONFIG, skills: [{ id: "Bad Id!", description: "x", content: "x" }] }; + expect(() => HarnessConfigSchema.parse(badSkill)).toThrow(); +}); + +test("round-trips through JSON", () => { + const parsed = HarnessConfigSchema.parse(JSON.parse(JSON.stringify(BASELINE_CONFIG))); + expect(parsed).toEqual(BASELINE_CONFIG); +}); From c60140a23950ec04c860a8e8aed1b03a3318319b Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:23:54 -0700 Subject: [PATCH 06/28] feat: prompts loader with train/holdout split Co-Authored-By: Claude Fable 5 --- src/prompts.ts | 29 +++++++++++++++++++++++++++++ tests/prompts.test.ts | 12 ++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/prompts.ts create mode 100644 tests/prompts.test.ts diff --git a/src/prompts.ts b/src/prompts.ts new file mode 100644 index 0000000..7317e7a --- /dev/null +++ b/src/prompts.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import { readFileSync } from "node:fs"; + +const PromptSpecSchema = z.object({ + id: z.string().min(1), + category: z.string().min(1), + split: z.enum(["train", "holdout"]), + prompt: z.string().min(1), +}); +const PromptFileSchema = z.object({ + version: z.number(), + description: z.string(), + prompts: z.array(PromptSpecSchema).min(1), +}); + +export type PromptSpec = z.infer; + +export function loadPrompts(path = "prompts.json"): PromptSpec[] { + const file = PromptFileSchema.parse(JSON.parse(readFileSync(path, "utf8"))); + const ids = new Set(); + for (const p of file.prompts) { + if (ids.has(p.id)) throw new Error(`duplicate prompt id: ${p.id}`); + ids.add(p.id); + } + return file.prompts; +} + +export const trainPrompts = (all: PromptSpec[]) => all.filter((p) => p.split === "train"); +export const holdoutPrompts = (all: PromptSpec[]) => all.filter((p) => p.split === "holdout"); diff --git a/tests/prompts.test.ts b/tests/prompts.test.ts new file mode 100644 index 0000000..60c1036 --- /dev/null +++ b/tests/prompts.test.ts @@ -0,0 +1,12 @@ +import { expect, test } from "bun:test"; +import { loadPrompts, trainPrompts, holdoutPrompts } from "../src/prompts"; + +test("loads real prompts.json with valid splits", () => { + const all = loadPrompts(); + expect(all.length).toBeGreaterThan(10); + const train = trainPrompts(all); + const holdout = holdoutPrompts(all); + expect(train.length + holdout.length).toBe(all.length); + expect(holdout.every((p) => p.split === "holdout")).toBe(true); + expect(new Set(all.map((p) => p.id)).size).toBe(all.length); +}); From 68ee53391a60976ae531f0bb5b01349fc02d29d5 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:25:54 -0700 Subject: [PATCH 07/28] feat: deterministic resolver from config genome to pi harness Co-Authored-By: Claude Fable 5 --- src/config/resolver.ts | 52 ++++++++++++++++++++++++++++++++++++++++++ tests/resolver.test.ts | 38 ++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/config/resolver.ts create mode 100644 tests/resolver.test.ts diff --git a/src/config/resolver.ts b/src/config/resolver.ts new file mode 100644 index 0000000..e8591a9 --- /dev/null +++ b/src/config/resolver.ts @@ -0,0 +1,52 @@ +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import type { HarnessConfig } from "./schema"; + +export type ResolvedHarness = { + dir: string; + systemPromptPath: string; + skillDirs: string[]; + piArgs: string[]; +}; + +export function resolveHarness(config: HarnessConfig, outDir: string): ResolvedHarness { + mkdirSync(outDir, { recursive: true }); + + const parts: string[] = [config.system_instructions]; + for (const sub of config.subagents) { + parts.push( + [ + `## Internal pass: ${sub.name}`, + `Before finishing, perform this pass as "${sub.name}" (${sub.description}):`, + sub.system_instructions, + ].join("\n"), + ); + } + const systemPromptPath = join(outDir, "system-prompt.md"); + writeFileSync(systemPromptPath, parts.join("\n\n") + "\n"); + + const skillDirs: string[] = []; + const sortedSkills = [...config.skills].sort((a, b) => a.id.localeCompare(b.id)); + for (const skill of sortedSkills) { + const dir = join(outDir, "skills", skill.id); + mkdirSync(dir, { recursive: true }); + const frontmatter = `---\nname: ${skill.id}\ndescription: ${skill.description.replaceAll("\n", " ")}\n---\n\n`; + writeFileSync(join(dir, "SKILL.md"), frontmatter + skill.content + "\n"); + skillDirs.push(dir); + } + + const piArgs = [ + "--print", + "--no-session", + "--no-extensions", + "--no-context-files", + "--no-prompt-templates", + "--model", config.model.name, + "--thinking", config.model.thinking_level, + "--tools", config.tools.join(","), + "--append-system-prompt", systemPromptPath, + ...skillDirs.flatMap((d) => ["--skill", d]), + ]; + + return { dir: outDir, systemPromptPath, skillDirs, piArgs }; +} diff --git a/tests/resolver.test.ts b/tests/resolver.test.ts new file mode 100644 index 0000000..e69c271 --- /dev/null +++ b/tests/resolver.test.ts @@ -0,0 +1,38 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, readFileSync, readdirSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { resolveHarness } from "../src/config/resolver"; +import { BASELINE_CONFIG, type HarnessConfig } from "../src/config/schema"; + +const cfg: HarnessConfig = { + ...BASELINE_CONFIG, + skills: [{ id: "visual-hierarchy", description: "Layout guidance", content: "Use a clear grid." }], + subagents: [ + { name: "critic", description: "Design critic", system_instructions: "List 3 flaws, then fix them.", tools: ["read"] }, + ], +}; + +test("materializes system prompt, skills, and pi args", () => { + const dir = mkdtempSync(join(tmpdir(), "resolve-")); + const r = resolveHarness(cfg, dir); + const sys = readFileSync(r.systemPromptPath, "utf8"); + expect(sys).toContain("self-contained HTML"); + expect(sys).toContain("## Internal pass: critic"); + expect(sys).toContain("List 3 flaws"); + const skill = readFileSync(join(dir, "skills", "visual-hierarchy", "SKILL.md"), "utf8"); + expect(skill).toContain("name: visual-hierarchy"); + expect(skill).toContain("Use a clear grid."); + expect(r.piArgs).toContain("--print"); + expect(r.piArgs).toContain("anthropic/claude-sonnet-4-6"); + expect(r.piArgs.join(" ")).toContain("--skill"); +}); + +test("deterministic: same config twice → identical bytes", () => { + const a = mkdtempSync(join(tmpdir(), "ra-")); + const b = mkdtempSync(join(tmpdir(), "rb-")); + resolveHarness(cfg, a); + resolveHarness(cfg, b); + expect(readFileSync(join(a, "system-prompt.md"), "utf8")).toBe(readFileSync(join(b, "system-prompt.md"), "utf8")); + expect(readdirSync(join(a, "skills")).sort()).toEqual(readdirSync(join(b, "skills")).sort()); +}); From a43f193410d52739652af6641793217147962f3d Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:28:32 -0700 Subject: [PATCH 08/28] feat: pi subprocess builder with hermetic flags and failure capture Co-Authored-By: Claude Fable 5 --- src/inner/build.ts | 47 +++++++++++++++++++++++++++++++++++++++++++++ tests/build.test.ts | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/inner/build.ts create mode 100644 tests/build.test.ts diff --git a/src/inner/build.ts b/src/inner/build.ts new file mode 100644 index 0000000..cddd5ab --- /dev/null +++ b/src/inner/build.ts @@ -0,0 +1,47 @@ +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import type { ResolvedHarness } from "../config/resolver"; +import type { PromptSpec } from "../prompts"; + +export type BuildResult = + | { ok: true; htmlPath: string; logPath: string } + | { ok: false; error: string; logPath: string }; + +const BUILD_INSTRUCTION = + "Build the landing page described below. Write exactly one self-contained file named output.html " + + "in the current directory (inline CSS/JS, no external requests). Brief:\n\n"; + +export async function buildPage(opts: { + resolved: ResolvedHarness; + prompt: PromptSpec; + workspaceDir: string; + timeoutMs?: number; +}): Promise { + const { resolved, prompt, workspaceDir, timeoutMs = 10 * 60 * 1000 } = opts; + const logPath = join(dirname(workspaceDir), "build.log"); + const bin = process.env.PI_BIN ?? "pi"; + + const proc = Bun.spawn([bin, ...resolved.piArgs, BUILD_INSTRUCTION + prompt.prompt], { + cwd: workspaceDir, + stdout: "pipe", + stderr: "pipe", + env: { ...process.env }, + }); + const timer = setTimeout(() => proc.kill(), timeoutMs); + const [stdout, stderr, exitCode] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + proc.exited, + ]); + clearTimeout(timer); + writeFileSync(logPath, `# exit ${exitCode}\n## stdout\n${stdout}\n## stderr\n${stderr}\n`); + + const htmlPath = join(workspaceDir, "output.html"); + if (exitCode !== 0) return { ok: false, error: `pi exited ${exitCode}`, logPath }; + if (!existsSync(htmlPath)) return { ok: false, error: "no output.html produced", logPath }; + const html = readFileSync(htmlPath, "utf8"); + if (html.length < 100 || !/ { + const base = mkdtempSync(join(tmpdir(), "build-")); + const ws = join(base, "workspace"); + mkdirSync(ws, { recursive: true }); + const filler = "x".repeat(200); + process.env.PI_BIN = stubPi(base, `echo 'hi ${filler}' > output.html`); + const resolved = resolveHarness(BASELINE_CONFIG, join(base, "resolved")); + const r = await buildPage({ resolved, prompt, workspaceDir: ws }); + expect(r.ok).toBe(true); +}); + +test("failure when no output.html", async () => { + const base = mkdtempSync(join(tmpdir(), "build2-")); + const ws = join(base, "workspace"); + mkdirSync(ws, { recursive: true }); + process.env.PI_BIN = stubPi(base, `echo did nothing`); + const resolved = resolveHarness(BASELINE_CONFIG, join(base, "resolved")); + const r = await buildPage({ resolved, prompt, workspaceDir: ws }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.error).toContain("output.html"); +}); From 46f626fedbf7246255fe01ba34625293faa250c3 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:31:11 -0700 Subject: [PATCH 09/28] feat: playwright screenshotter with per-viewport scroll segments Co-Authored-By: Claude Fable 5 --- src/inner/screenshot.ts | 38 ++++++++++++++++++++++++++++++++++++++ tests/screenshot.test.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/inner/screenshot.ts create mode 100644 tests/screenshot.test.ts diff --git a/src/inner/screenshot.ts b/src/inner/screenshot.ts new file mode 100644 index 0000000..0f0f7a2 --- /dev/null +++ b/src/inner/screenshot.ts @@ -0,0 +1,38 @@ +import { chromium } from "playwright"; +import { pathToFileURL } from "node:url"; +import { join } from "node:path"; + +export type Screenshots = { desktop: string[]; mobile: string[] }; + +export const MAX_SEGMENTS = 8; + +const VIEWPORTS = [ + { key: "desktop", width: 1440, height: 900 }, + { key: "mobile", width: 390, height: 844 }, +] as const; + +export async function screenshotPage(htmlPath: string, outDir: string, baseName = "candidate"): Promise { + const browser = await chromium.launch(); + try { + const out: Record = { desktop: [], mobile: [] }; + for (const vp of VIEWPORTS) { + const page = await browser.newPage({ viewport: { width: vp.width, height: vp.height } }); + await page.goto(pathToFileURL(htmlPath).href, { waitUntil: "load", timeout: 30000 }); + await page.waitForTimeout(500); // settle fonts/animations + const scrollHeight: number = await page.evaluate(() => document.documentElement.scrollHeight); + const segments = Math.min(MAX_SEGMENTS, Math.max(1, Math.ceil(scrollHeight / vp.height))); + for (let i = 0; i < segments; i++) { + const y = Math.min(i * vp.height, Math.max(0, scrollHeight - vp.height)); // bottom-align last segment + await page.evaluate((top) => window.scrollTo(0, top), y); + await page.waitForTimeout(150); // let scroll-triggered rendering settle + const path = join(outDir, `${baseName}.${vp.key}.${i}.png`); + await page.screenshot({ path }); // viewport-sized, NOT fullPage + out[vp.key].push(path); + } + await page.close(); + } + return { desktop: out.desktop, mobile: out.mobile }; + } finally { + await browser.close(); + } +} diff --git a/tests/screenshot.test.ts b/tests/screenshot.test.ts new file mode 100644 index 0000000..c995212 --- /dev/null +++ b/tests/screenshot.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, statSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { screenshotPage, MAX_SEGMENTS } from "../src/inner/screenshot"; + +test("long page yields multiple scroll segments per viewport", async () => { + const dir = mkdtempSync(join(tmpdir(), "shot-")); + const html = join(dir, "output.html"); + // ~4 desktop screens tall + writeFileSync(html, `

Hello

`); + const shots = await screenshotPage(html, dir); + expect(shots.desktop.length).toBeGreaterThanOrEqual(3); + expect(shots.desktop.length).toBeLessThanOrEqual(MAX_SEGMENTS); + expect(shots.mobile.length).toBeGreaterThanOrEqual(4); + expect(shots.desktop[0]).toContain("candidate.desktop.0.png"); + for (const p of [...shots.desktop, ...shots.mobile]) expect(statSync(p).size).toBeGreaterThan(1000); +}, 60000); + +test("short page yields exactly one segment per viewport", async () => { + const dir = mkdtempSync(join(tmpdir(), "shot2-")); + const html = join(dir, "output.html"); + writeFileSync(html, `

Tiny

`); + const shots = await screenshotPage(html, dir); + expect(shots.desktop.length).toBe(1); + expect(shots.mobile.length).toBe(1); +}, 60000); From ca64ec509ca892311bc55f1f6b25e1a94ff0ac3b Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:37:16 -0700 Subject: [PATCH 10/28] feat: schema-forced tool call helper with retry Co-Authored-By: Claude Fable 5 --- src/llm.ts | 62 +++++++++++++++++++++++++++++++++++++++++++++++ tests/llm.test.ts | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 src/llm.ts create mode 100644 tests/llm.test.ts diff --git a/src/llm.ts b/src/llm.ts new file mode 100644 index 0000000..3f78d77 --- /dev/null +++ b/src/llm.ts @@ -0,0 +1,62 @@ +import Anthropic from "@anthropic-ai/sdk"; +import { readFileSync } from "node:fs"; +import { z } from "zod"; + +export interface LlmClient { + messages: { + create(params: Record): Promise<{ content: Array<{ type: string; name?: string; input?: unknown }> }>; + }; +} + +export function realClient(): LlmClient { + return new Anthropic() as unknown as LlmClient; +} + +export function imageBlock(pngPath: string): unknown { + return { + type: "image", + source: { type: "base64", media_type: "image/png", data: readFileSync(pngPath).toString("base64") }, + }; +} + +export async function forcedToolCall( + client: LlmClient, + opts: { + model: string; + system?: string; + content: unknown[]; + toolName: string; + description: string; + zodSchema: z.ZodType; + maxRetries?: number; + maxTokens?: number; + }, +): Promise { + const { maxRetries = 2, maxTokens = 8192 } = opts; + const jsonSchema = z.toJSONSchema(opts.zodSchema); + const messages: Array<{ role: string; content: unknown }> = [{ role: "user", content: opts.content }]; + let lastError = ""; + + for (let attempt = 0; attempt <= maxRetries; attempt++) { + if (attempt > 0) { + messages.push({ + role: "user", + content: [{ type: "text", text: `Previous tool input was invalid: ${lastError}. Call ${opts.toolName} again with corrected input.` }], + }); + } + const res = await client.messages.create({ + model: opts.model, + max_tokens: maxTokens, + ...(opts.system ? { system: opts.system } : {}), + messages, + tools: [{ name: opts.toolName, description: opts.description, input_schema: jsonSchema }], + tool_choice: { type: "tool", name: opts.toolName }, + }); + const block = res.content.find((b) => b.type === "tool_use" && b.name === opts.toolName); + if (!block) { lastError = "no tool_use block returned"; continue; } + const parsed = opts.zodSchema.safeParse(block.input); + if (parsed.success) return parsed.data; + lastError = parsed.error.message; + } + throw new Error(`forcedToolCall(${opts.toolName}) failed after ${maxRetries + 1} attempts: ${lastError}`); +} diff --git a/tests/llm.test.ts b/tests/llm.test.ts new file mode 100644 index 0000000..d93ebb6 --- /dev/null +++ b/tests/llm.test.ts @@ -0,0 +1,42 @@ +import { expect, test } from "bun:test"; +import { z } from "zod"; +import { forcedToolCall, type LlmClient } from "../src/llm"; + +const schema = z.object({ score: z.number().min(0).max(10) }); + +function fakeClient(responses: unknown[]): LlmClient { + let i = 0; + return { messages: { create: async () => ({ content: responses[i++] as any }) } }; +} + +test("parses a valid tool call", async () => { + const client = fakeClient([[{ type: "tool_use", name: "grade", input: { score: 7 } }]]); + const r = await forcedToolCall(client, { + model: "m", content: [{ type: "text", text: "grade it" }], + toolName: "grade", description: "d", zodSchema: schema, + }); + expect(r.score).toBe(7); +}); + +test("retries on invalid then succeeds", async () => { + const client = fakeClient([ + [{ type: "tool_use", name: "grade", input: { score: 99 } }], + [{ type: "tool_use", name: "grade", input: { score: 5 } }], + ]); + const r = await forcedToolCall(client, { + model: "m", content: [{ type: "text", text: "grade it" }], + toolName: "grade", description: "d", zodSchema: schema, + }); + expect(r.score).toBe(5); +}); + +test("throws after retries exhausted", async () => { + const bad = [{ type: "tool_use", name: "grade", input: { score: 99 } }]; + const client = fakeClient([bad, bad, bad]); + await expect( + forcedToolCall(client, { + model: "m", content: [{ type: "text", text: "x" }], + toolName: "grade", description: "d", zodSchema: schema, maxRetries: 2, + }), + ).rejects.toThrow(); +}); From 4e4926473b123ca65a5de5abd9434de287d08d31 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:40:04 -0700 Subject: [PATCH 11/28] feat: opus vision evaluator with fixed rubric and reference comparison Co-Authored-By: Claude Fable 5 --- src/eval/rubric.md | 19 ++++++++++++++ src/inner/evaluate.ts | 58 ++++++++++++++++++++++++++++++++++++++++++ tests/evaluate.test.ts | 42 ++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 src/eval/rubric.md create mode 100644 src/inner/evaluate.ts create mode 100644 tests/evaluate.test.ts diff --git a/src/eval/rubric.md b/src/eval/rubric.md new file mode 100644 index 0000000..4e8dab1 --- /dev/null +++ b/src/eval/rubric.md @@ -0,0 +1,19 @@ +# Landing Page UI/UX Rubric + +Score each dimension 0-10: + +- **hierarchy** — Clear visual hierarchy: obvious primary message, scannable sections, sensible reading order. +- **typography** — Type scale, pairing, line length, weight contrast; no default-browser look. +- **spacing** — Consistent rhythm, breathing room, aligned grid; no cramped or floaty regions. +- **color_contrast** — Cohesive palette, sufficient text contrast (WCAG-ish), intentional accent usage. +- **requirement_coverage** — Every must-include item from the brief is present and functional-looking. +- **polish** — Overall craft: imagery/placeholder quality, component consistency, micro-details (buttons, cards, footer). + +**overall (0-100):** weighted judgment, NOT a straight sum. requirement_coverage is a gate: if any required +section is missing, overall must not exceed 50 regardless of visual quality. A generic bootstrap-looking page +that covers everything sits around 50-60. Reserve 85+ for pages that would pass as a real product's site. + +**vs_reference:** compare the candidate against the provided reference screenshot for the same brief: +`behind`, `on_par`, or `ahead`, and list the dimensions where they differ in diff_dimensions. + +**critique:** 2-4 sentences: the single biggest weakness and the most impactful concrete improvement. diff --git a/src/inner/evaluate.ts b/src/inner/evaluate.ts new file mode 100644 index 0000000..250d647 --- /dev/null +++ b/src/inner/evaluate.ts @@ -0,0 +1,58 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { z } from "zod"; +import { forcedToolCall, imageBlock, type LlmClient } from "../llm"; +import type { PromptSpec } from "../prompts"; +import type { Screenshots } from "./screenshot"; + +const sub = z.number().min(0).max(10); +export const EvalResultSchema = z.object({ + subscores: z.object({ + hierarchy: sub, typography: sub, spacing: sub, + color_contrast: sub, requirement_coverage: sub, polish: sub, + }), + overall: z.number().min(0).max(100), + vs_reference: z.enum(["behind", "on_par", "ahead"]), + diff_dimensions: z.array(z.string()), + critique: z.string().min(1), +}); +export type EvalResult = z.infer; + +const RUBRIC = readFileSync(join(import.meta.dir, "../eval/rubric.md"), "utf8"); + +const MAX_MOBILE = 3; +const MAX_REFERENCE = 4; + +export async function evaluatePage(opts: { + client: LlmClient; + model: string; + prompt: PromptSpec; + candidate: Screenshots; + referenceDesktopPngs: string[]; +}): Promise { + const content: unknown[] = [ + { type: "text", text: `You are a strict design reviewer.\n\n${RUBRIC}\n\n## Brief\n${opts.prompt.prompt}` }, + { type: "text", text: "Screenshots are scrolled viewport segments in top-to-bottom order." }, + ]; + const desktop = opts.candidate.desktop; + desktop.forEach((p, i) => { + content.push({ type: "text", text: `Candidate desktop — screen ${i + 1}/${desktop.length}:` }, imageBlock(p)); + }); + const mobile = opts.candidate.mobile.slice(0, MAX_MOBILE); + mobile.forEach((p, i) => { + content.push({ type: "text", text: `Candidate mobile — screen ${i + 1}/${mobile.length}:` }, imageBlock(p)); + }); + const refs = opts.referenceDesktopPngs.slice(0, MAX_REFERENCE); + refs.forEach((p, i) => { + content.push({ type: "text", text: `Reference page for the same brief, desktop — screen ${i + 1}/${refs.length}:` }, imageBlock(p)); + }); + content.push({ type: "text", text: "Evaluate the candidate per the rubric and call submit_evaluation." }); + + return forcedToolCall(opts.client, { + model: opts.model, + toolName: "submit_evaluation", + description: "Submit the structured rubric evaluation of the candidate landing page.", + zodSchema: EvalResultSchema, + content, + }); +} diff --git a/tests/evaluate.test.ts b/tests/evaluate.test.ts new file mode 100644 index 0000000..592b15e --- /dev/null +++ b/tests/evaluate.test.ts @@ -0,0 +1,42 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { evaluatePage, EvalResultSchema } from "../src/inner/evaluate"; +import type { LlmClient } from "../src/llm"; + +const png = (dir: string, name: string) => { + const p = join(dir, name); + // 1x1 png + writeFileSync(p, Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64")); + return p; +}; + +const valid = { + subscores: { hierarchy: 7, typography: 6, spacing: 7, color_contrast: 8, requirement_coverage: 9, polish: 6 }, + overall: 68, vs_reference: "behind", diff_dimensions: ["typography"], critique: "Weak type scale.", +}; + +test("returns parsed eval and sends capped image segments + rubric", async () => { + const dir = mkdtempSync(join(tmpdir(), "eval-")); + let captured: any; + const client: LlmClient = { + messages: { create: async (params) => { captured = params; return { content: [{ type: "tool_use", name: "submit_evaluation", input: valid }] }; } }, + }; + const r = await evaluatePage({ + client, model: "test-model", + prompt: { id: "p", category: "c", split: "train", prompt: "Landing page for X. Must include: hero." }, + candidate: { + desktop: [png(dir, "d0.png"), png(dir, "d1.png")], + mobile: [png(dir, "m0.png"), png(dir, "m1.png"), png(dir, "m2.png"), png(dir, "m3.png")], // 4 → capped to 3 + }, + referenceDesktopPngs: [png(dir, "r0.png"), png(dir, "r1.png"), png(dir, "r2.png"), png(dir, "r3.png"), png(dir, "r4.png")], // 5 → capped to 4 + }); + expect(r.overall).toBe(68); + expect(EvalResultSchema.parse(r)).toEqual(valid as any); + const text = JSON.stringify(captured.messages); + expect(text).toContain("requirement_coverage"); // rubric included + expect(text).toContain("screen 1/2"); // segment labeling + const images = captured.messages[0].content.filter((b: any) => b.type === "image"); + expect(images.length).toBe(2 + 3 + 4); // desktop + capped mobile + capped reference +}); From 482c388c03a8cc0aa954af38f136ec1dd0125a27 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:42:22 -0700 Subject: [PATCH 12/28] feat: run store for configs, iterations, history Co-Authored-By: Claude Fable 5 --- src/store/run-store.ts | 89 +++++++++++++++++++++++++++++++++++++++++ tests/run-store.test.ts | 27 +++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 src/store/run-store.ts create mode 100644 tests/run-store.test.ts diff --git a/src/store/run-store.ts b/src/store/run-store.ts new file mode 100644 index 0000000..3293d6e --- /dev/null +++ b/src/store/run-store.ts @@ -0,0 +1,89 @@ +import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { HarnessConfigSchema, type HarnessConfig } from "../config/schema"; +import type { EvalResult } from "../inner/evaluate"; + +export type HistoryEntry = { + iteration: number; config_version: number; mean_overall: number; best_version: number; best_score: number; +}; +export type PromptOutcome = { + prompt_id: string; + status: "ok" | "build_failed" | "screenshot_failed" | "eval_failed"; + overall: number; + eval?: EvalResult; + error?: string; +}; +export type IterationSummary = { + iteration: number; config_version: number; mean_overall: number; + outcomes: PromptOutcome[]; dimension_means: Record; mutator_rationale?: string; +}; + +export class RunStore { + readonly root: string; + constructor(runsDir: string, runId: string) { + this.root = join(runsDir, runId); + } + initRun(meta: Record): void { + mkdirSync(join(this.root, "configs"), { recursive: true }); + mkdirSync(join(this.root, "iterations"), { recursive: true }); + if (!existsSync(join(this.root, "run.json"))) { + writeFileSync(join(this.root, "run.json"), JSON.stringify({ started_at: new Date().toISOString(), ...meta }, null, 2)); + } + } + saveConfig(cfg: HarnessConfig): void { + writeFileSync(join(this.root, "configs", `v${cfg.version}.json`), JSON.stringify(cfg, null, 2)); + } + loadConfig(version: number): HarnessConfig { + return HarnessConfigSchema.parse(JSON.parse(readFileSync(join(this.root, "configs", `v${version}.json`), "utf8"))); + } + listConfigVersions(): number[] { + return readdirSync(join(this.root, "configs")) + .map((f) => Number(f.match(/^v(\d+)\.json$/)?.[1])) + .filter((n) => Number.isFinite(n)) + .sort((a, b) => a - b); + } + nextConfigVersion(): number { + const vs = this.listConfigVersions(); + return vs.length ? Math.max(...vs) + 1 : 0; + } + iterationDir(n: number): string { + const d = join(this.root, "iterations", String(n)); + mkdirSync(d, { recursive: true }); + return d; + } + promptDir(n: number, promptId: string): string { + const d = join(this.iterationDir(n), "prompts", promptId); + mkdirSync(join(d, "workspace"), { recursive: true }); + return d; + } + saveSummary(s: IterationSummary): void { + writeFileSync(join(this.iterationDir(s.iteration), "summary.json"), JSON.stringify(s, null, 2)); + } + loadSummaries(): IterationSummary[] { + return this.completedIterations().map((n) => + JSON.parse(readFileSync(join(this.root, "iterations", String(n), "summary.json"), "utf8")), + ); + } + appendHistory(e: HistoryEntry): void { + appendFileSync(join(this.root, "history.jsonl"), JSON.stringify(e) + "\n"); + } + readHistory(): HistoryEntry[] { + const p = join(this.root, "history.jsonl"); + if (!existsSync(p)) return []; + return readFileSync(p, "utf8").trim().split("\n").filter(Boolean).map((l) => JSON.parse(l)); + } + bestVersion(): { version: number; score: number } { + const h = this.readHistory(); + if (!h.length) return { version: 0, score: -1 }; + const best = h.reduce((a, b) => (b.mean_overall > a.mean_overall ? b : a)); + return { version: best.config_version, score: best.mean_overall }; + } + completedIterations(): number[] { + const dir = join(this.root, "iterations"); + if (!existsSync(dir)) return []; + return readdirSync(dir) + .map(Number) + .filter((n) => Number.isFinite(n) && existsSync(join(dir, String(n), "summary.json"))) + .sort((a, b) => a - b); + } +} diff --git a/tests/run-store.test.ts b/tests/run-store.test.ts new file mode 100644 index 0000000..b4e8ac2 --- /dev/null +++ b/tests/run-store.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { RunStore } from "../src/store/run-store"; +import { BASELINE_CONFIG } from "../src/config/schema"; + +test("full store lifecycle", () => { + const dir = mkdtempSync(join(tmpdir(), "store-")); + const s = new RunStore(dir, "run1"); + s.initRun({ note: "test" }); + expect(existsSync(join(dir, "run1", "run.json"))).toBe(true); + + s.saveConfig(BASELINE_CONFIG); + expect(s.loadConfig(0)).toEqual(BASELINE_CONFIG); + expect(s.nextConfigVersion()).toBe(1); + + const pd = s.promptDir(1, "saas-crm"); + expect(existsSync(join(pd, "workspace"))).toBe(true); + + s.saveSummary({ iteration: 1, config_version: 0, mean_overall: 55, outcomes: [], dimension_means: {} }); + s.appendHistory({ iteration: 1, config_version: 0, mean_overall: 55, best_version: 0, best_score: 55 }); + s.appendHistory({ iteration: 2, config_version: 1, mean_overall: 61, best_version: 1, best_score: 61 }); + expect(s.bestVersion()).toEqual({ version: 1, score: 61 }); + expect(s.completedIterations()).toEqual([1]); + expect(s.readHistory().length).toBe(2); +}); From d8b1d3693542e90fdec5c270f72f2452bb50121c Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:44:51 -0700 Subject: [PATCH 13/28] Add tests for completedIterations filtering, bestVersion tie-break, and nextConfigVersion zero-state Co-Authored-By: Claude Fable 5 --- tests/run-store.test.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/run-store.test.ts b/tests/run-store.test.ts index b4e8ac2..0bd6ad8 100644 --- a/tests/run-store.test.ts +++ b/tests/run-store.test.ts @@ -25,3 +25,42 @@ test("full store lifecycle", () => { expect(s.completedIterations()).toEqual([1]); expect(s.readHistory().length).toBe(2); }); + +test("completedIterations excludes partial iterations and sorts numerically", () => { + const dir = mkdtempSync(join(tmpdir(), "store-")); + const s = new RunStore(dir, "run1"); + s.initRun({ note: "test" }); + + // Iteration 1: directory created but no summary.json written (partial). + s.iterationDir(1); + + // Iterations 10 and 2 (out of lexicographic order) get summaries saved. + s.saveSummary({ iteration: 10, config_version: 0, mean_overall: 50, outcomes: [], dimension_means: {} }); + s.saveSummary({ iteration: 2, config_version: 0, mean_overall: 60, outcomes: [], dimension_means: {} }); + + expect(s.completedIterations()).toEqual([2, 10]); +}); + +test("bestVersion keeps the first-appended entry on ties, and falls back on empty history", () => { + const dir = mkdtempSync(join(tmpdir(), "store-")); + const s = new RunStore(dir, "run1"); + s.initRun({ note: "test" }); + + expect(s.bestVersion()).toEqual({ version: 0, score: -1 }); + + s.appendHistory({ iteration: 1, config_version: 0, mean_overall: 70, best_version: 0, best_score: 70 }); + s.appendHistory({ iteration: 2, config_version: 1, mean_overall: 70, best_version: 0, best_score: 70 }); + + expect(s.bestVersion()).toEqual({ version: 0, score: 70 }); +}); + +test("nextConfigVersion returns 0 before any config is saved", () => { + const dir = mkdtempSync(join(tmpdir(), "store-")); + const s = new RunStore(dir, "run1"); + s.initRun({ note: "test" }); + + expect(s.nextConfigVersion()).toBe(0); + + s.saveConfig(BASELINE_CONFIG); + expect(s.nextConfigVersion()).toBe(1); +}); From b02028e4069984f31ae9d42ca9eb75139f5775a9 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:47:38 -0700 Subject: [PATCH 14/28] feat: iteration aggregation with failure semantics Co-Authored-By: Claude Fable 5 --- src/outer/aggregate.ts | 16 ++++++++++++++++ tests/aggregate.test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/outer/aggregate.ts create mode 100644 tests/aggregate.test.ts diff --git a/src/outer/aggregate.ts b/src/outer/aggregate.ts new file mode 100644 index 0000000..4ee3237 --- /dev/null +++ b/src/outer/aggregate.ts @@ -0,0 +1,16 @@ +import type { IterationSummary, PromptOutcome } from "../store/run-store"; + +export function aggregate(iteration: number, configVersion: number, outcomes: PromptOutcome[]): IterationSummary { + const scored = outcomes.filter((o) => o.status !== "eval_failed"); + const mean_overall = scored.length ? scored.reduce((s, o) => s + o.overall, 0) / scored.length : 0; + + const oks = outcomes.filter((o) => o.status === "ok" && o.eval); + const dimension_means: Record = {}; + if (oks.length) { + const keys = Object.keys(oks[0]!.eval!.subscores) as Array; + for (const k of keys) { + dimension_means[k] = oks.reduce((s, o) => s + (o.eval!.subscores as any)[k], 0) / oks.length; + } + } + return { iteration, config_version: configVersion, mean_overall, outcomes, dimension_means }; +} diff --git a/tests/aggregate.test.ts b/tests/aggregate.test.ts new file mode 100644 index 0000000..cd9e04f --- /dev/null +++ b/tests/aggregate.test.ts @@ -0,0 +1,24 @@ +import { expect, test } from "bun:test"; +import { aggregate } from "../src/outer/aggregate"; +import type { PromptOutcome } from "../src/store/run-store"; + +const ok = (id: string, overall: number, hierarchy: number): PromptOutcome => ({ + prompt_id: id, status: "ok", overall, + eval: { + subscores: { hierarchy, typography: 5, spacing: 5, color_contrast: 5, requirement_coverage: 5, polish: 5 }, + overall, vs_reference: "on_par", diff_dimensions: [], critique: "c", + }, +}); + +test("means: failures are 0, eval_failed excluded", () => { + const s = aggregate(3, 7, [ + ok("a", 80, 8), + ok("b", 60, 6), + { prompt_id: "c", status: "build_failed", overall: 0, error: "no html" }, + { prompt_id: "d", status: "eval_failed", overall: 0, error: "api" }, + ]); + expect(s.iteration).toBe(3); + expect(s.config_version).toBe(7); + expect(s.mean_overall).toBeCloseTo((80 + 60 + 0) / 3); + expect(s.dimension_means.hierarchy).toBeCloseTo(7); +}); From 6e337fc72ec695899a38f47b8f8a2053865f0ae7 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:49:47 -0700 Subject: [PATCH 15/28] feat: schema-constrained config mutator with elitist anchoring Co-Authored-By: Claude Fable 5 --- src/outer/mutate.ts | 61 ++++++++++++++++++++++++++++++++++++++++++++ tests/mutate.test.ts | 24 +++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/outer/mutate.ts create mode 100644 tests/mutate.test.ts diff --git a/src/outer/mutate.ts b/src/outer/mutate.ts new file mode 100644 index 0000000..65a1100 --- /dev/null +++ b/src/outer/mutate.ts @@ -0,0 +1,61 @@ +import { forcedToolCall, type LlmClient } from "../llm"; +import { HarnessConfigSchema, type HarnessConfig } from "../config/schema"; +import type { HistoryEntry, IterationSummary } from "../store/run-store"; + +const MUTATOR_SYSTEM = `You are a harness engineer optimizing an AI coding agent's configuration so it produces +better-designed landing pages. You may change: system_instructions, skills (add/edit/remove markdown skill +documents), subagents (internal review passes), tools, model thinking_level. Make ONE focused, well-motivated +change per proposal — a targeted hypothesis, not a rewrite. Ground it in the evaluation critiques and the +weakest rubric dimensions. Avoid repeating past changes that did not improve the score.`; + +export async function mutateConfig(opts: { + client: LlmClient; + model: string; + bestConfig: HarnessConfig; + latestSummary: IterationSummary; + history: HistoryEntry[]; + pastRationales: Array<{ version: number; rationale: string; mean_overall: number | null }>; + nextVersion: number; +}): Promise { + const critiques = opts.latestSummary.outcomes + .filter((o) => o.eval) + .map((o) => `- ${o.prompt_id} (${o.overall}, ${o.eval!.vs_reference}): ${o.eval!.critique}`) + .join("\n"); + const failures = opts.latestSummary.outcomes + .filter((o) => o.status !== "ok") + .map((o) => `- ${o.prompt_id}: ${o.status} ${o.error ?? ""}`) + .join("\n"); + + const proposal = await forcedToolCall(opts.client, { + model: opts.model, + system: MUTATOR_SYSTEM, + toolName: "propose_config", + description: "Propose the complete next harness configuration.", + zodSchema: HarnessConfigSchema, + maxTokens: 16384, + content: [ + { + type: "text", + text: [ + `## Current best config (version ${opts.bestConfig.version}, derive your proposal from this)`, + JSON.stringify(opts.bestConfig, null, 2), + `## Latest iteration summary (config v${opts.latestSummary.config_version}, mean ${opts.latestSummary.mean_overall.toFixed(1)})`, + `Dimension means: ${JSON.stringify(opts.latestSummary.dimension_means)}`, + `Per-prompt critiques:\n${critiques || "(none)"}`, + failures ? `Failures:\n${failures}` : "", + `## Score history`, + opts.history.map((h) => `iter ${h.iteration}: v${h.config_version} → ${h.mean_overall.toFixed(1)} (best v${h.best_version}=${h.best_score.toFixed(1)})`).join("\n"), + `## Past change rationales`, + opts.pastRationales.map((r) => `v${r.version} (${r.mean_overall ?? "unscored"}): ${r.rationale}`).join("\n"), + `Propose the next config now. Include a specific rationale explaining the hypothesis.`, + ].filter(Boolean).join("\n\n"), + }, + ], + }); + + return HarnessConfigSchema.parse({ + ...proposal, + version: opts.nextVersion, + parent_version: opts.bestConfig.version, + }); +} diff --git a/tests/mutate.test.ts b/tests/mutate.test.ts new file mode 100644 index 0000000..ff358f4 --- /dev/null +++ b/tests/mutate.test.ts @@ -0,0 +1,24 @@ +import { expect, test } from "bun:test"; +import { mutateConfig } from "../src/outer/mutate"; +import { BASELINE_CONFIG } from "../src/config/schema"; +import type { LlmClient } from "../src/llm"; + +test("returns validated config with pinned version fields", async () => { + const proposal = { ...BASELINE_CONFIG, version: 999, parent_version: 42, rationale: "Add typography skill", system_instructions: BASELINE_CONFIG.system_instructions + "\nUse a modular type scale." }; + let captured: any; + const client: LlmClient = { + messages: { create: async (p) => { captured = p; return { content: [{ type: "tool_use", name: "propose_config", input: proposal }] }; } }, + }; + const next = await mutateConfig({ + client, model: "m", bestConfig: BASELINE_CONFIG, + latestSummary: { iteration: 1, config_version: 0, mean_overall: 52, outcomes: [], dimension_means: { typography: 4.1 } }, + history: [{ iteration: 1, config_version: 0, mean_overall: 52, best_version: 0, best_score: 52 }], + pastRationales: [{ version: 0, rationale: "baseline", mean_overall: 52 }], + nextVersion: 5, + }); + expect(next.version).toBe(5); + expect(next.parent_version).toBe(0); + const sent = JSON.stringify(captured.messages); + expect(sent).toContain("typography"); // summary reached the prompt + expect(sent).toContain("baseline"); // history reached the prompt +}); From 5eb8eccdf744b60ee4a5605c2aff2c79d1dacb37 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:52:36 -0700 Subject: [PATCH 16/28] feat: per-prompt build/screenshot/evaluate pipeline with pLimit Co-Authored-By: Claude Fable 5 --- src/inner/pipeline.ts | 57 ++++++++++++++++++++++++++++++++++++++++ src/util/concurrency.ts | 14 ++++++++++ tests/pipeline.test.ts | 58 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 src/inner/pipeline.ts create mode 100644 src/util/concurrency.ts create mode 100644 tests/pipeline.test.ts diff --git a/src/inner/pipeline.ts b/src/inner/pipeline.ts new file mode 100644 index 0000000..55866e5 --- /dev/null +++ b/src/inner/pipeline.ts @@ -0,0 +1,57 @@ +import { readdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import type { ResolvedHarness } from "../config/resolver"; +import type { PromptSpec } from "../prompts"; +import type { LlmClient } from "../llm"; +import type { PromptOutcome } from "../store/run-store"; +import { buildPage } from "./build"; +import { screenshotPage } from "./screenshot"; +import { evaluatePage } from "./evaluate"; + +export function referenceSegments(referenceDir: string, promptId: string): string[] { + const re = new RegExp(`^${promptId}\\.desktop\\.(\\d+)\\.png$`); + return readdirSync(referenceDir) + .map((f) => ({ f, m: f.match(re) })) + .filter((x) => x.m) + .sort((a, b) => Number(a.m![1]) - Number(b.m![1])) + .map((x) => join(referenceDir, x.f)); +} + +export async function runPromptPipeline(opts: { + resolved: ResolvedHarness; + prompt: PromptSpec; + promptDir: string; + client: LlmClient; + evalModel: string; + referenceDir: string; +}): Promise { + const { prompt, promptDir } = opts; + + const build = await buildPage({ resolved: opts.resolved, prompt, workspaceDir: join(promptDir, "workspace") }); + if (!build.ok) return { prompt_id: prompt.id, status: "build_failed", overall: 0, error: build.error }; + + let shots; + try { + shots = await screenshotPage(build.htmlPath, promptDir); + } catch (e) { + return { prompt_id: prompt.id, status: "screenshot_failed", overall: 0, error: String(e) }; + } + + try { + const refs = referenceSegments(opts.referenceDir, prompt.id); + if (!refs.length) { + return { prompt_id: prompt.id, status: "eval_failed", overall: 0, error: "no reference segments found" }; + } + const evalResult = await evaluatePage({ + client: opts.client, + model: opts.evalModel, + prompt, + candidate: shots, + referenceDesktopPngs: refs, + }); + writeFileSync(join(promptDir, "eval.json"), JSON.stringify(evalResult, null, 2)); + return { prompt_id: prompt.id, status: "ok", overall: evalResult.overall, eval: evalResult }; + } catch (e) { + return { prompt_id: prompt.id, status: "eval_failed", overall: 0, error: String(e) }; + } +} diff --git a/src/util/concurrency.ts b/src/util/concurrency.ts new file mode 100644 index 0000000..3bbf5cd --- /dev/null +++ b/src/util/concurrency.ts @@ -0,0 +1,14 @@ +export function pLimit(n: number): (fn: () => Promise) => Promise { + let active = 0; + const queue: Array<() => void> = []; + const next = () => { + if (active >= n || queue.length === 0) return; + active++; + queue.shift()!(); + }; + return (fn: () => Promise) => + new Promise((resolve, reject) => { + queue.push(() => fn().then(resolve, reject).finally(() => { active--; next(); })); + next(); + }); +} diff --git a/tests/pipeline.test.ts b/tests/pipeline.test.ts new file mode 100644 index 0000000..9dc1f0b --- /dev/null +++ b/tests/pipeline.test.ts @@ -0,0 +1,58 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { runPromptPipeline } from "../src/inner/pipeline"; +import { pLimit } from "../src/util/concurrency"; +import { resolveHarness } from "../src/config/resolver"; +import { BASELINE_CONFIG } from "../src/config/schema"; +import type { LlmClient } from "../src/llm"; + +const PAGE = `

Test page

${"

content

".repeat(30)}`; +const EVAL = { + subscores: { hierarchy: 6, typography: 6, spacing: 6, color_contrast: 6, requirement_coverage: 8, polish: 5 }, + overall: 62, vs_reference: "behind", diff_dimensions: [], critique: "fine", +}; + +function setup() { + const base = mkdtempSync(join(tmpdir(), "pipe-")); + const stub = join(base, "pi.sh"); + writeFileSync(stub, `#!/bin/bash\ncat > /dev/null <<'EOF'\nEOF\nprintf '%s' '${PAGE}' > output.html\n`); + chmodSync(stub, 0o755); + process.env.PI_BIN = stub; + const refDir = join(base, "reference"); + mkdirSync(refDir, { recursive: true }); + const png1 = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64"); + writeFileSync(join(refDir, "t1.desktop.0.png"), png1); + writeFileSync(join(refDir, "t1.desktop.1.png"), png1); + const promptDir = join(base, "p", "t1"); + mkdirSync(join(promptDir, "workspace"), { recursive: true }); + const client: LlmClient = { messages: { create: async () => ({ content: [{ type: "tool_use", name: "submit_evaluation", input: EVAL }] }) } }; + return { base, refDir, promptDir, client }; +} + +test("happy path produces ok outcome with eval.json", async () => { + const { refDir, promptDir, client } = setup(); + const resolved = resolveHarness(BASELINE_CONFIG, join(promptDir, "resolved")); + const out = await runPromptPipeline({ + resolved, prompt: { id: "t1", category: "c", split: "train", prompt: "x" }, + promptDir, client, evalModel: "m", referenceDir: refDir, + }); + expect(out.status).toBe("ok"); + expect(out.overall).toBe(62); +}, 60000); + +test("pLimit caps concurrency", async () => { + const limit = pLimit(2); + let active = 0, peak = 0; + await Promise.all( + Array.from({ length: 6 }, () => + limit(async () => { + active++; peak = Math.max(peak, active); + await new Promise((r) => setTimeout(r, 20)); + active--; + }), + ), + ); + expect(peak).toBeLessThanOrEqual(2); +}); From 4ffe65c2bfb4ff13b8ba51a827fc45d5a4565404 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 16:55:15 -0700 Subject: [PATCH 17/28] feat: one-time reference set generator with fixed opus harness Co-Authored-By: Claude Fable 5 --- src/reference/build-reference.ts | 61 ++++++++++++++++++++++++++++++++ tests/reference.test.ts | 28 +++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/reference/build-reference.ts create mode 100644 tests/reference.test.ts diff --git a/src/reference/build-reference.ts b/src/reference/build-reference.ts new file mode 100644 index 0000000..b04a206 --- /dev/null +++ b/src/reference/build-reference.ts @@ -0,0 +1,61 @@ +import { copyFileSync, existsSync, mkdirSync } from "node:fs"; +import { join } from "node:path"; +import { BASELINE_CONFIG, type HarnessConfig } from "../config/schema"; +import { resolveHarness } from "../config/resolver"; +import { buildPage } from "../inner/build"; +import { screenshotPage } from "../inner/screenshot"; +import { pLimit } from "../util/concurrency"; +import type { PromptSpec } from "../prompts"; + +export const REFERENCE_CONFIG: HarnessConfig = { + ...BASELINE_CONFIG, + rationale: "Fixed reference harness — not part of the search space.", + model: { name: "anthropic/claude-opus-4-8", thinking_level: "high" }, + system_instructions: [ + BASELINE_CONFIG.system_instructions, + "You are the reference standard: produce the best landing page you possibly can.", + "Invest in typography (real type scale), a cohesive palette, generous consistent spacing,", + "distinctive hero treatment, and polished components. Take your time; quality over speed.", + ].join("\n"), +}; + +export async function buildReferenceSet(opts: { + prompts: PromptSpec[]; + referenceDir: string; + concurrency?: number; + force?: boolean; +}): Promise<{ built: string[]; skipped: string[]; failed: Array<{ id: string; error: string }> }> { + const { prompts, referenceDir, concurrency = 3, force = false } = opts; + mkdirSync(referenceDir, { recursive: true }); + const resolved = resolveHarness(REFERENCE_CONFIG, join(referenceDir, ".work", "resolved")); + const limit = pLimit(concurrency); + const built: string[] = [], skipped: string[] = [], failed: Array<{ id: string; error: string }> = []; + + await Promise.all( + prompts.map((prompt) => + limit(async () => { + if (!force && existsSync(join(referenceDir, `${prompt.id}.desktop.0.png`))) { + skipped.push(prompt.id); + return; + } + const ws = join(referenceDir, ".work", prompt.id, "workspace"); + mkdirSync(ws, { recursive: true }); + const r = await buildPage({ resolved, prompt, workspaceDir: ws }); + if (!r.ok) { failed.push({ id: prompt.id, error: r.error }); return; } + try { + await screenshotPage(r.htmlPath, referenceDir, prompt.id); + copyFileSync(r.htmlPath, join(referenceDir, `${prompt.id}.html`)); + built.push(prompt.id); + } catch (e) { + failed.push({ id: prompt.id, error: String(e) }); + } + }), + ), + ); + return { built, skipped, failed }; +} + +export function assertReferencesExist(prompts: PromptSpec[], referenceDir: string): void { + const missing = prompts.filter((p) => !existsSync(join(referenceDir, `${p.id}.desktop.0.png`))).map((p) => p.id); + if (missing.length) throw new Error(`missing reference screenshots: ${missing.join(", ")} — run \`bun run reference\` first`); +} diff --git a/tests/reference.test.ts b/tests/reference.test.ts new file mode 100644 index 0000000..d26cf6d --- /dev/null +++ b/tests/reference.test.ts @@ -0,0 +1,28 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { buildReferenceSet, assertReferencesExist } from "../src/reference/build-reference"; + +const PAGE = `

Ref

${"

content

".repeat(30)}`; + +test("builds and caches reference pages, skips existing", async () => { + const base = mkdtempSync(join(tmpdir(), "ref-")); + const stub = join(base, "pi.sh"); + writeFileSync(stub, `#!/bin/bash\nprintf '%s' '${PAGE}' > output.html\n`); + chmodSync(stub, 0o755); + process.env.PI_BIN = stub; + const refDir = join(base, "reference"); + const prompts = [{ id: "r1", category: "c", split: "train" as const, prompt: "x" }]; + + const first = await buildReferenceSet({ prompts, referenceDir: refDir }); + expect(first.built).toEqual(["r1"]); + expect(existsSync(join(refDir, "r1.desktop.0.png"))).toBe(true); + expect(existsSync(join(refDir, "r1.html"))).toBe(true); + + const second = await buildReferenceSet({ prompts, referenceDir: refDir }); + expect(second.skipped).toEqual(["r1"]); + + expect(() => assertReferencesExist(prompts, refDir)).not.toThrow(); + expect(() => assertReferencesExist([{ id: "missing", category: "c", split: "train", prompt: "x" }], refDir)).toThrow("missing"); +}, 120000); From e081e70fb342269b8c3bc3a22d52a132ce281610 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 17:01:09 -0700 Subject: [PATCH 18/28] Make reference-page comparison optional in the evaluator The reference-page comparison is deferred (TBD); the evaluator now scores candidates on the rubric alone when no reference screenshots are available. vs_reference/diff_dimensions become optional in EvalResult, evaluatePage accepts referenceDesktopPngs as optional, the pipeline no longer fails when no reference segments exist for a prompt, and the mutator tolerates a missing vs_reference when building critique summaries. Co-Authored-By: Claude Fable 5 --- src/inner/evaluate.ts | 19 ++++++++++++------- src/inner/pipeline.ts | 3 --- src/outer/mutate.ts | 2 +- tests/evaluate.test.ts | 26 ++++++++++++++++++++++++++ tests/pipeline.test.ts | 14 +++++++++++++- 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/inner/evaluate.ts b/src/inner/evaluate.ts index 250d647..fb453c8 100644 --- a/src/inner/evaluate.ts +++ b/src/inner/evaluate.ts @@ -12,8 +12,8 @@ export const EvalResultSchema = z.object({ color_contrast: sub, requirement_coverage: sub, polish: sub, }), overall: z.number().min(0).max(100), - vs_reference: z.enum(["behind", "on_par", "ahead"]), - diff_dimensions: z.array(z.string()), + vs_reference: z.enum(["behind", "on_par", "ahead"]).optional(), + diff_dimensions: z.array(z.string()).optional(), critique: z.string().min(1), }); export type EvalResult = z.infer; @@ -28,8 +28,9 @@ export async function evaluatePage(opts: { model: string; prompt: PromptSpec; candidate: Screenshots; - referenceDesktopPngs: string[]; + referenceDesktopPngs?: string[]; }): Promise { + const refs = (opts.referenceDesktopPngs ?? []).slice(0, MAX_REFERENCE); const content: unknown[] = [ { type: "text", text: `You are a strict design reviewer.\n\n${RUBRIC}\n\n## Brief\n${opts.prompt.prompt}` }, { type: "text", text: "Screenshots are scrolled viewport segments in top-to-bottom order." }, @@ -42,10 +43,14 @@ export async function evaluatePage(opts: { mobile.forEach((p, i) => { content.push({ type: "text", text: `Candidate mobile — screen ${i + 1}/${mobile.length}:` }, imageBlock(p)); }); - const refs = opts.referenceDesktopPngs.slice(0, MAX_REFERENCE); - refs.forEach((p, i) => { - content.push({ type: "text", text: `Reference page for the same brief, desktop — screen ${i + 1}/${refs.length}:` }, imageBlock(p)); - }); + if (refs.length > 0) { + refs.forEach((p, i) => { + content.push({ type: "text", text: `Reference page for the same brief, desktop — screen ${i + 1}/${refs.length}:` }, imageBlock(p)); + }); + content.push({ type: "text", text: "Compare the candidate against the reference page above. Score vs_reference (behind/on_par/ahead) and list diff_dimensions where they differ, in addition to the rubric subscores." }); + } else { + content.push({ type: "text", text: "No reference page is provided for this brief. Score the candidate on the rubric alone; vs_reference and diff_dimensions may be omitted." }); + } content.push({ type: "text", text: "Evaluate the candidate per the rubric and call submit_evaluation." }); return forcedToolCall(opts.client, { diff --git a/src/inner/pipeline.ts b/src/inner/pipeline.ts index 55866e5..0bb651c 100644 --- a/src/inner/pipeline.ts +++ b/src/inner/pipeline.ts @@ -39,9 +39,6 @@ export async function runPromptPipeline(opts: { try { const refs = referenceSegments(opts.referenceDir, prompt.id); - if (!refs.length) { - return { prompt_id: prompt.id, status: "eval_failed", overall: 0, error: "no reference segments found" }; - } const evalResult = await evaluatePage({ client: opts.client, model: opts.evalModel, diff --git a/src/outer/mutate.ts b/src/outer/mutate.ts index 65a1100..2a55dd4 100644 --- a/src/outer/mutate.ts +++ b/src/outer/mutate.ts @@ -19,7 +19,7 @@ export async function mutateConfig(opts: { }): Promise { const critiques = opts.latestSummary.outcomes .filter((o) => o.eval) - .map((o) => `- ${o.prompt_id} (${o.overall}, ${o.eval!.vs_reference}): ${o.eval!.critique}`) + .map((o) => `- ${o.prompt_id} (${o.overall}, ${o.eval!.vs_reference ?? "n/a"}): ${o.eval!.critique}`) .join("\n"); const failures = opts.latestSummary.outcomes .filter((o) => o.status !== "ok") diff --git a/tests/evaluate.test.ts b/tests/evaluate.test.ts index 592b15e..269e8b9 100644 --- a/tests/evaluate.test.ts +++ b/tests/evaluate.test.ts @@ -40,3 +40,29 @@ test("returns parsed eval and sends capped image segments + rubric", async () => const images = captured.messages[0].content.filter((b: any) => b.type === "image"); expect(images.length).toBe(2 + 3 + 4); // desktop + capped mobile + capped reference }); + +const validNoRef = { + subscores: { hierarchy: 7, typography: 6, spacing: 7, color_contrast: 8, requirement_coverage: 9, polish: 6 }, + overall: 68, critique: "Weak type scale.", +}; + +test("evaluates with no reference provided: rubric-only, zero reference images", async () => { + const dir = mkdtempSync(join(tmpdir(), "eval-")); + let captured: any; + const client: LlmClient = { + messages: { create: async (params) => { captured = params; return { content: [{ type: "tool_use", name: "submit_evaluation", input: validNoRef }] }; } }, + }; + const r = await evaluatePage({ + client, model: "test-model", + prompt: { id: "p", category: "c", split: "train", prompt: "Landing page for X. Must include: hero." }, + candidate: { + desktop: [png(dir, "d0.png"), png(dir, "d1.png")], + mobile: [png(dir, "m0.png"), png(dir, "m1.png")], + }, + }); + expect(EvalResultSchema.parse(r)).toEqual(validNoRef as any); + const images = captured.messages[0].content.filter((b: any) => b.type === "image"); + expect(images.length).toBe(2 + 2); // desktop + mobile, no reference images + const text = JSON.stringify(captured.messages); + expect(text).not.toContain("Reference page"); +}); diff --git a/tests/pipeline.test.ts b/tests/pipeline.test.ts index 9dc1f0b..90f66c4 100644 --- a/tests/pipeline.test.ts +++ b/tests/pipeline.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test"; -import { mkdtempSync, writeFileSync, chmodSync, mkdirSync } from "node:fs"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync, existsSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { runPromptPipeline } from "../src/inner/pipeline"; @@ -42,6 +42,18 @@ test("happy path produces ok outcome with eval.json", async () => { expect(out.overall).toBe(62); }, 60000); +test("reference-free path: no reference segments still produces ok outcome", async () => { + const { refDir, promptDir, client } = setup(); + const resolved = resolveHarness(BASELINE_CONFIG, join(promptDir, "resolved")); + const out = await runPromptPipeline({ + resolved, prompt: { id: "no-such-prompt", category: "c", split: "train", prompt: "x" }, + promptDir, client, evalModel: "m", referenceDir: refDir, + }); + expect(out.status).toBe("ok"); + expect(out.overall).toBe(62); + expect(existsSync(join(promptDir, "eval.json"))).toBe(true); +}, 60000); + test("pLimit caps concurrency", async () => { const limit = pLimit(2); let active = 0, peak = 0; From 9272c0d8c098f7793300f130d485b2e8cdc3aeab Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 17:05:30 -0700 Subject: [PATCH 19/28] feat: outer loop orchestrator, resume, holdout, CLI (reference-free) Wires runLoop/runHoldout and the CLI per task 14, but deviates from the brief: reference-page comparison is deferred, so loop/resume/holdout no longer call or import assertReferencesExist and run fine with an empty reference dir. The `reference` subcommand stays wired to buildReferenceSet so it remains runnable later. Co-Authored-By: Claude Fable 5 --- src/cli.ts | 75 +++++++++++++++++++++++++ src/orchestrator.ts | 112 +++++++++++++++++++++++++++++++++++++ tests/orchestrator.test.ts | 75 +++++++++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 src/cli.ts create mode 100644 src/orchestrator.ts create mode 100644 tests/orchestrator.test.ts diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..6fa30bd --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,75 @@ +import { parseArgs } from "node:util"; +import { join } from "node:path"; +import { loadPrompts, trainPrompts, holdoutPrompts } from "./prompts"; +import { RunStore } from "./store/run-store"; +import { realClient } from "./llm"; +import { runLoop, runHoldout } from "./orchestrator"; +import { buildReferenceSet } from "./reference/build-reference"; + +const RUNS_DIR = "runs"; +const REFERENCE_DIR = join(RUNS_DIR, "reference"); +const EVAL_MODEL = process.env.EVAL_MODEL ?? "claude-opus-4-8"; + +const [command] = Bun.argv.slice(2); +const { values } = parseArgs({ + args: Bun.argv.slice(3), + options: { + iterations: { type: "string", default: "5" }, + "run-id": { type: "string" }, + concurrency: { type: "string", default: "5" }, + version: { type: "string" }, + force: { type: "boolean", default: false }, + }, +}); + +const all = loadPrompts(); +const concurrency = Number(values.concurrency); + +async function main() { + switch (command) { + case "reference": { + // Reference building remains wired up so it can be run later, but it is no longer + // a prerequisite for loop/resume/holdout — those tolerate an empty reference dir + // and score rubric-only (reference comparison is deferred). + const r = await buildReferenceSet({ prompts: all, referenceDir: REFERENCE_DIR, concurrency, force: values.force }); + console.log(`built: ${r.built.length}, skipped: ${r.skipped.length}, failed: ${r.failed.length}`); + for (const f of r.failed) console.error(` FAILED ${f.id}: ${f.error}`); + if (r.failed.length) process.exit(1); + break; + } + case "loop": + case "resume": { + const runId = values["run-id"] ?? `run-${new Date().toISOString().slice(0, 16).replace(/[:T]/g, "-")}`; + if (command === "resume" && !values["run-id"]) throw new Error("resume requires --run-id"); + const train = trainPrompts(all); + const store = new RunStore(RUNS_DIR, runId); + store.initRun({ eval_model: EVAL_MODEL, concurrency }); + console.log(`run: ${runId}`); + await runLoop({ + store, prompts: train, iterations: Number(values.iterations), concurrency, + client: realClient(), evalModel: EVAL_MODEL, referenceDir: REFERENCE_DIR, + }); + const best = store.bestVersion(); + console.log(`done. best config: v${best.version} (mean ${best.score.toFixed(1)})`); + break; + } + case "holdout": { + if (!values["run-id"]) throw new Error("holdout requires --run-id"); + const store = new RunStore(RUNS_DIR, values["run-id"]); + const holdout = holdoutPrompts(all); + const version = values.version ? Number(values.version) : store.bestVersion().version; + const stamp = new Date().toISOString().replace(/[:.]/g, "-"); + const summary = await runHoldout({ + store, prompts: holdout, configVersion: version, concurrency, + client: realClient(), evalModel: EVAL_MODEL, referenceDir: REFERENCE_DIR, + outDir: join(store.root, "holdout", stamp), + }); + console.log(`holdout mean for v${version}: ${summary.mean_overall.toFixed(1)}`); + break; + } + default: + console.error("usage: bun src/cli.ts [options]"); + process.exit(1); + } +} +main(); diff --git a/src/orchestrator.ts b/src/orchestrator.ts new file mode 100644 index 0000000..74cfed8 --- /dev/null +++ b/src/orchestrator.ts @@ -0,0 +1,112 @@ +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { BASELINE_CONFIG } from "./config/schema"; +import { resolveHarness } from "./config/resolver"; +import { runPromptPipeline } from "./inner/pipeline"; +import { aggregate } from "./outer/aggregate"; +import { mutateConfig } from "./outer/mutate"; +import { pLimit } from "./util/concurrency"; +import type { LlmClient } from "./llm"; +import type { PromptSpec } from "./prompts"; +import type { RunStore, IterationSummary } from "./store/run-store"; + +export async function runLoop(opts: { + store: RunStore; + prompts: PromptSpec[]; + iterations: number; + concurrency: number; + client: LlmClient; + evalModel: string; + referenceDir: string; + startIteration?: number; +}): Promise { + const { store, prompts, client, evalModel, referenceDir, concurrency } = opts; + if (prompts.some((p) => p.split !== "train")) throw new Error("runLoop accepts train prompts only"); + + if (store.listConfigVersions().length === 0) store.saveConfig(BASELINE_CONFIG); + const completed = store.completedIterations(); + const start = opts.startIteration ?? (completed.length ? Math.max(...completed) + 1 : 1); + + for (let iter = start; iter < start + opts.iterations; iter++) { + // The config to evaluate this iteration: the newest saved config (last mutation's proposal, or baseline). + const versions = store.listConfigVersions(); + const configVersion = Math.max(...versions); + const config = store.loadConfig(configVersion); + + const iterDir = store.iterationDir(iter); + writeFileSync(join(iterDir, "config-version.txt"), String(configVersion)); + const resolved = resolveHarness(config, join(iterDir, "resolved")); + + console.log(`[iter ${iter}] config v${configVersion} — building ${prompts.length} prompts…`); + const limit = pLimit(concurrency); + const outcomes = await Promise.all( + prompts.map((prompt) => + limit(() => + runPromptPipeline({ + resolved, prompt, promptDir: store.promptDir(iter, prompt.id), + client, evalModel, referenceDir, + }).then((o) => { console.log(`[iter ${iter}] ${prompt.id}: ${o.status} ${o.overall}`); return o; }), + ), + ), + ); + + const summary = aggregate(iter, configVersion, outcomes); + const prevBest = store.bestVersion(); + store.appendHistory({ + iteration: iter, + config_version: configVersion, + mean_overall: summary.mean_overall, + best_version: summary.mean_overall > prevBest.score ? configVersion : prevBest.version, + best_score: Math.max(summary.mean_overall, prevBest.score), + }); + + const best = store.bestVersion(); + const bestConfig = store.loadConfig(best.version); + const historyEntries = store.readHistory(); + const pastRationales = store.listConfigVersions().map((v) => { + const c = store.loadConfig(v); + const scored = historyEntries.find((h) => h.config_version === v); + return { version: v, rationale: c.rationale, mean_overall: scored ? scored.mean_overall : null }; + }); + + console.log(`[iter ${iter}] mean ${summary.mean_overall.toFixed(1)} (best v${best.version}=${best.score.toFixed(1)}) — mutating…`); + const next = await mutateConfig({ + client, model: evalModel, bestConfig, latestSummary: summary, + history: historyEntries, pastRationales, nextVersion: store.nextConfigVersion(), + }); + store.saveConfig(next); + summary.mutator_rationale = next.rationale; + store.saveSummary(summary); + } +} + +export async function runHoldout(opts: { + store: RunStore; + prompts: PromptSpec[]; + configVersion: number; + concurrency: number; + client: LlmClient; + evalModel: string; + referenceDir: string; + outDir: string; +}): Promise { + const config = opts.store.loadConfig(opts.configVersion); + mkdirSync(opts.outDir, { recursive: true }); + const resolved = resolveHarness(config, join(opts.outDir, "resolved")); + const limit = pLimit(opts.concurrency); + const outcomes = await Promise.all( + opts.prompts.map((prompt) => + limit(() => { + const promptDir = join(opts.outDir, "prompts", prompt.id); + mkdirSync(join(promptDir, "workspace"), { recursive: true }); + return runPromptPipeline({ + resolved, prompt, promptDir, + client: opts.client, evalModel: opts.evalModel, referenceDir: opts.referenceDir, + }); + }), + ), + ); + const summary = aggregate(0, opts.configVersion, outcomes); + writeFileSync(join(opts.outDir, "summary.json"), JSON.stringify(summary, null, 2)); + return summary; +} diff --git a/tests/orchestrator.test.ts b/tests/orchestrator.test.ts new file mode 100644 index 0000000..40225a2 --- /dev/null +++ b/tests/orchestrator.test.ts @@ -0,0 +1,75 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { runLoop, runHoldout } from "../src/orchestrator"; +import { RunStore } from "../src/store/run-store"; +import { BASELINE_CONFIG } from "../src/config/schema"; +import type { LlmClient } from "../src/llm"; + +const PAGE = `

P

${"

c

".repeat(40)}`; +const EVAL = { + subscores: { hierarchy: 6, typography: 6, spacing: 6, color_contrast: 6, requirement_coverage: 8, polish: 5 }, + overall: 62, vs_reference: "behind", diff_dimensions: [], critique: "ok", +}; + +function fakeClient(): LlmClient { + return { + messages: { + create: async (params: any) => { + const isMutate = JSON.stringify(params.tools).includes("propose_config"); + if (isMutate) { + return { content: [{ type: "tool_use", name: "propose_config", input: { ...BASELINE_CONFIG, rationale: "tweak", version: 0, parent_version: null } }] }; + } + return { content: [{ type: "tool_use", name: "submit_evaluation", input: EVAL }] }; + }, + }, + }; +} + +// References are optional / deferred — no reference PNGs are written in setup. +// The loop must work reference-free: the pipeline tolerates an empty reference dir +// and scores rubric-only, so outcomes are still `ok` with the fake eval's overall score. +function setup() { + const base = mkdtempSync(join(tmpdir(), "orch-")); + const stub = join(base, "pi.sh"); + writeFileSync(stub, `#!/bin/bash\nprintf '%s' '${PAGE}' > output.html\n`); + chmodSync(stub, 0o755); + process.env.PI_BIN = stub; + const refDir = join(base, "reference"); // exists but empty — reference-free + mkdirSync(refDir, { recursive: true }); + const store = new RunStore(join(base, "runs"), "test-run"); + store.initRun({}); + return { base, refDir, store }; +} +const P = (id: string, split: "train" | "holdout") => ({ id, category: "c", split, prompt: "make page " + id }); + +test("two iterations: configs, summaries, history, best tracking", async () => { + const { refDir, store } = setup(); + await runLoop({ + store, prompts: [P("a", "train"), P("b", "train")], iterations: 2, concurrency: 2, + client: fakeClient(), evalModel: "m", referenceDir: refDir, + }); + expect(store.completedIterations()).toEqual([1, 2]); + expect(store.readHistory().length).toBe(2); + expect(store.listConfigVersions()).toEqual([0, 1, 2]); // baseline + 2 proposals + expect(store.bestVersion().score).toBeCloseTo(62); + const s = store.loadSummaries()[0]; + expect(s.mutator_rationale).toBe("tweak"); + expect(existsSync(join(store.root, "iterations", "1", "config-version.txt"))).toBe(true); +}, 120000); + +test("holdout writes report without touching history", async () => { + const { refDir, store, base } = setup(); + await runLoop({ + store, prompts: [P("a", "train")], iterations: 1, concurrency: 1, + client: fakeClient(), evalModel: "m", referenceDir: refDir, + }); + const before = store.readHistory().length; + const summary = await runHoldout({ + store, prompts: [P("h1", "holdout")], configVersion: 0, concurrency: 1, + client: fakeClient(), evalModel: "m", referenceDir: refDir, outDir: join(base, "runs", "test-run", "holdout", "t1"), + }); + expect(summary.mean_overall).toBeCloseTo(62); + expect(store.readHistory().length).toBe(before); // unchanged +}, 120000); From bd6f8a08fd0032ccbe33aa8612f8433e34746c81 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 17:09:13 -0700 Subject: [PATCH 20/28] docs: reference-free usage; document deferred reference generator Co-Authored-By: Claude Fable 5 --- README.md | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bbe0073..ea276d9 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,40 @@ Automated research loop that evolves a Pi harness config for better UI/UX output. See `docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md`. +Each iteration builds one self-contained landing page per train prompt (via the +`pi` CLI), screenshots it in scrolled viewport segments (Playwright), and scores +it with an Opus vision call against a fixed rubric. The best-scoring harness +config is then mutated (schema-constrained) into the next candidate. All state +lives under `runs/`. + +## Status + +- **Reference comparison is deferred (TBD).** The evaluator currently scores on + the rubric alone. The reference-page generator (`src/reference/build-reference.ts`, + `bun run reference`) is implemented and tested but not wired into the loop; when + a reference exists for a prompt the evaluator will use it, but none are required. + +## Setup + +```bash +bun install +bunx playwright install chromium +export ANTHROPIC_API_KEY=sk-ant-... # needed for evaluate + mutate (and reference, if used) +# `pi` must be on PATH (the builder shells out to it). Override with PI_BIN=/path/to/pi. +``` + ## Commands -- `bun run reference` — build the fixed reference pages (one-time, needs ANTHROPIC_API_KEY + pi). -- `bun run loop --iterations N [--run-id X] [--concurrency 5]` — run the research loop. -- `bun run holdout --run-id X [--version V]` — score a config on holdout prompts (report only). -- `bun run resume --run-id X` — continue an interrupted run. +- `bun run loop --iterations N [--run-id X] [--concurrency 5]` — run the research loop over the train prompts. +- `bun run holdout --run-id X [--version V]` — score a config on holdout prompts (report only; never mutates or touches history). +- `bun run resume --run-id X` — continue an interrupted run from the last completed iteration. +- `bun run reference [--force] [--concurrency 3]` — (optional, TBD) build cached reference pages for every prompt with a fixed high-effort Opus harness. + +Environment overrides: `EVAL_MODEL` (default `claude-opus-4-8`) for the evaluator/mutator model; `PI_BIN` for the builder CLI. + +## Tests + +```bash +bun test # full suite (uses stubbed pi + fake LLM clients + real Playwright) +bunx tsc --noEmit # type-check +``` From ed8ea4b7dc9d818c7047202d34687a23b8cd0d76 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 17:18:48 -0700 Subject: [PATCH 21/28] Fix reference-free path crash and orchestrator resume idempotency referenceSegments() threw ENOENT when runs/reference/ was absent, which was silently caught by runPromptPipeline and turned every prompt into eval_failed / overall 0 on a fresh checkout. Guard with existsSync and return [] (already handled downstream as reference-free eval). runLoop's resume path could duplicate a history row and re-evaluate a different config than the crashed attempt if the process died between appendHistory/saveConfig and saveSummary. Pin the eval config to an existing iteration's config-version.txt when present, and skip appendHistory when a row for that iteration already exists. Also drops a dead .gitignore negation for the no-longer-required runs/reference/ dir, and locks in the all-eval_failed aggregate() edge case with a test. Co-Authored-By: Claude Fable 5 --- .gitignore | 1 - src/inner/pipeline.ts | 3 ++- src/orchestrator.ts | 39 ++++++++++++++++++++++++-------------- tests/aggregate.test.ts | 9 +++++++++ tests/orchestrator.test.ts | 34 ++++++++++++++++++++++++++++++++- tests/pipeline.test.ts | 21 +++++++++++++++++++- 6 files changed, 89 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index f47607e..7ed43d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ node_modules/ runs/ -!runs/reference/.gitkeep *.log .env diff --git a/src/inner/pipeline.ts b/src/inner/pipeline.ts index 0bb651c..077ca58 100644 --- a/src/inner/pipeline.ts +++ b/src/inner/pipeline.ts @@ -1,4 +1,4 @@ -import { readdirSync, writeFileSync } from "node:fs"; +import { existsSync, readdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import type { ResolvedHarness } from "../config/resolver"; import type { PromptSpec } from "../prompts"; @@ -9,6 +9,7 @@ import { screenshotPage } from "./screenshot"; import { evaluatePage } from "./evaluate"; export function referenceSegments(referenceDir: string, promptId: string): string[] { + if (!existsSync(referenceDir)) return []; const re = new RegExp(`^${promptId}\\.desktop\\.(\\d+)\\.png$`); return readdirSync(referenceDir) .map((f) => ({ f, m: f.match(re) })) diff --git a/src/orchestrator.ts b/src/orchestrator.ts index 74cfed8..838c962 100644 --- a/src/orchestrator.ts +++ b/src/orchestrator.ts @@ -1,4 +1,4 @@ -import { mkdirSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { BASELINE_CONFIG } from "./config/schema"; import { resolveHarness } from "./config/resolver"; @@ -28,13 +28,21 @@ export async function runLoop(opts: { const start = opts.startIteration ?? (completed.length ? Math.max(...completed) + 1 : 1); for (let iter = start; iter < start + opts.iterations; iter++) { - // The config to evaluate this iteration: the newest saved config (last mutation's proposal, or baseline). - const versions = store.listConfigVersions(); - const configVersion = Math.max(...versions); - const config = store.loadConfig(configVersion); - + // The config to evaluate this iteration: normally the newest saved config (last mutation's + // proposal, or baseline). But if a prior attempt at this iteration already wrote + // config-version.txt (crashed before saveSummary), pin to that same version so a resume + // re-evaluates the identical config rather than a newer one saved by the partial attempt. const iterDir = store.iterationDir(iter); - writeFileSync(join(iterDir, "config-version.txt"), String(configVersion)); + const configVersionFile = join(iterDir, "config-version.txt"); + let configVersion: number; + if (existsSync(configVersionFile)) { + configVersion = Number(readFileSync(configVersionFile, "utf8")); + } else { + const versions = store.listConfigVersions(); + configVersion = Math.max(...versions); + writeFileSync(configVersionFile, String(configVersion)); + } + const config = store.loadConfig(configVersion); const resolved = resolveHarness(config, join(iterDir, "resolved")); console.log(`[iter ${iter}] config v${configVersion} — building ${prompts.length} prompts…`); @@ -52,13 +60,16 @@ export async function runLoop(opts: { const summary = aggregate(iter, configVersion, outcomes); const prevBest = store.bestVersion(); - store.appendHistory({ - iteration: iter, - config_version: configVersion, - mean_overall: summary.mean_overall, - best_version: summary.mean_overall > prevBest.score ? configVersion : prevBest.version, - best_score: Math.max(summary.mean_overall, prevBest.score), - }); + const alreadyRecorded = store.readHistory().some((h) => h.iteration === iter); + if (!alreadyRecorded) { + store.appendHistory({ + iteration: iter, + config_version: configVersion, + mean_overall: summary.mean_overall, + best_version: summary.mean_overall > prevBest.score ? configVersion : prevBest.version, + best_score: Math.max(summary.mean_overall, prevBest.score), + }); + } const best = store.bestVersion(); const bestConfig = store.loadConfig(best.version); diff --git a/tests/aggregate.test.ts b/tests/aggregate.test.ts index cd9e04f..35b0b40 100644 --- a/tests/aggregate.test.ts +++ b/tests/aggregate.test.ts @@ -22,3 +22,12 @@ test("means: failures are 0, eval_failed excluded", () => { expect(s.mean_overall).toBeCloseTo((80 + 60 + 0) / 3); expect(s.dimension_means.hierarchy).toBeCloseTo(7); }); + +test("all eval_failed: mean_overall is 0 and dimension_means is empty", () => { + const s = aggregate(1, 0, [ + { prompt_id: "a", status: "eval_failed", overall: 0, error: "api" }, + { prompt_id: "b", status: "eval_failed", overall: 0, error: "api" }, + ]); + expect(s.mean_overall).toBe(0); + expect(s.dimension_means).toEqual({}); +}); diff --git a/tests/orchestrator.test.ts b/tests/orchestrator.test.ts index 40225a2..97c64f8 100644 --- a/tests/orchestrator.test.ts +++ b/tests/orchestrator.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test"; -import { mkdtempSync, writeFileSync, chmodSync, mkdirSync, existsSync } from "node:fs"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync, existsSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { runLoop, runHoldout } from "../src/orchestrator"; @@ -59,6 +59,38 @@ test("two iterations: configs, summaries, history, best tracking", async () => { expect(existsSync(join(store.root, "iterations", "1", "config-version.txt"))).toBe(true); }, 120000); +test("resume after mid-iteration crash does not duplicate history or re-pick config", async () => { + const { refDir, store } = setup(); + await runLoop({ + store, prompts: [P("a", "train"), P("b", "train")], iterations: 1, concurrency: 2, + client: fakeClient(), evalModel: "m", referenceDir: refDir, + }); + + expect(store.readHistory().length).toBe(1); + expect(store.completedIterations()).toEqual([1]); + const configVersionFile = join(store.root, "iterations", "1", "config-version.txt"); + const pinnedVersionBefore = readFileSync(configVersionFile, "utf8"); + expect(store.listConfigVersions()).toEqual([0, 1]); + + // Simulate a crash that happened after appendHistory/saveConfig but before saveSummary: + // delete iteration 1's summary.json so completedIterations() no longer counts it, while + // leaving its history row and config-version.txt in place. + rmSync(join(store.root, "iterations", "1", "summary.json")); + expect(store.completedIterations()).toEqual([]); + + // Resume: startIteration defaults to last-completed+1 == 1 again. + await runLoop({ + store, prompts: [P("a", "train"), P("b", "train")], iterations: 1, concurrency: 2, + client: fakeClient(), evalModel: "m", referenceDir: refDir, + }); + + const history = store.readHistory(); + expect(history.filter((h) => h.iteration === 1).length).toBe(1); // no duplicate row + const pinnedVersionAfter = readFileSync(configVersionFile, "utf8"); + expect(pinnedVersionAfter).toBe(pinnedVersionBefore); // pinned to the same config version + expect(store.completedIterations()).toEqual([1]); +}, 120000); + test("holdout writes report without touching history", async () => { const { refDir, store, base } = setup(); await runLoop({ diff --git a/tests/pipeline.test.ts b/tests/pipeline.test.ts index 90f66c4..6dcfe56 100644 --- a/tests/pipeline.test.ts +++ b/tests/pipeline.test.ts @@ -2,7 +2,7 @@ import { expect, test } from "bun:test"; import { mkdtempSync, writeFileSync, chmodSync, mkdirSync, existsSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { runPromptPipeline } from "../src/inner/pipeline"; +import { runPromptPipeline, referenceSegments } from "../src/inner/pipeline"; import { pLimit } from "../src/util/concurrency"; import { resolveHarness } from "../src/config/resolver"; import { BASELINE_CONFIG } from "../src/config/schema"; @@ -54,6 +54,25 @@ test("reference-free path: no reference segments still produces ok outcome", asy expect(existsSync(join(promptDir, "eval.json"))).toBe(true); }, 60000); +test("missing reference dir: referenceSegments returns [] without throwing", () => { + const { base } = setup(); + expect(referenceSegments(join(base, "nope"), "x")).toEqual([]); +}); + +test("missing reference dir: runPromptPipeline still produces ok outcome", async () => { + const { base, promptDir, client } = setup(); + const resolved = resolveHarness(BASELINE_CONFIG, join(promptDir, "resolved")); + const missingRefDir = join(base, "does-not-exist"); + expect(existsSync(missingRefDir)).toBe(false); + const out = await runPromptPipeline({ + resolved, prompt: { id: "t1", category: "c", split: "train", prompt: "x" }, + promptDir, client, evalModel: "m", referenceDir: missingRefDir, + }); + expect(out.status).toBe("ok"); + expect(out.overall).toBe(62); + expect(existsSync(join(promptDir, "eval.json"))).toBe(true); +}, 60000); + test("pLimit caps concurrency", async () => { const limit = pLimit(2); let active = 0, peak = 0; From e258d28b074daa44eeb28e3452e195b73e716c05 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 17:53:14 -0700 Subject: [PATCH 22/28] Tolerate mutator flattening model to a string or omitting thinking_level The mutator LLM sometimes returns model as a bare string or without thinking_level, which crashed the whole run after 3 forcedToolCall retries. Normalize both shapes to the canonical object before validation; the emitted JSON schema stays strict so the tool still guides the model correctly. Co-Authored-By: Claude Fable 5 --- src/config/schema.ts | 22 ++++++++++++++++++---- tests/config-schema.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/config/schema.ts b/src/config/schema.ts index ddb2c6d..e7421e1 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -16,14 +16,28 @@ export const SubagentSchema = z.object({ tools: z.array(z.enum(ALLOWED_TOOLS)), }); +export const ModelSchema = z.object({ + name: z.string().min(1), + thinking_level: z.enum(THINKING_LEVELS), +}); + +// The mutator LLM sometimes flattens `model` to a bare string (e.g. "anthropic/claude-sonnet-4-6") +// or omits thinking_level. Normalize those into the canonical object before validation so a single +// stray shape doesn't crash a whole run. z.toJSONSchema still emits the strict object, so the tool +// schema keeps guiding the model toward the correct shape. +const ModelField = z.preprocess((v) => { + if (typeof v === "string") return { name: v, thinking_level: "medium" }; + if (v && typeof v === "object" && !Array.isArray(v) && !("thinking_level" in (v as Record))) { + return { thinking_level: "medium", ...(v as Record) }; + } + return v; +}, ModelSchema); + export const HarnessConfigSchema = z.object({ version: z.number().int().nonnegative(), parent_version: z.number().int().nonnegative().nullable(), rationale: z.string(), - model: z.object({ - name: z.string().min(1), - thinking_level: z.enum(THINKING_LEVELS), - }), + model: ModelField, tools: z.array(z.enum(ALLOWED_TOOLS)).min(1), system_instructions: z.string().min(1), skills: z.array(SkillSchema).max(8), diff --git a/tests/config-schema.test.ts b/tests/config-schema.test.ts index b7f04a4..7f68bc0 100644 --- a/tests/config-schema.test.ts +++ b/tests/config-schema.test.ts @@ -18,3 +18,25 @@ test("round-trips through JSON", () => { const parsed = HarnessConfigSchema.parse(JSON.parse(JSON.stringify(BASELINE_CONFIG))); expect(parsed).toEqual(BASELINE_CONFIG); }); + +test("normalizes model given as a bare string (mutator flattening)", () => { + const flattened = { ...BASELINE_CONFIG, model: "anthropic/claude-sonnet-4-6" }; + const parsed = HarnessConfigSchema.parse(flattened); + expect(parsed.model).toEqual({ name: "anthropic/claude-sonnet-4-6", thinking_level: "medium" }); +}); + +test("normalizes model object missing thinking_level", () => { + const partial = { ...BASELINE_CONFIG, model: { name: "anthropic/claude-opus-4-8" } }; + const parsed = HarnessConfigSchema.parse(partial); + expect(parsed.model).toEqual({ name: "anthropic/claude-opus-4-8", thinking_level: "medium" }); +}); + +test("preserves an explicit thinking_level", () => { + const explicit = { ...BASELINE_CONFIG, model: { name: "x", thinking_level: "high" } }; + expect(HarnessConfigSchema.parse(explicit).model).toEqual({ name: "x", thinking_level: "high" }); +}); + +test("still rejects a model with an invalid thinking_level", () => { + const bad = { ...BASELINE_CONFIG, model: { name: "x", thinking_level: "ultra" } }; + expect(() => HarnessConfigSchema.parse(bad)).toThrow(); +}); From c76c78d399b16fa5bba2aea02107f2b934aad9a1 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 18:10:30 -0700 Subject: [PATCH 23/28] Harden builder model: allowlist + sanitize; add --model to pin a run's model A mutator config leaked tool-call markup into model.name, so pi got a garbage model and every build failed. Constrain model.name to a verified allowlist, strip embedded markup, and coerce unknown names to the default. Add a --model CLI flag (and runLoop builderModel) that pins the builder model across seed and mutations so an experiment stays on the chosen model. Co-Authored-By: Claude Fable 5 --- src/cli.ts | 18 ++++++++++++---- src/config/schema.ts | 41 ++++++++++++++++++++++++++++--------- src/orchestrator.ts | 14 +++++++++---- tests/config-schema.test.ts | 24 ++++++++++++++++------ tests/orchestrator.test.ts | 14 +++++++++++++ 5 files changed, 87 insertions(+), 24 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 6fa30bd..37e3bb9 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,6 +1,7 @@ import { parseArgs } from "node:util"; import { join } from "node:path"; import { loadPrompts, trainPrompts, holdoutPrompts } from "./prompts"; +import { ALLOWED_MODELS } from "./config/schema"; import { RunStore } from "./store/run-store"; import { realClient } from "./llm"; import { runLoop, runHoldout } from "./orchestrator"; @@ -19,11 +20,19 @@ const { values } = parseArgs({ concurrency: { type: "string", default: "5" }, version: { type: "string" }, force: { type: "boolean", default: false }, + limit: { type: "string" }, + model: { type: "string" }, }, }); const all = loadPrompts(); const concurrency = Number(values.concurrency); +const limit = values.limit ? Number(values.limit) : undefined; +const builderModel = values.model; +if (builderModel && !(ALLOWED_MODELS as readonly string[]).includes(builderModel)) { + console.error(`--model must be one of: ${ALLOWED_MODELS.join(", ")}`); + process.exit(1); +} async function main() { switch (command) { @@ -41,13 +50,14 @@ async function main() { case "resume": { const runId = values["run-id"] ?? `run-${new Date().toISOString().slice(0, 16).replace(/[:T]/g, "-")}`; if (command === "resume" && !values["run-id"]) throw new Error("resume requires --run-id"); - const train = trainPrompts(all); + const train = limit ? trainPrompts(all).slice(0, limit) : trainPrompts(all); const store = new RunStore(RUNS_DIR, runId); - store.initRun({ eval_model: EVAL_MODEL, concurrency }); - console.log(`run: ${runId}`); + store.initRun({ eval_model: EVAL_MODEL, concurrency, prompt_count: train.length, builder_model: builderModel ?? "(baseline default)" }); + console.log(`run: ${runId} — ${train.length} train prompt(s): ${train.map((p) => p.id).join(", ")}${builderModel ? ` — builder model: ${builderModel}` : ""}`); await runLoop({ store, prompts: train, iterations: Number(values.iterations), concurrency, client: realClient(), evalModel: EVAL_MODEL, referenceDir: REFERENCE_DIR, + builderModel, }); const best = store.bestVersion(); console.log(`done. best config: v${best.version} (mean ${best.score.toFixed(1)})`); @@ -68,7 +78,7 @@ async function main() { break; } default: - console.error("usage: bun src/cli.ts [options]"); + console.error("usage: bun src/cli.ts [--iterations N] [--limit N] [--concurrency N] [--run-id X] [--version V]"); process.exit(1); } } diff --git a/src/config/schema.ts b/src/config/schema.ts index e7421e1..e4f7d91 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -16,21 +16,42 @@ export const SubagentSchema = z.object({ tools: z.array(z.enum(ALLOWED_TOOLS)), }); +// Builder model must be a string `pi` actually accepts. Only allowlisted names are permitted; the +// mutator cannot invent model ids. Extend this list once other `pi` model strings are verified to build. +export const DEFAULT_MODEL = "anthropic/claude-sonnet-4-6"; +// Verified against `pi --list-models` — these exact strings resolve for the builder subprocess. +export const ALLOWED_MODELS = [ + "anthropic/claude-sonnet-4-6", + "anthropic/claude-haiku-4-5", + "anthropic/claude-opus-4-8", +] as const; + export const ModelSchema = z.object({ - name: z.string().min(1), + name: z.enum(ALLOWED_MODELS), thinking_level: z.enum(THINKING_LEVELS), }); -// The mutator LLM sometimes flattens `model` to a bare string (e.g. "anthropic/claude-sonnet-4-6") -// or omits thinking_level. Normalize those into the canonical object before validation so a single -// stray shape doesn't crash a whole run. z.toJSONSchema still emits the strict object, so the tool -// schema keeps guiding the model toward the correct shape. +// The mutator LLM misbehaves in two observed ways: (1) it flattens `model` to a bare string or omits +// thinking_level; (2) it leaks tool-call markup into the value, e.g. `\nanthropic/...`, +// which `pi` then rejects as an unknown model and every build fails. Normalize before validation: strip any +// embedded markup/whitespace, keep the name only if it is allowlisted, otherwise fall back to DEFAULT_MODEL, +// and default a missing thinking_level to medium. This keeps one stray shape from crashing or wedging a run. +const cleanModelName = (n: unknown): (typeof ALLOWED_MODELS)[number] => { + const s = typeof n === "string" ? n.replace(/<[^>]*>/g, "").trim() : ""; + return (ALLOWED_MODELS as readonly string[]).includes(s) ? (s as (typeof ALLOWED_MODELS)[number]) : DEFAULT_MODEL; +}; + const ModelField = z.preprocess((v) => { - if (typeof v === "string") return { name: v, thinking_level: "medium" }; - if (v && typeof v === "object" && !Array.isArray(v) && !("thinking_level" in (v as Record))) { - return { thinking_level: "medium", ...(v as Record) }; - } - return v; + const obj = + typeof v === "string" + ? { name: v } + : v && typeof v === "object" && !Array.isArray(v) + ? { ...(v as Record) } + : {}; + return { + name: cleanModelName((obj as Record).name), + thinking_level: "thinking_level" in obj ? (obj as Record).thinking_level : "medium", + }; }, ModelSchema); export const HarnessConfigSchema = z.object({ diff --git a/src/orchestrator.ts b/src/orchestrator.ts index 838c962..fce092c 100644 --- a/src/orchestrator.ts +++ b/src/orchestrator.ts @@ -1,6 +1,6 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; -import { BASELINE_CONFIG } from "./config/schema"; +import { BASELINE_CONFIG, HarnessConfigSchema } from "./config/schema"; import { resolveHarness } from "./config/resolver"; import { runPromptPipeline } from "./inner/pipeline"; import { aggregate } from "./outer/aggregate"; @@ -19,11 +19,17 @@ export async function runLoop(opts: { evalModel: string; referenceDir: string; startIteration?: number; + builderModel?: string; }): Promise { - const { store, prompts, client, evalModel, referenceDir, concurrency } = opts; + const { store, prompts, client, evalModel, referenceDir, concurrency, builderModel } = opts; if (prompts.some((p) => p.split !== "train")) throw new Error("runLoop accepts train prompts only"); - if (store.listConfigVersions().length === 0) store.saveConfig(BASELINE_CONFIG); + // Optionally pin the builder model for the whole run (an experiment parameter, not something the + // mutator controls) so the run stays on the chosen model regardless of what the mutator proposes. + const pinModel = (c: typeof BASELINE_CONFIG) => + builderModel ? HarnessConfigSchema.parse({ ...c, model: { ...c.model, name: builderModel } }) : c; + + if (store.listConfigVersions().length === 0) store.saveConfig(pinModel(BASELINE_CONFIG)); const completed = store.completedIterations(); const start = opts.startIteration ?? (completed.length ? Math.max(...completed) + 1 : 1); @@ -85,7 +91,7 @@ export async function runLoop(opts: { client, model: evalModel, bestConfig, latestSummary: summary, history: historyEntries, pastRationales, nextVersion: store.nextConfigVersion(), }); - store.saveConfig(next); + store.saveConfig(pinModel(next)); summary.mutator_rationale = next.rationale; store.saveSummary(summary); } diff --git a/tests/config-schema.test.ts b/tests/config-schema.test.ts index 7f68bc0..5fa504b 100644 --- a/tests/config-schema.test.ts +++ b/tests/config-schema.test.ts @@ -25,18 +25,30 @@ test("normalizes model given as a bare string (mutator flattening)", () => { expect(parsed.model).toEqual({ name: "anthropic/claude-sonnet-4-6", thinking_level: "medium" }); }); -test("normalizes model object missing thinking_level", () => { - const partial = { ...BASELINE_CONFIG, model: { name: "anthropic/claude-opus-4-8" } }; +test("defaults a missing thinking_level to medium", () => { + const partial = { ...BASELINE_CONFIG, model: { name: "anthropic/claude-sonnet-4-6" } }; const parsed = HarnessConfigSchema.parse(partial); - expect(parsed.model).toEqual({ name: "anthropic/claude-opus-4-8", thinking_level: "medium" }); + expect(parsed.model).toEqual({ name: "anthropic/claude-sonnet-4-6", thinking_level: "medium" }); }); test("preserves an explicit thinking_level", () => { - const explicit = { ...BASELINE_CONFIG, model: { name: "x", thinking_level: "high" } }; - expect(HarnessConfigSchema.parse(explicit).model).toEqual({ name: "x", thinking_level: "high" }); + const explicit = { ...BASELINE_CONFIG, model: { name: "anthropic/claude-sonnet-4-6", thinking_level: "high" } }; + expect(HarnessConfigSchema.parse(explicit).model).toEqual({ name: "anthropic/claude-sonnet-4-6", thinking_level: "high" }); +}); + +test("recovers the real model name from leaked tool-call markup", () => { + // Exact corruption observed from the mutator that broke every build in a run. + const corrupted = { ...BASELINE_CONFIG, model: { name: '\nanthropic/claude-sonnet-4-6', thinking_level: "medium" } }; + const parsed = HarnessConfigSchema.parse(corrupted); + expect(parsed.model.name).toBe("anthropic/claude-sonnet-4-6"); +}); + +test("coerces an unknown/unrunnable model name to the default", () => { + const unknown = { ...BASELINE_CONFIG, model: { name: "gpt-4o-mega", thinking_level: "medium" } }; + expect(HarnessConfigSchema.parse(unknown).model.name).toBe("anthropic/claude-sonnet-4-6"); }); test("still rejects a model with an invalid thinking_level", () => { - const bad = { ...BASELINE_CONFIG, model: { name: "x", thinking_level: "ultra" } }; + const bad = { ...BASELINE_CONFIG, model: { name: "anthropic/claude-sonnet-4-6", thinking_level: "ultra" } }; expect(() => HarnessConfigSchema.parse(bad)).toThrow(); }); diff --git a/tests/orchestrator.test.ts b/tests/orchestrator.test.ts index 97c64f8..949460a 100644 --- a/tests/orchestrator.test.ts +++ b/tests/orchestrator.test.ts @@ -91,6 +91,20 @@ test("resume after mid-iteration crash does not duplicate history or re-pick con expect(store.completedIterations()).toEqual([1]); }, 120000); +test("builderModel pins the model across seed and mutations", async () => { + const { refDir, store } = setup(); + await runLoop({ + store, prompts: [P("a", "train")], iterations: 2, concurrency: 1, + client: fakeClient(), evalModel: "m", referenceDir: refDir, + builderModel: "anthropic/claude-haiku-4-5", + }); + // seeded baseline (v0) and every mutated config must carry the pinned model, even though the + // fake mutator proposes a config with the default sonnet model. + for (const v of store.listConfigVersions()) { + expect(store.loadConfig(v).model.name).toBe("anthropic/claude-haiku-4-5"); + } +}, 120000); + test("holdout writes report without touching history", async () => { const { refDir, store, base } = setup(); await runLoop({ From 943058e5a3df29da0b5fc49eecd6b2f937e0419f Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 18:19:11 -0700 Subject: [PATCH 24/28] Make the outer loop agentic: Pi + Fable mutator that inspects artifacts Replaces the one-shot Opus mutator with an agentic Pi session (default Fable). It reads the iteration's candidate + reference screenshots and generated HTML, then authors the next genome (skills, subagents, system instructions, tools, thinking level) as next-config.json, which is schema-validated with version lineage pinned. MUTATOR_MODEL overrides the model. Tests stub pi to serve both builder and mutator roles. Co-Authored-By: Claude Fable 5 --- README.md | 4 +- src/cli.ts | 7 +- src/orchestrator.ts | 38 ++++++-- src/outer/mutate.ts | 186 +++++++++++++++++++++++++++---------- tests/mutate.test.ts | 94 +++++++++++++++---- tests/orchestrator.test.ts | 27 +++--- 6 files changed, 269 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index ea276d9..c2bce3f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,9 @@ export ANTHROPIC_API_KEY=sk-ant-... # needed for evaluate + mutate (and refer - `bun run resume --run-id X` — continue an interrupted run from the last completed iteration. - `bun run reference [--force] [--concurrency 3]` — (optional, TBD) build cached reference pages for every prompt with a fixed high-effort Opus harness. -Environment overrides: `EVAL_MODEL` (default `claude-opus-4-8`) for the evaluator/mutator model; `PI_BIN` for the builder CLI. +The outer loop's mutator is itself an agentic Pi session (default model `anthropic/claude-fable-5`): it inspects the iteration's candidate + reference screenshots and the generated HTML, then authors the next genome (skills, subagents, system instructions, tools, thinking level) as a validated `next-config.json`. + +Environment overrides: `EVAL_MODEL` (default `claude-opus-4-8`) for the vision evaluator; `MUTATOR_MODEL` (default `anthropic/claude-fable-5`) for the agentic outer-loop mutator; `PI_BIN` for the builder/mutator CLI. `--model ` pins the builder model for a run (allowlist: `anthropic/claude-sonnet-4-6`, `anthropic/claude-haiku-4-5`, `anthropic/claude-opus-4-8`); `--limit N` restricts to the first N train prompts. ## Tests diff --git a/src/cli.ts b/src/cli.ts index 37e3bb9..ec75360 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -10,6 +10,7 @@ import { buildReferenceSet } from "./reference/build-reference"; const RUNS_DIR = "runs"; const REFERENCE_DIR = join(RUNS_DIR, "reference"); const EVAL_MODEL = process.env.EVAL_MODEL ?? "claude-opus-4-8"; +const MUTATOR_MODEL = process.env.MUTATOR_MODEL ?? "anthropic/claude-fable-5"; const [command] = Bun.argv.slice(2); const { values } = parseArgs({ @@ -52,12 +53,12 @@ async function main() { if (command === "resume" && !values["run-id"]) throw new Error("resume requires --run-id"); const train = limit ? trainPrompts(all).slice(0, limit) : trainPrompts(all); const store = new RunStore(RUNS_DIR, runId); - store.initRun({ eval_model: EVAL_MODEL, concurrency, prompt_count: train.length, builder_model: builderModel ?? "(baseline default)" }); - console.log(`run: ${runId} — ${train.length} train prompt(s): ${train.map((p) => p.id).join(", ")}${builderModel ? ` — builder model: ${builderModel}` : ""}`); + store.initRun({ eval_model: EVAL_MODEL, mutator_model: MUTATOR_MODEL, concurrency, prompt_count: train.length, builder_model: builderModel ?? "(baseline default)" }); + console.log(`run: ${runId} — ${train.length} train prompt(s): ${train.map((p) => p.id).join(", ")}${builderModel ? ` — builder: ${builderModel}` : ""} — mutator: ${MUTATOR_MODEL}`); await runLoop({ store, prompts: train, iterations: Number(values.iterations), concurrency, client: realClient(), evalModel: EVAL_MODEL, referenceDir: REFERENCE_DIR, - builderModel, + builderModel, mutatorModel: MUTATOR_MODEL, }); const best = store.bestVersion(); console.log(`done. best config: v${best.version} (mean ${best.score.toFixed(1)})`); diff --git a/src/orchestrator.ts b/src/orchestrator.ts index fce092c..3d96598 100644 --- a/src/orchestrator.ts +++ b/src/orchestrator.ts @@ -1,15 +1,39 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { BASELINE_CONFIG, HarnessConfigSchema } from "./config/schema"; import { resolveHarness } from "./config/resolver"; -import { runPromptPipeline } from "./inner/pipeline"; +import { runPromptPipeline, referenceSegments } from "./inner/pipeline"; import { aggregate } from "./outer/aggregate"; -import { mutateConfig } from "./outer/mutate"; +import { mutateConfig, type MutationArtifact } from "./outer/mutate"; import { pLimit } from "./util/concurrency"; import type { LlmClient } from "./llm"; import type { PromptSpec } from "./prompts"; import type { RunStore, IterationSummary } from "./store/run-store"; +// Collect the artifact paths the agentic mutator inspects for one iteration. +function collectArtifacts(store: RunStore, iter: number, referenceDir: string, summary: IterationSummary): MutationArtifact[] { + return summary.outcomes.map((o) => { + const promptDir = store.promptDir(iter, o.prompt_id); + const candidateScreens = existsSync(promptDir) + ? readdirSync(promptDir) + .filter((f) => /^candidate\.desktop\.\d+\.png$/.test(f)) + .sort((a, b) => Number(a.match(/\d+/)![0]) - Number(b.match(/\d+/)![0])) + .map((f) => join(promptDir, f)) + : []; + const htmlPath = join(promptDir, "workspace", "output.html"); + return { + promptId: o.prompt_id, + overall: o.overall, + status: o.status, + critique: o.eval?.critique, + vsReference: o.eval?.vs_reference, + htmlPath: existsSync(htmlPath) ? htmlPath : undefined, + candidateScreens, + referenceScreens: referenceSegments(referenceDir, o.prompt_id), + }; + }); +} + export async function runLoop(opts: { store: RunStore; prompts: PromptSpec[]; @@ -20,8 +44,9 @@ export async function runLoop(opts: { referenceDir: string; startIteration?: number; builderModel?: string; + mutatorModel: string; }): Promise { - const { store, prompts, client, evalModel, referenceDir, concurrency, builderModel } = opts; + const { store, prompts, client, evalModel, referenceDir, concurrency, builderModel, mutatorModel } = opts; if (prompts.some((p) => p.split !== "train")) throw new Error("runLoop accepts train prompts only"); // Optionally pin the builder model for the whole run (an experiment parameter, not something the @@ -86,10 +111,11 @@ export async function runLoop(opts: { return { version: v, rationale: c.rationale, mean_overall: scored ? scored.mean_overall : null }; }); - console.log(`[iter ${iter}] mean ${summary.mean_overall.toFixed(1)} (best v${best.version}=${best.score.toFixed(1)}) — mutating…`); + console.log(`[iter ${iter}] mean ${summary.mean_overall.toFixed(1)} (best v${best.version}=${best.score.toFixed(1)}) — mutating (Pi ${mutatorModel})…`); const next = await mutateConfig({ - client, model: evalModel, bestConfig, latestSummary: summary, + mutatorModel, bestConfig, latestSummary: summary, history: historyEntries, pastRationales, nextVersion: store.nextConfigVersion(), + workDir: iterDir, artifacts: collectArtifacts(store, iter, referenceDir, summary), }); store.saveConfig(pinModel(next)); summary.mutator_rationale = next.rationale; diff --git a/src/outer/mutate.ts b/src/outer/mutate.ts index 2a55dd4..6ae977a 100644 --- a/src/outer/mutate.ts +++ b/src/outer/mutate.ts @@ -1,61 +1,151 @@ -import { forcedToolCall, type LlmClient } from "../llm"; +import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { z } from "zod"; import { HarnessConfigSchema, type HarnessConfig } from "../config/schema"; import type { HistoryEntry, IterationSummary } from "../store/run-store"; -const MUTATOR_SYSTEM = `You are a harness engineer optimizing an AI coding agent's configuration so it produces -better-designed landing pages. You may change: system_instructions, skills (add/edit/remove markdown skill -documents), subagents (internal review passes), tools, model thinking_level. Make ONE focused, well-motivated -change per proposal — a targeted hypothesis, not a rewrite. Ground it in the evaluation critiques and the -weakest rubric dimensions. Avoid repeating past changes that did not improve the score.`; +// Per-prompt artifacts the agentic mutator can inspect for the latest iteration. +export type MutationArtifact = { + promptId: string; + overall: number; + status: string; + critique?: string; + vsReference?: string; + htmlPath?: string; + candidateScreens: string[]; + referenceScreens: string[]; +}; + +const MUTATOR_SYSTEM = `You are a harness engineer optimizing an AI coding agent ("the builder") that generates +marketing landing pages as a single self-contained output.html. Your goal: propose the NEXT builder configuration +("genome") that will score higher on the design rubric. + +You may change any of: system_instructions (the builder's design guidance), skills (add / edit / remove markdown +SKILL.md documents — each has id, description, content), subagents (internal review passes the builder performs +before finishing — each has name, description, system_instructions, tools), tools, and model.thinking_level. +You may NOT change model.name (it is fixed for the run). + +Make ONE focused, well-motivated change per proposal — a targeted hypothesis, not a rewrite. Prefer concrete, +teachable guidance (e.g. author a skill capturing a specific technique the pages are missing) over vague +exhortations. Ground every change in what you actually SEE in the screenshots plus the evaluator critiques and the +weakest rubric dimensions. Do not repeat past changes that did not improve the score. + +Workflow: +1. Use your read tool to open the candidate screenshots and the reference screenshots (real, high-quality pages), + and the generated output.html files, to root-cause why the pages lost points. +2. Decide on one improvement. +3. WRITE the complete next configuration as JSON to a file named exactly "next-config.json" in your current working + directory (use your write tool). Copy every unchanged field from the current best config verbatim; include ALL + fields required by the schema; set a specific "rationale" explaining your hypothesis. Do not print the config to + stdout — only write the file. Do not create any other files.`; + +function mutationPrompt(opts: { + bestConfig: HarnessConfig; + latestSummary: IterationSummary; + history: HistoryEntry[]; + pastRationales: Array<{ version: number; rationale: string; mean_overall: number | null }>; + artifacts: MutationArtifact[]; +}): string { + const schema = JSON.stringify(z.toJSONSchema(HarnessConfigSchema)); + const perPrompt = opts.artifacts + .map((a) => { + const lines = [ + `- ${a.promptId} — overall ${a.overall}, status ${a.status}${a.vsReference ? `, vs_reference ${a.vsReference}` : ""}`, + a.critique ? ` critique: ${a.critique}` : "", + a.htmlPath ? ` generated html: ${a.htmlPath}` : "", + a.candidateScreens.length ? ` candidate screenshots: ${a.candidateScreens.join(", ")}` : "", + a.referenceScreens.length ? ` reference screenshots: ${a.referenceScreens.join(", ")}` : "", + ]; + return lines.filter(Boolean).join("\n"); + }) + .join("\n"); + + return [ + `## Task`, + `Derive the next builder config from the current best config (version ${opts.bestConfig.version}) and make ONE improvement grounded in the artifacts below. Write the result to next-config.json in your current directory.`, + `## JSON schema for next-config.json`, + schema, + `## Current best config (copy unchanged fields verbatim)`, + JSON.stringify(opts.bestConfig, null, 2), + `## Latest iteration (config v${opts.latestSummary.config_version}, mean ${opts.latestSummary.mean_overall.toFixed(1)})`, + `Dimension means (0-10): ${JSON.stringify(opts.latestSummary.dimension_means)}`, + `Per-prompt results and artifact paths (open these with your read tool):\n${perPrompt || "(none)"}`, + `## Score history`, + opts.history + .map((h) => `iter ${h.iteration}: v${h.config_version} → ${h.mean_overall.toFixed(1)} (best v${h.best_version}=${h.best_score.toFixed(1)})`) + .join("\n") || "(none)", + `## Past change rationales (do not repeat what did not help)`, + opts.pastRationales.map((r) => `v${r.version} (${r.mean_overall ?? "unscored"}): ${r.rationale}`).join("\n") || "(none)", + `## Now`, + `Inspect the screenshots and HTML, then write the complete next-config.json (all fields) to your current directory. version and parent_version will be overwritten for you, so their values do not matter.`, + ].join("\n\n"); +} export async function mutateConfig(opts: { - client: LlmClient; - model: string; + mutatorModel: string; bestConfig: HarnessConfig; latestSummary: IterationSummary; history: HistoryEntry[]; pastRationales: Array<{ version: number; rationale: string; mean_overall: number | null }>; nextVersion: number; + workDir: string; + artifacts: MutationArtifact[]; + timeoutMs?: number; + maxRetries?: number; }): Promise { - const critiques = opts.latestSummary.outcomes - .filter((o) => o.eval) - .map((o) => `- ${o.prompt_id} (${o.overall}, ${o.eval!.vs_reference ?? "n/a"}): ${o.eval!.critique}`) - .join("\n"); - const failures = opts.latestSummary.outcomes - .filter((o) => o.status !== "ok") - .map((o) => `- ${o.prompt_id}: ${o.status} ${o.error ?? ""}`) - .join("\n"); + const { workDir, timeoutMs = 10 * 60 * 1000, maxRetries = 2 } = opts; + const bin = process.env.PI_BIN ?? "pi"; + const outPath = join(workDir, "next-config.json"); + const sysPath = join(workDir, "mutator-system.md"); + const logPath = join(workDir, "mutate.log"); + writeFileSync(sysPath, MUTATOR_SYSTEM + "\n"); + const basePrompt = mutationPrompt(opts); + + let lastError = ""; + for (let attempt = 0; attempt <= maxRetries; attempt++) { + if (existsSync(outPath)) rmSync(outPath); + const instruction = + attempt === 0 + ? basePrompt + : `${basePrompt}\n\n## Previous attempt was invalid\n${lastError}\nWrite a corrected next-config.json now.`; + + const piArgs = [ + "--print", + "--no-session", + "--no-extensions", + "--no-context-files", + "--no-prompt-templates", + "--model", opts.mutatorModel, + "--thinking", "high", + "--tools", "read,write,bash", + "--append-system-prompt", sysPath, + instruction, + ]; + const proc = Bun.spawn([bin, ...piArgs], { cwd: workDir, stdout: "pipe", stderr: "pipe", env: { ...process.env } }); + const timer = setTimeout(() => proc.kill(), timeoutMs); + const [stdout, stderr, exitCode] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + proc.exited, + ]); + clearTimeout(timer); + writeFileSync(logPath, `# attempt ${attempt} exit ${exitCode}\n## stdout\n${stdout}\n## stderr\n${stderr}\n`); - const proposal = await forcedToolCall(opts.client, { - model: opts.model, - system: MUTATOR_SYSTEM, - toolName: "propose_config", - description: "Propose the complete next harness configuration.", - zodSchema: HarnessConfigSchema, - maxTokens: 16384, - content: [ - { - type: "text", - text: [ - `## Current best config (version ${opts.bestConfig.version}, derive your proposal from this)`, - JSON.stringify(opts.bestConfig, null, 2), - `## Latest iteration summary (config v${opts.latestSummary.config_version}, mean ${opts.latestSummary.mean_overall.toFixed(1)})`, - `Dimension means: ${JSON.stringify(opts.latestSummary.dimension_means)}`, - `Per-prompt critiques:\n${critiques || "(none)"}`, - failures ? `Failures:\n${failures}` : "", - `## Score history`, - opts.history.map((h) => `iter ${h.iteration}: v${h.config_version} → ${h.mean_overall.toFixed(1)} (best v${h.best_version}=${h.best_score.toFixed(1)})`).join("\n"), - `## Past change rationales`, - opts.pastRationales.map((r) => `v${r.version} (${r.mean_overall ?? "unscored"}): ${r.rationale}`).join("\n"), - `Propose the next config now. Include a specific rationale explaining the hypothesis.`, - ].filter(Boolean).join("\n\n"), - }, - ], - }); - - return HarnessConfigSchema.parse({ - ...proposal, - version: opts.nextVersion, - parent_version: opts.bestConfig.version, - }); + if (!existsSync(outPath)) { lastError = "no next-config.json was written"; continue; } + let raw: unknown; + try { + raw = JSON.parse(readFileSync(outPath, "utf8")); + } catch (e) { + lastError = `next-config.json is not valid JSON: ${String(e)}`; + continue; + } + const parsed = HarnessConfigSchema.safeParse({ + ...(raw as Record), + version: opts.nextVersion, + parent_version: opts.bestConfig.version, + }); + if (parsed.success) return parsed.data; + lastError = parsed.error.message; + } + throw new Error(`agentic mutateConfig failed after ${maxRetries + 1} attempts: ${lastError}`); } diff --git a/tests/mutate.test.ts b/tests/mutate.test.ts index ff358f4..2b58f9f 100644 --- a/tests/mutate.test.ts +++ b/tests/mutate.test.ts @@ -1,24 +1,86 @@ import { expect, test } from "bun:test"; -import { mutateConfig } from "../src/outer/mutate"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { mutateConfig, type MutationArtifact } from "../src/outer/mutate"; import { BASELINE_CONFIG } from "../src/config/schema"; -import type { LlmClient } from "../src/llm"; +import type { IterationSummary } from "../src/store/run-store"; -test("returns validated config with pinned version fields", async () => { - const proposal = { ...BASELINE_CONFIG, version: 999, parent_version: 42, rationale: "Add typography skill", system_instructions: BASELINE_CONFIG.system_instructions + "\nUse a modular type scale." }; - let captured: any; - const client: LlmClient = { - messages: { create: async (p) => { captured = p; return { content: [{ type: "tool_use", name: "propose_config", input: proposal }] }; } }, - }; - const next = await mutateConfig({ - client, model: "m", bestConfig: BASELINE_CONFIG, - latestSummary: { iteration: 1, config_version: 0, mean_overall: 52, outcomes: [], dimension_means: { typography: 4.1 } }, +function stubPi(dir: string, script: string): string { + const p = join(dir, "pi-stub.sh"); + writeFileSync(p, `#!/bin/bash\n${script}\n`); + chmodSync(p, 0o755); + return p; +} + +const SUMMARY: IterationSummary = { + iteration: 1, + config_version: 0, + mean_overall: 52, + outcomes: [ + { + prompt_id: "saas-crm", + status: "ok", + overall: 52, + eval: { + subscores: { hierarchy: 5, typography: 4, spacing: 5, color_contrast: 4, requirement_coverage: 8, polish: 5 }, + overall: 52, + vs_reference: "behind", + diff_dimensions: ["typography"], + critique: "Weak type scale.", + }, + }, + ], + dimension_means: { typography: 4 }, +}; + +function baseOpts(workDir: string, artifacts: MutationArtifact[] = []) { + return { + mutatorModel: "anthropic/claude-fable-5", + bestConfig: BASELINE_CONFIG, + latestSummary: SUMMARY, history: [{ iteration: 1, config_version: 0, mean_overall: 52, best_version: 0, best_score: 52 }], pastRationales: [{ version: 0, rationale: "baseline", mean_overall: 52 }], nextVersion: 5, - }); + workDir, + artifacts, + }; +} + +test("reads pi-written next-config.json and pins version/parent_version", async () => { + const base = mkdtempSync(join(tmpdir(), "mut-")); + const work = join(base, "work"); + mkdirSync(work, { recursive: true }); + const cfg = JSON.stringify({ ...BASELINE_CONFIG, version: 999, parent_version: 42, rationale: "Add a typography skill." }); + process.env.PI_BIN = stubPi(base, `cat > next-config.json <<'CFGEOF'\n${cfg}\nCFGEOF`); + + const next = await mutateConfig(baseOpts(work)); + expect(next.version).toBe(5); // pinned from nextVersion + expect(next.parent_version).toBe(0); // pinned from bestConfig.version + expect(next.rationale).toBe("Add a typography skill."); +}); + +test("retries when the first attempt writes nothing, then succeeds", async () => { + const base = mkdtempSync(join(tmpdir(), "mut2-")); + const work = join(base, "work"); + mkdirSync(work, { recursive: true }); + const cfg = JSON.stringify({ ...BASELINE_CONFIG, rationale: "second attempt" }); + // count invocations in the cwd; only write on the 2nd+ call + process.env.PI_BIN = stubPi( + base, + `n=$(cat .n 2>/dev/null || echo 0); n=$((n+1)); echo $n > .n\nif [ "$n" -ge 2 ]; then cat > next-config.json <<'CFGEOF'\n${cfg}\nCFGEOF\nfi`, + ); + + const next = await mutateConfig(baseOpts(work)); + expect(next.rationale).toBe("second attempt"); expect(next.version).toBe(5); - expect(next.parent_version).toBe(0); - const sent = JSON.stringify(captured.messages); - expect(sent).toContain("typography"); // summary reached the prompt - expect(sent).toContain("baseline"); // history reached the prompt +}); + +test("throws after retries when pi never writes a valid config", async () => { + const base = mkdtempSync(join(tmpdir(), "mut3-")); + const work = join(base, "work"); + mkdirSync(work, { recursive: true }); + process.env.PI_BIN = stubPi(base, `echo "did nothing useful"`); + + await expect(mutateConfig({ ...baseOpts(work), maxRetries: 1 })).rejects.toThrow(/failed after 2 attempts/); }); diff --git a/tests/orchestrator.test.ts b/tests/orchestrator.test.ts index 949460a..83528a7 100644 --- a/tests/orchestrator.test.ts +++ b/tests/orchestrator.test.ts @@ -13,16 +13,11 @@ const EVAL = { overall: 62, vs_reference: "behind", diff_dimensions: [], critique: "ok", }; +// The evaluator is the only remaining Anthropic-SDK call; mutation now goes through the pi subprocess. function fakeClient(): LlmClient { return { messages: { - create: async (params: any) => { - const isMutate = JSON.stringify(params.tools).includes("propose_config"); - if (isMutate) { - return { content: [{ type: "tool_use", name: "propose_config", input: { ...BASELINE_CONFIG, rationale: "tweak", version: 0, parent_version: null } }] }; - } - return { content: [{ type: "tool_use", name: "submit_evaluation", input: EVAL }] }; - }, + create: async () => ({ content: [{ type: "tool_use", name: "submit_evaluation", input: EVAL }] }), }, }; } @@ -30,10 +25,16 @@ function fakeClient(): LlmClient { // References are optional / deferred — no reference PNGs are written in setup. // The loop must work reference-free: the pipeline tolerates an empty reference dir // and scores rubric-only, so outcomes are still `ok` with the fake eval's overall score. +// The pi stub serves BOTH roles: the agentic mutator invocation (its instruction names +// next-config.json) writes a valid config; every other invocation is a builder and writes output.html. function setup() { const base = mkdtempSync(join(tmpdir(), "orch-")); const stub = join(base, "pi.sh"); - writeFileSync(stub, `#!/bin/bash\nprintf '%s' '${PAGE}' > output.html\n`); + const CONFIG = JSON.stringify({ ...BASELINE_CONFIG, rationale: "tweak" }); + writeFileSync( + stub, + `#!/bin/bash\nif printf '%s ' "$@" | grep -q 'next-config.json'; then\ncat > next-config.json <<'CFGEOF'\n${CONFIG}\nCFGEOF\nelse\nprintf '%s' '${PAGE}' > output.html\nfi\n`, + ); chmodSync(stub, 0o755); process.env.PI_BIN = stub; const refDir = join(base, "reference"); // exists but empty — reference-free @@ -48,7 +49,7 @@ test("two iterations: configs, summaries, history, best tracking", async () => { const { refDir, store } = setup(); await runLoop({ store, prompts: [P("a", "train"), P("b", "train")], iterations: 2, concurrency: 2, - client: fakeClient(), evalModel: "m", referenceDir: refDir, + client: fakeClient(), evalModel: "m", referenceDir: refDir, mutatorModel: "m", }); expect(store.completedIterations()).toEqual([1, 2]); expect(store.readHistory().length).toBe(2); @@ -63,7 +64,7 @@ test("resume after mid-iteration crash does not duplicate history or re-pick con const { refDir, store } = setup(); await runLoop({ store, prompts: [P("a", "train"), P("b", "train")], iterations: 1, concurrency: 2, - client: fakeClient(), evalModel: "m", referenceDir: refDir, + client: fakeClient(), evalModel: "m", referenceDir: refDir, mutatorModel: "m", }); expect(store.readHistory().length).toBe(1); @@ -81,7 +82,7 @@ test("resume after mid-iteration crash does not duplicate history or re-pick con // Resume: startIteration defaults to last-completed+1 == 1 again. await runLoop({ store, prompts: [P("a", "train"), P("b", "train")], iterations: 1, concurrency: 2, - client: fakeClient(), evalModel: "m", referenceDir: refDir, + client: fakeClient(), evalModel: "m", referenceDir: refDir, mutatorModel: "m", }); const history = store.readHistory(); @@ -95,7 +96,7 @@ test("builderModel pins the model across seed and mutations", async () => { const { refDir, store } = setup(); await runLoop({ store, prompts: [P("a", "train")], iterations: 2, concurrency: 1, - client: fakeClient(), evalModel: "m", referenceDir: refDir, + client: fakeClient(), evalModel: "m", referenceDir: refDir, mutatorModel: "m", builderModel: "anthropic/claude-haiku-4-5", }); // seeded baseline (v0) and every mutated config must carry the pinned model, even though the @@ -109,7 +110,7 @@ test("holdout writes report without touching history", async () => { const { refDir, store, base } = setup(); await runLoop({ store, prompts: [P("a", "train")], iterations: 1, concurrency: 1, - client: fakeClient(), evalModel: "m", referenceDir: refDir, + client: fakeClient(), evalModel: "m", referenceDir: refDir, mutatorModel: "m", }); const before = store.readHistory().length; const summary = await runHoldout({ From aef4e1547aaf5cecfc482d381a773cbc760f8b86 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 18:23:50 -0700 Subject: [PATCH 25/28] Default mutator to Opus (Fable is gated behind workspace data retention) Co-Authored-By: Claude Fable 5 --- README.md | 4 ++-- src/cli.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c2bce3f..9fab0b2 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ export ANTHROPIC_API_KEY=sk-ant-... # needed for evaluate + mutate (and refer - `bun run resume --run-id X` — continue an interrupted run from the last completed iteration. - `bun run reference [--force] [--concurrency 3]` — (optional, TBD) build cached reference pages for every prompt with a fixed high-effort Opus harness. -The outer loop's mutator is itself an agentic Pi session (default model `anthropic/claude-fable-5`): it inspects the iteration's candidate + reference screenshots and the generated HTML, then authors the next genome (skills, subagents, system instructions, tools, thinking level) as a validated `next-config.json`. +The outer loop's mutator is itself an agentic Pi session (default model `anthropic/claude-opus-4-8`): it inspects the iteration's candidate + reference screenshots and the generated HTML, then authors the next genome (skills, subagents, system instructions, tools, thinking level) as a validated `next-config.json`. To use Fable (`anthropic/claude-fable-5`) instead, enable data retention on your Anthropic workspace and set `MUTATOR_MODEL=anthropic/claude-fable-5`. -Environment overrides: `EVAL_MODEL` (default `claude-opus-4-8`) for the vision evaluator; `MUTATOR_MODEL` (default `anthropic/claude-fable-5`) for the agentic outer-loop mutator; `PI_BIN` for the builder/mutator CLI. `--model ` pins the builder model for a run (allowlist: `anthropic/claude-sonnet-4-6`, `anthropic/claude-haiku-4-5`, `anthropic/claude-opus-4-8`); `--limit N` restricts to the first N train prompts. +Environment overrides: `EVAL_MODEL` (default `claude-opus-4-8`) for the vision evaluator; `MUTATOR_MODEL` (default `anthropic/claude-opus-4-8`) for the agentic outer-loop mutator; `PI_BIN` for the builder/mutator CLI. `--model ` pins the builder model for a run (allowlist: `anthropic/claude-sonnet-4-6`, `anthropic/claude-haiku-4-5`, `anthropic/claude-opus-4-8`); `--limit N` restricts to the first N train prompts. ## Tests diff --git a/src/cli.ts b/src/cli.ts index ec75360..972ac41 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -10,7 +10,9 @@ import { buildReferenceSet } from "./reference/build-reference"; const RUNS_DIR = "runs"; const REFERENCE_DIR = join(RUNS_DIR, "reference"); const EVAL_MODEL = process.env.EVAL_MODEL ?? "claude-opus-4-8"; -const MUTATOR_MODEL = process.env.MUTATOR_MODEL ?? "anthropic/claude-fable-5"; +// Fable (anthropic/claude-fable-5) is gated behind workspace data-retention; default to Opus, which +// is accessible. Set MUTATOR_MODEL=anthropic/claude-fable-5 once data retention is enabled to use Fable. +const MUTATOR_MODEL = process.env.MUTATOR_MODEL ?? "anthropic/claude-opus-4-8"; const [command] = Bun.argv.slice(2); const { values } = parseArgs({ From 7c3f9d8a98d0844b29e44121aef0e981d34c8369 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 18:33:39 -0700 Subject: [PATCH 26/28] Add --prompts flag and a stylistically-demanding prompt set New prompts-styled.json: brutalist, editorial, luxury-minimal, cinematic, retro-maximalist, playful, plus swiss + organic holdouts. --prompts lets a run target any brief set, so experiments can probe headroom the generic baseline lacks on standard SaaS pages. Co-Authored-By: Claude Fable 5 --- prompts-styled.json | 54 +++++++++++++++++++++++++++++++++++++++++++++ src/cli.ts | 3 ++- 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 prompts-styled.json diff --git a/prompts-styled.json b/prompts-styled.json new file mode 100644 index 0000000..babe9da --- /dev/null +++ b/prompts-styled.json @@ -0,0 +1,54 @@ +{ + "version": 1, + "description": "Stylistically demanding landing-page prompts for the autodesign loop. Each brief mandates strong, specific art direction where a generic 'clean modern' page scores poorly, creating headroom for the outer loop to discover skills about typography, color systems, and layout. split=train prompts are optimized against; split=holdout are reserved for final validation.", + "prompts": [ + { + "id": "brutalist-agency", + "category": "brutalist", + "split": "train", + "prompt": "Landing page for 'CONCRETE', a contrarian brand design studio. Hard brutalist web aesthetic: monospace or grotesque type, raw black-on-white with one aggressive accent, visible grid lines, oversized headline that breaks the grid, no rounded corners, no soft shadows, deliberate asymmetry. Must include: a screen-filling type-only hero, a numbered list of services (01–04), a raw bordered case-study grid, a marquee of client names, and a stark footer with a giant email link." + }, + { + "id": "editorial-magazine", + "category": "editorial", + "split": "train", + "prompt": "Landing page for 'MERIDIAN', an independent culture and design magazine. Editorial print aesthetic: refined serif display type, multi-column article layout, drop caps, generous whitespace, restrained palette with a single ink accent, hairline rules. Must include: a masthead-style hero with issue number and date, a featured-story block with pull quote, a three-column latest-articles grid with kicker/headline/byline, a subscribe block styled like a print subscription card, and a minimal footer." + }, + { + "id": "luxury-minimal", + "category": "luxury", + "split": "train", + "prompt": "Landing page for 'AURELIA', a haute horlogerie watch maison. Luxury-minimal aesthetic: enormous negative space, understated high-contrast serif type, near-monochrome palette with warm metallic accent, slow and reverent pacing, product treated like a museum object. Must include: a centered hero with a single product statement and a thin outlined CTA, a craftsmanship section with three restrained feature callouts, a heritage timeline, a discreet concierge/enquire block, and a whisper-quiet footer. No loud colors, no busy layouts." + }, + { + "id": "cinematic-dark", + "category": "cinematic", + "split": "train", + "prompt": "Landing page for 'NOCTURNE', a moody narrative video game. Cinematic dark aesthetic: near-black background, dramatic contrast, atmospheric gradient glow, condensed display type, letter-spaced labels, a sense of depth and mood. Must include: a full-bleed hero with a trailer placeholder and a single glowing CTA, a screenshot gallery treated cinematically, a three-beat story/features section, a wishlist-on-Steam CTA band, and press pull-quotes. Restrained, tense, premium — not a generic dark-mode SaaS page." + }, + { + "id": "retro-maximalist", + "category": "maximalist", + "split": "train", + "prompt": "Landing page for 'SUPERBLOOM', a summer music festival. Retro-maximalist aesthetic: saturated clashing color blocks, chunky retro display type, stickers/badges, visible texture, playful overlap and rotation, high energy. Must include: a loud hero with dates and location and a ticket CTA, a poster-style lineup grid of artist names at varied scales, a day-by-day schedule, ticket tiers as bold cards, and a sponsor strip. Maximal and joyful — the opposite of minimalist." + }, + { + "id": "playful-illustrated", + "category": "playful", + "split": "train", + "prompt": "Landing page for 'Sprout', a friendly kids' learning app. Playful illustrated aesthetic: rounded soft shapes, warm cheerful palette, hand-drawn illustration placeholders, bouncy chunky type, generous rounded cards, a sense of fun. Must include: a hero with a mascot illustration placeholder and a 'Get the app' CTA, a three-step 'How it works' with illustrated icons, an age-range selector, parent-trust reassurance, app store badges, and a colorful footer. Warm and whimsical, never corporate." + }, + { + "id": "holdout-swiss-fintech", + "category": "swiss", + "split": "holdout", + "prompt": "Landing page for 'LEDGER NORTH', a private banking platform. Swiss/International Typographic Style: strict grid, Helvetica-style neutral sans, flush-left rag-right text, mathematical spacing, black/white with a single primary accent, absolute restraint and order. Must include: a grid-locked hero, a precise three-column capabilities section, a data-backed trust section with aligned figures, a compliance/credentials row, and a spare footer. Order and precision over decoration." + }, + { + "id": "holdout-organic-wellness", + "category": "organic", + "split": "holdout", + "prompt": "Landing page for 'TERRA', a botanical skincare brand. Organic/earthy aesthetic: warm natural palette (clay, sage, cream), elegant serif paired with humanist sans, soft grain texture, botanical imagery placeholders, calm generous spacing. Must include: a serene hero with product statement and shop CTA, an ingredients/sourcing section with three natural callouts, a ritual/how-to-use section, real-sounding testimonial cards, and a newsletter capture. Warm, natural, premium — not clinical." + } + ] +} diff --git a/src/cli.ts b/src/cli.ts index 972ac41..8a44e46 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -25,10 +25,11 @@ const { values } = parseArgs({ force: { type: "boolean", default: false }, limit: { type: "string" }, model: { type: "string" }, + prompts: { type: "string" }, }, }); -const all = loadPrompts(); +const all = loadPrompts(values.prompts); const concurrency = Number(values.concurrency); const limit = values.limit ? Number(values.limit) : undefined; const builderModel = values.model; From 5affd549494d3d2ec43adbd99a929cca425521e1 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 18:45:42 -0700 Subject: [PATCH 27/28] Fix subprocess timeout hang; speed up agentic mutator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pi timeout killed the child but we kept awaiting its stdout/stderr streams, which a lingering grandchild held open — so a timed-out mutation hung the whole loop forever instead of retrying. Add runPiCapped: drains concurrently, races the timeout, SIGKILLs, and never awaits the streams past a grace period. Both builder and mutator use it. Also drop mutator thinking to medium (MUTATOR_THINKING override), shorten the mutation timeout to 6m, and cap the screenshots the mutator reads (4 desktop candidate + 3 reference) to cut its latency and token load. Co-Authored-By: Claude Fable 5 --- src/inner/build.ts | 17 +++++------------ src/orchestrator.ts | 5 ++++- src/outer/mutate.ts | 18 +++++++----------- src/util/pi.ts | 36 ++++++++++++++++++++++++++++++++++++ tests/pi.test.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 24 deletions(-) create mode 100644 src/util/pi.ts create mode 100644 tests/pi.test.ts diff --git a/src/inner/build.ts b/src/inner/build.ts index cddd5ab..cf892b1 100644 --- a/src/inner/build.ts +++ b/src/inner/build.ts @@ -2,6 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join, dirname } from "node:path"; import type { ResolvedHarness } from "../config/resolver"; import type { PromptSpec } from "../prompts"; +import { runPiCapped } from "../util/pi"; export type BuildResult = | { ok: true; htmlPath: string; logPath: string } @@ -21,22 +22,14 @@ export async function buildPage(opts: { const logPath = join(dirname(workspaceDir), "build.log"); const bin = process.env.PI_BIN ?? "pi"; - const proc = Bun.spawn([bin, ...resolved.piArgs, BUILD_INSTRUCTION + prompt.prompt], { + const { stdout, stderr, exitCode, timedOut } = await runPiCapped(bin, [...resolved.piArgs, BUILD_INSTRUCTION + prompt.prompt], { cwd: workspaceDir, - stdout: "pipe", - stderr: "pipe", - env: { ...process.env }, + timeoutMs, }); - const timer = setTimeout(() => proc.kill(), timeoutMs); - const [stdout, stderr, exitCode] = await Promise.all([ - new Response(proc.stdout).text(), - new Response(proc.stderr).text(), - proc.exited, - ]); - clearTimeout(timer); - writeFileSync(logPath, `# exit ${exitCode}\n## stdout\n${stdout}\n## stderr\n${stderr}\n`); + writeFileSync(logPath, `# exit ${exitCode}${timedOut ? " (timed out)" : ""}\n## stdout\n${stdout}\n## stderr\n${stderr}\n`); const htmlPath = join(workspaceDir, "output.html"); + if (timedOut) return { ok: false, error: "pi build timed out", logPath }; if (exitCode !== 0) return { ok: false, error: `pi exited ${exitCode}`, logPath }; if (!existsSync(htmlPath)) return { ok: false, error: "no output.html produced", logPath }; const html = readFileSync(htmlPath, "utf8"); diff --git a/src/orchestrator.ts b/src/orchestrator.ts index 3d96598..987c9be 100644 --- a/src/orchestrator.ts +++ b/src/orchestrator.ts @@ -14,11 +14,14 @@ import type { RunStore, IterationSummary } from "./store/run-store"; function collectArtifacts(store: RunStore, iter: number, referenceDir: string, summary: IterationSummary): MutationArtifact[] { return summary.outcomes.map((o) => { const promptDir = store.promptDir(iter, o.prompt_id); + // Keep the mutator's read load bounded: at most the first 4 desktop candidate segments and the + // first 3 reference segments. Too many large PNGs slows the agentic mutation and inflates tokens. const candidateScreens = existsSync(promptDir) ? readdirSync(promptDir) .filter((f) => /^candidate\.desktop\.\d+\.png$/.test(f)) .sort((a, b) => Number(a.match(/\d+/)![0]) - Number(b.match(/\d+/)![0])) .map((f) => join(promptDir, f)) + .slice(0, 4) : []; const htmlPath = join(promptDir, "workspace", "output.html"); return { @@ -29,7 +32,7 @@ function collectArtifacts(store: RunStore, iter: number, referenceDir: string, s vsReference: o.eval?.vs_reference, htmlPath: existsSync(htmlPath) ? htmlPath : undefined, candidateScreens, - referenceScreens: referenceSegments(referenceDir, o.prompt_id), + referenceScreens: referenceSegments(referenceDir, o.prompt_id).slice(0, 3), }; }); } diff --git a/src/outer/mutate.ts b/src/outer/mutate.ts index 6ae977a..e0c65ca 100644 --- a/src/outer/mutate.ts +++ b/src/outer/mutate.ts @@ -3,6 +3,7 @@ import { join } from "node:path"; import { z } from "zod"; import { HarnessConfigSchema, type HarnessConfig } from "../config/schema"; import type { HistoryEntry, IterationSummary } from "../store/run-store"; +import { runPiCapped } from "../util/pi"; // Per-prompt artifacts the agentic mutator can inspect for the latest iteration. export type MutationArtifact = { @@ -93,8 +94,9 @@ export async function mutateConfig(opts: { timeoutMs?: number; maxRetries?: number; }): Promise { - const { workDir, timeoutMs = 10 * 60 * 1000, maxRetries = 2 } = opts; + const { workDir, timeoutMs = 6 * 60 * 1000, maxRetries = 2 } = opts; const bin = process.env.PI_BIN ?? "pi"; + const thinking = process.env.MUTATOR_THINKING ?? "medium"; const outPath = join(workDir, "next-config.json"); const sysPath = join(workDir, "mutator-system.md"); const logPath = join(workDir, "mutate.log"); @@ -116,21 +118,15 @@ export async function mutateConfig(opts: { "--no-context-files", "--no-prompt-templates", "--model", opts.mutatorModel, - "--thinking", "high", + "--thinking", thinking, "--tools", "read,write,bash", "--append-system-prompt", sysPath, instruction, ]; - const proc = Bun.spawn([bin, ...piArgs], { cwd: workDir, stdout: "pipe", stderr: "pipe", env: { ...process.env } }); - const timer = setTimeout(() => proc.kill(), timeoutMs); - const [stdout, stderr, exitCode] = await Promise.all([ - new Response(proc.stdout).text(), - new Response(proc.stderr).text(), - proc.exited, - ]); - clearTimeout(timer); - writeFileSync(logPath, `# attempt ${attempt} exit ${exitCode}\n## stdout\n${stdout}\n## stderr\n${stderr}\n`); + const { stdout, stderr, exitCode, timedOut } = await runPiCapped(bin, piArgs, { cwd: workDir, timeoutMs }); + writeFileSync(logPath, `# attempt ${attempt} exit ${exitCode}${timedOut ? " (timed out)" : ""}\n## stdout\n${stdout}\n## stderr\n${stderr}\n`); + if (timedOut) { lastError = "pi mutation timed out"; continue; } if (!existsSync(outPath)) { lastError = "no next-config.json was written"; continue; } let raw: unknown; try { diff --git a/src/util/pi.ts b/src/util/pi.ts new file mode 100644 index 0000000..8bccd77 --- /dev/null +++ b/src/util/pi.ts @@ -0,0 +1,36 @@ +export type PiResult = { stdout: string; stderr: string; exitCode: number; timedOut: boolean }; + +// Run a `pi` subprocess with a hard timeout that never hangs. We drain stdout/stderr concurrently +// (so a chatty child can't deadlock on a full pipe buffer) and race the whole thing against the +// timeout. On timeout we SIGKILL the child and, crucially, stop awaiting the output streams after a +// short grace — a killed `pi` can leave a grandchild holding the pipe open, which would otherwise +// make `new Response(stream).text()` hang forever. +export async function runPiCapped(bin: string, args: string[], opts: { cwd: string; timeoutMs: number }): Promise { + const proc = Bun.spawn([bin, ...args], { cwd: opts.cwd, stdout: "pipe", stderr: "pipe", env: { ...process.env } }); + const drain = Promise.all([ + new Response(proc.stdout).text().catch(() => ""), + new Response(proc.stderr).text().catch(() => ""), + ]); + + const completed = Promise.all([drain, proc.exited]).then( + ([[stdout, stderr], exitCode]) => ({ stdout, stderr, exitCode, timedOut: false }) as PiResult, + ); + const timeout = new Promise((resolve) => + setTimeout(() => { + try { + proc.kill(9); + } catch { + // already gone + } + resolve(null); + }, opts.timeoutMs), + ); + + const raced = await Promise.race([completed, timeout]); + if (raced) return raced; + + // Timed out and killed. Grab whatever buffered output is available, but never hang on the streams. + const grace = new Promise<[string, string]>((r) => setTimeout(() => r(["", ""]), 2000)); + const [stdout, stderr] = await Promise.race([drain, grace]); + return { stdout, stderr, exitCode: proc.exitCode ?? -1, timedOut: true }; +} diff --git a/tests/pi.test.ts b/tests/pi.test.ts new file mode 100644 index 0000000..5bcc3cf --- /dev/null +++ b/tests/pi.test.ts @@ -0,0 +1,42 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { runPiCapped } from "../src/util/pi"; + +function stub(dir: string, script: string): string { + const p = join(dir, "stub.sh"); + writeFileSync(p, `#!/bin/bash\n${script}\n`); + chmodSync(p, 0o755); + return p; +} + +test("returns output and exit code for a normal process", async () => { + const dir = mkdtempSync(join(tmpdir(), "pi-")); + const bin = stub(dir, `echo hello; echo oops 1>&2; exit 0`); + const r = await runPiCapped(bin, [], { cwd: dir, timeoutMs: 5000 }); + expect(r.timedOut).toBe(false); + expect(r.exitCode).toBe(0); + expect(r.stdout).toContain("hello"); + expect(r.stderr).toContain("oops"); +}); + +test("propagates a non-zero exit code", async () => { + const dir = mkdtempSync(join(tmpdir(), "pi2-")); + const bin = stub(dir, `exit 3`); + const r = await runPiCapped(bin, [], { cwd: dir, timeoutMs: 5000 }); + expect(r.timedOut).toBe(false); + expect(r.exitCode).toBe(3); +}); + +test("times out and does not hang when a grandchild keeps the pipe open", async () => { + const dir = mkdtempSync(join(tmpdir(), "pi3-")); + // spawn a background grandchild that holds stdout open, then the parent 'exits' but the pipe stays + // open — the classic hang. runPiCapped must still return within the timeout. + const bin = stub(dir, `sleep 30 & echo started; wait`); + const start = Date.now(); + const r = await runPiCapped(bin, [], { cwd: dir, timeoutMs: 1000 }); + const elapsed = Date.now() - start; + expect(r.timedOut).toBe(true); + expect(elapsed).toBeLessThan(8000); // returned promptly, did not hang on the open pipe +}, 15000); From d72c5067ed5d2e04c72187dafe76925dd4c7d9c3 Mon Sep 17 00:00:00 2001 From: Daniel Bukac Date: Sat, 18 Jul 2026 18:57:36 -0700 Subject: [PATCH 28/28] Add --seed-config and showcase setup for an aggressive-improvement run --seed-config seeds the loop from a custom genome; weak-seed.json is a deliberately crippled baseline (Haiku, thinking off, no design guidance/skills) so iteration 1 fails. prompts-showcase.json is one design-critical dev-tool page, scored against strong Mobbin references, so the smart Opus mutator's design skills produce a steep, legible climb. MUTATOR_TIMEOUT_MS override added. Co-Authored-By: Claude Fable 5 --- prompts-showcase.json | 12 ++++++++++++ src/cli.ts | 9 +++++++-- src/orchestrator.ts | 3 ++- src/outer/mutate.ts | 2 +- weak-seed.json | 10 ++++++++++ 5 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 prompts-showcase.json create mode 100644 weak-seed.json diff --git a/prompts-showcase.json b/prompts-showcase.json new file mode 100644 index 0000000..dfa1b02 --- /dev/null +++ b/prompts-showcase.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "description": "Single design-critical prompt for the improvement showcase run. Scored against strong Mobbin references (Linear/Neon/Vercel-class dev-tool pages), so a naive baseline page scores low and the outer loop's design skills produce a steep, legible climb.", + "prompts": [ + { + "id": "showcase-devtool", + "category": "dev-tool", + "split": "train", + "prompt": "Landing page for 'Volt', a developer platform for instant global edge deployments. Sleek, modern, developer-focused design. Must include: a hero with a bold headline, a supporting subhead, and a primary 'Start deploying' CTA; a terminal-style code snippet showing a one-line deploy; a three-feature section (instant rollbacks, global edge network, zero-config); a logos-of-trust bar; a simple three-tier pricing section (Hobby, Pro, Enterprise); and a footer with links. Aim for a polished, premium feel worthy of a top developer tool." + } + ] +} diff --git a/src/cli.ts b/src/cli.ts index 8a44e46..1dda08a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,7 +1,8 @@ import { parseArgs } from "node:util"; import { join } from "node:path"; +import { readFileSync } from "node:fs"; import { loadPrompts, trainPrompts, holdoutPrompts } from "./prompts"; -import { ALLOWED_MODELS } from "./config/schema"; +import { ALLOWED_MODELS, HarnessConfigSchema } from "./config/schema"; import { RunStore } from "./store/run-store"; import { realClient } from "./llm"; import { runLoop, runHoldout } from "./orchestrator"; @@ -26,6 +27,7 @@ const { values } = parseArgs({ limit: { type: "string" }, model: { type: "string" }, prompts: { type: "string" }, + "seed-config": { type: "string" }, }, }); @@ -37,6 +39,9 @@ if (builderModel && !(ALLOWED_MODELS as readonly string[]).includes(builderModel console.error(`--model must be one of: ${ALLOWED_MODELS.join(", ")}`); process.exit(1); } +const seedConfig = values["seed-config"] + ? HarnessConfigSchema.parse(JSON.parse(readFileSync(values["seed-config"], "utf8"))) + : undefined; async function main() { switch (command) { @@ -61,7 +66,7 @@ async function main() { await runLoop({ store, prompts: train, iterations: Number(values.iterations), concurrency, client: realClient(), evalModel: EVAL_MODEL, referenceDir: REFERENCE_DIR, - builderModel, mutatorModel: MUTATOR_MODEL, + builderModel, mutatorModel: MUTATOR_MODEL, seedConfig, }); const best = store.bestVersion(); console.log(`done. best config: v${best.version} (mean ${best.score.toFixed(1)})`); diff --git a/src/orchestrator.ts b/src/orchestrator.ts index 987c9be..5ea577a 100644 --- a/src/orchestrator.ts +++ b/src/orchestrator.ts @@ -48,6 +48,7 @@ export async function runLoop(opts: { startIteration?: number; builderModel?: string; mutatorModel: string; + seedConfig?: typeof BASELINE_CONFIG; }): Promise { const { store, prompts, client, evalModel, referenceDir, concurrency, builderModel, mutatorModel } = opts; if (prompts.some((p) => p.split !== "train")) throw new Error("runLoop accepts train prompts only"); @@ -57,7 +58,7 @@ export async function runLoop(opts: { const pinModel = (c: typeof BASELINE_CONFIG) => builderModel ? HarnessConfigSchema.parse({ ...c, model: { ...c.model, name: builderModel } }) : c; - if (store.listConfigVersions().length === 0) store.saveConfig(pinModel(BASELINE_CONFIG)); + if (store.listConfigVersions().length === 0) store.saveConfig(pinModel(opts.seedConfig ?? BASELINE_CONFIG)); const completed = store.completedIterations(); const start = opts.startIteration ?? (completed.length ? Math.max(...completed) + 1 : 1); diff --git a/src/outer/mutate.ts b/src/outer/mutate.ts index e0c65ca..9df6005 100644 --- a/src/outer/mutate.ts +++ b/src/outer/mutate.ts @@ -94,7 +94,7 @@ export async function mutateConfig(opts: { timeoutMs?: number; maxRetries?: number; }): Promise { - const { workDir, timeoutMs = 6 * 60 * 1000, maxRetries = 2 } = opts; + const { workDir, timeoutMs = Number(process.env.MUTATOR_TIMEOUT_MS) || 6 * 60 * 1000, maxRetries = 2 } = opts; const bin = process.env.PI_BIN ?? "pi"; const thinking = process.env.MUTATOR_THINKING ?? "medium"; const outPath = join(workDir, "next-config.json"); diff --git a/weak-seed.json b/weak-seed.json new file mode 100644 index 0000000..3f8b1cc --- /dev/null +++ b/weak-seed.json @@ -0,0 +1,10 @@ +{ + "version": 0, + "parent_version": null, + "rationale": "Deliberately weak showcase baseline: no design guidance, fast/weak builder, no thinking, no skills or subagents. The point is for iteration 1 to fail so the outer loop's improvement is dramatic and legible.", + "model": { "name": "anthropic/claude-haiku-4-5", "thinking_level": "off" }, + "tools": ["read", "write", "bash"], + "system_instructions": "Create the landing page as a single output.html file that satisfies the brief.", + "skills": [], + "subagents": [] +}