fjx.json — per-project config

Each project directory the supervisor manages must contain an fjx.json at its root. It is loaded once at supervisor startup and re-read on SIGHUP / fjx supervise reload. The loader lives in src/project-config.ts.

Shape

{
  "images": {
    "pm": "fjx-base",
    "dev": "fjx-base",
    "qa": "fjx-base"
  }
}

The top-level value MUST be a JSON object. Unknown top-level fields are tolerated and preserved verbatim — a newer supervisor writing extra keys won't break an older fjx reading the same file.

Fields

images (object, optional)

Maps a role name to the container image the supervisor spawns for that role's tick. Keys are role identifiers (pm, dev, qa today; more may be added). Values are image references resolvable by the host's container runtime — fjx-base, ghcr.io/example/fjx-dev:v1.2.3, a local tag from just images::build, etc.

  • Missing images is equivalent to {}: no role has an image override.
  • A role without an entry falls back to the supervisor's default image. If there is no default and no entry, that role's ticks are skipped (the supervisor logs and moves on).
  • Values MUST be strings; an object or array under images.<role> is a hard error.
  • Unknown role keys are preserved but ignored by the current supervisor.

Validation errors

The loader fails fast with a ProjectConfigError when:

  • The file exists but is not valid JSON.
  • The top-level value is not a JSON object (a bare array, string, or null).
  • images is present but not an object.
  • images.<role> is present but not a string.

A missing fjx.json is not an error — loadProjectConfig returns undefined and the supervisor treats the project as having no overrides. This is intentional so a fresh project directory can be added to the supervisor's project list before its config lands.

Reloading

The supervisor re-reads each project's fjx.json on:

  • SIGHUP to the daemon process
  • fjx supervise reload over the control socket (see supervisor setup)

In-flight ticks finish under their previous config; the next scheduled tick picks up the new values. There is no partial reload — the whole file is re-parsed atomically.