Feature: Consolidated Change Tracking Combined scenarios from: change_tracking, changeset_capture, streamedchange_coverage # ============================================================ # Originally from: change_tracking.feature # Feature: ChangeSet Models and Invocation Tracking # ============================================================ Scenario: ChangeType is an alias for ChangeOperation Given I import ChangeType and ChangeOperation Then ChangeType should be the same class as ChangeOperation And ChangeType CREATE should equal "create" # ---- ChangeEntry Validators ---- Scenario: CREATE entry rejects before_hash When I create a ChangeEntry with CREATE and before_hash "abc" Then a validation error should be raised mentioning "before_hash" Scenario: DELETE entry rejects after_hash When I create a ChangeEntry with DELETE and after_hash "abc" Then a validation error should be raised mentioning "after_hash" Scenario: CREATE entry accepts after_hash only When I create a valid CREATE ChangeEntry with after_hash "abc123" Then the change entry operation should be "create" And the change entry after_hash should be "abc123" Scenario: MODIFY entry accepts both hashes When I create a valid MODIFY ChangeEntry with both hashes Then the change entry operation should be "modify" And the change entry has_integrity_hashes should be true Scenario: RENAME entry without hashes is valid When I create a RENAME ChangeEntry without hashes Then the change entry operation should be "rename" And the change entry has_integrity_hashes should be true Scenario: CREATE entry without after_hash has no integrity When I create a CREATE ChangeEntry without any hashes Then the change entry has_integrity_hashes should be false # ---- Path Normalization ---- Scenario: Normalize absolute path with repo root Given a repo root "/home/user/project" When I normalize the path "/home/user/project/src/main.py" Then the normalized path should be "src/main.py" Scenario: Normalize relative path stays unchanged Given an empty repo root When I normalize the path "src/main.py" Then the normalized path should be "src/main.py" Scenario: Normalize path outside repo root is unchanged Given a repo root "/other/project" When I normalize the path "/home/user/project/src/main.py" Then the normalized path should be "home/user/project/src/main.py" # ---- SpecChangeSet Ordering ---- Scenario: sorted_entries returns deterministic order Given a SpecChangeSet with entries in random order When I call sorted_entries Then entries should be sorted by resource_id then path then timestamp Scenario: grouped_by_resource groups correctly Given a SpecChangeSet with entries for multiple resources When I call grouped_by_resource Then entries should be grouped by resource_id And each group should be sorted by path and timestamp Scenario: add_change appends to entries Given an empty SpecChangeSet When I add a change entry to it Then the spec changeset should contain 1 entry # ---- ToolInvocation Model ---- Scenario: Create a ToolInvocation with minimal fields When I create a ToolInvocation for plan "plan-1" and tool "builtin/file-write" Then the invocation should have a ULID invocation_id And the invocation plan_id should be "plan-1" And the invocation tool_name should be "builtin/file-write" And the invocation success should be true Scenario: Create a failed ToolInvocation When I create a failed ToolInvocation with error "file not found" Then the invocation success should be false And the invocation error should be "file not found" Scenario: ToolInvocation with change_ids When I create a ToolInvocation with change_ids Then the invocation should have 2 change_ids Scenario: ToolInvocation with provider metadata When I create a ToolInvocation with provider metadata Then the invocation provider_metadata should contain model name # ---- InMemoryInvocationTracker ---- Scenario: Track invocations and retrieve by plan Given an InMemoryInvocationTracker When I track 3 invocations for plan "plan-A" And I track 2 invocations for plan "plan-B" Then get_invocations for "plan-A" should return 3 And get_invocations for "plan-B" should return 2 Scenario: Retrieve invocations for a specific skill Given an InMemoryInvocationTracker When I track invocations for different skills Then get_invocations_for_skill should filter correctly Scenario: Get change IDs across invocations Given an InMemoryInvocationTracker When I track invocations with change_ids Then get_changes should return all change_ids for the plan Scenario: Invocations are ordered by sequence number Given an InMemoryInvocationTracker When I track invocations out of order Then get_invocations should return them in sequence order # ---- SpecChangeSet summary ---- Scenario: summary returns correct counts for mixed operations Given a SpecChangeSet with mixed operation entries When I call summary on the changeset Then the summary total should be 4 And the summary creates should be 1 And the summary modifies should be 1 And the summary deletes should be 1 And the summary renames should be 1 And the summary paths_changed should be 4 And the summary resources_involved should be 2 # ---- add_change plan_id validation ---- Scenario: add_change rejects entry with mismatched plan_id Given an empty SpecChangeSet When I add a change entry with a different plan_id Then a validation error should be raised mentioning "plan_id" # ---- Empty tracker queries ---- Scenario: get_invocations returns empty for unknown plan Given an InMemoryInvocationTracker Then get_invocations for "nonexistent-plan" should return 0 Scenario: get_changes returns empty for unknown plan Given an InMemoryInvocationTracker Then get_changes for "nonexistent-plan" should return 0 ids Scenario: get_invocations_for_skill returns empty for unknown skill Given an InMemoryInvocationTracker When I track 1 invocations for plan "plan-X" Then get_invocations_for_skill "plan-X" "no-such-skill" should return 0 # ---- get_changes ordering proof ---- Scenario: get_changes respects sequence order not insertion order Given an InMemoryInvocationTracker When I track change invocations in reverse sequence order Then get_changes should return change_ids in sequence order # ---- Path normalization edge cases ---- Scenario: Normalize path that equals repo root returns dot Given a repo root "/home/user/project" When I normalize the path "/home/user/project" Then the normalized path should be "." Scenario: Normalize path with dot-dot components preserves them Given a repo root "/repo" When I normalize the path "/repo/foo/../bar/file.py" Then the normalized path should be "foo/../bar/file.py" Scenario: Normalize empty path returns dot Given an empty repo root When I normalize the path "." Then the normalized path should be "." # ============================================================ # Originally from: changeset_capture.feature # Feature: ChangeSet capture and domain model # ============================================================ Scenario: ChangeOperation enum has expected members Given I import the ChangeOperation enum Then it should have members CREATE, MODIFY, DELETE, RENAME # ---- ChangeEntry model ---- Scenario: Create a ChangeEntry with all fields Given I import the ChangeEntry model When I create a ChangeEntry for a create operation Then the entry should have a ULID entry_id And the entry should have operation "create" And the entry should have a UTC timestamp And the before_hash should be None Scenario: ChangeEntry for modify operation has before and after hashes Given I import the ChangeEntry model When I create a ChangeEntry for a modify operation with hashes Then the before_hash should not be None And the after_hash should not be None Scenario: ChangeEntry for delete operation has no after_hash Given I import the ChangeEntry model When I create a ChangeEntry for a delete operation Then the after_hash should be None And the before_hash should not be None Scenario: ChangeEntry for rename operation Given I import the ChangeEntry model When I create a ChangeEntry for a rename operation Then the entry should have operation "rename" # ---- SpecChangeSet model ---- Scenario: SpecChangeSet summary counts are correct Given I import the SpecChangeSet model When I create a SpecChangeSet with mixed operations Then the creates count should be 1 And the modifies count should be 2 And the deletes count should be 1 And the renames count should be 1 And paths_changed should have 5 entries And resources_involved should have 2 entries Scenario: SpecChangeSet summary dict Given I import the SpecChangeSet model When I create a SpecChangeSet with mixed operations Then the summary dict should have correct totals Scenario: Empty SpecChangeSet has zero counts Given I import the SpecChangeSet model When I create an empty SpecChangeSet Then the creates count should be 0 And the modifies count should be 0 And the deletes count should be 0 And the renames count should be 0 # ---- InMemoryChangeSetStore ---- Scenario: InMemoryChangeSetStore start/record/get flow Given I have an InMemoryChangeSetStore When I start a changeset for plan "plan-abc" And I record a create entry in the changeset Then I can get the changeset by ID And the store changeset should have 1 entry Scenario: InMemoryChangeSetStore get_for_plan Given I have an InMemoryChangeSetStore When I start a changeset for plan "plan-abc" And I start a changeset for plan "plan-abc" And I start a changeset for plan "plan-xyz" Then get_for_plan "plan-abc" should return 2 changesets And get_for_plan "plan-xyz" should return 1 changeset Scenario: InMemoryChangeSetStore summarize Given I have an InMemoryChangeSetStore When I start a changeset for plan "plan-s" And I record a create entry in the changeset And I record a modify entry in the changeset Then the summarized changeset should show 2 total Scenario: InMemoryChangeSetStore get returns None for unknown ID Given I have an InMemoryChangeSetStore Then getting a non-existent changeset returns None Scenario: InMemoryChangeSetStore record raises on unknown ID Given I have an InMemoryChangeSetStore Then recording to a non-existent changeset raises KeyError Scenario: InMemoryChangeSetStore summarize returns empty for unknown Given I have an InMemoryChangeSetStore Then summarizing a non-existent changeset returns empty dict # ---- ChangeSetCapture integration ---- Scenario: ChangeSetCapture records resource_id and tool_name Given I have a ChangeSetCapture with resource_id "res-1" When I create a ChangeSetEntry via capture Then the entry resource_id should be "res-1" And the entry tool_name should be set Scenario: ChangeSetCapture normalizes paths Given I have a ChangeSetCapture with sandbox_root When I capture a write to a nested path Then the captured path should be repo-relative Scenario: ChangeSetCapture converts to spec changeset Given I have a ChangeSetCapture with resource_id "res-2" When I add several entries via capture Then to_spec_changeset should return a SpecChangeSet # ---- Multi-resource plan ---- Scenario: Multi-resource plan captures correctly Given I have two ChangeSetCapture instances for different resources When each capture records changes for its resource Then each changeset should only have its resource entries # ============================================================ # Originally from: streamedchange_coverage.feature # Feature: Streamed Change Models Coverage # ============================================================ Scenario: Create StreamedChangeSection with valid line numbers Given I import the StreamedChangeSection class When I create a StreamedChangeSection with start_line 10 And I set end_line to 20 And I set start_line_string to "def function():" And I set end_line_string to "return result" Then the StreamedChangeSection instance should be created successfully And the start_line should be 10 And the end_line should be 20 And the start_line_string should be "def function():" And the end_line_string should be "return result" Scenario: Create StreamedChangeSection with single line change Given I import the StreamedChangeSection class When I create a StreamedChangeSection with start_line 1 And I set end_line to 1 And I set start_line_string to "import os" And I set end_line_string to "import os" Then the StreamedChangeSection instance should be created successfully And the start_line should be 1 And the end_line should be 1 And the start_line_string should be "import os" And the end_line_string should be "import os" Scenario: Create StreamedChangeSection with large line numbers Given I import the StreamedChangeSection class When I create a StreamedChangeSection with start_line 1000 And I set end_line to 2000 And I set start_line_string to "class LargeClass:" And I set end_line_string to "# End of class" Then the StreamedChangeSection instance should be created successfully And the start_line should be 1000 And the end_line should be 2000 Scenario: Create StreamedChangeSection using field aliases Given I import the StreamedChangeSection class When I create a StreamedChangeSection using aliases Then the StreamedChangeSection should accept the aliased fields And the section values should be properly mapped Scenario: Validate StreamedChangeSection configuration Given I import the StreamedChangeSection class When I create a StreamedChangeSection with valid data Then the section configuration should have str_strip_whitespace as true And the section configuration should have validate_assignment as true And the section configuration should have arbitrary_types_allowed as false And the section configuration should have populate_by_name as true And the section configuration should have use_enum_values as true Scenario: Create StreamedChangeWithLineNums with included boundaries Given I import the StreamedChangeWithLineNums class And I have a valid StreamedChangeSection for old section When I create a StreamedChangeWithLineNums with the old section And I set start_line_included to true And I set end_line_included to true And I set new content to "Updated code block" Then the StreamedChangeWithLineNums instance should be created successfully And the old section should match the provided section And the start_line_included should be true And the end_line_included should be true And the new content should be "Updated code block" Scenario: Create StreamedChangeWithLineNums with excluded boundaries Given I import the StreamedChangeWithLineNums class And I have a valid StreamedChangeSection for old section When I create a StreamedChangeWithLineNums with the old section And I set start_line_included to false And I set end_line_included to false And I set new content to "Replacement text" Then the StreamedChangeWithLineNums instance should be created successfully And the start_line_included should be false And the end_line_included should be false And the new content should be "Replacement text" Scenario: Create StreamedChangeWithLineNums with mixed boundaries Given I import the StreamedChangeWithLineNums class And I have a valid StreamedChangeSection for old section When I create a StreamedChangeWithLineNums with the old section And I set start_line_included to true And I set end_line_included to false And I set new content to "Partial replacement" Then the StreamedChangeWithLineNums instance should be created successfully And the start_line_included should be true And the end_line_included should be false Scenario: Create StreamedChangeWithLineNums with empty new content Given I import the StreamedChangeWithLineNums class And I have a valid StreamedChangeSection for old section When I create a StreamedChangeWithLineNums with the old section And I set start_line_included to true And I set end_line_included to true And I set new content to "" Then the StreamedChangeWithLineNums instance should be created successfully And the new content should be "" Scenario: Create StreamedChangeWithLineNums with multiline new content Given I import the StreamedChangeWithLineNums class And I have a valid StreamedChangeSection for old section When I create a StreamedChangeWithLineNums with the old section And I set start_line_included to true And I set end_line_included to true And I set new content to multiline text Then the StreamedChangeWithLineNums instance should be created successfully And the new content should contain multiple lines Scenario: Create StreamedChangeWithLineNums using field aliases Given I import the StreamedChangeWithLineNums class And I have a valid StreamedChangeSection for old section When I create a StreamedChangeWithLineNums using aliases Then the StreamedChangeWithLineNums should accept the aliased fields And the change values should be properly mapped Scenario: Validate StreamedChangeWithLineNums configuration Given I import the StreamedChangeWithLineNums class When I create a StreamedChangeWithLineNums with valid data Then the change configuration should have str_strip_whitespace as true And the change configuration should have validate_assignment as true And the change configuration should have arbitrary_types_allowed as false And the change configuration should have populate_by_name as true And the change configuration should have use_enum_values as true Scenario: StreamedChangeSection strips whitespace from strings Given I import the StreamedChangeSection class When I create a StreamedChangeSection with whitespace in strings Then the whitespace should be stripped from string fields And the section should be created successfully Scenario: StreamedChangeWithLineNums strips whitespace from new content Given I import the StreamedChangeWithLineNums class And I have a valid StreamedChangeSection for old section When I create a StreamedChangeWithLineNums with whitespace in new content Then the whitespace should be stripped from new field And the change should be created successfully Scenario: Complex StreamedChangeWithLineNums with nested section Given I import the StreamedChangeWithLineNums class When I create a complex StreamedChangeWithLineNums Then the nested StreamedChangeSection should be accessible And all nested fields should be properly initialized Scenario: StreamedChangeSection with special characters in strings Given I import the StreamedChangeSection class When I create a StreamedChangeSection with special characters Then the special characters should be preserved And the section should handle unicode properly Scenario: Validate StreamedChangeSection field types Given I import the StreamedChangeSection class When I create a StreamedChangeSection with different field types Then the integer fields should be validated And the string fields should be validated Scenario: Validate StreamedChangeWithLineNums field types Given I import the StreamedChangeWithLineNums class And I have a valid StreamedChangeSection for old section When I create a StreamedChangeWithLineNums with different field types Then the boolean fields should be validated And the nested object should be validated Scenario: StreamedChangeSection with boundary line numbers Given I import the StreamedChangeSection class When I create a StreamedChangeSection with start_line 0 And I set end_line to 0 And I set start_line_string to "# First line" And I set end_line_string to "# First line" Then the StreamedChangeSection instance should be created successfully And the start_line should be 0 And the end_line should be 0 Scenario: StreamedChangeWithLineNums represents code deletion Given I import the StreamedChangeWithLineNums class And I have a StreamedChangeSection representing lines to delete When I create a StreamedChangeWithLineNums for deletion And I set start_line_included to true And I set end_line_included to true And I set new content to "" Then the StreamedChangeWithLineNums instance should represent a deletion And the old section should contain the deleted content