Imports:
  - Types:
      - ProjectConfig
      - BuildConfig
      - ReviewConfig
      - AdditionalReviewConfig
      - load_project_config
    From: goga/config
  - Types:
      - ConfigHooks
      - ConfigOverlay
    From: goga/config/hooks
  - Types:
      - resolve_wrapper_path
    Usages:
      - resolve-wrapper-path
    From: goga/agents
  - Types:
      - ensure_in_docker
      - decode_extra_env
    Usages:
      - ensure-in-docker
      - extra-env-carriage
    From: goga/docker
  - Types:
      - run_ralphex
    Usages:
      - run-ralphex
    From: goga/ralphex
  - Types:
      - BuildHooks
      - BuildMoment
      - StageFacts
      - WorkIdentity
      - RelocationOutcome
      - GateVerdict
    Usages:
      - checkpoints
    From: goga/build/hooks
  - Types:
      - resolve_current_branch_name
      - resolve_topic_dir
      - collect_topic_statuses
    Usages:
      - topic-paths
      - topic-statuses
    From: goga/history

Usages:
  conventions: .goga/usages/conventions.md
  ralphex: .goga/usages/cooks/ralphex.md
  agent-wrappers: .goga/usages/cooks/agent-as-claude-wrappers.md

Annotations: |
  The `conventions` practice is used for:
  - Working with the codebase
  - Organizing the REPL development cycle
  - Debugging and testing
  - Organizing the test infrastructure
  - Understanding the general principles and rules of development and testing in the project

  This cell owns the build domain: manifest-commit verification, two-part
  settings resolution with root inheritance, the stable two-pass cycle with
  the five checkpoints, ralphex config generation with the external-review
  surface, vendored defaults sync with finalize materialization, and plan
  relocation. Every non-skipped run is exactly two ralphex invocations — a
  tasks pass then a review pass. It
  delegates the ralphex launch to `run_ralphex` (per the
  `run-ralphex` practice) — ralphex is launched through `run_ralphex`, never
  directly from this cell.

  The checkpoint facts resolve in this operation before delivery (per the
  `checkpoints` practice): the work identity via `resolve_current_branch_name`
  with the "unknown" fallback, the topic hosting via `resolve_topic_dir`, and
  the completion statuses via `collect_topic_statuses` — all before the
  checkpoint moments, never at them. Env values are never delivered to any
  context and never printed — presence travels as names only.

  The entrypoint owns the in-container load-and-amend of the project
  configuration: the authored load, the config amendment delivery to the tool
  packages installed in the image, and the handover of the effective
  configuration to the orchestration. The delivery follows the
  one-action-two-moments rule — the same action, vocabulary, and semantics
  as the host delivery; each side consumes only the fields it owns.

  The orchestration enforces the agent value requirement on the effective
  configuration before the first state write, so a container-side amendment
  supplying build.agent satisfies it. The pass env layers compose per
  `extra-env-carriage`: the effective task env layer of the pass with the CLI
  entries payload applied above it, engine-variable keys dropped — applied
  through the env layer of the ralphex launch only.

  Use the `conventions` practice for development and testing.
  Use the `ralphex` practice for the ralphex config-generation contract (the
  .ralphex/config key layout, the external-review surface, the finalize step
  files) written before launch.
  Use the `agent-wrappers` practice for the in-container wrapper naming
  convention referenced when writing claude_command into .ralphex/config.
  Use the `resolve-wrapper-path` practice when calling `resolve_wrapper_path`.
  Use the `run-ralphex` practice to delegate the launch.
  Use the `checkpoints` practice for the checkpoint integration order and the
  fact resolution.
  Use the `topic-paths` practice for the work identity resolution behind the
  branch hosting and the `topic-statuses` practice for the completion status
  facts.
  Write all output to sys.stderr (click is not used).
  Run git external commands via subprocess (the manifest pre-check and the
  branch resolution) — never at a checkpoint moment.

---

"build(plan: str, config: ProjectConfig, cli_options: dict) -> exit_code:int":
  location: build.py
  annotations: |
    Orchestrates the stable two-pass build cycle with the five hooks
    checkpoints.

    `plan`: path to the plan file (markdown)
    `config`: loaded project configuration object
    `cli_options`: dictionary of CLI options (dry_run, skip_manifest_check,
                   skip_review, base_ref, review_patience, session_timeout,
                   idle_timeout, wait, max_iterations)
    `exit_code`: process exit code (0 = success, 1 = failure)

    Algorithm:
    0. (pre-check) When skip_manifest_check is not set: verify all project
       CODEMANIFEST files are committed to git; reject with exit code 1 when
       any are uncommitted. A pre-check failure fires no events
    1. Resolve the run settings via `resolve_run_settings` — the two parts
       with root inheritance, the tri-state skip, the strategy default medium
    2. Validate the review configuration via `validate_review_config` when
       the review pass will run; a validation failure fires no events
    3. Guard the tasks agent on the effective configuration: when the
       resolved tasks agent of the settings is None — one clean error naming
       the missing agent requirement, exit code 1, no events fire, nothing
       is written; a container-side amendment supplying build.agent
       satisfies the guard
    4. Rewrite .ralphex/prompts/ and .ralphex/agents/ from the vendored
       defaults via `sync_ralphex_defaults` — roles filtering, finalize
       materialization when the prompt is set
    5. Decode the CLI entries payload once via `decode_extra_env` from the
       dedicated engine variable (per `extra-env-carriage`); a damaged
       payload is one clean error — nothing partially applied, no pass
       launches
    6. Resolve the checkpoint facts from this operation's own data: the
       `WorkIdentity` (branch via `resolve_current_branch_name` with the
       "unknown" fallback, slug/year via `resolve_topic_dir` when the branch
       hosts a topic), the `BuildMoment` (plan, work, dry_run), and both
       `StageFacts` (env as names)
    7. Run the validation gate via `BuildHooks` (its validate_build
       checkpoint); when the
       returned `GateVerdict` is not approved: print one merged error listing
       every violation (tool, hook, reason) to sys.stderr and return exit
       code 1 — no pass launches, the plan is not relocated, no further
       events fire
    8. Emit build_started with the same facts the gate saw
    9. Tasks pass: compose the options via `compose_pass_options` (stage
       tasks), resolve the root agent wrapper per the `resolve-wrapper-path`
       practice, compose the tasks env layer — the effective build.env task
       env layer with the decoded CLI entries applied above it,
       engine-variable keys dropped (per `extra-env-carriage`) — emit
       pass_started, launch via `run_build_pass` with the composed layer as
       the env layer, emit pass_completed with the actual exit code
    10. When the tasks pass succeeded and review is not skipped: review pass
        under the review agent's wrapper (the additional agent's wrapper under
        the short strategy), the review env layer — the effective
        build.review.env task env layer with the same decoded CLI entries
        applied above it, engine-variable keys dropped — and the
        strategy-bound options; pass_started / launch / pass_completed
        around it. A failed tasks pass never launches the review pass
    11. Relocate the plan via `move_completed_plan` with outcome = success of
        the final pass (dry-run and failure leave the plan in place); take the
        returned `RelocationOutcome`
    12. Recompute the work's history statuses via `collect_topic_statuses`
        AFTER the relocation attempt (moved or not; branch-only form — an
        empty list)
    13. Emit build_completed with the final exit code, the executed stage
        sequence, the relocation outcome, and the recomputed statuses
    14. Return the exit code of the last executed pass

    Apply `conventions` for docstring style and intra-package imports.
    Apply `ralphex` for the config-generation contract.
    Apply `agent-wrappers` for the wrapper path semantics.
    Apply `resolve-wrapper-path` when calling `resolve_wrapper_path`.
    Apply `run-ralphex` when delegating the launch.
    Apply `checkpoints` for the checkpoint order and fact resolution.
    Apply `topic-paths` and `topic-statuses` for the work identity and the
    completion statuses.

    Requirements:
    - `ralphex` is launched only through `run_ralphex` — never via a direct
      subprocess call
    - The agent value guard runs before the first state write — the
      .ralphex/ rewrite never starts on a run the guard rejects
    - The guard reads the effective configuration — an amendment supplying
      build.agent satisfies it
    - The CLI entries payload decodes once per run and feeds both pass
      compositions; the payload wins over the task env layer on key conflict
    - Engine-variable keys never enter a composed layer — dropped silently,
      the inherited launch values stand
    - Every non-skipped run is exactly two passes; a skipped review yields
      exactly one tasks pass
    - The run's exit code is the last executed pass's; plan relocation
      happens only on success of the final pass
    - The task env never reaches the review pass (secret-safe, never printed)
    - On dry-run: fire the identical event structure with the dry_run fact,
      print the commands of both passes without env layers, relocate nothing
    - Pre-launch failures (steps 0-5) fire no events — the moment never
      happened
    - A blocked run (step 7) fires nothing after the gate
    - Warnings and errors of the checkpoints name the tool, the action, and
      the reason (delivered by the zone per its practices)

    Constraints:
    - Do not assemble the ralphex command or invoke ralphex directly —
      delegate to `run_ralphex`
    - Do not read the environment for the payload anywhere but the single
      decode step
    - Do not apply any layer to the process environment — layers travel only
      through the env parameter of the ralphex launch
    - Do not read git at a checkpoint moment — the facts resolve before
      delivery
    - Do not deliver or print env values — names only, in every fact
    - The .ralphex/ directory lifecycle is owned by the host launcher
      (goga/commands/build)

"main() -> exit_code:int":
  location: __main__.py
  annotations: |
    Entry point for python -m goga.build execution inside a Docker container.

    `exit_code`: process exit code (0 = success, 1 = failure)

    Apply the `ensure-in-docker` practice for the container guard.
    Apply `conventions` for docstring style and intra-package imports.

    Algorithm:
    0. Call `ensure_in_docker` as the very first statement (per the
       `ensure-in-docker` practice)
    1. Parse CLI arguments via argparse (plan + options); the argparse surface
       carries the --skip-review / --no-skip-review pair resolving
       to skip_review: bool | None, --base-ref, --dry-run,
       --skip-manifest-check, --session-timeout, --idle-timeout, --wait,
       --max-iterations, and --review-patience (addressing
       build.review.additional.patience)
    2. Load the authored project configuration via `load_project_config` —
       a load failure is one clean error, exit 1; ralphex never launches
    3. Deliver the config amendment via the `ConfigHooks` checkpoint surface
       with the authored configuration — the first failing tool, or a tool
       package whose facade fails to import, converts to one clean error
       naming the tool and the action, exit 1; ralphex never launches
    4. Print the amendment summary lines to stderr (nothing when empty)
    5. Build cli_options from the parsed argparse results
    6. Invoke `build`(plan, the effective configuration of the
       `ConfigOverlay`, cli_options)
    7. Return the resulting `exit_code`

    Requirements:
    - The guard at step 0 MUST be covered by tests for both branches
    - Exactly one configuration load and exactly one delivery — the
      orchestration receives the effective configuration, never the authored
      one
    - No configuration value appears in any output — the summary lines carry
      the tool, the path, set or forced only

"resolve_run_settings(config: BuildConfig, cli_options: dict) -> settings: RunSettings":
  location: run_settings.py
  annotations: |
    Resolve the run settings of one build from the two-part configuration
    and the CLI options — root inheritance applied, tri-state skip resolved,
    strategy defaulted.

    `config`: build configuration (`BuildConfig`) in the two-part form;
    its review part may be None — step 0 covers it
    `cli_options`: CLI options dictionary; the keys read here are skip_review
    (bool | None), base_ref (str | None — empty/whitespace counts as unset),
    review_patience, session_timeout, idle_timeout, wait, max_iterations
    (each None when the flag was not given)
    `settings`: the resolved run plan (`RunSettings`)

    Algorithm:
    0. When config.review is None (the build.review key is absent),
       treat every review field as unset: skip resolves False, the agent
       and session knobs inherit the root values per step 4,
       base_ref/roles/finalize/max_iterations stay None, and the additional
       part resolves with agent = the resolved review agent and
       patience/max_iterations None
    1. Resolve skip: cli_options skip_review when not None, otherwise the
       skip field of `ReviewConfig`, otherwise False
    2. Resolve strategy: the strategy field of `ReviewConfig` when set,
       otherwise medium
    3. Tasks part: the root agent and env verbatim; max_iterations and
       each session knob resolve the cli_options value when given (not
       None), otherwise the root value
    4. Review part: agent from `ReviewConfig` when set, otherwise the root
       value; each session knob resolves the cli_options value when given,
       otherwise the review value when set, otherwise the root value;
       max_iterations resolves the review value verbatim — neither the
       root value nor the CLI flag (both tasks-pass sources) ever reaches
       it (env never inherits — the root env is
       the tasks-pass layer only, the review env is exactly
       build.review.env)
    5. Additional: agent from `AdditionalReviewConfig` when set, otherwise
       the resolved review agent; patience resolves the cli_options
       review_patience when given, otherwise the `AdditionalReviewConfig`
       value verbatim; max_iterations from `AdditionalReviewConfig`
       verbatim (None when the block is absent)
    6. base_ref: cli_options base_ref when not None (empty/whitespace →
       unset; padded → stripped), otherwise `ReviewConfig` base_ref

    Requirements:
    - Precedence CLI > config > default > omit for every resolved knob; the
      review max_iterations is the one exception — review-sourced only (no
      CLI flag, no root inheritance)
    - Unset at both levels resolves to None — the key stays absent from the
      ralphex options
    - env dicts pass verbatim; the review env never inherits the root env
      (secret-safe — the root env is the tasks-pass layer); an empty
      review env means no review layer

    Constraints:
    - Pure — no side effects, no validation of values (separate routine)
    - Do not resolve wrappers — wrapper resolution belongs to the
      orchestrator and the validation routine
    - Do not validate the strategy value — the whitelist check belongs to
      `validate_review_config`

"RunSettings(skip: bool, tasks: PassSettings, review: ReviewPassSettings)":
  location: run_settings.py
  annotations: |
    The resolved run plan of a single build.

    `skip`: the final skip decision (False when neither source is set)
    `tasks`: the resolved tasks-pass part
    `review`: the resolved review-pass part (inheritance applied; always
              present — a skipped run still carries the resolved review
              facts)

    Requirements:
    - Immutable frozen dataclass (frozen=True, kw_only=True), per
      `conventions`
    - Computed by `resolve_run_settings` — never loaded from YAML directly
  properties:
    "skip -> bool": |
      The final skip decision of the tri-state resolution.
    "tasks -> PassSettings": |
      The resolved tasks-pass part.
    "review -> ReviewPassSettings": |
      The resolved review-pass part with root inheritance applied.

"PassSettings(agent: str | None, env: dict[str, str], max_iterations: int | None, session_timeout: str | None, idle_timeout: str | None, wait: str | None)":
  location: run_settings.py
  annotations: |
    The resolved tasks-pass part of the run plan.

    Apply the `conventions` practice for the data-model rules and
    intra-package imports.
  properties:
    "agent -> str | None": |
      The tasks-pass executor agent name, None when unset.
    "env -> dict[str, str]": |
      The tasks-pass env layer, verbatim; the review pass never receives it.
    "max_iterations -> int | None": |
      The tasks-pass iteration cap; None when unset.
    "session_timeout -> str | None": |
      The tasks-pass session timeout; None when unset.
    "idle_timeout -> str | None": |
      The tasks-pass idle timeout; None when unset.
    "wait -> str | None": |
      The tasks-pass rate-limit wait; None when unset.

"PassSettings::ReviewPassSettings(roles: list[str] | None, base_ref: str | None, strategy: str, finalize: str | None, additional: AdditionalReviewConfig)":
  location: run_settings.py
  annotations: |
    The resolved review-pass part — a concretization of the base pass part:
    the inherited agent and session-knob fields, the verbatim review env
    layer (never inherited from the root), the review-sourced iteration
    cap (the inherited max_iterations field, filled from
    build.review.max_iterations only — never the root value, never the CLI
    flag), plus the review-only members.

    `roles`: the declared reviewer composition, verbatim
    `base_ref`: the resolved review diff base, None when unset
    `strategy`: the resolved strategy — exactly full, medium, or short
    `finalize`: the user-authored finalize prompt, None when unset
    `additional`: the resolved external-review block (agent inherited from
                   the review agent when unset)

    Requirements:
    - Immutable frozen dataclass (frozen=True, kw_only=True), per
      `conventions`
  properties:
    "roles -> list[str] | None": |
      The declared reviewer composition; None or empty list mean the full
      default set to the consumer.
    "base_ref -> str | None": |
      The resolved review diff base; None when unset.
    "strategy -> str": |
      The resolved review strategy — full, medium, or short.
    "finalize -> str | None": |
      The finalize prompt; None leaves the step at the ralphex default (off).
    "additional -> AdditionalReviewConfig": |
      The resolved external-review block; its agent field carries the
      inherited review agent when unset in config.

"compose_pass_options(settings: RunSettings, stage: str) -> options: dict[str, str | int | bool]":
  location: pass_options.py
  annotations: |
    Compose the ralphex options of one pass from the resolved run settings.

    `settings`: the resolved run plan
    `stage`: exactly tasks or review
    `options`: the ralphex options of the pass, consumed by `run_build_pass`

    Algorithm:
    1. tasks: the tasks_only mode flag plus the resolved tasks knobs
    2. review: the mode flag by strategy — review, or external_only under
       short — plus the resolved review knobs (session knobs and the
       review-sourced max_iterations), base_ref, review_patience
       from additional.patience, and max_external_iterations from
       additional.max_iterations (0 = ralphex auto, passed verbatim)

    Requirements:
    - Unset knobs stay absent from the dict — the assembled ralphex command
      carries no flag for them
    - Review-only options never appear on the tasks pass
    - Exactly one pass-mode flag per composition — the modes are mutually
      exclusive

    Constraints:
    - Pure — no side effects, no config reads beyond `settings`

"validate_review_config(settings: RunSettings) -> none: None":
  location: review_config.py
  annotations: |
    Semantically validate the review configuration of a run whose review
    pass will execute; raise ValueError naming the invalid value.

    `settings`: the resolved run plan (`RunSettings`) — carries every
                review fact the checks read (roles, env, agent,
                additional, strategy)

    Algorithm:
    1. Return without checks when `settings` says skip — a skipped run does
       not validate review fields
    2. Check every role of the review part against the ralphex whitelist
       (quality, implementation, testing, simplification, documentation); a
       role outside the whitelist raises ValueError naming the role
    3. When the review part carries a non-empty env and no review agent —
       raise ValueError naming the problem (env requires agent)
    4. When the resolved review agent is None — raise ValueError naming the
       problem (no review agent). Reachable when neither the authored
       configuration nor an amendment supplies a review agent
    5. Resolve the review-agent wrapper via `resolve_wrapper_path` and
       require the wrapper file to exist; absence raises ValueError naming
       the agent
    6. When the strategy engages the external review (short always; full
       when additional.agent is set): resolve the additional-agent wrapper
       the same way and require its existence
    7. Check the resolved strategy of the review part against the
       whitelist full | medium | short; a value outside the whitelist
       raises ValueError naming the value

    Requirements:
    - Runs before any side effect — before writing .ralphex/ and before the
      first checkpoint
    - The error message names the invalid value

    Constraints:
    - Do not validate the tasks agent wrapper here — its absence surfaces at
      ralphex time
    - Do not check review fields of a skipped run

"sync_ralphex_defaults(config: BuildConfig, settings: RunSettings) -> none: None":
  location: ralphex_runtime.py
  annotations: |
    Fully rewrite .ralphex/prompts/ and .ralphex/agents/ from the vendored
    ralphex defaults (or the configured custom directories), apply the
    declared reviewer composition to the review prompts, and materialize the
    ralphex files of the finalize step when the finalize prompt is set.

    `config`: build configuration (`BuildConfig`) with optional prompts_dir /
    agents_dir
    `settings`: the resolved run plan (`RunSettings`)

    Algorithm:
    1. Choose the prompts source: the prompts_dir field of `BuildConfig`
       when set, otherwise the vendored package defaults; choose the agents
       source the same way
    2. Fully rewrite both target directories (clear, then copy)
    3. When the roles of the review part are a non-empty list: filter both
       review prompts — keep only the {{agent:X}} lines of the selected
       roles; adapt the accompanying text to the actual number of remaining
       roles
    4. Copy the definition files of all review agents regardless of the
       selection
    5. When the finalize prompt is set: materialize the ralphex files of the
       finalize step from the prompt string per the `ralphex` practice (the
       finalize prompt file of the review step)

    Requirements:
    - The full rewrite happens once per build run, regardless of roles
    - With the full default set (or no roles) the prompts are byte-identical
      to the vendored defaults
    - An empty intersection of roles with a phase's default set is a regular
      phase without subagents — no error, no fallback
    - Custom prompts_dir / agents_dir sources are copied as-is, without
      filtering
    - The finalize materialization happens only when the prompt is set;
      unset leaves the vendored tree untouched

    Constraints:
    - Do not touch .ralphex/config — it is written by the config routine

"write_ralphex_config(settings: RunSettings, wrapper_path: str) -> none: None":
  location: ralphex_config.py
  annotations: |
    Generate .ralphex/config for one ralphex pass.

    `settings`: the resolved run plan (`RunSettings`) — the external surface
    and the finalize fact of the review pass
    `wrapper_path`: executor wrapper path of the current pass (the
    claude_command value)

    Algorithm:
    1. Set claude_command to `wrapper_path`
    2. Apply claude_args defaults when missing
    3. Set preserve_anthropic_api_key to true
    4. Set move_plan_on_completion to false — always, for every pass
    5. External surface (the review pass): under the medium strategy set
       codex_enabled to false — the external review is explicitly disabled
       (internal agents only); under full or short leave the ralphex
       default (enabled), and when the resolved additional agent is set,
       set external_review_tool to custom and custom_review_script to the
       additional agent's wrapper path; when unset, leave the ralphex
       default (codex)
    6. When the finalize prompt is set: set finalize_enabled to true; unset
       leaves the ralphex default (false)

    Requirements:
    - In a two-pass run this routine is called twice — each pass passes its
      own executor wrapper (task wrapper; review wrapper — the additional
      wrapper under the short strategy)
    - Under the medium strategy the external review is explicitly disabled
      (codex_enabled false); full and short leave it enabled

    Constraints:
    - Do not write prompts or agents here
    - Do not derive the external surface from anything but the resolved
      settings

"run_build_pass(plan: str, settings: RunSettings, options: dict[str, str | int | bool], wrapper_path: str, dry_run: bool, env: dict[str, str] | None = None) -> exit_code: int":
  location: build_pass.py
  annotations: |
    Execute one ralphex pass: write the pass config, delegate the launch.

    `plan`: path to the plan file (markdown)
    `settings`: the resolved run plan (`RunSettings`) — carried to the
                config routine
    `options`: the resolved ralphex options of the pass (composed by
               `compose_pass_options`; carries exactly one pass-mode flag)
    `wrapper_path`: executor wrapper of the current pass
    `dry_run`: when True, print instead of launching
    `env`: optional environment layer forwarded verbatim to `run_ralphex` —
           the tasks pass receives the root env, the review pass the review
           env; values are never printed
    `exit_code`: the exit code returned by ralphex

    Algorithm:
    1. Write .ralphex/config via the config routine with `wrapper_path` and
       the pass settings
    2. Delegate the launch to `run_ralphex` with `plan`, `options`, `dry_run`,
       and `env`
    3. Return the exit code of `run_ralphex`

    Constraints:
    - Do not assemble or invoke the ralphex command directly — only through
      `run_ralphex`

"move_completed_plan(plan: str, outcome: bool, dry_run: bool) -> relocation: RelocationOutcome":
  location: plan_relocation.py
  annotations: |
    Relocate a completed plan file into the completed/ subdirectory of the
    directory holding the plan, and report the outcome.

    `plan`: path to the plan file
    `outcome`: True when the run succeeded
    `dry_run`: when True, nothing was launched — leave the plan in place
    `relocation`: the outcome facts — moved with the destination, or not
                  moved

    Algorithm:
    1. When `outcome` is False or `dry_run` is True: return the not-moved
       outcome
    2. Move the plan file to <plan_dir>/completed/<plan_name>, creating the
       completed/ subdirectory when missing
    3. Return the moved outcome with the destination

    Requirements:
    - Called after any started run — after the success of the last pass

    Constraints:
    - Do not hard-code docs/plans/ — the directory follows the plan file
      location

---

Author: Goga
CreatedAt: 18/08/26

Description: |
  Manifest describing the code build orchestration logic through ralphex —
  the stable two-pass cycle with the five hooks checkpoints: two-part
  settings resolution with root inheritance, the validation gate, the four
  notifications, the external-review surface, finalize materialization, and
  plan relocation.
