Skip to main content
.ralphy/workspaces/<ws>/projects/<id>/asset-manifest.json is the single pointer table for everything a project has generated. One key per asset slot, one record per pointer. The runtime shape is defined in cli/commands/generate.ts (Manifest type, readManifest / writeManifest). This page documents the schema, the slot-id convention, and how auto-versioning interacts with the pointer.

Full example

Everything generated by ralphy generate <kind> lands here as a slot. The manifest is the “current pointer”; the file on disk is the latest version.

Schema

The runtime shape from cli/commands/generate.ts:

Fields

There is no top-level projectId, version, or totalAssets field in the runtime shape. (Some older example manifests under profiles/ have a richer shape — those are legacy and not used by the current ralphy generate flow.)

Slot ID convention

Slot ids are lowercase kebab-case. The canonical pattern is:
Examples:
  • scene-01-bg-image — background image for scene 01
  • scene-01-vid — video for scene 01
  • scene-01-vo — voiceover for scene 01
  • scene-04-bg-image — background image for scene 04
  • bg-music — project-wide background music (no scene prefix)
The validator in cli/commands/generate.ts → normalizeSlot() accepts [a-zA-Z0-9_-]+ and normalizes uppercase to lowercase and _ to -, with a stderr warning. The canonical form is [a-z0-9-]+. Characters outside the relaxed set (spaces, dots, slashes, unicode) raise E_INPUT_INVALID.
The auto-normalization exists because six of ten project postmortems flagged the previous hard-reject as their highest-frequency CLI friction.

Auto-versioning

When you regenerate a slot that already has a file, cli/lib/providers/media.ts → protectExistingAsset() archives the existing file before the new one lands:
A second regen archives v1 → v2:
The pattern is <base>.v{N}<ext> where N is the next free version number. The archiver scans siblings, finds the highest existing N, and uses N+1. Bypass with --force-overwrite only when the user explicitly asks for legacy destructive behavior.

Manifest interaction

The manifest’s path field always points at the latest version — the unversioned file. The archive files (.v1.png, .v2.png, …) are not listed in the manifest. To enumerate them, walk the assets directory. Every generation also appends a row to generations.jsonl (see Memory schemas). The full history of a slot lives in those rows; the manifest is the current pointer only.

Promote workflow

If the user prefers v1 over the latest, the promote flow is manual and user-driven — Ralphy does not flip the pointer without explicit consent.
The manifest’s path does not change (the unversioned filename is the same). The new file is the new latest. A future ralphy project doctor verb may automate this, but until then the swap is by hand.

Manifest vs generations.jsonl

The manifest is the read-cache for the editor and renderer — it knows where every slot’s file is right now. The log is the audit trail. The two never disagree about the current file on disk: every successful ralphy generate <kind> writes the file, appends to the log, and updates the manifest in the same call.

Writer behaviour

readManifest returns {slots: {}} when the file is absent or unparseable — empty-slot recovery is silent so a corrupted manifest does not block a fresh generation. writeManifest does a full rewrite of the file with JSON.stringify(m, null, 2). The file is small (a few hundred bytes to a few KB per project), so the rewrite cost is negligible. This is the one place in the project memory where rewrites happen. The JSONL logs are append-only; the manifest is rewrite-allowed because it tracks current pointers, not history.

Sister files: render output

render/final.mp4 and friends are written by ralphy render <id> and do not appear in the manifest. The renderer reads the manifest to find slot files, composes the video, and writes the output to render/ directly. Render outputs are tracked separately by ralphy project show <id>.

Doctor verb

The doctor verb walks the manifest slot-by-slot and ffprobes every file, flagging divergences (missing file, wrong duration, wrong dimensions, broken codec). It exits non-zero on any red. Postmortems (tokyo, kbo) flagged that the manifest claims drift from the actual on-disk file on long sessions; the doctor is the recovery path.