# render/

`sync.mjs` turns a project's two hand-maintained files into native Claude Code and Codex setups.

```
config.json + context.md + kit ──sync.mjs──► .claude/agents/       Claude roster
                                           .claude/skills/       Claude skills
                                           CLAUDE.md             Claude guidance
                                           .codex/agents/        Codex subagents
                                           .agents/skills/       Codex skills
                                           AGENTS.md             Codex guidance
                                           .maestro.lock         content hashes
```

The Codex output follows the official conventions for
[AGENTS.md](https://developers.openai.com/codex/guides/agents-md),
[skills](https://developers.openai.com/codex/skills), and
[custom subagents](https://developers.openai.com/codex/subagents).

## Usage

```bash
node render/sync.mjs --project <dir> [--kit <dir>] [--check]
```

- `--project` — the managed project directory (must contain `config.json`; `context.md`
  optional but recommended).
- `--kit` — the AI Maestro kit root. Defaults to `config.kitSource.path` (relative to the
  project), else the kit this script lives in.
- `--check` — verify the committed generated files match what would be rendered; exit 1 on
  drift. Use it in a pre-commit hook or gate so generated files never go stale.

## Contract

- **Generated files are committed** (so the project works without re-rendering) but
  **never hand-edited** — change `config.json` / `context.md` and re-run.
- The lock file is **deterministic** (no timestamps) — only the kit version and content
  hashes are baked in, so `--check` is stable.
- Removing an agent from `roster` or a skill from `skills` and re-rendering deletes the
  stale generated file. Only files recorded in the previous `.maestro.lock` are pruned, so
  anything else you keep under `.claude/` is never touched.
- **Your existing `.claude/` is adopted, not overwritten.** On the first render, any
  `.claude/agents/*.md` or `.claude/skills/<name>/SKILL.md` that isn't in the previous lock was
  written by you, not by this tool. It is moved into `<project>/custom/` and reported — which
  both preserves it and keeps it the thing that renders, since `custom/` overrides a kit file of
  the same name. `CLAUDE.md` / `AGENTS.md` have no `custom/` slot, so those are left in place
  with a warning instead.
- **Existing Codex files are never overwritten on first render.** An unmanaged
  `.codex/agents/*.toml` or `.agents/skills/*/SKILL.md` remains in place and is excluded from
  Maestro's lock. Once Maestro has generated a Codex file, later syncs update it normally.
- **Adding vs replacing.** A project file whose name the kit doesn't ship *adds* an agent and
  costs nothing. One whose name the kit *does* ship *replaces* it — this project then stops
  receiving kit improvements to it. `sync` reports the two separately and names the alternative
  (`<name>.overlay.md`) for the common case of only wanting to add a rule.
- **Project overlay:** `<project>/custom/agents/*.md` and `<project>/custom/skills/<name>/SKILL.md`
  are merged in, overriding a kit file of the same name, and are **not** filtered by `roster` /
  `skills` — those select which *kit* files you take; your own are always included. `custom/` is
  never a vendored kit folder, so `maestro update` cannot touch it.
- **Legacy overlay location:** `<project>/agents` and `<project>/skills` are still read, but
  only when the project dir and the kit dir are different (the `init` capsule layout). Under
  `setup` the kit is vendored *into* the project, so those paths are the kit's own folders —
  reading them there re-added every kit agent regardless of `roster`, and `update` swept away
  anything a project had put in them. `update` migrates what it finds there into `custom/`.

## config.json shape

See `starters/orchestrated-project/config.json` for a full example. Keys:

| Key | Meaning |
| --- | --- |
| `project.name` | Substituted into generated files as `{{PROJECT_NAME}}`. |
| `project.areas` | The areas your tickets use (backend/frontend/infra/…). |
| `roster` | Agent file basenames to include (omit = all). |
| `skills` | Skill dir names to include (omit = all). |
| `model.default` / `model.floors` | Default model tier + per-area floors. |
| `humanGates` | Allowed `human_gate` phrases for tickets. |
| `orchestration.maxWorktrees` | Maximum concurrent worktree lanes: an integer from 1 to 5, or `"unlimited"`. |
| `ticketReadiness.enabled` | Opt-in: every live ticket needs `gates` and `inputs`, and `ticket add`/`import` refuse an incomplete ticket. See docs/KIT-UPGRADES.md. |
| `checks.processRatio` / `checks.evidenceBudget` | Targets for `maestro check process-ratio` / `evidence-budget`. |
| `orchestration.swarm` | Optional continuous-pool policy; disabled by default. Configure with `maestro swarm`. |
| `targets.claude` / `targets.codex` | Enable either runtime target; both default to `true`. |
| `kitSource.mode` / `kitSource.path` | Where the kit lives (`sibling` / `vendor` / `self`). A vendored kit uses `path: "."`; a path that names no kit falls back to the kit running `sync`, and `maestro update` rewrites it. |
