Feature: Consolidated Quality Review
  Combined scenarios from: adr_compliance_checker_coverage, definition_of_done, diff_review, quality_gates_checker_coverage, review_playbook

  # ============================================================
  # Originally from: adr_compliance_checker_coverage.feature
  # Feature: ADR compliance checker script coverage
  # ============================================================

  Scenario: ADR-002 checker returns empty list when application directory does not exist
    Given a temporary source directory with no application subdirectory
    When the ADR-002 async model checker runs against that source directory
    Then the ADR-002 checker should return an empty violations list


  Scenario: ADR-002 checker returns empty list when no threading imports found
    Given a temporary source directory with a clean application module
    When the ADR-002 async model checker runs against that source directory
    Then the ADR-002 checker should return an empty violations list


  Scenario: ADR-002 checker detects import threading violation
    Given a temporary source directory with an application module importing threading directly
    When the ADR-002 async model checker runs against that source directory
    Then the ADR-002 checker should return a violation mentioning "imports threading directly"


  Scenario: ADR-002 checker detects from threading import violation
    Given a temporary source directory with an application module using from-threading-import
    When the ADR-002 async model checker runs against that source directory
    Then the ADR-002 checker should return a violation mentioning "imports threading directly"

    # --- ADR-003: Dependency Injection Framework ---


  Scenario: ADR-003 checker returns empty list when services directory does not exist
    Given a temporary source directory with no services subdirectory
    When the ADR-003 dependency injection checker runs against that source directory
    Then the ADR-003 checker should return an empty violations list


  Scenario: ADR-003 checker skips dunder init files
    Given a temporary source directory with only an __init__.py in services
    When the ADR-003 dependency injection checker runs against that source directory
    Then the ADR-003 checker should return an empty violations list


  Scenario: ADR-003 checker skips files with syntax errors
    Given a temporary source directory with a syntactically invalid Python file in services
    When the ADR-003 dependency injection checker runs against that source directory
    Then the ADR-003 checker should return an empty violations list


  Scenario: ADR-003 checker reports class with empty init having no injected dependencies
    Given a temporary source directory with a service class whose init takes only self
    When the ADR-003 dependency injection checker runs against that source directory
    Then the ADR-003 checker should return a violation mentioning "no injected dependencies"


  Scenario: ADR-003 checker passes class with proper dependency injection
    Given a temporary source directory with a service class whose init takes self and a dependency
    When the ADR-003 dependency injection checker runs against that source directory
    Then the ADR-003 checker should return an empty violations list


  Scenario: ADR-003 checker ignores classes without init method
    Given a temporary source directory with a service class that has no init method
    When the ADR-003 dependency injection checker runs against that source directory
    Then the ADR-003 checker should return an empty violations list

    # --- ADR-007: Repository Pattern for Persistence ---


  Scenario: ADR-007 checker returns empty list when services directory does not exist
    Given a temporary source directory with no services subdirectory for ADR-007
    When the ADR-007 repository pattern checker runs against that source directory
    Then the ADR-007 checker should return an empty violations list


  Scenario: ADR-007 checker detects direct SQLAlchemy imports in services
    Given a temporary source directory with a service file importing from sqlalchemy
    When the ADR-007 repository pattern checker runs against that source directory
    Then the ADR-007 checker should return a violation mentioning "SQLAlchemy directly"


  Scenario: ADR-007 checker detects direct session query usage in services
    Given a temporary source directory with a service file using session.query
    When the ADR-007 repository pattern checker runs against that source directory
    Then the ADR-007 checker should return a violation mentioning "session directly"


  Scenario: ADR-007 checker detects direct session execute usage in services
    Given a temporary source directory with a service file using session.execute
    When the ADR-007 repository pattern checker runs against that source directory
    Then the ADR-007 checker should return a violation mentioning "session directly"


  Scenario: ADR-007 checker passes clean services with no SQLAlchemy usage
    Given a temporary source directory with a clean service file using no SQLAlchemy
    When the ADR-007 repository pattern checker runs against that source directory
    Then the ADR-007 checker should return an empty violations list

    # --- main() function ---


  Scenario: ADR compliance main returns error when source directory does not exist
    When the ADR compliance main function runs with a nonexistent source directory
    Then the ADR compliance main should return exit code 1
    And the ADR compliance main output should contain "not found"


  Scenario: ADR compliance main returns success when all checks pass
    Given a temporary project tree with clean source code for ADR compliance
    When the ADR compliance main function runs against that project tree
    Then the ADR compliance main should return exit code 0
    And the ADR compliance main output should contain "All ADR compliance checks passed"


  Scenario: ADR compliance main returns failure when violations are found
    Given a temporary project tree with ADR violations in the source code
    When the ADR compliance main function runs against that project tree
    Then the ADR compliance main should return exit code 1
    And the ADR compliance main output should contain "ADR compliance issue"


  # ============================================================
  # Originally from: definition_of_done.feature
  # Feature: Definition-of-Done gating
  # ============================================================

  Scenario: Parse DoD text into criteria
    Given a definition of done test environment
    When I parse the DoD text "All tests pass\nNo lint errors\nCoverage above 90%"
    Then there should be 3 criteria parsed
    And criterion 0 text should be "All tests pass"
    And criterion 1 text should be "No lint errors"


  Scenario: Parse DoD with bullet points
    Given a definition of done test environment
    When I parse the DoD text "- Tests pass\n- No errors\n* Coverage ok"
    Then there should be 3 criteria parsed
    And criterion 0 text should be "Tests pass"


  Scenario: Parse DoD with numbered items
    Given a definition of done test environment
    When I parse the DoD text "1. Tests pass\n2. No errors"
    Then there should be 2 criteria parsed
    And criterion 0 text should be "Tests pass"
    And criterion 1 text should be "No errors"


  Scenario: Parse empty DoD returns no criteria
    Given a definition of done test environment
    When I parse an empty DoD text
    Then there should be 0 criteria parsed

    # -- Template rendering --------------------------------------------------


  Scenario: Render DoD template with plan arguments
    Given a definition of done test environment
    When I render the DoD template "Tests for {module} must pass" with argument "module" as "auth"
    Then the rendered DoD should be "Tests for auth must pass"


  Scenario: Render DoD template preserves unmatched placeholders
    Given a definition of done test environment
    When I render the DoD template "Deploy {service} to {env}" with argument "service" as "api"
    Then the rendered DoD should contain "{env}"

    # -- Text match evaluation -----------------------------------------------


  Scenario: TextMatchEvaluator passes when context contains criterion text
    Given a definition of done test environment
    Given DoD criteria from "All tests pass"
    And evaluation context with key "status" value "all tests pass"
    When I evaluate the criteria with TextMatchEvaluator
    Then all criteria should have passed


  Scenario: TextMatchEvaluator fails when context lacks criterion text
    Given a definition of done test environment
    Given DoD criteria from "All tests pass"
    And evaluation context with key "status" value "3 tests failed"
    When I evaluate the criteria with TextMatchEvaluator
    Then criterion 0 should have status "failed"


  Scenario: TextMatchEvaluator with regex pattern passes on match
    Given a definition of done test environment
    Given a DoD criterion with text "Coverage check" and pattern "coverage.*9[0-9]"
    And evaluation context with key "coverage" value "coverage=95%"
    When I evaluate the criteria with TextMatchEvaluator
    Then all criteria should have passed


  Scenario: TextMatchEvaluator with regex pattern fails on no match
    Given a definition of done test environment
    Given a DoD criterion with text "Coverage check" and pattern "coverage.*9[0-9]"
    And evaluation context with key "coverage" value "coverage=80%"
    When I evaluate the criteria with TextMatchEvaluator
    Then criterion 0 should have status "failed"


  Scenario: TextMatchEvaluator skips when context is empty
    Given a definition of done test environment
    Given DoD criteria from "All tests pass"
    And an empty evaluation context
    When I evaluate the criteria with TextMatchEvaluator
    Then criterion 0 should have status "skipped"


  Scenario: TextMatchEvaluator handles invalid regex gracefully
    Given a definition of done test environment
    Given a DoD criterion with text "Bad regex" and pattern "[invalid"
    And evaluation context with key "data" value "anything"
    When I evaluate the criteria with TextMatchEvaluator
    Then criterion 0 should have status "failed"
    And criterion 0 reasoning should contain "Invalid regex"

    # -- DoDSummary model ----------------------------------------------------


  Scenario: DoDSummary reports all_passed when no failures
    Given a definition of done test environment
    Given a DoDSummary with 2 passed and 0 failed
    Then the summary should report all passed
    And the DoD summary total should be 2


  Scenario: DoDSummary reports not all passed when failures exist
    Given a definition of done test environment
    Given a DoDSummary with 1 passed and 1 failed
    Then the summary should not report all passed
    And the summary failed count should be 1


  Scenario: DoDSummary empty check
    Given a definition of done test environment
    Given an empty DoDSummary
    Then the summary should be empty
    And the summary should not report all passed


  Scenario: DoDSummary to_cli_dict has expected fields
    Given a definition of done test environment
    Given a DoDSummary with 1 passed and 0 failed
    Then the summary CLI dict should have key "total"
    And the summary CLI dict should have key "all_passed"
    And the summary CLI dict should have key "criteria"


  Scenario: DoDSummary to_validation_summary is compatible
    Given a definition of done test environment
    Given a DoDSummary with 2 passed and 1 failed
    Then the validation summary should have required_passed 2
    And the validation summary should have required_failed 1
    And the validation summary should have dod_evaluated true

    # -- Evaluation context matching -----------------------------------------


  Scenario: TextMatchEvaluator matches criterion text in context keys
    Given a definition of done test environment
    Given DoD criteria from "lint_errors"
    And evaluation context with key "lint_errors" value "0"
    When I evaluate the criteria with TextMatchEvaluator
    Then all criteria should have passed


  Scenario: Multiple criteria with mixed results
    Given a definition of done test environment
    Given DoD criteria from "tests pass\nlint errors"
    And evaluation context with key "result" value "tests pass, no issues"
    When I evaluate the criteria with TextMatchEvaluator
    Then criterion 0 should have status "passed"
    And criterion 1 should have status "failed"

    # -- Coverage: edge cases ------------------------------------------------


  Scenario: Parse DoD with blank lines between criteria
    Given a definition of done test environment
    When I parse the DoD text "Tests pass\n\nNo errors"
    Then there should be 2 criteria parsed


  Scenario: Parse DoD with whitespace only line
    Given a definition of done test environment
    When I parse the DoD text "  \n- Tests pass"
    Then there should be 1 criteria parsed
    And criterion 0 text should be "Tests pass"


  Scenario: TextMatchEvaluator matches via individual key lookup
    Given a definition of done test environment
    Given DoD criteria from "all_tests_passed"
    And evaluation context with key "all_tests_passed" value "true"
    When I evaluate the criteria with TextMatchEvaluator
    Then all criteria should have passed

    # -- T1: all-SKIPPED summary gate behavior --------------------------------


  Scenario: DoDSummary all_passed is false when all criteria skipped
    Given a definition of done test environment
    Given a DoDSummary with all criteria skipped
    Then the summary should not report all passed
    And the summary skipped count should be 2

    # -- T2: bullet prefix yielding empty text --------------------------------


  Scenario: Parse DoD with bullet prefix but no text
    Given a definition of done test environment
    When I parse the DoD text "- \n- Tests pass"
    Then there should be 1 criteria parsed
    And criterion 0 text should be "Tests pass"

    # -- T5: multi-argument template rendering --------------------------------


  Scenario: Render DoD template with multiple arguments
    Given a definition of done test environment
    When I render the DoD template "{svc} in {env} OK" with arguments "svc"="api" and "env"="prod"
    Then the rendered DoD should be "api in prod OK"


  Scenario: Render DoD template prevents injection via values
    Given a definition of done test environment
    When I render the DoD template "Test {name}" with argument "name" as "{secret}"
    Then the rendered DoD should be "Test {secret}"

    # -- S3: length limit on template -----------------------------------------


  Scenario: Render DoD rejects oversized template
    Given a definition of done test environment
    Given a DoD template that is 11000 characters long
    When I render the oversized DoD template with argument "x" as "y"
    Then the DoD render should fail with "exceeds maximum"


  Scenario: Render DoD rejects oversized output
    Given a definition of done test environment
    Given a short DoD template with a huge argument value
    When I render the DoD template that produces oversized output
    Then the DoD render should fail with "exceeds maximum"

    # -- S2: ReDoS protection -------------------------------------------------


  Scenario: TextMatchEvaluator rejects oversized regex pattern
    Given a definition of done test environment
    Given a DoD criterion with text "long regex" and pattern that is 600 chars
    And evaluation context with key "data" value "anything"
    When I evaluate the criteria with TextMatchEvaluator
    Then criterion 0 should have status "failed"
    And criterion 0 reasoning should contain "exceeds maximum"


  # ============================================================
  # Originally from: diff_review.feature
  # Feature: Diff review artifacts
  # ============================================================

  Scenario: Empty changeset produces empty review artifact
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    When I build a review artifact from the changeset
    Then the review artifact should have 0 total changes
    And the review artifact should have 0 groups
    And the review artifact should not be truncated


  Scenario: Single CREATE change produces a diff entry
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "src/main.py" in resource "RES001"
    And after content for "src/main.py" is "print('hello')\n"
    When I build a review artifact from the changeset
    Then the review artifact should have 1 total changes
    And the review artifact should have 1 groups
    And group 0 should have 1 entries
    And entry 0 in group 0 should have change type "create"
    And entry 0 in group 0 should have path "src/main.py"


  Scenario: MODIFY change generates unified diff
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "modify" for path "src/app.py" in resource "RES001"
    And before content for "src/app.py" is "x = 1\ny = 2\n"
    And after content for "src/app.py" is "x = 1\ny = 3\n"
    When I build a review artifact from the changeset
    Then entry 0 in group 0 should have change type "modify"
    And the diff text in entry 0 of group 0 should contain "---"
    And the diff text in entry 0 of group 0 should contain "+++"


  Scenario: DELETE change entry
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "delete" for path "old.txt" in resource "RES001"
    And before content for "old.txt" is "old content\n"
    When I build a review artifact from the changeset
    Then entry 0 in group 0 should have change type "delete"
    And the diff text in entry 0 of group 0 should contain "---"


  Scenario: Multiple changes across different resources
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "a.py" in resource "RES001"
    And a change entry with operation "modify" for path "b.py" in resource "RES002"
    And after content for "a.py" is "new\n"
    And before content for "b.py" is "old\n"
    And after content for "b.py" is "new\n"
    When I build a review artifact from the changeset
    Then the review artifact should have 2 total changes
    And the review artifact should have 2 groups


  Scenario: Changes grouped by resource
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "a.py" in resource "RES001"
    And a change entry with operation "modify" for path "b.py" in resource "RES001"
    And after content for "a.py" is "new\n"
    And before content for "b.py" is "old\n"
    And after content for "b.py" is "new\n"
    When I build a review artifact from the changeset
    Then the review artifact should have 1 groups
    And group 0 should have 2 entries


  Scenario: Binary file detection
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "image.bin" in resource "RES001"
    And after content for "image.bin" is "binary\x00data"
    When I build a review artifact from the changeset
    Then entry 0 in group 0 should be binary
    And the diff text in entry 0 of group 0 should contain "[binary file]"


  Scenario: Rename detection with matching content hash
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a delete entry for path "old/file.py" in resource "RES001" with before_hash "abc123"
    And a create entry for path "new/file.py" in resource "RES001" with after_hash "abc123"
    When I build a review artifact from the changeset
    Then the review artifact should have 1 groups
    And group 0 should have 1 entries
    And entry 0 in group 0 should be a rename
    And entry 0 in group 0 rename should be from "old/file.py" to "new/file.py"
    And group 0 should have 1 renamed


  Scenario: Rename not detected when hashes differ
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a delete entry for path "old/file.py" in resource "RES001" with before_hash "abc123"
    And a create entry for path "new/file.py" in resource "RES001" with after_hash "xyz789"
    When I build a review artifact from the changeset
    Then group 0 should have 2 entries
    And entry 0 in group 0 should not be a rename


  Scenario: Max diff size truncation
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "big.py" in resource "RES001"
    And after content for "big.py" is a string of 100000 characters
    When I build a review artifact with max diff size 100
    Then the review artifact should be truncated
    And entry 0 in group 0 should be truncated


  Scenario: Stable ordering by resource then path
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "z.py" in resource "RES002"
    And a change entry with operation "create" for path "a.py" in resource "RES001"
    And after content for "z.py" is "z\n"
    And after content for "a.py" is "a\n"
    When I build a review artifact from the changeset
    Then group 0 should have resource id "RES001"
    And group 1 should have resource id "RES002"


  Scenario: Before and after content hashes in metadata
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a hashed change entry "modify" path "f.py" resource "RES001" before_hash "hash1" after_hash "hash2"
    And before content for "f.py" is "old\n"
    And after content for "f.py" is "new\n"
    When I build a review artifact from the changeset
    Then entry 0 in group 0 should have before hash "hash1"
    And entry 0 in group 0 should have after hash "hash2"


  Scenario: File mode in metadata
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a moded change entry "modify" path "script.sh" resource "RES001" after_mode 0o755
    And before content for "script.sh" is "#!/bin/sh\n"
    And after content for "script.sh" is "#!/bin/bash\n"
    When I build a review artifact from the changeset
    Then entry 0 in group 0 should have file mode 493


  Scenario: Serialize to plain text
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "new.py" in resource "RES001"
    And after content for "new.py" is "hello\n"
    And resource name "RES001" is "my-resource"
    When I build a review artifact from the changeset
    And I serialize the artifact to plain text
    Then the plain text should contain "my-resource"
    And the plain text should contain "new.py"
    And the plain text should contain "Total changes: 1"


  Scenario: Serialize to JSON
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "new.py" in resource "RES001"
    And after content for "new.py" is "hello\n"
    When I build a review artifact from the changeset
    And I serialize the artifact to JSON
    Then the JSON output should be valid JSON
    And the JSON output should contain key "total_changes"
    And the JSON output should contain key "groups"


  Scenario: Serialize to rich format
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "new.py" in resource "RES001"
    And after content for "new.py" is "hello\n"
    When I build a review artifact from the changeset
    And I serialize the artifact to rich format
    Then the rich text should contain "[bold]"
    And the rich text should contain "new.py"


  Scenario: ResourceDiffGroup summary counts
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "a.py" in resource "RES001"
    And a change entry with operation "modify" for path "b.py" in resource "RES001"
    And a change entry with operation "delete" for path "c.py" in resource "RES001"
    And after content for "a.py" is "new\n"
    And before content for "b.py" is "old\n"
    And after content for "b.py" is "new\n"
    And before content for "c.py" is "old\n"
    When I build a review artifact from the changeset
    Then group 0 should have 1 added
    And group 0 should have 1 modified
    And group 0 should have 1 deleted


  Scenario: ReviewArtifact has plan and changeset IDs
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    When I build a review artifact from the changeset
    Then the review artifact plan ID should match the changeset plan ID
    And the review artifact changeset ID should match the changeset ID


  Scenario: Empty content produces empty diff
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "modify" for path "empty.py" in resource "RES001"
    When I build a review artifact from the changeset
    Then the diff text in entry 0 of group 0 should be empty


  Scenario: Multiple entries in same resource sorted by path
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "z.py" in resource "RES001"
    And a change entry with operation "create" for path "a.py" in resource "RES001"
    And after content for "z.py" is "z\n"
    And after content for "a.py" is "a\n"
    When I build a review artifact from the changeset
    Then entry 0 in group 0 should have path "a.py"
    And entry 1 in group 0 should have path "z.py"


  Scenario: Second entry fully truncated when budget exhausted
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "a.py" in resource "RES001"
    And a change entry with operation "create" for path "b.py" in resource "RES001"
    And after content for "a.py" is a string of 1000 characters
    And after content for "b.py" is a string of 1000 characters
    When I build a review artifact with max diff size 50
    Then the review artifact should be truncated
    And entry 1 in group 0 should be truncated
    And the diff text in entry 1 of group 0 should contain "[truncated]"


  Scenario: JSON serialization includes hashes and file mode
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a hashed change entry "modify" path "f.py" resource "RES001" before_hash "aaa" after_hash "bbb"
    And a moded change entry "modify" path "g.py" resource "RES001" after_mode 0o644
    And before content for "f.py" is "old\n"
    And after content for "f.py" is "new\n"
    And before content for "g.py" is "a\n"
    And after content for "g.py" is "b\n"
    When I build a review artifact from the changeset
    And I serialize the artifact to JSON
    Then the JSON output should be valid JSON
    And the JSON output should have entry with before hash "aaa"
    And the JSON output should have entry with after hash "bbb"
    And the JSON output should have entry with file mode


  Scenario: JSON serialization includes rename fields
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a delete entry for path "old/r.py" in resource "RES001" with before_hash "h1"
    And a create entry for path "new/r.py" in resource "RES001" with after_hash "h1"
    When I build a review artifact from the changeset
    And I serialize the artifact to JSON
    Then the JSON output should be valid JSON
    And the JSON output should have entry with rename from "old/r.py"


  Scenario: Rich format shows truncation warning
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "big.py" in resource "RES001"
    And after content for "big.py" is a string of 100000 characters
    When I build a review artifact with max diff size 100
    And I serialize the artifact to rich format
    Then the rich text should contain "[yellow]"
    And the rich text should contain "truncated"


  Scenario: Rich format shows binary marker
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "bin.dat" in resource "RES001"
    And after content for "bin.dat" is "binary\x00data"
    When I build a review artifact from the changeset
    And I serialize the artifact to rich format
    Then the rich text should contain "[binary]"


  Scenario: Rich format colorizes diff lines
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "modify" for path "c.py" in resource "RES001"
    And before content for "c.py" is "old\n"
    And after content for "c.py" is "new\n"
    When I build a review artifact from the changeset
    And I serialize the artifact to rich format
    Then the rich text should contain "[green]"
    And the rich text should contain "[red]"


  Scenario: Cross-resource delete and create are not renamed
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a delete entry for path "old/x.py" in resource "RES001" with before_hash "same"
    And a create entry for path "new/x.py" in resource "RES002" with after_hash "same"
    When I build a review artifact from the changeset
    Then the review artifact should have 2 groups
    And entry 0 in group 0 should not be a rename
    And entry 0 in group 1 should not be a rename


  Scenario: Multiple renames consume creates only once
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a delete entry for path "old/a.py" in resource "RES001" with before_hash "h1"
    And a delete entry for path "old/b.py" in resource "RES001" with before_hash "h1"
    And a create entry for path "new/a.py" in resource "RES001" with after_hash "h1"
    When I build a review artifact from the changeset
    Then group 0 should have 2 entries


  Scenario: Rename entry truncated when budget exhausted
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "a.py" in resource "RES001"
    And after content for "a.py" is a string of 1000 characters
    And a delete entry for path "old/r.py" in resource "RES001" with before_hash "rh1"
    And a create entry for path "new/r.py" in resource "RES001" with after_hash "rh1"
    When I build a review artifact with max diff size 50
    Then the review artifact should be truncated
    And entry 1 in group 0 should be a rename
    And entry 1 in group 0 should be truncated
    And the diff text in entry 1 of group 0 should contain "[truncated]"


  Scenario: Plain serialization shows binary marker
    Given a plan ID "01PLAN000000000000000001"
    And an empty changeset for the plan
    Given a change entry with operation "create" for path "img.png" in resource "RES001"
    And after content for "img.png" is "png\x00header"
    When I build a review artifact from the changeset
    And I serialize the artifact to plain text
    Then the plain text should contain "[binary file]"


  # ============================================================
  # Originally from: quality_gates_checker_coverage.feature
  # Feature: Quality gates checker script coverage
  # ============================================================

  Scenario: Coverage gate passes when coverage exceeds minimum threshold
    Given the coverage tool reports a total of 92.5 percent
    When the quality gates check_coverage function is called with minimum 85
    Then the quality gates coverage result should be passed
    And the quality gates coverage message should contain "92.5%"


  Scenario: Coverage gate fails when coverage is below minimum threshold
    Given the coverage tool reports a total of 70.0 percent
    When the quality gates check_coverage function is called with minimum 85
    Then the quality gates coverage result should be failed
    And the quality gates coverage message should contain "70.0%"


  Scenario: Coverage gate fails when coverage command returns non-zero exit code
    Given the coverage tool returns a non-zero exit code with stderr "No data to report"
    When the quality gates check_coverage function is called with minimum 85
    Then the quality gates coverage result should be failed
    And the quality gates coverage message should contain "Coverage command failed"


  Scenario: Coverage gate fails when coverage output cannot be parsed as a number
    Given the coverage tool returns unparseable output "not-a-number"
    When the quality gates check_coverage function is called with minimum 85
    Then the quality gates coverage result should be failed
    And the quality gates coverage message should contain "Could not parse coverage output"

    # --- check_typecheck ---


  Scenario: Typecheck gate passes when pyright reports zero errors
    Given pyright reports JSON output with zero errors
    When the quality gates check_typecheck function is called
    Then the quality gates typecheck result should be passed
    And the quality gates typecheck message should contain "0 errors"


  Scenario: Typecheck gate fails when pyright reports errors
    Given pyright reports JSON output with 5 errors
    When the quality gates check_typecheck function is called
    Then the quality gates typecheck result should be failed
    And the quality gates typecheck message should contain "5 errors"


  Scenario: Typecheck gate falls back to exit code when JSON parsing fails and passes
    Given pyright returns invalid JSON output with exit code 0
    When the quality gates check_typecheck function is called
    Then the quality gates typecheck result should be passed
    And the quality gates typecheck message should contain "passed"


  Scenario: Typecheck gate falls back to exit code when JSON parsing fails and fails
    Given pyright returns invalid JSON output with exit code 1
    When the quality gates check_typecheck function is called
    Then the quality gates typecheck result should be failed
    And the quality gates typecheck message should contain "failed (exit code 1)"

    # --- check_security ---


  Scenario: Security gate passes when bandit finds no high-severity issues
    Given bandit reports JSON output with zero results
    When the quality gates check_security function is called
    Then the quality gates security result should be passed
    And the quality gates security message should contain "0 high-severity issues"


  Scenario: Security gate fails when bandit finds high-severity issues
    Given bandit reports JSON output with 3 high-severity results
    When the quality gates check_security function is called
    Then the quality gates security result should be failed
    And the quality gates security message should contain "3 high-severity issues"


  Scenario: Security gate falls back to exit code on JSON parse error and passes
    Given bandit returns invalid JSON output with exit code 0
    When the quality gates check_security function is called
    Then the quality gates security result should be passed
    And the quality gates security message should contain "passed"


  Scenario: Security gate falls back to exit code on JSON parse error and fails
    Given bandit returns invalid JSON output with exit code 2
    When the quality gates check_security function is called
    Then the quality gates security result should be failed
    And the quality gates security message should contain "failed (exit code 2)"

    # --- check_dead_code ---


  Scenario: Dead code gate passes when vulture finds no dead code
    Given vulture returns exit code 0 with no output
    When the quality gates check_dead_code function is called
    Then the quality gates dead code result should be passed
    And the quality gates dead code message should contain "none detected"


  Scenario: Dead code gate fails when vulture detects dead code issues
    Given vulture returns exit code 1 with 3 lines of dead code output
    When the quality gates check_dead_code function is called
    Then the quality gates dead code result should be failed
    And the quality gates dead code message should contain "3 issue(s) found"

    # --- check_complexity ---


  Scenario: Complexity gate passes when no blocks exceed maximum grade
    Given radon reports JSON output with zero complex blocks
    When the quality gates check_complexity function is called with max grade "F"
    Then the quality gates complexity result should be passed
    And the quality gates complexity message should contain "no blocks with grade >= F"


  Scenario: Complexity gate fails when blocks exceed maximum grade
    Given radon reports JSON output with 2 complex blocks
    When the quality gates check_complexity function is called with max grade "F"
    Then the quality gates complexity result should be failed
    And the quality gates complexity message should contain "2 block(s) with grade >= F"


  Scenario: Complexity gate passes on JSON parse error as fallback
    Given radon returns invalid JSON output
    When the quality gates check_complexity function is called with max grade "F"
    Then the quality gates complexity result should be passed
    And the quality gates complexity message should contain "passed (no extreme complexity)"

    # --- main function ---


  Scenario: Quality gates main reports all gates passed when everything succeeds
    Given all quality gate functions are mocked to return success
    When the quality gates main function is invoked with default arguments
    Then the quality gates main should return exit code 0


  Scenario: Quality gates main reports failure when any gate fails
    Given the quality gate coverage function is mocked to return failure
    When the quality gates main function is invoked with default arguments
    Then the quality gates main should return exit code 1


  Scenario: Quality gates main creates build directory if it does not exist
    Given a temporary working directory with no build subdirectory
    When the quality gates main function is invoked from that temporary directory
    Then a build directory should exist in the temporary working directory


  # ============================================================
  # Originally from: review_playbook.feature
  # Feature: Review playbook documentation validation
  # ============================================================

  Scenario: Review playbook file exists
    Given the review playbook file at "docs/development/review_playbook.md"
    Then the review playbook file should exist


  Scenario: Review playbook contains priority matrix
    Given the review playbook file at "docs/development/review_playbook.md"
    When I read the review playbook content
    Then the playbook should contain the section "Priority Matrix"


  Scenario: Review playbook contains routing table
    Given the review playbook file at "docs/development/review_playbook.md"
    When I read the review playbook content
    Then the playbook should contain the section "PR Review Routing Table"


  Scenario: Review playbook contains focus areas
    Given the review playbook file at "docs/development/review_playbook.md"
    When I read the review playbook content
    Then the playbook should contain the section "Focus Areas and Skip Rules"


  Scenario: Review playbook contains review SLA guidance
    Given the review playbook file at "docs/development/review_playbook.md"
    When I read the review playbook content
    Then the playbook should contain the section "Review SLA Guidance"


  Scenario: Review playbook contains architecture checklist
    Given the review playbook file at "docs/development/review_playbook.md"
    When I read the review playbook content
    Then the playbook should contain the section "Architecture Review Checklist"


  Scenario: Review playbook contains CLI checklist
    Given the review playbook file at "docs/development/review_playbook.md"
    When I read the review playbook content
    Then the playbook should contain the section "CLI Review Checklist"


  Scenario: Review playbook contains DB migration checklist
    Given the review playbook file at "docs/development/review_playbook.md"
    When I read the review playbook content
    Then the playbook should contain the section "DB Migration Review Checklist"


  Scenario: Review playbook contains security checklists
    Given the review playbook file at "docs/development/review_playbook.md"
    When I read the review playbook content
    Then the playbook should contain the section "Security-Sensitive Change Checklists"


  Scenario: Review playbook contains test matrix
    Given the review playbook file at "docs/development/review_playbook.md"
    When I read the review playbook content
    Then the playbook should contain the section "Required Test Matrix for Reviewers"


  Scenario: Review playbook contains acceptable vs blocking examples
    Given the review playbook file at "docs/development/review_playbook.md"
    When I read the review playbook content
    Then the playbook should contain the section "Acceptable vs Blocking Findings"


  Scenario: Review playbook references required nox sessions
    Given the review playbook file at "docs/development/review_playbook.md"
    When I read the review playbook content
    Then the playbook should reference these nox sessions:
      | session            |
      | lint               |
      | typecheck          |
      | unit_tests         |
      | integration_tests  |
      | coverage_report    |
      | security_scan      |
      | dead_code          |
      | complexity         |
      | benchmark          |


  Scenario: Review playbook defines all severity levels
    Given the review playbook file at "docs/development/review_playbook.md"
    When I read the review playbook content
    Then the playbook should contain the text "P0"
    And the playbook should contain the text "P1"
    And the playbook should contain the text "P2"
    And the playbook should contain the text "P3"
