Files
cleveragents-core/features/consolidated_quality_review.feature
HAL9000 f808abff86 chore(ci): fix pre-commit hook failures
Fix JSON syntax errors in .devcontainer/devcontainer.json (removed
invalid JS-style // comments) and .devcontainer/opencode.json (removed
90+ trailing commas). Apply auto-fixes for end-of-file and trailing
whitespace issues across 100+ files. Fix SIM105 ruff violations in
benchmarks/core_circuit_breaker_bench.py (use contextlib.suppress).

Note: The security fix from issue #7478 (validate_path startswith bypass)
was already delivered to master in commit e18ac5f2. This PR as currently
structured is non-atomic (35 commits across 10+ issues) and needs
significant restructure before merge. This commit only addresses the
CI/pre-commit failures.

ISSUES CLOSED: #7478
2026-06-14 09:51:14 -04:00

959 lines
42 KiB
Gherkin

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"