---
estimated_steps: 22
estimated_files: 1
skills_used: []
---

# T02: Add compose subcommand to test harness

Add a `compose` subcommand to `backend/pipeline/test_harness.py` that loads an existing page JSON + new moments fixture, builds a compose user prompt, calls the LLM via the compose prompt, and validates the output. Also extract the compose user-prompt builder as a testable function.

Steps:
1. Read `backend/pipeline/test_harness.py` fully to understand the existing `run` and `promote` subcommand patterns.
2. Add a `build_compose_prompt()` function that takes:
   - `existing_page: dict` (SynthesizedPage as dict, from prior synthesis output)
   - `existing_moments: list[tuple[MockKeyMoment, dict]]` (the original moments the page was built from)
   - `new_moments: list[tuple[MockKeyMoment, dict]]` (new video's moments to compose in)
   - `creator_name: str`
   Returns: the user prompt string with `<existing_page>`, `<existing_moments>`, `<new_moments>`, and `<creator>` XML tags. Existing moments get indices [0]-[N-1], new moments get [N]-[N+M-1]. Uses `build_moments_text()` for formatting moment lists.
3. Add a `run_compose()` function (parallel to `run_synthesis()`) that:
   - Loads the existing page JSON and validates as SynthesizedPage
   - Loads the new fixture via existing `load_fixture()`
   - Filters new moments by category to match `existing_page.topic_category`
   - Needs the existing moments for citation context — accept them as a separate fixture or extract from the existing page's moment_indices + a moments fixture
   - Calls `build_compose_prompt()` to build the user prompt
   - Calls LLM with compose system prompt
   - Parses response as SynthesisResult
   - Runs citation validation with moment_count = len(existing_moments) + len(new_moments)
   - Logs compose-specific metrics: word count before/after, sections before/after, new sections added
4. Register `compose` subcommand in the `main()` argparse with args: `--existing-page` (required), `--fixture` (required), `--existing-fixture` (required, original moments fixture for citation context), `--prompt`, `--output`, `--category`, `--model`, `--modality`.
5. Wire the subcommand handler to call `run_compose()` and write output JSON.
6. Verify: `cd backend && python -m pipeline.test_harness compose --help` exits 0.

## Inputs

- ``backend/pipeline/test_harness.py` — existing harness with run/promote subcommands`
- ``backend/pipeline/schemas.py` — SynthesizedPage, SynthesisResult for validation`
- ``backend/pipeline/citation_utils.py` — validate_citations() for output checking`
- ``prompts/stage5_compose.txt` — default compose prompt path (from T01)`

## Expected Output

- ``backend/pipeline/test_harness.py` — updated with compose subcommand, build_compose_prompt() function, run_compose() function`

## Verification

cd backend && python -m pipeline.test_harness compose --help
