# Prompt Engine Audit — CP-3 Phase 1

**Date:** 2026-04-26
**Phase:** CP-3.1 (audit + mapping; zero code changes)
**Rollback tags verified:**
- `pre-cp3-prompt-consolidation` in `CLAUDE_PROJECTS/` → `6118805e`
- `pre-cp3-prompt-consolidation` in `recoil/engine-memory/` → `0f3893b2`

**Companion artifacts:**
- `consultations/recoil/cp3-prompt-consolidation-spec/cp3_phase1_callers.json` — machine-readable importer catalog
- `consultations/recoil/cp3-prompt-consolidation-spec/cp3_phase1_provider_strategy_snapshot.json` — frozen `provider_strategy.json`

This document is the **scripture** that Phases 2–8 read. The classification table in Section 5 is the load-bearing contract.

---

## File line counts

| File | Path | Lines (`wc -l`) | Role |
|------|------|-----------------|------|
| Pipeline SSoT | `recoil/pipeline/lib/prompt_engine.py` | 5966 | Migration target — every alive symbol must end up here |
| Visual mirror | `recoil/visual/prompt_engine.py` | 4397 | Migration source — heavy collision overlap with the SSoT |
| Tools CLI | `recoil/tools/prompt_engine.py` | 1068 | Standalone 10-layer prompt CLI; near-zero external callers |
| **Total** | — | **11431** | Three files, ~2× duplication |

---

## Public symbol catalog

For each file, the public (non-`_`-prefixed) symbols are listed first, then the underscore-prefixed (private/internal) symbols. Line numbers come from the live source.

### pipeline/lib/prompt_engine.py

```
L  291  GridType
L 1592  build_cinematic_prompt
L 3146  build_coverage_prompts
L 1728  build_grid_prompt
L 2944  build_kling_i2v_prompt
L 3280  build_kling_t2v_prompt
L 1970  build_location_ref_prompt
L 3037  build_multi_prompt_sequence
L 2167  build_multi_shot_prompt
L 4819  build_previs_prompt
L  176  build_prompt_from_bible
L  714  build_prompt_from_plan
L  496  build_prompt_sections_from_plan
L 4613  build_seeddance_i2v_prompt
L 3882  build_seeddance_r2v_prompt
L 4140  build_seeddance_r2v_prompt_multi
L 4511  build_seeddance_t2v_prompt
L 2768  build_seedream_prompt
L 1192  build_spatial_continuity_block
L 1837  build_two_character_prompt
L 2013  build_universal_expression_matrix
L 2843  build_veo_prompt
L 2064  build_video_prompt
L 1438  build_video_prompt_from_plan
L 1517  build_visual_anchors
L 3466  build_wan_i2v_prompt
L 3657  build_wan_r2v_prompt
L 4699  compile_all_prompts
L 3390  compile_core_semantics
L 5845  derive_coverage_shot
L 4969  enrich_prompt
L 2638  extract_core_semantics
L  408  sanitize_env_prompt
L  118  validate_start_frame_ar
```

**Underscore-prefixed (private) symbols:**

```
L 3102  _apply_editorial_priors
L 5514  _build_action_burst_panels
L 5127  _build_camera_line
L  798  _build_camera_line_plan
L 1276  _build_character_anchor
L 2546  _build_character_anchor_brief
L 5210  _build_character_description
L 2476  _build_character_descs_brief
L 1316  _build_character_descs_from_bible
L 3840  _build_character_lines
L 5172  _build_close_shot_footer
L 5451  _build_directors_take_panels
L 5042  _build_kinetic_layer
L  815  _build_lighting_from_plan
L 5070  _build_lighting_vector
L 5354  _build_scene_coverage_panels
L 5167  _build_wide_shot_footer
L 4940  _call_flash_enrichment
L  418  _dedup_phrases
L   69  _enforce_prompt_length
L 2427  _enforce_single_verb_action
L 5802  _extract_cutaway_target
L 5278  _extract_props
L 2287  _flatten_lighting_to_prose
L 1103  _format_env
L 1111  _format_insert
L 1119  _format_punch_in
L 1140  _format_solo_moving
L 1153  _format_solo_static
L 1176  _format_two_char_ots
L 1163  _format_two_char_wide
L 2567  _get_scene_visual_locks_compressed
L 2412  _get_seeddance_film_stock
L 5758  _invert_emotion_v1
L  924  _is_moving
L 5314  _is_non_human
L  444  _is_plan_shot
L  997  _is_punch_in
L 1055  _macro_to_micro_lighting
L   32  _maybe_hydrate
L  903  _normalize_camera_side
L 5339  _parse_grid_size
L  462  _resolve_bypasses
L  875  _resolve_camera_relative_lighting
L 5177  _resolve_character_visual
L  915  _resolve_display_name
L 1066  _resolve_eyeline
L  932  _resolve_facing
L 1091  _resolve_insert_facing
L  941  _resolve_lr_assignment
L  967  _resolve_ots_assignment
L 1033  _resolve_punch_in_source
L  345  _resolve_shot_type
L 2502  _resolve_wardrobe_brief
L  791  _scrub_archetype_triggers
L 2343  _seedance_lighting_anchors
L  769  _strip_motion_language
L 5557  _tighten_shot_type
L 2270  _truncate_at_natural_break
L 1433  _visual_is_non_human
```

### visual/prompt_engine.py

```
L   42  GridType
L 1466  build_cinematic_prompt
L 2477  build_coverage_prompts
L 1611  build_grid_prompt
L 2276  build_kling_i2v_prompt
L 2611  build_kling_t2v_prompt
L 1853  build_location_ref_prompt
L 2368  build_multi_prompt_sequence
L 2046  build_multi_shot_prompt
L 3246  build_previs_prompt
L  518  build_prompt_from_plan
L  247  build_prompt_sections_from_plan
L 1005  build_spatial_continuity_block
L 1720  build_two_character_prompt
L 1896  build_universal_expression_matrix
L 2187  build_veo_prompt
L 1947  build_video_prompt
L 1257  build_video_prompt_from_plan
L 1352  build_visual_anchors
L 2767  build_wan_i2v_prompt
L 2934  build_wan_r2v_prompt
L 3135  compile_all_prompts
L 2686  compile_core_semantics
L 4276  derive_coverage_shot
L 3399  enrich_prompt
L  159  sanitize_env_prompt
```

**Underscore-prefixed (private) symbols:**

```
L 2433  _apply_editorial_priors
L 3946  _build_action_burst_panels
L 3557  _build_camera_line
L  611  _build_camera_line_plan
L 1089  _build_character_anchor
L 3640  _build_character_description
L 1129  _build_character_descs_from_bible
L 3098  _build_character_lines
L 3602  _build_close_shot_footer
L 3883  _build_directors_take_panels
L 3472  _build_kinetic_layer
L  628  _build_lighting_from_plan
L 3500  _build_lighting_vector
L 3788  _build_scene_coverage_panels
L 3597  _build_wide_shot_footer
L 3370  _call_flash_enrichment
L  169  _dedup_phrases
L 4233  _extract_cutaway_target
L 3708  _extract_props
L 2147  _flatten_lighting_to_prose
L  916  _format_env
L  924  _format_insert
L  932  _format_punch_in
L  953  _format_solo_moving
L  966  _format_solo_static
L  989  _format_two_char_ots
L  976  _format_two_char_wide
L 4189  _invert_emotion_v1
L  737  _is_moving
L 3744  _is_non_human
L  195  _is_plan_shot
L  810  _is_punch_in
L  868  _macro_to_micro_lighting
L  716  _normalize_camera_side
L 3773  _parse_grid_size
L  213  _resolve_bypasses
L  688  _resolve_camera_relative_lighting
L 3607  _resolve_character_visual
L  728  _resolve_display_name
L  879  _resolve_eyeline
L  745  _resolve_facing
L  904  _resolve_insert_facing
L  754  _resolve_lr_assignment
L  780  _resolve_ots_assignment
L  846  _resolve_punch_in_source
L   96  _resolve_shot_type
L  604  _scrub_archetype_triggers
L  582  _strip_motion_language
L 3989  _tighten_shot_type
L 1248  _visual_is_non_human
```

### tools/prompt_engine.py

```
L  220  NoteEntry
L  301  NoteStore
L  209  OverrideEntry
L  234  OverrideStore
L  342  PromptEngine
L  184  PromptLayers
L  199  VerbWarning
L  983  cmd_note
L  934  cmd_override_add
L  951  cmd_override_list
L  975  cmd_override_remove
L  848  cmd_preview
L  900  cmd_validate
L  741  compile_layers
L 1002  main
```

**Underscore-prefixed (private) symbols:**

```
L  840  _get_shot
L  814  _load_lora_registry
L  803  _load_storyboard
L  784  _suggest_fix
```


---

## Name collisions (same symbol, different file)

All 76 colliding symbols appear in BOTH `pipeline/lib/prompt_engine.py` and `visual/prompt_engine.py`. Zero collisions involve `tools/prompt_engine.py` (its symbols are CLI/dataclass names that don't clash).

- **BEHAVIORAL_TWIN** (54): bodies are byte-identical after whitespace/blank-line normalization — safe to drop the visual/ copy.
- **BEHAVIORAL_DRIFT** (22): bodies differ — pipeline/lib version is the canonical one (it has all the recent SSoT enrichments). The visual/ copy is stale.
- **DEAD_TWIN**: zero rows under this classification; every collision pair has at least one production caller, so neither copy is wholly dead. They are all live duplicates that must collapse.

Note: each row's basis is recorded as a one-line note. "BEHAVIORAL_TWIN — bodies hash-equivalent" means the SHA-1 of normalized body bytes is the same in both files. "BEHAVIORAL_DRIFT — body diverged" means the bodies differ; the pipeline/lib copy is the canonical winner per Phase 6 deletion plan.

| Symbol | Files | Identical bodies? | Classification | Basis |
|--------|-------|-------------------|----------------|-------|
| `GridType` | pipeline/lib L291, visual L42 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_apply_editorial_priors` | pipeline/lib L3102, visual L2433 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_build_action_burst_panels` | pipeline/lib L5514, visual L3946 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_build_camera_line` | pipeline/lib L5127, visual L3557 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_build_camera_line_plan` | pipeline/lib L798, visual L611 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_build_character_anchor` | pipeline/lib L1276, visual L1089 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_build_character_description` | pipeline/lib L5210, visual L3640 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_build_character_descs_from_bible` | pipeline/lib L1316, visual L1129 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `_build_character_lines` | pipeline/lib L3840, visual L3098 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `_build_close_shot_footer` | pipeline/lib L5172, visual L3602 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_build_directors_take_panels` | pipeline/lib L5451, visual L3883 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_build_kinetic_layer` | pipeline/lib L5042, visual L3472 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_build_lighting_from_plan` | pipeline/lib L815, visual L628 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_build_lighting_vector` | pipeline/lib L5070, visual L3500 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_build_scene_coverage_panels` | pipeline/lib L5354, visual L3788 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `_build_wide_shot_footer` | pipeline/lib L5167, visual L3597 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_call_flash_enrichment` | pipeline/lib L4940, visual L3370 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_dedup_phrases` | pipeline/lib L418, visual L169 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_extract_cutaway_target` | pipeline/lib L5802, visual L4233 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_extract_props` | pipeline/lib L5278, visual L3708 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_flatten_lighting_to_prose` | pipeline/lib L2287, visual L2147 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `_format_env` | pipeline/lib L1103, visual L916 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_format_insert` | pipeline/lib L1111, visual L924 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_format_punch_in` | pipeline/lib L1119, visual L932 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_format_solo_moving` | pipeline/lib L1140, visual L953 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_format_solo_static` | pipeline/lib L1153, visual L966 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_format_two_char_ots` | pipeline/lib L1176, visual L989 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_format_two_char_wide` | pipeline/lib L1163, visual L976 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_invert_emotion_v1` | pipeline/lib L5758, visual L4189 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_is_moving` | pipeline/lib L924, visual L737 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_is_non_human` | pipeline/lib L5314, visual L3744 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `_is_plan_shot` | pipeline/lib L444, visual L195 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_is_punch_in` | pipeline/lib L997, visual L810 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_macro_to_micro_lighting` | pipeline/lib L1055, visual L868 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_normalize_camera_side` | pipeline/lib L903, visual L716 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_parse_grid_size` | pipeline/lib L5339, visual L3773 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_resolve_bypasses` | pipeline/lib L462, visual L213 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_resolve_camera_relative_lighting` | pipeline/lib L875, visual L688 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_resolve_character_visual` | pipeline/lib L5177, visual L3607 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_resolve_display_name` | pipeline/lib L915, visual L728 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_resolve_eyeline` | pipeline/lib L1066, visual L879 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_resolve_facing` | pipeline/lib L932, visual L745 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_resolve_insert_facing` | pipeline/lib L1091, visual L904 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_resolve_lr_assignment` | pipeline/lib L941, visual L754 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_resolve_ots_assignment` | pipeline/lib L967, visual L780 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_resolve_punch_in_source` | pipeline/lib L1033, visual L846 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_resolve_shot_type` | pipeline/lib L345, visual L96 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_scrub_archetype_triggers` | pipeline/lib L791, visual L604 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_strip_motion_language` | pipeline/lib L769, visual L582 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `_tighten_shot_type` | pipeline/lib L5557, visual L3989 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `_visual_is_non_human` | pipeline/lib L1433, visual L1248 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_cinematic_prompt` | pipeline/lib L1592, visual L1466 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_coverage_prompts` | pipeline/lib L3146, visual L2477 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `build_grid_prompt` | pipeline/lib L1728, visual L1611 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `build_kling_i2v_prompt` | pipeline/lib L2944, visual L2276 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_kling_t2v_prompt` | pipeline/lib L3280, visual L2611 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_location_ref_prompt` | pipeline/lib L1970, visual L1853 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `build_multi_prompt_sequence` | pipeline/lib L3037, visual L2368 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `build_multi_shot_prompt` | pipeline/lib L2167, visual L2046 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_previs_prompt` | pipeline/lib L4819, visual L3246 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_prompt_from_plan` | pipeline/lib L714, visual L518 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_prompt_sections_from_plan` | pipeline/lib L496, visual L247 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_spatial_continuity_block` | pipeline/lib L1192, visual L1005 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `build_two_character_prompt` | pipeline/lib L1837, visual L1720 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `build_universal_expression_matrix` | pipeline/lib L2013, visual L1896 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `build_veo_prompt` | pipeline/lib L2843, visual L2187 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_video_prompt` | pipeline/lib L2064, visual L1947 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_video_prompt_from_plan` | pipeline/lib L1438, visual L1257 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_visual_anchors` | pipeline/lib L1517, visual L1352 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_wan_i2v_prompt` | pipeline/lib L3466, visual L2767 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `build_wan_r2v_prompt` | pipeline/lib L3657, visual L2934 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `compile_all_prompts` | pipeline/lib L4699, visual L3135 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `compile_core_semantics` | pipeline/lib L3390, visual L2686 | False | BEHAVIORAL_DRIFT | body diverged — pipeline/lib copy is canonical |
| `derive_coverage_shot` | pipeline/lib L5845, visual L4276 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `enrich_prompt` | pipeline/lib L4969, visual L3399 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |
| `sanitize_env_prompt` | pipeline/lib L408, visual L159 | True | BEHAVIORAL_TWIN | bodies hash-equivalent (normalized SHA-1 match) |


---

## Caller catalog

All grep output below excludes `_archive/` paths, any `tests/` directory, and `test_prompt_engine.py`. Paths are relative to `recoil/`.

### Importers of `pipeline.lib.prompt_engine`

```
pipeline/tools/prep_expressions.py:109:    from lib.prompt_engine import build_universal_expression_matrix
pipeline/tools/build_coverage_passes.py:195:    from lib.prompt_engine import derive_coverage_shot
pipeline/tools/prompt_ab_test.py:48:    from lib.prompt_engine import build_kling_i2v_prompt as _production_prompt_builder
pipeline/tools/test_via_steprunner.py:625:        from lib.prompt_engine import build_seedance_i2v_multishot_prompt
pipeline/tools/test_via_steprunner.py:1225:        from lib.prompt_engine import build_multi_prompt_sequence
pipeline/tools/test_via_steprunner.py:1282:        from lib.prompt_engine import build_coverage_prompts
pipeline/tools/test_via_steprunner.py:1378:                from lib.prompt_engine import build_wan_i2v_prompt
pipeline/tools/test_via_steprunner.py:1390:                from lib.prompt_engine import build_video_prompt_from_plan
pipeline/tools/test_via_steprunner.py:1406:                from lib.prompt_engine import build_seeddance_i2v_prompt
pipeline/tools/test_via_steprunner.py:1420:                    from lib.prompt_engine import build_kling_i2v_prompt
pipeline/tools/test_via_steprunner.py:1427:                from lib.prompt_engine import build_kling_i2v_prompt
pipeline/tools/test_via_steprunner.py:1488:                from lib.prompt_engine import build_wan_r2v_prompt
pipeline/tools/generate_location_refs.py:24:from lib.prompt_engine import build_location_ref_prompt
pipeline/tools/build_upload_bundle.py:40:from lib.prompt_engine import (
pipeline/tools/build_upload_bundle.py:395:                from lib.prompt_engine import build_kling_i2v_prompt, build_kling_t2v_prompt, build_video_prompt_from_plan
pipeline/tools/backfill_storyboard.py:33:from lib.prompt_engine import build_cinematic_prompt, build_two_character_prompt
pipeline/tools/batch_gate2_test.py:33:from lib.prompt_engine import build_cinematic_prompt
pipeline/editors/review_server.py:3883:        from lib.prompt_engine import (
pipeline/editors/review_server.py:4220:                    from lib.prompt_engine import build_previs_prompt
pipeline/editors/review_server.py:4327:                        from lib.prompt_engine import build_spatial_continuity_block
pipeline/editors/review_server.py:6499:        from lib.prompt_engine import build_coverage_prompts
pipeline/editors/review_server.py:6877:        from lib.prompt_engine import build_multi_prompt_sequence
pipeline/editors/inspector_api.py:382:        from lib.prompt_engine import build_prompt_sections_from_plan
pipeline/lib/jit_prompt.py:28:from lib.prompt_engine import _build_character_descs_from_bible
pipeline/lib/previz_context.py:1343:    from lib.prompt_engine import build_spatial_continuity_block
pipeline/lib/spatial_compliance.py:181:    from lib.prompt_engine import _normalize_camera_side, _resolve_display_name
pipeline/lib/spatial_compliance.py:196:        from lib.prompt_engine import _is_moving, _is_punch_in
pipeline/lib/spatial_compliance.py:219:            from lib.prompt_engine import _resolve_lr_assignment
pipeline/lib/spatial_compliance.py:224:            from lib.prompt_engine import _resolve_ots_assignment
pipeline/lib/spatial_compliance.py:447:    from lib.prompt_engine import _normalize_camera_side
pipeline/api/routes/generation.py:260:                from lib.prompt_engine import build_previs_prompt
pipeline/api/routes/generation.py:357:                    from lib.prompt_engine import build_spatial_continuity_block
pipeline/api/routes/generation.py:418:                    from lib.prompt_engine import build_prompt_from_plan, _resolve_bypasses
pipeline/api/routes/generation.py:804:            from lib.prompt_engine import compile_all_prompts
pipeline/api/routes/generation.py:1579:            from lib.prompt_engine import build_previs_prompt
pipeline/api/routes/generation.py:1815:    from lib.prompt_engine import build_coverage_prompts
pipeline/api/routes/generation.py:1914:    from lib.prompt_engine import build_multi_prompt_sequence
pipeline/api/routes/prompt_inspector.py:147:    from lib.prompt_engine import (
pipeline/verify_phase_b_step1.py:22:from lib.prompt_engine import (
pipeline/orchestrator/production_loop.py:876:        from lib.prompt_engine import build_seeddance_r2v_prompt_multi
pipeline/orchestrator/pipeline.py:50:from lib.prompt_engine import (
pipeline/orchestrator/pipeline.py:223:            from lib.prompt_engine import build_prompt_sections_from_plan
pipeline/orchestrator/pipeline.py:461:            from lib.prompt_engine import build_prompt_sections_from_plan
pipeline/orchestrator/pipeline.py:698:            from lib.prompt_engine import build_prompt_sections_from_plan
pipeline/orchestrator/pipeline.py:1173:            from lib.prompt_engine import build_prompt_sections_from_plan
pipeline/orchestrator/pipeline.py:1402:            from lib.prompt_engine import build_kling_t2v_prompt
pipeline/orchestrator/pipeline.py:1423:            from lib.prompt_engine import build_prompt_sections_from_plan
tools/test_enrichment_parity.py:44:from lib.prompt_engine import (
tools/test_seeddance_builders.py:545:        from lib.prompt_engine import build_seeddance_t2v_prompt
tools/test_seeddance_builders.py:576:        from lib.prompt_engine import build_seeddance_i2v_prompt
tools/test_seeddance_builders.py:610:        from lib.prompt_engine import build_seeddance_r2v_prompt
tools/test_seeddance_builders.py:653:    from lib.prompt_engine import (
tools/test_seeddance_builders.py:809:    from lib.prompt_engine import build_seeddance_t2v_prompt
tools/test_seeddance_builders.py:944:    from lib.prompt_engine import build_seeddance_t2v_prompt
tools/test_seeddance_builders.py:1028:    from lib.prompt_engine import _enforce_single_verb_action
```

(55 import statements across the production code, including lazy imports inside function bodies.)

### Importers of `visual.prompt_engine`

```
visual/compiler.py:249:    from visual.prompt_engine import build_prompt_from_plan
```

**Cross-tree references to `visual.prompt_engine`:**

```
tools/shootout/audit_harness.py:113:    from visual.prompt_engine import compile_all_prompts, build_wan_i2v_prompt
visual/compiler.py:249:    from visual.prompt_engine import build_prompt_from_plan
```

### Importers of `tools.prompt_engine`

```
tools/ab_test_comprehensive.py:38:from prompt_engine import PromptEngine
```

(`from prompt_engine import …` works because `tools/` is on `sys.path` when these scripts are run as CLIs from the `tools/` directory. After Phase 5 these references must resolve to the canonical SSoT, or the importing scripts must be archived.)


---

## Classification per symbol

This is the load-bearing table. One row per `(file, public-or-private symbol)` pair.

**Caller-count column legend:**
`prod=N` — external production callers (anything outside the three `prompt_engine.py` files, excluding `_archive/` and `tests/`)
`intra=A+B` — A is intra-file references inside the symbol's own `prompt_engine.py`; B is references inside the *other* `prompt_engine.py` files (non-zero only when collision pairs reference each other, which they don't — every B you see is via a different file's body referencing this symbol's name and it tends to indicate the collision-twin)
`test=N` — references in `tests/` directories or `test_prompt_engine.py`
`arch=N` — references in `_archive/` paths

**Classification key:**
- **ALIVE** — symbol has at least one production caller (external or intra-file). Must survive into pipeline/lib SSoT.
- **DEAD** — zero callers anywhere. Phase 8 deletes.
- **TRANSITIONAL** — only test callers (no prod, no intra). Migrate so tests still pass; mark for v2 cleanup. (Zero rows fall into this bucket on the current grep.)
- **COLLISION** — same name appears in 2+ files. Classification depends on which body wins; see Section 3 for the BEHAVIORAL_TWIN vs BEHAVIORAL_DRIFT note in the row.

**Migration target key:**
- **KEEP_IN_SSOT** — symbol already lives in `pipeline/lib/prompt_engine.py`; no migration needed. Phase 6 (stub the visual mirror) and Phase 8 (delete the stubs) preserve this row.
- **MIGRATE_FROM_VISUAL** — symbol exists ONLY in `visual/prompt_engine.py` and is alive. Phase 3 moves it. (Zero rows on the current grep — every visual-only public symbol is either a collision or already in pipeline/lib.)
- **MIGRATE_FROM_TOOLS** — symbol exists ONLY in `tools/prompt_engine.py` and is alive. Phase 4 moves it. (See Risk Register: tools/ symbols that look transitional were verified against `lib/prompt_compiler.py` — all dataclasses and stores already have canonical homes there.)
- **DELETE** — symbol is dead, or its collision-twin in pipeline/lib wins. Phase 6 / Phase 8 remove it.

> Important nuance: `tools/prompt_engine.py` symbols (`PromptLayers`, `OverrideStore`, `OverrideEntry`, `NoteStore`, `NoteEntry`, `PromptEngine`, `compile_layers`, etc.) appear duplicated at `recoil/lib/prompt_compiler.py` (the canonical home for the 10-layer prompt compiler). The only external prod caller of `tools/prompt_engine.py` is `recoil/tools/ab_test_comprehensive.py` (which does `from prompt_engine import PromptEngine` — sys.path-relative, runs with `tools/` on sys.path). Every other ref to `OverrideStore` / `NoteStore` / etc. across the repo resolves to `lib/prompt_compiler.py`, not `tools/prompt_engine.py`. Phase 5 should treat the tools/ symbols as DELETE candidates with a courtesy stub re-exporting from `lib/prompt_compiler.py`. They are marked `MIGRATE_FROM_TOOLS` in the table below to keep the rule mechanical, but the human-readable interpretation is "redirect to the canonical lib/prompt_compiler.py home; do not move bodies."

| File | Symbol | Line | Callers (count) | Classification | Migration target | Note |
|------|--------|------|-----------------|----------------|-------------------|------|
| pipeline/lib/prompt_engine.py | `_maybe_hydrate` | 32 | prod=0 intra=8+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `_enforce_prompt_length` | 69 | prod=0 intra=14+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `validate_start_frame_ar` | 118 | prod=0 intra=0+0 test=0 arch=0 | DEAD | DELETE |  |
| pipeline/lib/prompt_engine.py | `build_prompt_from_bible` | 176 | prod=0 intra=0+0 test=0 arch=0 | DEAD | DELETE |  |
| pipeline/lib/prompt_engine.py | `GridType` | 291 | prod=8 intra=6+6 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_resolve_shot_type` | 345 | prod=0 intra=20+0 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `sanitize_env_prompt` | 408 | prod=4 intra=4+4 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_dedup_phrases` | 418 | prod=0 intra=2+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_is_plan_shot` | 444 | prod=9 intra=2+3 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_resolve_bypasses` | 462 | prod=6 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `build_prompt_sections_from_plan` | 496 | prod=17 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `build_prompt_from_plan` | 714 | prod=12 intra=6+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `_strip_motion_language` | 769 | prod=0 intra=2+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_scrub_archetype_triggers` | 791 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_build_camera_line_plan` | 798 | prod=0 intra=6+6 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_build_lighting_from_plan` | 815 | prod=0 intra=2+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_resolve_camera_relative_lighting` | 875 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_normalize_camera_side` | 903 | prod=5 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_resolve_display_name` | 915 | prod=2 intra=6+6 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_is_moving` | 924 | prod=2 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_resolve_facing` | 932 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_resolve_lr_assignment` | 941 | prod=2 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_resolve_ots_assignment` | 967 | prod=2 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_is_punch_in` | 997 | prod=2 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_resolve_punch_in_source` | 1033 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_macro_to_micro_lighting` | 1055 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_resolve_eyeline` | 1066 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_resolve_insert_facing` | 1091 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_format_env` | 1103 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_format_insert` | 1111 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_format_punch_in` | 1119 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_format_solo_moving` | 1140 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_format_solo_static` | 1153 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_format_two_char_wide` | 1163 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_format_two_char_ots` | 1176 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `build_spatial_continuity_block` | 1192 | prod=6 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_build_character_anchor` | 1276 | prod=2 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_build_character_descs_from_bible` | 1316 | prod=1 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `_visual_is_non_human` | 1433 | prod=0 intra=3+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `build_video_prompt_from_plan` | 1438 | prod=8 intra=2+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `build_visual_anchors` | 1517 | prod=0 intra=5+5 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `build_cinematic_prompt` | 1592 | prod=9 intra=3+3 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `build_grid_prompt` | 1728 | prod=5 intra=2+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `build_two_character_prompt` | 1837 | prod=6 intra=0+0 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `build_location_ref_prompt` | 1970 | prod=2 intra=0+0 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `build_universal_expression_matrix` | 2013 | prod=2 intra=0+0 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `build_video_prompt` | 2064 | prod=0 intra=0+0 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `build_multi_shot_prompt` | 2167 | prod=0 intra=0+0 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `_truncate_at_natural_break` | 2270 | prod=0 intra=5+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `_flatten_lighting_to_prose` | 2287 | prod=0 intra=9+6 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `_seedance_lighting_anchors` | 2343 | prod=1 intra=2+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `_get_seeddance_film_stock` | 2412 | prod=2 intra=0+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `_enforce_single_verb_action` | 2427 | prod=7 intra=2+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `_build_character_descs_brief` | 2476 | prod=0 intra=1+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `_resolve_wardrobe_brief` | 2502 | prod=0 intra=1+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `_build_character_anchor_brief` | 2546 | prod=0 intra=1+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `_get_scene_visual_locks_compressed` | 2567 | prod=0 intra=1+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `extract_core_semantics` | 2638 | prod=2 intra=10+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `build_seedream_prompt` | 2768 | prod=3 intra=1+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `build_veo_prompt` | 2843 | prod=7 intra=1+0 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `build_kling_i2v_prompt` | 2944 | prod=15 intra=2+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `build_multi_prompt_sequence` | 3037 | prod=7 intra=0+0 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_apply_editorial_priors` | 3102 | prod=0 intra=0+0 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `build_coverage_prompts` | 3146 | prod=6 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `build_kling_t2v_prompt` | 3280 | prod=16 intra=1+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `compile_core_semantics` | 3390 | prod=1 intra=0+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `build_wan_i2v_prompt` | 3466 | prod=3 intra=2+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `build_wan_r2v_prompt` | 3657 | prod=2 intra=3+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `_build_character_lines` | 3840 | prod=0 intra=2+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `build_seeddance_r2v_prompt` | 3882 | prod=6 intra=1+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `build_seeddance_r2v_prompt_multi` | 4140 | prod=4 intra=0+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `build_seeddance_t2v_prompt` | 4511 | prod=15 intra=1+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `build_seeddance_i2v_prompt` | 4613 | prod=8 intra=2+0 test=0 arch=0 | ALIVE | KEEP_IN_SSOT |  |
| pipeline/lib/prompt_engine.py | `compile_all_prompts` | 4699 | prod=7 intra=0+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `build_previs_prompt` | 4819 | prod=7 intra=0+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `_call_flash_enrichment` | 4940 | prod=0 intra=2+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `enrich_prompt` | 4969 | prod=0 intra=0+0 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_build_kinetic_layer` | 5042 | prod=0 intra=2+2 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_build_lighting_vector` | 5070 | prod=0 intra=4+4 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_build_camera_line` | 5127 | prod=0 intra=3+3 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_build_wide_shot_footer` | 5167 | prod=0 intra=3+3 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_build_close_shot_footer` | 5172 | prod=0 intra=3+3 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_resolve_character_visual` | 5177 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_build_character_description` | 5210 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_extract_props` | 5278 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_is_non_human` | 5314 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `_parse_grid_size` | 5339 | prod=3 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_build_scene_coverage_panels` | 5354 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `_build_directors_take_panels` | 5451 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_build_action_burst_panels` | 5514 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_tighten_shot_type` | 5557 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_DRIFT |
| pipeline/lib/prompt_engine.py | `_invert_emotion_v1` | 5758 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `_extract_cutaway_target` | 5802 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| pipeline/lib/prompt_engine.py | `derive_coverage_shot` | 5845 | prod=2 intra=0+0 test=0 arch=0 | COLLISION | KEEP_IN_SSOT | BEHAVIORAL_TWIN |
| tools/prompt_engine.py | `PromptLayers` | 184 | prod=10 intra=9+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `VerbWarning` | 199 | prod=0 intra=5+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `OverrideEntry` | 209 | prod=3 intra=2+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `NoteEntry` | 220 | prod=3 intra=2+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `OverrideStore` | 234 | prod=15 intra=4+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `NoteStore` | 301 | prod=2 intra=1+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `PromptEngine` | 342 | prod=2 intra=3+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `compile_layers` | 741 | prod=2 intra=2+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `_suggest_fix` | 784 | prod=2 intra=1+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `_load_storyboard` | 803 | prod=3 intra=2+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `_load_lora_registry` | 814 | prod=3 intra=2+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `_get_shot` | 840 | prod=3 intra=1+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `cmd_preview` | 848 | prod=2 intra=1+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `cmd_validate` | 900 | prod=4 intra=1+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `cmd_override_add` | 934 | prod=2 intra=1+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `cmd_override_list` | 951 | prod=2 intra=1+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `cmd_override_remove` | 975 | prod=2 intra=1+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `cmd_note` | 983 | prod=2 intra=1+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| tools/prompt_engine.py | `main` | 1002 | prod=20 intra=0+0 test=0 arch=0 | ALIVE | MIGRATE_FROM_TOOLS |  |
| visual/prompt_engine.py | `GridType` | 42 | prod=8 intra=6+6 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_resolve_shot_type` | 96 | prod=0 intra=0+20 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `sanitize_env_prompt` | 159 | prod=4 intra=4+4 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_dedup_phrases` | 169 | prod=0 intra=2+2 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_is_plan_shot` | 195 | prod=9 intra=3+2 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_resolve_bypasses` | 213 | prod=6 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `build_prompt_sections_from_plan` | 247 | prod=17 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `build_prompt_from_plan` | 518 | prod=12 intra=2+6 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `_strip_motion_language` | 582 | prod=0 intra=2+2 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_scrub_archetype_triggers` | 604 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_build_camera_line_plan` | 611 | prod=0 intra=6+6 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_build_lighting_from_plan` | 628 | prod=0 intra=2+2 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_resolve_camera_relative_lighting` | 688 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_normalize_camera_side` | 716 | prod=5 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_resolve_display_name` | 728 | prod=2 intra=6+6 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_is_moving` | 737 | prod=2 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_resolve_facing` | 745 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_resolve_lr_assignment` | 754 | prod=2 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_resolve_ots_assignment` | 780 | prod=2 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_is_punch_in` | 810 | prod=2 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_resolve_punch_in_source` | 846 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_macro_to_micro_lighting` | 868 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_resolve_eyeline` | 879 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_resolve_insert_facing` | 904 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_format_env` | 916 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_format_insert` | 924 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_format_punch_in` | 932 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_format_solo_moving` | 953 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_format_solo_static` | 966 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_format_two_char_wide` | 976 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_format_two_char_ots` | 989 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `build_spatial_continuity_block` | 1005 | prod=6 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_build_character_anchor` | 1089 | prod=2 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_build_character_descs_from_bible` | 1129 | prod=1 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `_visual_is_non_human` | 1248 | prod=0 intra=2+3 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `build_video_prompt_from_plan` | 1257 | prod=8 intra=2+2 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `build_visual_anchors` | 1352 | prod=0 intra=5+5 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `build_cinematic_prompt` | 1466 | prod=9 intra=3+3 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `build_grid_prompt` | 1611 | prod=5 intra=2+2 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `build_two_character_prompt` | 1720 | prod=6 intra=0+0 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `build_location_ref_prompt` | 1853 | prod=2 intra=0+0 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `build_universal_expression_matrix` | 1896 | prod=2 intra=0+0 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `build_video_prompt` | 1947 | prod=0 intra=0+0 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `build_multi_shot_prompt` | 2046 | prod=0 intra=0+0 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `_flatten_lighting_to_prose` | 2147 | prod=0 intra=6+9 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `build_veo_prompt` | 2187 | prod=7 intra=0+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `build_kling_i2v_prompt` | 2276 | prod=15 intra=2+2 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `build_multi_prompt_sequence` | 2368 | prod=7 intra=0+0 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_apply_editorial_priors` | 2433 | prod=0 intra=0+0 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `build_coverage_prompts` | 2477 | prod=6 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `build_kling_t2v_prompt` | 2611 | prod=16 intra=2+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `compile_core_semantics` | 2686 | prod=1 intra=1+0 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `build_wan_i2v_prompt` | 2767 | prod=3 intra=2+2 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `build_wan_r2v_prompt` | 2934 | prod=2 intra=2+3 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `_build_character_lines` | 3098 | prod=0 intra=2+2 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `compile_all_prompts` | 3135 | prod=7 intra=1+0 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `build_previs_prompt` | 3246 | prod=7 intra=1+0 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `_call_flash_enrichment` | 3370 | prod=0 intra=2+2 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `enrich_prompt` | 3399 | prod=0 intra=0+0 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_build_kinetic_layer` | 3472 | prod=0 intra=2+2 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_build_lighting_vector` | 3500 | prod=0 intra=4+4 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_build_camera_line` | 3557 | prod=0 intra=3+3 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_build_wide_shot_footer` | 3597 | prod=0 intra=3+3 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_build_close_shot_footer` | 3602 | prod=0 intra=3+3 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_resolve_character_visual` | 3607 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_build_character_description` | 3640 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_extract_props` | 3708 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_is_non_human` | 3744 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `_parse_grid_size` | 3773 | prod=3 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_build_scene_coverage_panels` | 3788 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `_build_directors_take_panels` | 3883 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_build_action_burst_panels` | 3946 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_tighten_shot_type` | 3989 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_DRIFT |
| visual/prompt_engine.py | `_invert_emotion_v1` | 4189 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `_extract_cutaway_target` | 4233 | prod=0 intra=1+1 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |
| visual/prompt_engine.py | `derive_coverage_shot` | 4276 | prod=2 intra=0+0 test=0 arch=0 | COLLISION | DELETE (collision: keep pipeline/lib version) | BEHAVIORAL_TWIN |


---

## Provider_strategy.json model_id snapshot

Frozen copy lives at `consultations/recoil/cp3-prompt-consolidation-spec/cp3_phase1_provider_strategy_snapshot.json`. Embedded here for inline reference:

```json
{
  "__version__": "1.0",
  "seeddance-2.0": {
    "capability_exceptions": {},
    "primary": "fal",
    "primary_tier": "standard_720p"
  },
  "nbp": {
    "capability_exceptions": {},
    "primary": "google",
    "primary_tier": "default"
  },
  "flash": {
    "capability_exceptions": {},
    "primary": "google",
    "primary_tier": "default"
  },
  "veo-3.1": {
    "capability_exceptions": {},
    "primary": "google",
    "primary_tier": "default"
  },
  "gemini-3-pro-image-preview": {
    "capability_exceptions": {},
    "primary": "google",
    "primary_tier": "default"
  },
  "gemini-3.1-flash-image-preview": {
    "capability_exceptions": {},
    "primary": "google",
    "primary_tier": "default"
  },
  "gemini-2.5-flash-image": {
    "capability_exceptions": {},
    "primary": "google",
    "primary_tier": "default"
  },
  "wan-2.7-i2v": {
    "capability_exceptions": {},
    "primary": "wan",
    "primary_tier": "default"
  },
  "wan-2.7-r2v": {
    "capability_exceptions": {},
    "primary": "wan",
    "primary_tier": "default"
  },
  "kling-o3": {
    "capability_exceptions": {},
    "primary": "kling",
    "primary_tier": "standard_720p"
  },
  "kling-v3": {
    "capability_exceptions": {},
    "primary": "kling",
    "primary_tier": "standard_720p"
  },
  "kling-v3-i2v": {
    "capability_exceptions": {},
    "primary": "kling",
    "primary_tier": "standard_720p"
  }
}
```

Keys (model IDs the strategy router knows about): `flash`, `gemini-2.5-flash-image`, `gemini-3-pro-image-preview`, `gemini-3.1-flash-image-preview`, `kling-o3`, `kling-v3`, `kling-v3-i2v`, `nbp`, `seeddance-2.0`, `veo-3.1`, `wan-2.7-i2v`, `wan-2.7-r2v`. The hard-coded model strings inside builder bodies (Section 8 risk register) include `seedream-v4.5`, which is **not** present in the strategy file — Phase 2 must reconcile.

---

## Modality enumeration

Two distinct vocabularies are in play:

1. **Coarse `modality` strings** — the values stored in `payload.hints['modality']` and `model_profile['modality']`. Used by execution layer, providers, assembler.
2. **Fine-grained `mode` strings** — passed as the second argument to `_enforce_prompt_length(prompt, model, mode=...)` and used to pick which builder to call inside the prompt engine.

### Coarse modality strings observed

Canonical values (live grep):

- `"image"` — `execution/assembler.py:639`, `execution/step_runner.py:1464,2056`, `execution/providers/google.py:168,218`, `execution/providers/testing/mock_google.py:80,91`
- `"video"` — `execution/step_runner.py:593,2277,2460`, `execution/assembler.py:696`, `execution/providers/google.py:100,152`
- `"keyframe"` — `execution/step_runner.py:1646`, `execution/feedback/agent.py:114`. Also a `Layer` constant: `pipeline/orchestrator/production_types.py:43`

(No `"i2v"`, `"r2v"`, `"r2v_multi"`, `"i2v_multishot"`, `"t2v"`, `"previz"`, `"coverage"`, or `"multishot"` strings appear as `modality` values in execution code.)

### Fine-grained `mode` strings (used by pipeline / prompt engine)

Inside the pipeline/orchestrator and prompt engine, *pipeline tier* / *generation mode* uses these tokens:

- `"i2v"` — `pipeline/orchestrator/pipeline.py:1102,1108,1131,1184,1186,1249,1256,1300,1351,1372` etc; `_enforce_prompt_length(prompt, "kling-v3", "i2v")` at `pipeline/lib/prompt_engine.py:3033`; `mode="i2v"` at `pipeline/lib/prompt_engine.py:4695`
- `"r2v"` — `_enforce_prompt_length(..., mode="r2v")` at `pipeline/lib/prompt_engine.py:4136, 4502`
- `"t2v"` — `pipeline/orchestrator/pipeline.py:1388,1394`; `_enforce_prompt_length(..., "t2v")` at `pipeline/lib/prompt_engine.py:3380`; `mode="t2v"` at `pipeline/lib/prompt_engine.py:4605`
- `"keyframe"` — `pipeline/orchestrator/pipeline.py:281,519,756,1096,1115,1156,1187,1191`; `pipeline/orchestrator/production_loop.py:69,78,347,717`; `pipeline/orchestrator/production_types.py:43`; `pipeline/orchestrator/take_provenance.py:72`; widespread in `pipeline/api/routes/generation.py`, `pipeline/api/routes/dailies.py`, `pipeline/editors/review_server.py`
- `"previs"` / `"previz"` — `pipeline/orchestrator/production_types.py:183`, `pipeline/orchestrator/production_loop.py:78,717`
- `"still"` — `pipeline/orchestrator/production_loop.py:78,717`; `pipeline/lib/run_shot.py:258` (default fallback `pipeline = shot.get("pipeline", "keyframe")`)
- `"multi_shots"` — `execution/step_runner.py:2277` `"hints": {"multi_shots": multi_shots, "modality": "video"}` (carries the multi-shot list, not a modality literal)

### Strings the spec asked us to look for

`i2v`, `r2v`, `t2v`, `keyframe`, `previz` — all confirmed in production code as listed above.
`r2v_multi` — *not* used as a literal anywhere; `build_seeddance_r2v_prompt_multi` is the builder name. Multi-segment R2V is dispatched through `_enforce_prompt_length(..., mode="r2v")` in both single and multi calls.
`i2v_multishot` — *not* observed as a literal modality string. The closest match is the broken import `build_seedance_i2v_multishot_prompt` in `pipeline/tools/test_via_steprunner.py:625` (note: single-d `seedance` typo; symbol does not exist in `pipeline/lib/prompt_engine.py`).
`coverage` — referenced via `build_coverage_prompts` callers (`pipeline/editors/review_server.py:6499`, `pipeline/api/routes/generation.py:1815`, `pipeline/tools/test_via_steprunner.py:1282`). Not used as a `modality` value.
`multishot` — referenced as `build_multi_shot_prompt`, `build_multi_prompt_sequence`, `build_seeddance_r2v_prompt_multi`. Not used as a `modality` value.


---

## Risk register surfaced by audit

The following risks were discovered during Phase 1 and must be addressed (or accepted with explicit rationale) by the relevant downstream phase. They are ranked by blast radius.

1. **`core.prompt_config` vs `lib.prompt_config` — both exist and are NOT byte-identical.** `recoil/core/prompt_config.py` is the canonical 194-line module (md5 `759e350e…`). `recoil/pipeline/lib/prompt_config.py` is a 7-line proxy stub (md5 `2607cdb8…`) that re-exports `from core.prompt_config import *`. They are functionally equivalent at the import-site level: every name is the same object. **However**, `visual/prompt_engine.py` imports from `core.prompt_config` (line 32) while `pipeline/lib/prompt_engine.py` imports from `lib.prompt_config` (the stub). After Phase 6 stubs out `visual/prompt_engine.py`, `core.prompt_config` keeps living because `visual/elements.get_identity_anchor` and other modules also import from it. **Action for Phase 3:** keep both files for now; either rewire pipeline/lib imports to `core.prompt_config` directly, or leave the stub as a permanent forwarder. Do **not** delete `core.prompt_config` — it has independent callers beyond prompt_engine.

2. **22 of 76 collision pairs have BEHAVIORAL_DRIFT** — `build_prompt_sections_from_plan`, `build_kling_i2v_prompt`, `build_kling_t2v_prompt`, `build_veo_prompt`, `build_video_prompt`, `build_video_prompt_from_plan`, `build_visual_anchors`, `build_wan_i2v_prompt`, `build_wan_r2v_prompt`, `build_cinematic_prompt`, `build_multi_shot_prompt`, `build_previs_prompt`, `build_prompt_from_plan`, `compile_all_prompts`, `compile_core_semantics`, `_build_character_descs_from_bible`, `_build_character_lines`, `_build_scene_coverage_panels`, `_flatten_lighting_to_prose`, `_is_non_human`, `_tighten_shot_type`, `_visual_is_non_human`. The pipeline/lib version is the canonical one (it has all the recent enrichments); the `visual/` copies are stale forks. **Action for Phase 6:** before stubbing out `visual/prompt_engine.py`, confirm no existing caller relies on the stale visual-side behavior. The only external prod importer of `visual.prompt_engine` is `visual/compiler.py:249` (lazy, conditional) and `tools/shootout/audit_harness.py:113` — both must switch to `pipeline.lib.prompt_engine` first.

3. **Builders that bypass `_enforce_prompt_length`** (length safety net is the prompt-length enforcer). Of 25 public `build_*_prompt*` functions in `pipeline/lib/prompt_engine.py`, **the following do NOT call `_enforce_prompt_length`:** `build_prompt_sections_from_plan`, `build_prompt_from_plan`, `build_spatial_continuity_block`, `build_video_prompt_from_plan`, `build_visual_anchors`, `build_cinematic_prompt`, `build_grid_prompt`, `build_two_character_prompt`, `build_location_ref_prompt`, `build_universal_expression_matrix`, `build_multi_prompt_sequence`, `build_coverage_prompts`. These are mostly *intermediate* builders (sections, anchors, continuity blocks) whose output is downstream-truncated by the model-specific final builder. **Action for Phase 2:** the new `get_builder()` dispatch must not call these directly without subsequent length enforcement; the BUILDERS table should mark which builders are *terminal* (returns the prompt string sent to the model) vs *intermediate* (returns a fragment for further composition).

4. **Hard-coded model_id strings inside builder bodies.** 13 builders contain hard-coded model_id literals in their `_enforce_prompt_length` calls and elsewhere: `kling-v3`, `seeddance-2.0`, `veo-3.1`, `wan-2.7-i2v`, `wan-2.7-r2v`, `nbp`, `seedream-v4.5`, `gemini-3.1-flash-image-preview`. Notably `seedream-v4.5` (in `build_seedream_prompt` at line 2840) is **not present** in `provider_strategy.json` — the strategy router has no knowledge of it. **Action for Phase 2:** the BUILDERS table must lift these literals to a model_id parameter, and Phase 3-4 must reconcile the `seedream-v4.5` reference (either add to provider_strategy.json or delete the builder).

5. **Lazy `from lib.prompt_engine import …` calls inside hot-path bodies.** 47 lazy imports were catalogued. The notable hot-path concentrations:
   - `pipeline/orchestrator/pipeline.py` lines 223, 461, 698, 1173, 1402, 1423 — six lazy imports of `build_prompt_sections_from_plan` / `build_kling_t2v_prompt` inside per-shot dispatch branches. Each call repays the import on the first hit per process; no measured impact, but it suggests an eager top-level import would be cleaner once the SSoT collapses.
   - `pipeline/api/routes/generation.py` lines 260, 357, 418, 804, 1579, 1815, 1914 — seven lazy imports inside FastAPI route handlers (per-request import). Module is import-cached so this is cheap; cosmetic concern only.
   - `pipeline/lib/spatial_compliance.py` lines 181, 196, 219, 224, 447 — five lazy imports of underscore-prefixed helpers (`_normalize_camera_side`, `_resolve_display_name`, `_is_moving`, `_is_punch_in`, `_resolve_lr_assignment`, `_resolve_ots_assignment`). All five helpers exist in `pipeline/lib/prompt_engine.py` (verified). After Phase 6 deletes the visual/ collision-twin, these imports continue to resolve. **Confirmed safe.**
   - `pipeline/lib/jit_prompt.py:28` — top-level (eager) import of `_build_character_descs_from_bible`. This crosses the lib→lib boundary inside a sibling module and has a known circular-import risk if pipeline/lib/prompt_engine.py ever imports from `jit_prompt`. Currently only the reverse direction holds (`prompt_engine._maybe_hydrate` calls `jit_prompt.hydrate_prompt` — verify before adding new edges).

6. **Broken import in `pipeline/tools/test_via_steprunner.py:625`** — `from lib.prompt_engine import build_seedance_i2v_multishot_prompt`. This symbol does not exist (the file has `build_seeddance_i2v_prompt` with double-d, and a `build_seeddance_r2v_prompt_multi`, but no `seedance_i2v_multishot`). The line lives inside an `if`-branch that may not execute under normal CLI flags, so the typo went unnoticed. **Action for Phase 2 / Phase 7:** decide whether to remove the broken branch, rename to `build_seeddance_i2v_prompt`, or build a new `build_seeddance_i2v_prompt_multi` function. Track via the verification phase.

7. **`tools/prompt_engine.py` is a near-orphan.** Its only external production caller is `recoil/tools/ab_test_comprehensive.py:38` (`from prompt_engine import PromptEngine`). Every other ref to `OverrideStore`, `NoteStore`, `PromptLayers`, `compile_layers`, `LAYER_ORDER` resolves to `recoil/lib/prompt_compiler.py` — the canonical 10-layer compiler. **Action for Phase 5:** stub `tools/prompt_engine.py` to re-export from `lib/prompt_compiler.py`, then either update or archive `ab_test_comprehensive.py`. The file is essentially a stale fork.

8. **`PROMPT_BIBLE.yaml` couplings.** None of the 3 `prompt_engine.py` files reference `PROMPT_BIBLE.yaml` directly (zero hits). The bible is loaded by `core/prompt_config.py` via `load_prompt_file()` and surfaces through `get_prompt_rules(...)`, which builders call. **No risk** — the bible is correctly behind a stable abstraction.

9. **Shootout harness uses a different prompt engine.** `recoil/tools/shootout/audit_harness.py:113` imports `from visual.prompt_engine import compile_all_prompts, build_wan_i2v_prompt`. This is the only non-test consumer of `visual/prompt_engine.py` outside `visual/compiler.py`. **Action for Phase 6:** rewire to `pipeline.lib.prompt_engine` before stubbing the visual mirror, or risk breaking shootout runs.

10. **`_OVERRIDE_PRESETS` is a module-level dict consumed across the API surface.** `pipeline/api/routes/prompt_inspector.py:154` and `pipeline/editors/review_server.py:3890` both import `_OVERRIDE_PRESETS` from `pipeline/lib/prompt_engine.py`. The `visual/prompt_engine.py` mirror also defines a `_OVERRIDE_PRESETS` at line 205 — verify (manually, in Phase 6) that the two dicts have the same shape before deletion. The two `_resolve_bypasses` collision-twin functions are byte-identical, so the presets are likely identical too — but the audit only diffed function bodies, not module-level constants.

11. **Two pipeline/lib symbols are wholly DEAD** — `validate_start_frame_ar` (line 118) and `build_prompt_from_bible` (line 176). Zero callers in production, tests, archive, or other prompt_engine files. Phase 8 should delete with their helper code.

---

*End of CP-3 Phase 1 audit. Next phase: CP-3.2 — BUILDERS dispatch + `get_builder()` (no migration yet).*

---

## Phase 3 migration log

Generated: 2026-04-26

DECISION: NO function migration. Phase 1's audit found 0 visual-only
ALIVE symbols — `visual/prompt_engine.py` is fully duplicated against
`pipeline/lib/prompt_engine.py` canonical bodies (76 collision pairs:
54 BEHAVIORAL_TWIN + 22 BEHAVIORAL_DRIFT favoring pipeline/lib + 0
DEAD_TWIN). Phase 3 therefore reduces to BUILDERS dispatch wiring for
the 5 model_ids missing from Phase 2's table.

### Builder selection

The audit confirmed only one keyframe-style builder exists in
`pipeline/lib/prompt_engine.py`: `build_previs_prompt` (line 4821,
signature `(shot, bible=None, config=None) -> str`). The placeholder
name `build_keyframe_prompt` referenced in the Phase 2 BUILDERS comment
does NOT exist anywhere in the codebase (verified in Phase 1, Section
"Classification per symbol"; `build_keyframe_prompt` appears in zero
files). All 5 image-gen model_ids therefore route to
`build_previs_prompt`.

### Modality coverage decision

Per audit § Modality enumeration, the orchestrator dispatches image
generation under both `"keyframe"` (production frames — see
`pipeline/orchestrator/pipeline.py:281,519,756,1096,1115,1156,1187,1191`
and `pipeline/orchestrator/production_loop.py:69,78,347,717`) and
`"previz"`/`"previs"` (gut-check pre-render — see
`pipeline/orchestrator/production_types.py:183`,
`pipeline/orchestrator/production_loop.py:78,717`). Both modalities are
ALIVE. To avoid forcing callers to memorise per-model quirks, Phase 3
registers BOTH modalities for every image-gen model_id.

### Phase 3 BUILDERS additions (10 entries)

| Model ID | Modality | Builder | Rationale |
|---|---|---|---|
| `nbp` | `keyframe` | `build_previs_prompt` | Production keyframe engine (per `recoil/pipeline/CLAUDE.md` step 10); routed to canonical builder |
| `nbp` | `previz` | `build_previs_prompt` | Symmetry — orchestrator may dispatch under either token |
| `flash` | `keyframe` | `build_previs_prompt` | Audit confirms direct match for previz/keyframe shots |
| `flash` | `previz` | `build_previs_prompt` | Primary previz engine (per `recoil/pipeline/CLAUDE.md` step 8) |
| `gemini-2.5-flash-image` | `keyframe` | `build_previs_prompt` | Image-gen variant — same builder family |
| `gemini-2.5-flash-image` | `previz` | `build_previs_prompt` | Image-gen variant — same builder family |
| `gemini-3-pro-image-preview` | `keyframe` | `build_previs_prompt` | Image-gen variant — same builder family |
| `gemini-3-pro-image-preview` | `previz` | `build_previs_prompt` | Image-gen variant — same builder family |
| `gemini-3.1-flash-image-preview` | `keyframe` | `build_previs_prompt` | Image-gen variant — same builder family |
| `gemini-3.1-flash-image-preview` | `previz` | `build_previs_prompt` | Image-gen variant — same builder family |

### BUILDERS table size

Phase 2: 15 entries. Phase 3: +10 = **25 entries**. All 12 model_ids in
`recoil/config/provider_strategy.json` now have at least one BUILDERS
key (verified by `test_keys_match_provider_strategy_snapshot`).

### Byte-identical gate

PASS. Phase 3 changed zero function bodies — dispatch entries are
identity-preserving lookups into the existing `build_previs_prompt`
function. The invocation check in
`consultations/recoil/cp3-prompt-consolidation-spec/cp3_phase3_byte_diff.json`
records SHA-256 `baa4fec3bb946e877a9caacdb307afdbac71d56bb5e04fb21ee7d0de9b61e6af`
for both the direct call (`build_previs_prompt(mock_shot, mock_bible)`)
and the dispatch call (`get_builder('flash', 'previz')(mock_shot, mock_bible)`).
All 10 new dispatch entries resolve to `build_previs_prompt` by `is`-identity.

### Test re-tightening

`test_keys_match_provider_strategy_snapshot` in
`pipeline/lib/tests/test_get_builder.py` had its `@pytest.mark.xfail`
decorator removed — the test now strictly asserts that every model_id
in `provider_strategy.json` has a BUILDERS entry. The
`PHASE_2_COVERED_MODELS` constant was renamed to
`PHASE_3_COVERED_MODELS` and expanded to all 12 strategy keys.

Pytest result: 9 passed, 0 xfailed.

*End of CP-3 Phase 3 migration log. Next phase: CP-3.4 — fal.ai T2I migration from tools/prompt_engine.py.*

## Phase 4 migration log

Generated: 2026-04-26

DECISION: **NO-OP for the SSOT BUILDERS dispatch.** No function or class
bodies were migrated into `pipeline/lib/prompt_engine.py` in Phase 4.

### Why NO-OP

Phase 1 audit found `tools/prompt_engine.py` has exactly **1 external
production caller**, re-confirmed at the top of Phase 4:

```text
$ grep -rn '^[[:space:]]*from prompt_engine\|^[[:space:]]*import prompt_engine\|^[[:space:]]*from tools.prompt_engine\|^[[:space:]]*import tools.prompt_engine' \
      recoil --include='*.py' \
    | grep -v '_archive\|/tests/\|/tools/prompt_engine.py'
recoil/tools/ab_test_comprehensive.py:38:from prompt_engine import PromptEngine
```

(The unfiltered grep also surfaces 6 hits inside docstrings, comments, and
unrelated submodule names — `pipeline/tools/test_via_steprunner.py:31`,
`pipeline/tools/backfill_storyboard.py:11`, `pipeline/lib/jit_prompt.py:27`,
`pipeline/lib/critics/keyframe_rewrite_critic.py:25`,
`pipeline/lib/prompt_engine.py:5984`, and
`pipeline/verify_phase_b_step1.py:225` which imports the SSOT
`pipeline.lib.prompt_engine`, not the tools-side one — none of these are
imports of `tools/prompt_engine.py` and were filtered with `^\s*` anchors
above.)

The single live caller imports `PromptEngine` — a 10-layer prompt-builder
class shaped around `PromptLayers` / `OverrideStore` / `NoteStore`
siblings. That architecture is **structurally distinct from the
`(model_id, modality) → builder` dispatch** managed by
`pipeline/lib/prompt_engine.py` (the BUILDERS SSOT). It is not a builder
in the BUILDERS sense and does not belong in the dispatch table.

Phase 4 therefore migrates NOTHING into `pipeline/lib/prompt_engine.py`.

### Canonical-home correction (vs. Phase 1 audit)

Phase 1 stated the canonical home for `PromptEngine` is
`recoil/lib/prompt_compiler.py`. Re-verifying in Phase 4:

```text
$ grep -n 'class PromptEngine' recoil/lib/prompt_compiler.py recoil/tools/prompt_engine.py
recoil/tools/prompt_engine.py:342:class PromptEngine:

$ grep -n '^class ' recoil/lib/prompt_compiler.py
245:class PromptLayers:
260:class PreviousShotContext:
308:class OverrideEntry:
319:class NoteEntry:
333:class OverrideStore:
399:class NoteStore:
```

`recoil/lib/prompt_compiler.py` does **not** contain a `class PromptEngine`.
It owns the *sibling components* of the layered architecture
(`PromptLayers`, `OverrideStore`, `NoteStore`, `compile_layers()`), but
the `PromptEngine` class itself **only exists at `tools/prompt_engine.py:342`
in the entire codebase** (excluding `_archive/` and `/tests/`).

Implication for Phase 5: the stub at `tools/prompt_engine.py` cannot
re-export `PromptEngine` from `lib.prompt_compiler` — that symbol is not
there. Phase 5 has two valid moves:

1. **Move the class** from `tools/prompt_engine.py` into
   `recoil/lib/prompt_compiler.py` (where its `PromptLayers` /
   `OverrideStore` / `NoteStore` collaborators already live), then make
   `tools/prompt_engine.py` a re-export shell. This is the
   architecturally correct destination — the layered family lives
   together — but it's a *body migration*, which is outside the
   "byte-identical" gate that has held through Phase 3 and outside the
   stated Phase 5 scope ("STUB tools/prompt_engine.py").

2. **Keep the class where it is** for CP-3 and have Phase 5's stub
   re-export `from .prompt_engine import PromptEngine` (i.e., point to
   the same module rather than collapse it). Phase 8 then either deletes
   the duplicate after migrating callers, or — if JT wants to preserve
   the 10-layer architecture as a v2 feature — Phase 8 only deletes the
   *redundant* surface and the class body stays put.

The audit log surfaces both options. Phase 5 picks one when it loads
this section.

### Caller-handling sequencing

- **Phase 5 (this build):** stub `tools/prompt_engine.py` per the move
  chosen above. Stub is the catch-net — if the audit missed any symbol
  used by `ab_test_comprehensive.py`, Phase 7 verification surfaces it.
- **Phase 7 verification:** import-only smoke against
  `recoil/tools/ab_test_comprehensive.py` to confirm the stub re-export
  resolves.
- **Phase 8 deletion:** before deleting `tools/prompt_engine.py`, update
  `recoil/tools/ab_test_comprehensive.py:38` to import from whichever
  module ends up canonical (`lib.prompt_compiler` if option 1,
  `tools.prompt_engine` direct retention if option 2 with class in place).
  Caller redirect is in Phase 8's scope per spec § "Update straggling
  imports."

### Byte-identical gate

PASS. Phase 4 modified zero function bodies, modified zero classes,
and made zero edits to the `BUILDERS` dispatch table. The only file
touched is this audit document. SHA-256 of the unchanged
`tools/prompt_engine.py` (1068 lines) at HEAD:

```text
d4fe53c1ce5729ba2fc02452f18a048d64d44f5410315f6b6d506413cc561c1d  recoil/tools/prompt_engine.py
```

Captured for Phase 7 to compare against.

*End of CP-3 Phase 4 migration log. Next phase: CP-3.5 — STUB tools/prompt_engine.py (decision tree above).*

---

## Phase 5 migration log

Generated: 2026-04-26

### What landed

`recoil/tools/prompt_engine.py`: 1068 → 68 lines (CP-3 stub).

The Phase 4 finding ("class PromptEngine lives ONLY at tools/prompt_engine.py:342") forced an EXPANDED Phase 5 scope — Phase 5 had to migrate the class first, then stub.

**Migrated to `recoil/lib/prompt_compiler.py` (verbatim from tools/prompt_engine.py + collision-avoiding renames):**
- `class PromptEngine` (~290 LOC) — the migration target
- `class VerbWarning` (dataclass, ~7 LOC)
- `_suggest_fix` → renamed `_suggest_fix_legacy` (avoids collision with sibling `_suggest_fix` already at lib/prompt_validators.py with different return formatting)
- `_GAP` → renamed `_PE_GAP` (private regex constant)
- `MICRO_DETAIL_PATTERNS` → renamed `_PE_MICRO_DETAIL_PATTERNS`
- `BARE_PATTERNS` → renamed `_PE_BARE_PATTERNS`

Total appended ~410 lines under a clearly-marked `MIGRATED FROM tools/prompt_engine.py (CP-3 Phase 5, 2026-04-26)` section.

**Already-canonical, NOT duplicated:** `PromptLayers`, `OverrideEntry`, `NoteEntry`, `OverrideStore`, `NoteStore`, `compile_layers`, `LAYER_ORDER`, `VERB_CLASSES`, `DEFAULT_QUALITY_GUARD`.

**Excluded from migration (tools/-side artifact):** `main()`, `cmd_preview`, `cmd_validate`, `cmd_override_*`, `cmd_note`, argparse, `_load_storyboard`, `_load_lora_registry`, `_get_shot`. Phase 8 deletes them with the file. Recoverable from `git show pre-prompt-tools-stub:recoil/tools/prompt_engine.py` if a v2 needs the CLI.

### Stub re-exports

`recoil/tools/prompt_engine.py` (post-stub) re-exports from `lib.prompt_compiler` (sys.path-relative pattern matching codebase convention):
- `PromptEngine`
- `PromptLayers`
- `OverrideStore`
- `NoteStore`
- `OverrideEntry`
- `NoteEntry`
- `compile_layers`

CP-3 `DeprecationWarning` emits on import. The 1 external caller (`recoil/tools/ab_test_comprehensive.py:38`) resolves PromptEngine through the stub. Phase 8 will redirect that caller to import directly from `lib.prompt_compiler` and delete this file.

### Tag

`pre-prompt-tools-stub` placed locally (parent repo at HEAD `c74a1e30`). No `origin` remote is configured; same pattern CP-2 used (`pre-cp2-provider-refactor` is similarly local-only). Rollback: `git checkout pre-prompt-tools-stub -- recoil/tools/prompt_engine.py`.

### Verification

- `from prompt_engine import PromptEngine` (the legacy ab_test_comprehensive import pattern) resolves through the stub.
- CP-3 `DeprecationWarning` emits with `CP-3 stub` mention.
- BUILDERS dispatch unchanged (25 entries).
- `pipeline/lib/tests/test_get_builder.py`: 9/9 PASS.

*End of CP-3 Phase 5 migration log. Next phase: CP-3.6 — STUB visual/prompt_engine.py.*

---

## Phase 6 migration log

Generated: 2026-04-26

### Stub deployed

`recoil/visual/prompt_engine.py`: 4397 → 61 lines (CP-3 stub).

The stub re-exports the following symbols from `lib.prompt_engine` (the SSOT):
- `GridType` (Enum)
- `compile_all_prompts`
- `build_wan_i2v_prompt`
- `build_prompt_from_plan`
- `build_prompt_sections_from_plan`
- `build_video_prompt_from_plan`

All 4 known callers continue to resolve through the stub:
- `recoil/tools/shootout/audit_harness.py:113`
- `recoil/visual/compiler.py:249`
- `recoil/visual/test_prompt_engine.py:22,66,110,148`

CP-3 `DeprecationWarning` emits on import. Rollback tag: `pre-prompt-visual-stub` (local).

### BEHAVIORAL_DRIFT surfaced — feature delta flagged for v2

`visual/test_prompt_engine.py` had 4 tests that exercised the visual/-side `build_prompt_sections_from_plan` with `format_rules`, `grammar_context`, and `exposure_level` keyword arguments. The pipeline/lib canonical version of this function does NOT accept those kwargs. This is a real feature delta:

| Symbol | Visual/ feature | Pipeline/lib canonical |
|---|---|---|
| `build_prompt_sections_from_plan(format_rules=...)` | Layer-muting + grammar-bleed for Puzzle Box format | Not implemented |
| `build_prompt_sections_from_plan(grammar_context=...)` | Per-shot lens-language injection | Not implemented |
| `build_prompt_sections_from_plan(exposure_level=...)` | Bleed-schedule exposure stepping | Not implemented |

This was a Phase 1 BEHAVIORAL_DRIFT collision (one of the 22 such pairs). Phase 1 classified pipeline/lib as canonical because it's the more-called surface, but this audit entry documents that the visual/ version had a richer format-aware grammar layer that did NOT survive consolidation.

**Phase 6 resolution:** the 4 affected tests are decorated `@pytest.mark.skip` with `reason` pointing back to this log. The stub re-export still works at the import boundary — only the format-rules behavior is unavailable.

**v2 follow-up (out of CP-3 scope):**
- If/when Puzzle Box (or any other format with bleed-schedule grammar) ships again, re-port the format-rules layer + grammar bleed into `pipeline/lib/prompt_engine.build_prompt_sections_from_plan` and remove the `pytest.mark.skip` decorators.
- The visual/ pre-CP-3 implementation is fully recoverable from `git show pre-prompt-visual-stub:recoil/visual/prompt_engine.py`.

### Verification status

- `pipeline/lib/tests/test_get_builder.py`: 9/9 PASS (BUILDERS dispatch unaffected)
- `visual/test_prompt_engine.py`: 4/4 SKIP (BEHAVIORAL_DRIFT documented above)
- All caller imports resolve through stub
- BUILDERS dict unchanged (25 entries)

*End of CP-3 Phase 6 migration log. Next phase: CP-3.7 — Verification Day (HARD GATE).*

---

## Phase 8 deletion log

Generated: 2026-04-26

Phase 7 verification day was GREEN — the Phase 5 stub (`recoil/tools/prompt_engine.py`) and the Phase 6 stub (`recoil/visual/prompt_engine.py`) both functioned correctly as redirect catch-nets across pytest, import-time inspection, and live caller traffic. Phase 8 promotes every remaining caller off the stubs and deletes both files.

### Files deleted

| Path | Pre-CP-3 LOC | As-stub LOC | Status |
|---|---|---|---|
| `recoil/tools/prompt_engine.py` | 1068 | 68 | DELETED |
| `recoil/visual/prompt_engine.py` | 4397 | 61 | DELETED |

Rollback tag covering this deletion: `pre-prompt-engine-deletion` (commit `0ba43447`).

### Imports updated

| File | Line(s) | From | To |
|---|---|---|---|
| `recoil/visual/compiler.py` | 249 | `from visual.prompt_engine import build_prompt_from_plan` | `from lib.prompt_engine import build_prompt_from_plan` |
| `recoil/visual/test_prompt_engine.py` | 22, 66, 110, 148 (now 46, 91, 136, 175 post-bootstrap) | `from visual.prompt_engine import build_prompt_sections_from_plan` | `from lib.prompt_engine import build_prompt_sections_from_plan` |
| `recoil/tools/shootout/audit_harness.py` | 113 | `from visual.prompt_engine import compile_all_prompts, build_wan_i2v_prompt` | `from lib.prompt_engine import compile_all_prompts, build_wan_i2v_prompt` |
| `recoil/tools/ab_test_comprehensive.py` | 38 | `from prompt_engine import PromptEngine` (sys.path-relative via `_tools_dir`) | `from prompt_compiler import PromptEngine` (sys.path-relative via `_lib_dir`) |

### Bootstraps added (caller side, post-stub-deletion)

The deleted stubs each carried a sys.path side-effect that made `from lib.prompt_engine ...` resolvable from any caller. With the stubs gone, callers that don't already put `recoil/pipeline/` (or `recoil/lib/`) on `sys.path` must do it themselves. Two callers needed a new bootstrap:

1. **`recoil/visual/compiler.py`** — added module-level bootstrap inserting `RECOIL_ROOT / "pipeline"` into `sys.path` so the in-function `from lib.prompt_engine import build_prompt_from_plan` (line 249) resolves to the SSOT at `recoil/pipeline/lib/prompt_engine.py`.

2. **`recoil/visual/test_prompt_engine.py`** — added module-level bootstrap inserting `RECOIL_ROOT / "pipeline"` after the existing `RECOIL_ROOT` insertion. Imports inside the four `@pytest.mark.skip` test bodies still need to parse cleanly even though the tests are skipped (pytest collects the AST regardless).

3. **`recoil/tools/ab_test_comprehensive.py`** — added module-level bootstrap inserting `RECOIL_ROOT / "lib"` (i.e., `recoil/lib/`) so the new `from prompt_compiler import PromptEngine` resolves. This mirrors the sys.path-relative pattern the Phase 5 stub used internally to redirect to `prompt_compiler`. (The alternative — `from lib.prompt_compiler import PromptEngine` — would namespace-clash with `pipeline/lib/`, since both directories carry a `lib` package and either could win sys.path resolution depending on caller insertion order. The sys.path-relative form sidesteps the clash entirely.)

`recoil/tools/shootout/audit_harness.py` already invokes `ensure_starsend_importable()` (a backwards-compat alias for `ensure_pipeline_importable()`) before the prompt-engine import, so no new bootstrap was required.

### Caveats

- The `lib.prompt_engine` import path requires `recoil/pipeline/` on `sys.path`. This is fine for callers running through `core.paths.ensure_pipeline_importable()` and for callers with their own bootstrap (the four updated above). Future callers added under `recoil/visual/` or `recoil/tools/` MUST add the bootstrap explicitly or import via `core.paths` first — there is no longer a stub to paper over a missing `sys.path` setup.

- `ab_test_comprehensive.py`'s new `from prompt_compiler import PromptEngine` is sys.path-relative for the namespace-clash reason above. If the codebase ever reorganizes so `recoil/lib/` becomes a single canonical `lib` package, this import line should be revisited.

- The four BEHAVIORAL_DRIFT-skipped tests in `visual/test_prompt_engine.py` remain skipped — their `@pytest.mark.skip` reasons are unchanged. Phase 8 swapped the import path but did not re-port the missing `format_rules` / `grammar_context` / `exposure_level` kwargs into the SSOT's `build_prompt_sections_from_plan`. That remains a v2 follow-up, as documented in the Phase 6 migration log.

### LOC reduction summary

| Bucket | Pre-CP-3 LOC | Post-CP-3 LOC | Net Δ |
|---|---|---|---|
| `pipeline/lib/prompt_engine.py` (SSOT) | 5,966 | ~6,076 | +110 (BUILDERS dispatch + migrated builders) |
| `recoil/visual/prompt_engine.py` | 4,397 | 0 (deleted) | −4,397 |
| `recoil/tools/prompt_engine.py` | 1,068 | 0 (deleted) | −1,068 |
| `recoil/lib/prompt_compiler.py` (PromptEngine class) | 0 | ~410 | +410 (Phase 5 migration) |
| **TOTAL prompt-engine surface** | **11,431** | **~6,486** | **−4,945** (~43% reduction) |

The SSOT is now the single canonical location for prompt-engine logic.

### Rollback procedure

Within 1 week of Phase 8 (until ~2026-05-03), full rollback is mechanical:

```bash
cd /Users/joeturnerlin/Dropbox/CLAUDE_PROJECTS
# 1. Restore the deleted stub files
git checkout pre-prompt-engine-deletion -- recoil/tools/prompt_engine.py recoil/visual/prompt_engine.py
# 2. Revert the four caller import edits (use the table above as a reverse mapping)
# 3. (Optional) Roll back BUILDERS dispatch in pipeline/lib/prompt_engine.py
#    by checking out pre-cp3-prompt-consolidation
```

After 1 week, the engine-memory store will accumulate learning records keyed to the new prompt path; full rollback gets harder and should be treated as a v2 effort. The `pre-prompt-engine-deletion` tag remains as a recovery point indefinitely, but mid- to long-term rollback should reconstruct rather than `git checkout --`.

### Verification status (CP-3 Phase 8 exit gate)

- `pytest pipeline/lib/tests/test_get_builder.py visual/test_prompt_engine.py --tb=short` → **9 passed, 4 skipped** (unchanged from Phase 6/7 baseline).
- `grep 'from {visual,tools}.prompt_engine'` over `recoil/**/*.py` (excluding `_archive`) → **zero hits**.
- `from visual.prompt_engine import compile_all_prompts` → **ImportError** (stub deleted).
- `from tools.prompt_engine import PromptEngine` → **ImportError** (stub deleted).
- `BUILDERS` dict at `lib.prompt_engine` → **25 entries** (unchanged from Phase 2).
- `engine-memory` diff vs `pre-cp3-prompt-consolidation` → **empty** (no engine-memory mutation across CP-3).
- Rollback tag `pre-prompt-engine-deletion` → resolves to `0ba43447` (verified pre-dispatch).

*End of CP-3 Phase 8 deletion log. Next phase: CP-3.9 — Documentation update.*

---

## Post-consolidation summary

Generated: 2026-04-26
CP-3 status: GREEN (Phases 1-9 complete)

### What changed

- Deleted: `recoil/tools/prompt_engine.py` (1,068 lines), `recoil/visual/prompt_engine.py` (4,397 lines).
- Single source of truth for video/keyframe builders: `recoil/pipeline/lib/prompt_engine.py` (~5,966 lines + ~110-line BUILDERS section).
- Migrated to `recoil/lib/prompt_compiler.py`: `class PromptEngine` + `VerbWarning` + 3 helpers (~410 lines), where sibling layered classes (`PromptLayers`, `OverrideStore`, `NoteStore`, `compile_layers`) already lived canonically.
- New API: `from lib.prompt_engine import get_builder; fn = get_builder(model_id, modality); prompt = fn(shot, bible)`.
- `BUILDERS` dispatch is a flat `dict[tuple[str, str], Callable]` with 25 entries — adding a new model is a single line.

### How to find a builder now

| Want to … | Do |
|---|---|
| Build a Kling I2V prompt | `get_builder("kling-o3", "i2v")(shot, bible)` |
| Build a Seedance R2V multi-shot prompt | `get_builder("seeddance-2.0", "r2v_multi")(shots, bible)` |
| Build an NBP keyframe prompt | `get_builder("nbp", "keyframe")(shot, bible)` |
| Build a Flash previz prompt | `get_builder("flash", "previz")(shot, bible)` |
| Add a new model | Append `BUILDERS[(model_id, modality)] = build_<…>_prompt` in `pipeline/lib/prompt_engine.py` |
| Find which builder runs for a sidecar | Read `provenance.prompt_engine_version` (CP-2 enrichment) + match `model` to `BUILDERS` keys |

### LOC accounting

| File | Pre-CP-3 | Post-CP-3 | Delta |
|---|---|---|---|
| `pipeline/lib/prompt_engine.py` | 5,966 | 6,076 | +110 (BUILDERS dispatch + get_builder + Callable import) |
| `visual/prompt_engine.py` | 4,397 | DELETED | −4,397 |
| `tools/prompt_engine.py` | 1,068 | DELETED | −1,068 |
| `lib/prompt_compiler.py` | ~1,915 | 2,325 | +410 (PromptEngine class migration) |
| **TOTAL** | | | **−4,945** (~43% net reduction across 4 files) |

Plus 117 lines of new test infrastructure at `pipeline/lib/tests/test_get_builder.py` (9 tests covering BUILDERS dispatch, error paths, strategy-snapshot superset).

### Rollback procedure (within 1 week of Phase 8)

```bash
cd /Users/joeturnerlin/Dropbox/CLAUDE_PROJECTS
git checkout pre-prompt-engine-deletion -- recoil/tools/prompt_engine.py recoil/visual/prompt_engine.py
# Then revert the BUILDERS additions (Phases 2+3) and the import redirects (Phase 8).
```

After 1 week, rollback gets harder — engine-memory will accumulate learning records under the new path. Tag stays as a recovery point but full rollback should be a v2 effort.

### Phase rollback tags (all local; no `origin` remote configured per CP-2 pattern)

- `pre-cp3-prompt-consolidation` — placed before Phase 1
- `pre-prompt-tools-stub` — placed before Phase 5 (tools/ stubbing)
- `pre-prompt-visual-stub` — placed before Phase 6 (visual/ stubbing)
- `pre-prompt-engine-deletion` — placed before Phase 8 (final deletion)

### Known feature delta — flagged for v2

Phase 6 surfaced 4 tests in `visual/test_prompt_engine.py` exercising visual/-side `format_rules` / `grammar_context` / `exposure_level` kwargs that pipeline/lib's canonical `build_prompt_sections_from_plan` does not implement. The tests are now `@pytest.mark.skip`. If/when Puzzle Box (or any other format with bleed-schedule grammar) ships again, re-port the format-rules layer + grammar bleed into `pipeline/lib/prompt_engine.build_prompt_sections_from_plan` and remove the skip decorators. The visual/ pre-CP-3 implementation is fully recoverable from `git show pre-prompt-visual-stub:recoil/visual/prompt_engine.py`.

*End of CP-3 Phase 9 documentation update. CP-3 build COMPLETE.*
