@phase3 @acms @acms_pipeline_phase3
Feature: ACMS Pipeline Phase 3 — Context Finalization and Advanced Strategies
  As a CleverAgents developer
  I want production-grade Phase 3 pipeline components and advanced strategies
  So that context assembly orders fragments coherently, generates provenance preambles,
  and supports ARCE, temporal archaeology, and plan decision context strategies

  # ===========================================================================
  # RelevanceCoherenceOrderer
  # ===========================================================================

  @orderer
  Scenario: Order fragments by relevance while grouping related nodes
    Given the following phase3 fragments:
      | uko_node              | content    | score | tokens | depth |
      | project://app/io.py   | io module  | 0.9   | 20     | 3     |
      | project://app/main.py | main entry | 0.5   | 15     | 3     |
      | project://lib/util.py | utility fn | 0.7   | 25     | 3     |
      | project://app/net.py  | network    | 0.8   | 10     | 3     |
    When I order the fragments with RelevanceCoherenceOrderer
    Then the first ordered fragment should have uko_node "project://app/io.py"
    And fragments from the same group should be adjacent

  @orderer
  Scenario: Order with empty fragment list
    Given an empty phase3 fragment list
    When I order the fragments with RelevanceCoherenceOrderer
    Then 0 fragments should remain after ordering

  @orderer
  Scenario: Order single fragment returns it unchanged
    Given the following phase3 fragments:
      | uko_node              | content | score | tokens | depth |
      | project://app/main.py | hello   | 0.5   | 10     | 3     |
    When I order the fragments with RelevanceCoherenceOrderer
    Then 1 fragment should remain after ordering

  @orderer
  Scenario: Order preserves all fragments
    Given the following phase3 fragments:
      | uko_node              | content | score | tokens | depth |
      | project://app/a.py    | alpha   | 0.3   | 10     | 3     |
      | project://app/b.py    | beta    | 0.9   | 10     | 5     |
      | project://app/c.py    | gamma   | 0.6   | 10     | 1     |
    When I order the fragments with RelevanceCoherenceOrderer
    Then 3 fragments should remain after ordering

  @orderer
  Scenario: Higher-depth fragments ordered after overview in same group
    Given the following phase3 fragments:
      | uko_node              | content | score | tokens | depth |
      | project://app/main.py | detail  | 0.8   | 10     | 9     |
      | project://app/main.py | brief   | 0.8   | 10     | 1     |
    When I order the fragments with RelevanceCoherenceOrderer
    Then 2 fragments should remain after ordering

  # ===========================================================================
  # ProvenancePreambleGenerator
  # ===========================================================================

  @preamble
  Scenario: Generate provenance preamble with strategy contributions
    Given the following phase3 fragments with strategy sources:
      | uko_node              | content | score | tokens | depth | strategy_source |
      | project://app/main.py | hello   | 0.8   | 100    | 3     | relevance       |
      | project://app/io.py   | world   | 0.6   | 50     | 5     | relevance       |
      | project://lib/util.py | util    | 0.7   | 75     | 2     | arce            |
    When I generate a preamble with ProvenancePreambleGenerator
    Then the preamble should contain "Strategy Contributions"
    And the preamble should contain "relevance"
    And the preamble should contain "arce"
    And the preamble should contain "Confidence"
    And the preamble should contain "Tier Distribution"
    And the preamble should contain "Depth Distribution"

  @preamble
  Scenario: Generate preamble with confidence indicators
    Given the following phase3 fragments:
      | uko_node              | content | score | tokens | depth |
      | project://app/main.py | hello   | 0.9   | 50     | 3     |
      | project://app/io.py   | world   | 0.3   | 50     | 5     |
    When I generate a preamble with ProvenancePreambleGenerator
    Then the preamble should contain "avg=0.600"
    And the preamble should contain "min=0.300"
    And the preamble should contain "max=0.900"

  @preamble
  Scenario: Generate preamble returns None for empty fragments
    Given an empty phase3 fragment list
    When I generate a preamble with ProvenancePreambleGenerator
    Then the preamble should be None

  @preamble
  Scenario: Preamble identifies coverage gaps for missing tiers
    Given the following phase3 fragments:
      | uko_node              | content | score | tokens | depth |
      | project://app/main.py | hello   | 0.8   | 50     | 3     |
    When I generate a preamble with ProvenancePreambleGenerator
    Then the preamble should contain "Coverage Gaps"
    And the preamble should contain "Missing tiers"

  @preamble
  Scenario: Preamble includes UKO node count
    Given the following phase3 fragments:
      | uko_node              | content | score | tokens | depth |
      | project://app/a.py    | alpha   | 0.8   | 50     | 3     |
      | project://app/b.py    | beta    | 0.7   | 50     | 5     |
      | project://lib/c.py    | gamma   | 0.6   | 50     | 2     |
    When I generate a preamble with ProvenancePreambleGenerator
    Then the preamble should contain "UKO Nodes: 3"

  # ===========================================================================
  # DepthReductionCompressor
  # ===========================================================================

  @compressor
  Scenario: DepthReductionCompressor reduces Python fragments to inherited overview levels
    Given the following phase3 skeleton fragments:
      | uko_node                  | content                     | score | tokens | depth | domain  | resource_type |
      | project://src/app/main.py | def main(): return runner() | 0.9   | 120    | 7     |         | python-source |
      | project://src/app/util.py | class Util: pass            | 0.6   | 90     | 5     | uko-py: | python-source |
    When I compress with DepthReductionCompressor and budget 40
    Then all compressed fragments should have detail depth at most 1
    And compressed fragments should fit within budget 40
    And the compressed fragments should include skeleton level "MODULE_GRAPH"
    And a compressed fragment should contain "[MODULE_GRAPH]: symbols=main"

  @compressor
  Scenario: DepthReductionCompressor uses document detail levels for markdown fragments
    Given the following phase3 skeleton fragments:
      | uko_node                       | content                         | score | tokens | depth | domain   | resource_type |
      | project://docs/specification.md | # Spec\n## Overview\n## Details | 0.8   | 100    | 8     |          | markdown      |
    When I compress with DepthReductionCompressor and budget 20
    Then all compressed fragments should have detail depth at most 1
    And compressed fragments should fit within budget 20
    And the compressed fragments should include skeleton level "TABLE_OF_CONTENTS_L1"
    And a compressed fragment should contain "Overview; Details"

  @compressor
  Scenario: DepthReductionCompressor returns no fragments for zero budget
    Given the following phase3 skeleton fragments:
      | uko_node               | content           | score | tokens | depth | domain  | resource_type |
      | project://src/app/a.py | def alpha(): pass | 0.7   | 80     | 6     | uko-py: | python-source |
    When I compress with DepthReductionCompressor and budget 0
    Then 0 fragments should remain after compression

  @compressor
  Scenario: DepthReductionCompressor keeps the highest-relevance fragment when budget is tight
    Given the following phase3 skeleton fragments:
      | uko_node                  | content                | score | tokens | depth | domain  | resource_type |
      | project://src/app/main.py | def main(): return run | 0.9   | 120    | 7     | uko-py: | python-source |
      | project://src/app/aux.py  | def aux(): return slow | 0.3   | 120    | 7     | uko-py: | python-source |
    When I compress with DepthReductionCompressor and budget 9
    Then 1 fragments should remain after compression
    And compressed fragments should fit within budget 9
    And a compressed fragment should contain "main.py"

  @compressor
  Scenario: DepthReductionCompressor renders data fragments with CSV-like summary
    Given the following phase3 skeleton fragments:
      | uko_node                  | content                              | score | tokens | depth | domain    | resource_type |
      | project://db/schema.sql   | users, orders, products, shipments   | 0.7   | 80     | 5     | uko-data: | sql           |
    When I compress with DepthReductionCompressor and budget 30
    Then all compressed fragments should have detail depth at most 1
    And compressed fragments should fit within budget 30
    And a compressed fragment should contain "schema.sql"

  @compressor
  Scenario: DepthReductionCompressor renders infrastructure fragments
    Given the following phase3 skeleton fragments:
      | uko_node                     | content                           | score | tokens | depth | domain     | resource_type |
      | project://deploy/main.tf     | resource "aws_instance" "web" {}  | 0.8   | 90     | 6     | uko-infra: | terraform     |
    When I compress with DepthReductionCompressor and budget 30
    Then all compressed fragments should have detail depth at most 1
    And compressed fragments should fit within budget 30
    And a compressed fragment should contain "main.tf"

  @compressor
  Scenario: DepthReductionCompressor renders generic fragments at overview depth
    Given the following phase3 skeleton fragments:
      | uko_node            | content           | score | tokens | depth | domain | resource_type |
      | uko:some/resource   | generic resource  | 0.6   | 80     | 4     | uko:   | unknown       |
    When I compress with DepthReductionCompressor and budget 30
    Then all compressed fragments should have detail depth at most 1
    And compressed fragments should fit within budget 30

  @compressor
  Scenario: DepthReductionCompressor preserves fragment already at target depth
    Given the following phase3 skeleton fragments:
      | uko_node                  | content     | score | tokens | depth | domain  | resource_type |
      | project://src/app/main.py | brief view  | 0.9   | 10     | 0     | uko-py: | python-source |
    When I compress with DepthReductionCompressor and budget 100
    Then 1 fragments should remain after compression
    And a compressed fragment should contain "brief view"

  @compressor
  Scenario: DepthReductionCompressor clips content when all renderings exceed budget
    Given the following phase3 skeleton fragments:
      | uko_node                  | content                                                 | score | tokens | depth | domain  | resource_type |
      | project://src/app/main.py | def main(): return very_long_function_name_placeholder() | 0.9   | 200    | 7     | uko-py: | python-source |
    When I compress with DepthReductionCompressor and budget 4
    Then 1 fragments should remain after compression
    And compressed fragments should fit within budget 4

  @compressor
  Scenario: DepthReductionCompressor resolves detail map from provenance URI prefix
    Given the following phase3 skeleton fragments:
      | uko_node                | content           | score | tokens | depth | domain | resource_type |
      | uko-doc:my/design-doc   | # Design Overview | 0.7   | 80     | 5     |        |               |
    When I compress with DepthReductionCompressor and budget 30
    Then all compressed fragments should have detail depth at most 1

  @compressor
  Scenario: DepthReductionCompressor resolves detail map from resource_type
    Given the following phase3 skeleton fragments:
      | uko_node                         | content                              | score | tokens | depth | domain | resource_type |
      | project://data/schema            | CREATE TABLE users (id INTEGER)      | 0.6   | 80     | 4     |        | database      |
    When I compress with DepthReductionCompressor and budget 30
    Then all compressed fragments should have detail depth at most 1

  @compressor
  Scenario: DepthReductionCompressor handles minimal content fragments
    Given the following phase3 skeleton fragments:
      | uko_node                   | content | score | tokens | depth | domain  | resource_type |
      | project://src/app/empty.py | x       | 0.5   | 1      | 3     | uko-py: | python-source |
    When I compress with DepthReductionCompressor and budget 10
    Then compressed fragments should fit within budget 10

  @compressor
  Scenario: DepthReductionCompressor resolves TypeScript and Java from file extension
    Given the following phase3 skeleton fragments:
      | uko_node                 | content                      | score | tokens | depth | domain | resource_type |
      | project://src/app.ts     | export function main() {}    | 0.8   | 80     | 6     |        |               |
      | project://src/Main.java  | public class Main {}         | 0.7   | 80     | 6     |        |               |
    When I compress with DepthReductionCompressor and budget 60
    Then compressed fragments should fit within budget 60

  @compressor
  Scenario: DepthReductionCompressor resolves Rust from file extension
    Given the following phase3 skeleton fragments:
      | uko_node                   | content               | score | tokens | depth | domain | resource_type |
      | project://src/main.rs      | fn main() {}          | 0.8   | 80     | 6     |        |               |
    When I compress with DepthReductionCompressor and budget 30
    Then compressed fragments should fit within budget 30

  @compressor
  Scenario: DepthReductionCompressor resolves infra from YAML extension
    Given the following phase3 skeleton fragments:
      | uko_node                   | content               | score | tokens | depth | domain | resource_type |
      | project://k8s/deploy.yaml  | apiVersion: apps/v1   | 0.7   | 80     | 5     |        |               |
    When I compress with DepthReductionCompressor and budget 30
    Then compressed fragments should fit within budget 30

  @compressor
  Scenario: DepthReductionCompressor detects doc resource_type
    Given the following phase3 skeleton fragments:
      | uko_node                        | content              | score | tokens | depth | domain | resource_type |
      | project://guides/getting-started | Welcome to the docs | 0.6   | 80     | 5     |        | markdown      |
    When I compress with DepthReductionCompressor and budget 30
    Then compressed fragments should fit within budget 30

  @compressor
  Scenario: DepthReductionCompressor detects infra resource_type
    Given the following phase3 skeleton fragments:
      | uko_node                         | content                         | score | tokens | depth | domain | resource_type        |
      | project://infra/cluster          | kubernetes deployment manifest  | 0.6   | 80     | 5     |        | kubernetes           |
    When I compress with DepthReductionCompressor and budget 30
    Then compressed fragments should fit within budget 30

  # ===========================================================================
  # ArceStrategy
  # ===========================================================================

  @arce
  Scenario: ARCE ranks fragments with iterative refinement
    Given an ArceStrategy with max_iterations 3
    And the following strategy3 fragments:
      | uko_node              | content                | score | tokens | depth |
      | project://app/io.py   | async IO handler       | 0.7   | 20     | 5     |
      | project://app/main.py | main application entry | 0.5   | 15     | 3     |
      | project://lib/util.py | utility library        | 0.3   | 25     | 2     |
    And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
    When I assemble with the ArceStrategy
    Then fragments should be returned by arce strategy
    And the arce result should respect the budget

  @arce
  Scenario: ARCE returns empty for empty input
    Given an ArceStrategy with max_iterations 5
    And an empty strategy3 fragment list
    And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
    When I assemble with the ArceStrategy
    Then 0 fragments should be returned by arce strategy

  @arce
  Scenario: ARCE respects budget constraint
    Given an ArceStrategy with max_iterations 3
    And the following strategy3 fragments:
      | uko_node              | content | score | tokens | depth |
      | project://app/a.py    | alpha   | 0.9   | 100    | 5     |
      | project://app/b.py    | beta    | 0.7   | 100    | 3     |
      | project://app/c.py    | gamma   | 0.5   | 100    | 2     |
    And a strategy3 budget with max_tokens 250 and reserved_tokens 0
    When I assemble with the ArceStrategy
    Then at most 2 fragments should be returned by arce strategy

  @arce
  Scenario: ARCE can_handle returns 0.95
    Given an ArceStrategy with max_iterations 5
    When I check can_handle on ArceStrategy
    Then the arce confidence should be 0.95

  @arce
  Scenario: ARCE reports capabilities
    Given an ArceStrategy with max_iterations 5
    Then the ArceStrategy name should be "arce"
    And the ArceStrategy should support semantic search

  @arce
  Scenario: ARCE explain returns description
    Given an ArceStrategy with max_iterations 5
    Then the ArceStrategy explain should contain "Adaptive Recursive"

  @arce
  Scenario: ARCE iteration limit prevents unbounded refinement
    Given an ArceStrategy with max_iterations 2
    And the following strategy3 fragments:
      | uko_node              | content         | score | tokens | depth |
      | project://app/a.py    | alpha content   | 0.9   | 50     | 5     |
      | project://app/b.py    | beta content    | 0.7   | 50     | 3     |
    And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
    When I assemble with the ArceStrategy
    Then fragments should be returned by arce strategy

  # ===========================================================================
  # TemporalArchaeologyStrategy
  # ===========================================================================

  @temporal
  Scenario: TemporalArchaeology prioritises cold-tier fragments
    Given a TemporalArchaeologyStrategy
    And the following strategy3 fragments with tiers:
      | uko_node              | content  | score | tokens | depth | tier |
      | project://app/old.py  | archived | 0.5   | 20     | 3     | cold |
      | project://app/new.py  | recent   | 0.9   | 15     | 3     | hot  |
    And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
    When I assemble with the TemporalArchaeologyStrategy
    Then the first temporal result fragment should have uko_node "project://app/old.py"

  @temporal
  Scenario: TemporalArchaeology returns empty for empty input
    Given a TemporalArchaeologyStrategy
    And an empty strategy3 fragment list
    And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
    When I assemble with the TemporalArchaeologyStrategy
    Then 0 fragments should be returned by temporal strategy

  @temporal
  Scenario: TemporalArchaeology can_handle returns 0.5
    Given a TemporalArchaeologyStrategy
    When I check can_handle on TemporalArchaeologyStrategy
    Then the temporal confidence should be 0.5

  @temporal
  Scenario: TemporalArchaeology reports capabilities
    Given a TemporalArchaeologyStrategy
    Then the TemporalArchaeologyStrategy name should be "temporal-archaeology"

  @temporal
  Scenario: TemporalArchaeology explain returns description
    Given a TemporalArchaeologyStrategy
    Then the TemporalArchaeologyStrategy explain should contain "Historical"

  @temporal
  Scenario: TemporalArchaeology respects budget
    Given a TemporalArchaeologyStrategy
    And the following strategy3 fragments with tiers:
      | uko_node              | content | score | tokens | depth | tier |
      | project://app/a.py    | alpha   | 0.9   | 100    | 3     | cold |
      | project://app/b.py    | beta    | 0.7   | 100    | 3     | cold |
      | project://app/c.py    | gamma   | 0.5   | 100    | 3     | warm |
    And a strategy3 budget with max_tokens 250 and reserved_tokens 0
    When I assemble with the TemporalArchaeologyStrategy
    Then at most 2 fragments should be returned by temporal strategy

  # ===========================================================================
  # PlanDecisionContextStrategy
  # ===========================================================================

  @plan_decision
  Scenario: PlanDecisionContext prioritises warm-tier fragments
    Given a PlanDecisionContextStrategy
    And the following strategy3 fragments with tiers:
      | uko_node              | content    | score | tokens | depth | tier |
      | project://app/plan.py | decision   | 0.7   | 20     | 3     | warm |
      | project://app/new.py  | new code   | 0.9   | 15     | 3     | hot  |
      | project://app/old.py  | archived   | 0.5   | 25     | 3     | cold |
    And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
    When I assemble with the PlanDecisionContextStrategy
    Then the first plan_decision result should have uko_node "project://app/plan.py"

  @plan_decision
  Scenario: PlanDecisionContext returns empty for empty input
    Given a PlanDecisionContextStrategy
    And an empty strategy3 fragment list
    And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
    When I assemble with the PlanDecisionContextStrategy
    Then 0 fragments should be returned by plan_decision strategy

  @plan_decision
  Scenario: PlanDecisionContext can_handle returns 0.7
    Given a PlanDecisionContextStrategy
    When I check can_handle on PlanDecisionContextStrategy
    Then the plan_decision confidence should be 0.7

  @plan_decision
  Scenario: PlanDecisionContext reports capabilities
    Given a PlanDecisionContextStrategy
    Then the PlanDecisionContextStrategy name should be "plan-decision-context"

  @plan_decision
  Scenario: PlanDecisionContext explain returns description
    Given a PlanDecisionContextStrategy
    Then the PlanDecisionContextStrategy explain should contain "decision"

  @plan_decision
  Scenario: PlanDecisionContext respects budget
    Given a PlanDecisionContextStrategy
    And the following strategy3 fragments with tiers:
      | uko_node              | content | score | tokens | depth | tier |
      | project://app/a.py    | alpha   | 0.9   | 100    | 3     | warm |
      | project://app/b.py    | beta    | 0.7   | 100    | 3     | cold |
      | project://app/c.py    | gamma   | 0.5   | 100    | 3     | hot  |
    And a strategy3 budget with max_tokens 250 and reserved_tokens 0
    When I assemble with the PlanDecisionContextStrategy
    Then at most 2 fragments should be returned by plan_decision strategy

  # ===========================================================================
  # Pipeline Registration
  # ===========================================================================

  @registration
  Scenario: Register ArceStrategy with pipeline
    Given an ACMS pipeline for phase3 strategy tests
    When I register ArceStrategy with the pipeline
    Then the phase3 pipeline should have strategy "arce"

  @registration
  Scenario: Register all Phase 3 strategies with pipeline
    Given an ACMS pipeline for phase3 strategy tests
    When I register all phase3 strategies with the pipeline
    Then the phase3 pipeline should have strategy "arce"
    And the phase3 pipeline should have strategy "temporal-archaeology"
    And the phase3 pipeline should have strategy "plan-decision-context"

  # ===========================================================================
  # Pipeline Integration — DI injection of Phase 3 components
  # ===========================================================================

  @pipeline_integration
  Scenario: Inject RelevanceCoherenceOrderer into pipeline
    Given the ACMS pipeline with a RelevanceCoherenceOrderer
    And the following phase3 fragments:
      | uko_node              | content | score | tokens | depth |
      | project://app/main.py | hello   | 0.8   | 10     | 3     |
      | project://lib/util.py | world   | 0.6   | 15     | 5     |
    When I assemble context through the phase3 pipeline
    Then the phase3 pipeline output should contain 2 fragments

  @pipeline_integration
  Scenario: Inject ProvenancePreambleGenerator into pipeline
    Given the ACMS pipeline with a ProvenancePreambleGenerator
    And the following phase3 fragments:
      | uko_node              | content | score | tokens | depth |
      | project://app/main.py | hello   | 0.8   | 10     | 3     |
    When I assemble context through the phase3 pipeline
    Then the phase3 pipeline output should have a preamble
    And the phase3 pipeline preamble should contain "Context Assembly Provenance"
