// C1 — Browser blueprint-engine: deterministic, pure, no network. // // `generate(idea)` builds the initial blueprint; `apply(state, instruction)` is the local // orchestrator that mutates the plan from a chat instruction (add / reduce / split batches, // switch stack, add components) and returns the patched state + a reply + which sections // changed. This is the primary path — the LLM (C3) only *refines* this result, optionally. import { type ArchitectureNode, type BlueprintDetailsData, type BlueprintSection, type DesignerBatch, type DesignerCandidate, type FilePlanItem, RMD_RULES, } from "@/types/blueprint-state"; type Domain = "web-game" | "api" | "web-app"; type Tier = { id: string; difficulty: string; scale: number }; const TIERS: Tier[] = [ { id: "minimal", difficulty: "Easy", scale: 0.55 }, { id: "standard", difficulty: "Medium", scale: 1.0 }, { id: "production", difficulty: "Hard", scale: 1.6 }, ]; // ---- generate ------------------------------------------------------------------------- export function generate(candidateId: string, idea: string): BlueprintDetailsData { const tier = TIERS.find((t) => t.id === candidateId) ?? TIERS[1]; const domain = inferDomain(idea); const full = roadmap(domain); const n = Math.max(3, Math.round(full.length * tier.scale)); const batches = n <= full.length ? full.slice(0, n) : full.concat(extra(full.length, n - full.length)); return { candidate_id: candidateId, overview: `This ${candidateId} blueprint builds ${idea.trim().slice(0, 120)}. Delivered in ordered, validated batches.`, architecture: candidateId === "minimal" ? architecture(domain).slice(0, 2) : architecture(domain), batches, file_plan: filePlan(domain), matrix_rules: RMD_RULES, acceptance_criteria: ["builds and runs", "core flow works with tests", "Matrix validation approves the build"], validation_plan: ["lint", "typecheck", "unit tests", "allowed-file check", "Matrix commit check"], risks: ["external credentials may be required"], assumptions: [domain === "web-game" ? "original assets only" : "auth required if data is sensitive"], design_brain: `Client engine · ${batches.length} batches · ${candidateId}.`, chat_history: [], }; } export function generateCandidates(idea: string): DesignerCandidate[] { const domain = inferDomain(idea); const stack = stackFor(domain); const meta: [string, string, string, string, boolean][] = [ ["minimal", "Minimal", "Easy", "a weekend", false], ["standard", "Standard", "Medium", "about one week", true], ["production", "Production", "Hard", "about three weeks", false], ]; return meta.map(([id, tier, difficulty, estimate, recommended]) => ({ id, tier, difficulty, estimate, recommended, stack, title: `${tier} ${id === "standard" ? "Matrix Bundle" : "controlled blueprint"}`, summary: `${recommended ? "Recommended " : ""}controlled blueprint for: ${idea.trim().slice(0, 90)}`, file_count: generate(id, idea).batches.length * (id === "production" ? 7 : 5), })); } // ---- apply — the local orchestrator --------------------------------------------------- export interface ApplyResult { data: BlueprintDetailsData; reply: string; updatedSections: BlueprintSection[]; } export function apply(state: BlueprintDetailsData, instruction: string): ApplyResult { const text = instruction.trim(); const m = text.toLowerCase(); const data = clone(state); const sections = new Set(); let reply = "Noted. Save latest edits to keep this."; const nextId = () => `batch-${String(data.batches.length + 1).padStart(2, "0")}`; // 1) split a batch into two if (/\bsplit\b/.test(m)) { const idx = pickBatchIndex(data, m); if (idx >= 0) { const b = data.batches[idx]; const partB: DesignerBatch = { ...clone(b), id: nextId(), name: `${b.name} (part 2)`, depends_on: [b.id], }; data.batches[idx] = { ...b, name: `${b.name} (part 1)` }; data.batches.splice(idx + 1, 0, partB); renumber(data); sections.add("batches"); reply = `Split “${b.name}” into two batches. Review and Save latest edits.`; return finish(data, sections, reply); } } // 2) reduce / remove scope if (/\b(reduce|smaller|simpler|remove|drop|trim)\b/.test(m)) { if (data.batches.length > 3) { const dropped = data.batches.pop()!; sections.add("batches"); reply = `Reduced scope — removed “${dropped.name}”.`; } else { reply = "Already at the minimal batch set."; } return finish(data, sections, reply); } // 3) switch stack / framework (architecture + file plan) const stackHit = detectStack(m); if (stackHit) { applyStack(data, stackHit); sections.add("architecture"); sections.add("filePlan"); reply = `Switched to ${stackHit.label}. Updated architecture and file plan.`; return finish(data, sections, reply); } // 4) add an architecture component const comp = detectComponent(m); if (comp) { if (!data.architecture.some((n) => n.name === comp.name)) { data.architecture.push(comp); sections.add("architecture"); } // also add a batch to build it data.batches.push(makeBatch(nextId(), comp.batchName, comp.allowed, text)); renumber(data); sections.add("batches"); reply = `Added ${comp.name} (architecture + a build batch).`; return finish(data, sections, reply); } // 5) add a feature / level / boss → a scoped batch if (/\b(add|include|introduce|boss|level|enemy|feature|analytic|metric|audit|auth|dashboard|observab)\b/.test(m)) { const name = featureName(m); data.batches.push(makeBatch(nextId(), name, ["src/**"], text)); renumber(data); sections.add("batches"); reply = `Added ${data.batches[data.batches.length - 1].id} — ${name}. Review and Save latest edits.`; return finish(data, sections, reply); } return finish(data, sections, reply); } // ---- helpers -------------------------------------------------------------------------- function finish(data: BlueprintDetailsData, sections: Set, reply: string): ApplyResult { if (sections.has("batches")) { data.design_brain = `Client engine · ${data.batches.length} batches · ${data.candidate_id}.`; sections.add("designBrain"); } return { data, reply, updatedSections: [...sections] }; } function makeBatch(id: string, name: string, allowed: string[], from: string): DesignerBatch { return { id, name, purpose: `Added from chat: ${from}`, tasks: [from], allowed_files: allowed, depends_on: [], acceptance_criteria: ["feature works", "no runtime errors"], validation_checks: ["unit tests"], must_not_change: [], }; } function renumber(data: BlueprintDetailsData): void { data.batches.forEach((b, i) => { b.id = `batch-${String(i + 1).padStart(2, "0")}`; }); } function pickBatchIndex(data: BlueprintDetailsData, m: string): number { const num = m.match(/\b(\d{1,2})\b/); if (num) { const i = parseInt(num[1], 10) - 1; if (i >= 0 && i < data.batches.length) return i; } return data.batches.length - 1; } function featureName(m: string): string { if (/boss/.test(m)) return "Boss encounter"; if (/level/.test(m)) return "Extra level"; if (/analytic|metric/.test(m)) return "Analytics & metrics"; if (/audit/.test(m)) return "Audit logging"; if (/auth/.test(m)) return "Auth & access"; if (/dashboard/.test(m)) return "Admin dashboard"; if (/observab/.test(m)) return "Observability"; return "Refinement"; } function detectStack(m: string): { label: string; nodes: ArchitectureNode[]; files: FilePlanItem[] } | null { if (/next\.?js|nextjs/.test(m)) { return { label: "Next.js", nodes: [{ name: "Web App", description: "Next.js", dependencies: [] }], files: [{ path: "apps/web", description: "Next.js app" }] }; } if (/fastapi/.test(m)) { return { label: "FastAPI", nodes: [{ name: "API", description: "FastAPI", dependencies: ["Web App"] }], files: [{ path: "services/api", description: "FastAPI service" }] }; } if (/\bexpress\b|\bnode\b/.test(m) && /(use|switch|instead)/.test(m)) { return { label: "Express", nodes: [{ name: "API", description: "Express", dependencies: ["Web App"] }], files: [{ path: "services/api", description: "Express service" }] }; } return null; } function applyStack(data: BlueprintDetailsData, hit: { nodes: ArchitectureNode[]; files: FilePlanItem[] }): void { for (const node of hit.nodes) { const i = data.architecture.findIndex((n) => n.name === node.name); if (i >= 0) data.architecture[i] = node; else data.architecture.push(node); } for (const f of hit.files) { const i = data.file_plan.findIndex((p) => p.path === f.path); if (i >= 0) data.file_plan[i] = f; else data.file_plan.unshift(f); } } function detectComponent(m: string): (ArchitectureNode & { batchName: string; allowed: string[] }) | null { if (/worker|queue/.test(m)) { return { name: "Worker / Queue", description: "Background jobs", dependencies: ["API"], batchName: "Worker & queue", allowed: ["services/worker/**"] }; } if (/redis|cache/.test(m)) { return { name: "Cache (Redis)", description: "Caching layer", dependencies: ["API"], batchName: "Caching layer", allowed: ["services/api/**"] }; } if (/database|postgres|\bdb\b/.test(m) && /(add|use)/.test(m)) { return { name: "Database", description: "Persistent storage", dependencies: ["API"], batchName: "Data model & migrations", allowed: ["db/**"] }; } return null; } function clone(v: T): T { return JSON.parse(JSON.stringify(v)) as T; } // ---- domain knowledge ----------------------------------------------------------------- function inferDomain(idea: string): Domain { const s = idea.toLowerCase(); if (/(game|platformer|phaser|arcade|canvas)/.test(s)) return "web-game"; if (/(api|fastapi|service|endpoint)/.test(s)) return "api"; return "web-app"; } function stackFor(domain: Domain): string[] { return domain === "web-game" ? ["Phaser 3", "TypeScript", "Vite"] : ["Next.js", "FastAPI", "PostgreSQL", "Docker"]; } function architecture(domain: Domain): ArchitectureNode[] { if (domain === "web-game") { return [ { name: "Web Client (Phaser)", description: "Canvas game", dependencies: [] }, { name: "Asset pipeline", description: "Original art", dependencies: ["Web Client (Phaser)"] }, { name: "Level engine", description: "Data-driven levels", dependencies: ["Web Client (Phaser)"] }, { name: "CI / Pages", description: "Build + deploy", dependencies: [] }, ]; } return [ { name: "Web App", description: "Frontend", dependencies: [] }, { name: "API", description: "Business logic", dependencies: ["Web App"] }, { name: "Database", description: "Storage", dependencies: ["API"] }, { name: "Worker / Queue", description: "Background jobs", dependencies: ["API"] }, { name: "Admin Dashboard", description: "Ops & analytics", dependencies: ["Web App", "API"] }, ]; } function filePlan(domain: Domain): FilePlanItem[] { if (domain === "web-game") { return [ { path: "src/scenes", description: "Boot/Preload/Game/…" }, { path: "src/levels", description: "level data + builder" }, { path: "src/entities", description: "Player, enemies, Coin" }, { path: "public/assets", description: "original art" }, { path: "README.md", description: "overview" }, ]; } return [ { path: "apps/web", description: "Web app and dashboard" }, { path: "services/api", description: "API and business logic" }, { path: "services/worker", description: "Background jobs" }, { path: "packages/shared", description: "Shared types" }, { path: "db", description: "Migrations" }, { path: "README.md", description: "overview" }, ]; } function roadmap(domain: Domain): DesignerBatch[] { const spec: [string, string[]][] = domain === "web-game" ? [ ["Game foundation", ["vite.config.ts", "src/main.ts"]], ["Asset pipeline", ["scripts/gen_assets.py", "public/assets/**"]], ["Tilemap world", ["src/levels/**"]], ["Player & controls", ["src/entities/Player.ts"]], ["Enemies & power-ups", ["src/entities/**"]], ["HUD & gate", ["src/ui/HUD.ts"]], ["Campaign & boss", ["src/levels/episodes.ts"]], ["Polish & release", [".github/workflows/deploy.yml"]], ] : [ ["Foundation", ["apps/web/**", "services/api/**"]], ["Integrations", ["services/api/**"]], ["Workflow & logic", ["services/api/**"]], ["Admin & analytics", ["apps/web/**"]], ["Hardening & deploy", ["docker-compose.yml", ".github/**"]], ["Validation & release", ["tests/**"]], ]; return spec.map(([name, allowed], i): DesignerBatch => ({ id: `batch-${String(i + 1).padStart(2, "0")}`, name, purpose: `${name}.`, tasks: [name], allowed_files: allowed, depends_on: i > 0 ? [`batch-${String(i).padStart(2, "0")}`] : [], acceptance_criteria: ["builds", "feature works", "no runtime errors"], validation_checks: ["lint", "typecheck", "tests"], must_not_change: [], })); } function extra(start: number, count: number): DesignerBatch[] { const names = ["Observability", "Performance pass", "Security review", "Docs & onboarding", "Accessibility"]; const out: DesignerBatch[] = []; for (let k = 0; k < count; k++) { const i = start + k + 1; out.push({ id: `batch-${String(i).padStart(2, "0")}`, name: names[k % names.length], purpose: "Production hardening.", tasks: ["hardening"], allowed_files: ["**"], depends_on: [`batch-${String(i - 1).padStart(2, "0")}`], acceptance_criteria: ["meets the production bar"], validation_checks: ["tests"], must_not_change: [], }); } return out; }