# Python Module Reference

All autobuilder daemon code lives in `skills/build-feature/`. Every module is **stdlib-only Python** (no pip dependencies required).

## Module Overview

| Module | Lines | Purpose |
|---|---|---|
| `builder_loop.py` | Entry point | CLI parsing, config resolution, main iteration loop, crash recovery |
| `iteration.py` | Core | Single iteration orchestration, phase selection, agent invocation |
| `prompt.py` | Core | Prompt construction, strategy selection, memory index generation |
| `project.py` | Config | Project config (`config.yaml`), skills sync, gitignore, credentials |
| `common.py` | Shared | Constants, review strategies, paths, logging utility |
| `tasks.py` | Data | Task CRUD, topological sorting, area grouping, numbering |
| `status.py` | Data | Status files, PID management, stop signal, iteration journal |
| `process.py` | Infra | Agent subprocess management, browser process cleanup |
| `hooks.py` | Infra | Optional hook discovery, execution, and prompt augmentation |
| `git_ops.py` | Infra | Git push, branch detection, remote management |
| `initiative.py` | Data | Initiative resolution, spec/constraints loading, directory management |
| `config.py` | Config | `config.json` project registry parsing |
| `frontmatter.py` | Util | YAML frontmatter extraction from task markdown files |

### Shared with hook-helpers

| Module | Purpose |
|---|---|
| `autobuilder_hook_helpers.executors` | Per-CLI registry (cursor / codex / claude / opencode): command shape, session handling, model list. Lives in `hook-helpers/` so both the daemon (which adds the dir to `sys.path` via `common.py`) and project-side hook scripts (which get it on `PYTHONPATH` via `hooks.build_hook_env`) share one source of truth. |

## Key Functions

### builder_loop.py

- `main()` — Entry point. Parses CLI args, resolves project config, runs the iteration loop.
- `_ensure_project_exists(project_dir)` — Creates project directory and initializes git if needed.
- `_migrate_to_initiative_layout(project_dir, root_initiative)` — Migrates legacy flat layouts to initiative-based structure.

### iteration.py

- `run_iteration(...)` — Executes a single iteration. Returns journal data dict. Raises `AgentFailed`, `GracefulStop`, or `NothingToImprove`.
- `_run_phased(...)` — Sends each phase as a chained agent request. Plan and execute phases fan out per-task by default; the executor / model are resolved per phase via `resolve_phase_executor` and a fresh chat session is opened when the executor changes.
- `_expand_phase_into_calls(phase, project_config, tasks_dir)` — Returns the list of task subsets for a phase (one entry per agent call). `[None]` = single legacy call; `[]` = phase has no work; `[[task1], [task2], ...]` = per-task fanout.
- `_chunk(items, size)` — Slice helper used to bundle tasks per call.

### prompt.py

- `build_phase_prompt(phase, include_header, task_subset=None, task_progress=None, ...)` — Constructs a single phase prompt (review / plan / execute). When `include_header=True`, prepends the full iteration header + project info + reference material; otherwise emits a compact reminder line for chained phases. `task_subset` (with `task_progress`) makes plan / execute prompts target a specific subset of tasks for per-task fanout.
- `build_iteration_header(...)` / `_build_review_step(...)` / `_build_plan_step(...)` / `_build_execute_step(...)` — Composable section builders used by `build_phase_prompt`.
- `pick_review_strategy(iteration, project_dir, strategies)` — Journal-weighted random strategy selection. Falls back to round-robin.
- `generate_memory_index(project_dir, tasks_dir, init_base, root_id)` — Generates the compact memory index document.
- `format_review_history(project_dir, max_show)` — One-liner of recent review outcomes.

### project.py

- `load_project_config(project_dir)` — Loads `.autobuilder/config.yaml` with type coercion. Returns defaults for missing keys.
- `ensure_project_config(project_dir)` — Creates `config.yaml` with defaults if absent. Idempotent.
- `resolve_review_strategies(project_config)` — Builds effective strategy list from config (filtered built-in + custom).
- `resolve_phase_executor(phase, project_config, default_executor, default_model)` — Returns `(executor, model)` for the given phase, honoring per-phase overrides from `phase_executors` and falling back to the daemon defaults.
- `validate_executor_config(default_executor, default_model, project_config)` — Validates daemon defaults + every per-phase override. Checks executor name (must be `cursor` / `codex` / `claude` / `opencode`), CLI binary presence on `$PATH` (or `$<EXEC>_CMD`), and — for `cursor` and `opencode` — that the model exists in the CLI's live list (`cursor agent --list-models` or `opencode models`). For `claude` and `codex` the model is passed through (no CLI list command). Raises `ExecutorConfigError` with all errors collected in one message and near-match suggestions on misses.
- `query_available_models(executor)` — Returns a list of available model IDs by shelling out to the executor's CLI (`cursor agent --list-models` for cursor, `opencode models` for opencode). Returns `None` if the executor doesn't expose a model-listing command (`claude`, `codex`) or the call fails. Memoised per (executor, binary); honours `$AUTOBUILDER_SKIP_MODEL_QUERY=1`.
- `_parse_config_yaml(text)` — Internal YAML-like config parser supporting scalars, lists, dicts, and lists-of-dicts.
- `sync_skills_to_project(project_dir)` — Manages the skills symlink.
- `ensure_gitignore_entries(project_dir, entries)` — Appends missing entries to `.gitignore`.
- `load_autobuilder_env(project_dir)` — Parses `.autobuilder/.env` key-value pairs.
- `load_test_credentials(project_dir)` — Extracts `TEST_USERNAME`/`TEST_PASSWORD` from `.autobuilder/.env`.
- `consume_backlog(backlog_path)` — Atomically reads and deletes the backlog file.

### common.py

**Constants:**

| Constant | Value | Description |
|---|---|---|
| `PHASE_HARD_TIMEOUT` | `5400` (90min) | Max execution time per phase |
| `REVIEW_POLISH_WEIGHT` | `0.9` | Default polish mode probability |
| `DEFAULT_MAX_TASKS_POLISH` | `5` | Default max tasks in polish mode |
| `DEFAULT_MAX_TASKS_FEATURE` | `3` | Default max tasks in feature mode |
| `MAX_CONSECUTIVE_CRASHES` | `10` | Crash limit before fatal exit |
| `MAX_CONSECUTIVE_AGENT_FAILURES` | `5` | Agent failure limit |
| `IDLE_BACKOFF_BASE` | `120` (2min) | Starting idle backoff |
| `IDLE_BACKOFF_CAP` | `1800` (30min) | Maximum idle backoff |

**`REVIEW_STRATEGIES`** — List of 9 dicts, each with `name` (string) and `focus` (instruction text).

### autobuilder_hook_helpers.executors

The single source of truth for everything any consumer needs to know
about each supported CLI agent (`cursor`, `codex`, `claude`, `opencode`).
Lives in `hook-helpers/autobuilder_hook_helpers/executors.py` so the
daemon and the project-side fix-agents share one file — adding a new
executor is one registry entry, no edits in either codebase.

- **`Executor`** (frozen dataclass) — one record per CLI, carrying
  the binary name + env-var override, default model, install hint, and
  the four small behaviour functions: `build_cmd`, `open_session`,
  `scan_session_line` (for executors that mint the session id on the
  output stream), `list_models` (for executors that expose a model-list
  command). Exposes a `resolved_binary` property and a
  `captures_session_from_stream` predicate.
- **`CURSOR`, `CODEX`, `CLAUDE`, `OPENCODE`** — module-level singletons.
- **`EXECUTORS`** — registry dict `name → Executor`.
- **`get(name)`** — case-insensitive lookup, returns `None` for unknown
  names (callers turn that into a validator error).

Consumers:

- **Daemon** — `common.py` adds `hook-helpers/` to `sys.path` early so
  `process.py`, `project.py`, `iteration.py`, and `builder_loop.py` can
  `from autobuilder_hook_helpers.executors import ...`.
- **Hook scripts** — `hooks.build_hook_env` puts `hook-helpers/` on the
  subprocess `PYTHONPATH`. `fix_agent.py` imports `get` and calls
  `ex.build_cmd(...)`, picking up every executor the daemon supports.

**Adding a new executor:** add one `Executor(...)` entry to the
registry. No edits to `process.py`, `project.py`, `iteration.py`,
`builder_loop.py`, `common.py`, or `fix_agent.py`.

### tasks.py

- `load_tasks(tasks_dir)` — Load all task files from a directory.
- `next_task_number(tasks_dir)` — Get the next available task number.
- `find_all_unplanned(tasks_dir, root_id)` — Find tasks with `planned: false`.
- `find_planned_not_executed(tasks_dir, root_id)` — Find tasks with `planned: true, executed: false`.
- `topological_sort(tasks)` — Sort tasks by dependency order.
- `group_all_tasks_by_area(tasks_dir)` — Group tasks by their `area` frontmatter field.
- `summarize_all_executed_tasks(tasks_dir)` — Compact summary of completed work.
- `summarize_executed_tasks(tasks_dir)` — Detailed summary of executed tasks.
- `get_task_ids(tasks_dir)` — Get all task IDs in a directory.
- `extract_area(task)` — Extract the area field from a task's frontmatter.

### status.py

- `write_status(status_path, project_id, iteration, phase, details)` — Update the status file.
- `append_journal_entry(project_dir, entry)` — Append a JSON entry to `journal.jsonl`.
- `load_journal(project_dir, max_entries)` — Load the last N journal entries.
- `write_pid_file(project_dir)` — Write the daemon PID.
- `remove_pid_file(project_dir)` — Remove the PID file.
- `check_stop_signal(project_dir)` — Check for `.autobuilder/stop` file.

### hooks.py

- `find_hook(project_dir, hook_name)` — Discover a hook script by name.
- `run_hook(project_dir, hook_name, log_fh, env)` — Execute a lifecycle hook.
- `run_augment_hook(project_dir, hook_name, log_fh, env)` — Execute an augmentation hook and return its stdout.
- `build_hook_env(...)` — Build environment variables dict for hooks.

### initiative.py

- `resolve_active_initiative(project_dir)` — Read the active initiative from `active-initiative` file.
- `list_initiatives(project_dir)` — List all initiative directories.
- `get_initiative_spec(project_dir, initiative)` — Read an initiative's `spec.md`.
- `get_initiative_constraints(project_dir, initiative)` — Read an initiative's `constraints.md`.
- `get_initiative_tasks_dir(project_dir, initiative)` — Get the tasks directory path.
- `create_root_task_from_spec(...)` — Auto-create a root task from `spec.md` when tasks is empty.

## Testing

Tests live in `tests/` and use pytest:

```bash
python3 -m pytest tests/ -v
```

The test suite uses `tmp_path` fixtures for file system isolation. No external services or network access required.
