import { type ForgeArtifact, type ForgeDeps, type ForgeJobInput, type ForgeLevelSummary, type ForgeLogger, type ForgeProgressEvent, type ForgeStorageEnv, type ForgeTransport, type ForgeWorldJsonSource, type ForgedWorkStore } from '@bitmagic/world-forger/pipeline/index.js'; import { type ForgeProgressTracker } from './progress.js'; /** The project's one world.json — the file the engine loads and the forge writes back to. */ export declare function projectWorldJsonPath(projectRoot: string): string; /** * `ForgeDeps.worldJson` for a CLI project. **Both reads resolve to the SAME file**, and that is * correct here rather than a shortcut. * * The seam has two reads because game-play-agent's web lane genuinely has two world.json files * mid-session — a per-session copy the agent writes and a monorepo work copy the creator/browser * writes — and which one a differential plan is computed against decides what survives a forge. * `world-json-source.ts` carries the full argument; the short version is that what each read * WANTS is the same thing, "the file the returned modifications will be applied to", and the web * lane cannot collapse them without changing behaviour it has no test for. * * A CLI project has exactly ONE world.json. `applyModificationsToWorld` writes it, the engine * loads it, and nothing else exists to diverge from it — so returning it from both reads is not * "picking one", it is what "plan against the write target" reduces to when there is a single * target. The seam's own header names this lane and says so. * * Both reads happen LAZILY, when step 5 calls them — never snapshotted when these deps are built. * That matters even here: the reads run after a bake that can take 20 minutes, and the scaffolded * project's own tooling (or the creator) may have written world.json during it. * * Both keep the seam's documented degradations. They are contract, not swallowed errors: a * game with an unreadable world.json must still be able to forge, and neither `{}` nor `null` * hides anything the creator could act on — a missing or corrupt world.json fails far more * loudly, and much earlier, in `readProjectGameData` when the game is loaded into the browser. */ export declare function createProjectWorldJsonSource(projectRoot: string): ForgeWorldJsonSource; /** * A `ForgeLogger` over a plain line printer. The pipeline logs with pino's object-first shape * (`info({ jobId, ... }, 'message')`), which is what makes step progress visible at all — so the * context object is rendered rather than dropped, but compactly: these lines go to a creator's * terminal, not to a log aggregator. */ export declare function createConsoleForgeLogger(log: (message: string) => void): ForgeLogger; /** * The `ForgeDeps` steps 3-5 read. Four seams, and the two the type also declares are omitted * deliberately: * * - `model` — steps 3-5 never call an LLM (that is step 1, on api-server). The type requires it, * so it is a client that THROWS rather than one that returns something plausible: a future * change that starts calling a model from the browser-driven half must fail loudly, not * silently produce a level designed by a stub. Same discipline api-server applies to the * `transport` and `worldJson` seams it does not implement. * - `assetCatalog` — omitted; absence degrades to "no reuse", never a failure. It feeds the * DESIGNER (step 1), which does not run here, so it would have no reader in this lane even if * the CLI built one. * - `hqAssetIds` — omitted. A CLI project has no HQ regeneration queue, and the absent seam * already means exactly "no jobs in flight" (see the field's own doc), so supplying an empty * set would restate the default in a second place. */ export declare function buildCliForgeDeps(options: { projectRoot: string; storage: ForgeStorageEnv; transport: ForgeTransport; logger: ForgeLogger; /** Where steps 3-5 report their progress. Steps 1-2's arrives over SSE; both feed one tracker. */ onProgress?: (event: ForgeProgressEvent) => void; }): ForgeDeps; /** * Put the upstream artifacts into the local store so steps 3-5 can read them. * * `forge` is written unconditionally: it is what the server just handed back, and on a resume the * server returns the SAME cached artifact (`forgeWorld` ends every run with `store.get('forge')` * whether or not either step re-ran), so the write is idempotent rather than a clobber. * * `platformer-movement` is written whenever the terminal frame carried one, on the same reasoning: * it is step 1's output, it lives in api-server's store, and step 5 reads it from THIS store to set * `worldForgerMovement` on the level record. Only a `--platformer` design produces one, so absence * is normal — and on an older server that does not send the field it is also possible, which is * what `warnIfPlatformerMovementMissing` still exists to catch. * * `input` is written ONLY when absent, mirroring api-server's own freeze. It is the job's frozen * request, and steps 4 and 5 read the level name, prompt and start-level intent off it — so a * resume must persist what was originally asked for, not what this invocation's flags happen to * say. */ export declare function seedForgeStore(store: ForgedWorkStore, artifact: ForgeArtifact, input: ForgeJobInput, platformerMovement?: Record): Promise; /** Read a job's frozen `input`, or null if this machine has never run it. */ export declare function readFrozenInput(storage: ForgeStorageEnv, jobId: string): Promise; export interface RunPipelineOptions { projectRoot: string; store: ForgedWorkStore; deps: ForgeDeps; /** Progress printer for the lines that are not step events (the sidecar write, the warnings). */ log: (message: string) => void; /** * The run's step model. `commands/forge.ts` passes the SAME tracker it fed steps 1-2's SSE frames * into, which is the whole point: one five-step picture across both machines. Omitted (in tests) * a local one is built over `log`, so the step banners print exactly as they always did. */ tracker?: ForgeProgressTracker; /** * Which lane's browser steps to run. The default ('threed') is today's * archetypes → voxelize → persist. 'sideon' (2D side-view games) skips the * level bake — there is no vwld; flat ground + the placed objects ARE the * level — and persists through the side-on plan (no heightfield sidecar * either: the ground is flat, runtime snapping is exactly right). */ lane?: 'threed' | 'sideon'; } export interface RunPipelineResult { summary: ForgeLevelSummary; /** False when a resume found the job already applied and nothing was written this run. */ applied: boolean; /** The world.json lines to print, empty on the already-applied path. */ changes: string[]; } /** * Drive steps 3-5 in order, skipping any whose output artifact already exists — the same * resume mechanism `run-world-forger.ts` uses, and the same one api-server applies to steps 1-2. * * The write half deliberately does NOT live in step 5: `placeAndPersistLevel` returns the * modifications and the heightfield, and the host applies both. The ordering below is a contract, * not a detail — `persisted` is the job-complete marker a resume returns directly, so recording it * before the world.json write would let a failed write resume into "done" over an unchanged world. * Unlike the web lane's applier (which catches everything and returns `{success:false}`), the * CLI's `applyModificationsToWorld` THROWS, so here that ordering does real work. */ export declare function runForgePipeline(options: RunPipelineOptions): Promise;