@unit @repository @coverage Feature: Repository coverage round 2 - Skill, Decision, Checkpoint, and ToolRepository Exercise uncovered lines in repositories.py for SkillRepository flattened-tool cache methods, DecisionRepository full CRUD, CheckpointRepository full CRUD, and ToolRepository legacy wrapper error paths. # =========================================================================== # SkillRepository – flattened tool cache methods (lines 4656-4821) # =========================================================================== Background: Given r2cov a fresh in-memory database with all tables # --- update_flattened_tools ------------------------------------------------ @skill @cache Scenario: SkillRepository.update_flattened_tools persists cache data for an existing skill Given r2cov a skill "local/my-skill" exists in the database When r2cov update_flattened_tools is called for "local/my-skill" with valid cache data Then r2cov the skill row should have non-null flattened_tools_json And r2cov the skill row should have the expected flattening_hash @skill @cache @error Scenario: SkillRepository.update_flattened_tools raises SkillNotFoundError for missing skill When r2cov update_flattened_tools is called for "local/nonexistent" with valid cache data Then r2cov a SkillNotFoundError should be raised # --- get_flattened_tools --------------------------------------------------- @skill @cache Scenario: SkillRepository.get_flattened_tools returns cache dict for existing skill Given r2cov a skill "local/cached-skill" exists in the database And r2cov the skill "local/cached-skill" has flattened tools cached When r2cov get_flattened_tools is called for "local/cached-skill" Then r2cov the result should be a dict with flattened_tools_json key And r2cov the result flattening_hash should not be None @skill @cache @error Scenario: SkillRepository.get_flattened_tools raises SkillNotFoundError for missing skill When r2cov get_flattened_tools is called for "local/ghost-skill" Then r2cov a SkillNotFoundError should be raised # --- needs_refresh --------------------------------------------------------- @skill @cache Scenario: SkillRepository.needs_refresh returns True when hash is missing Given r2cov a skill "local/stale-skill" exists in the database When r2cov needs_refresh is called for "local/stale-skill" with hash "abc123" Then r2cov the result should be True @skill @cache Scenario: SkillRepository.needs_refresh returns False when hash matches Given r2cov a skill "local/fresh-skill" exists in the database And r2cov the skill "local/fresh-skill" has flattening_hash "abc123" When r2cov needs_refresh is called for "local/fresh-skill" with hash "abc123" Then r2cov the result should be False @skill @cache Scenario: SkillRepository.needs_refresh returns True when hash differs Given r2cov a skill "local/changed-skill" exists in the database And r2cov the skill "local/changed-skill" has flattening_hash "old_hash" When r2cov needs_refresh is called for "local/changed-skill" with hash "new_hash" Then r2cov the result should be True @skill @cache @error Scenario: SkillRepository.needs_refresh raises SkillNotFoundError for missing skill When r2cov needs_refresh is called for "local/missing" with hash "abc" Then r2cov a SkillNotFoundError should be raised # --- recompute_flattening_hash --------------------------------------------- @skill @cache Scenario: SkillRepository.recompute_flattening_hash computes and stores SHA-256 Given r2cov a skill "local/hash-skill" exists in the database When r2cov recompute_flattening_hash is called for "local/hash-skill" with yaml "name: test" Then r2cov the returned hash should be the SHA-256 of "name: test" And r2cov the skill row "local/hash-skill" should have the recomputed hash stored @skill @cache @error Scenario: SkillRepository.recompute_flattening_hash raises SkillNotFoundError for missing skill When r2cov recompute_flattening_hash is called for "local/nope" with yaml "x" Then r2cov a SkillNotFoundError should be raised # --- invalidate_cached_summaries ------------------------------------------- @skill @cache Scenario: SkillRepository.invalidate_cached_summaries nulls cached fields Given r2cov a skill "local/inv-skill" exists in the database And r2cov the skill "local/inv-skill" has flattened tools cached When r2cov invalidate_cached_summaries is called for "local/inv-skill" Then r2cov the skill row "local/inv-skill" should have null flattened_tools_json And r2cov the skill row "local/inv-skill" should have null flattening_hash @skill @cache @error Scenario: SkillRepository.invalidate_cached_summaries raises SkillNotFoundError for missing skill When r2cov invalidate_cached_summaries is called for "local/void" Then r2cov a SkillNotFoundError should be raised # =========================================================================== # DecisionRepository CRUD (lines 4841-5255) # =========================================================================== # --- create ---------------------------------------------------------------- @decision Scenario: DecisionRepository.create persists a new decision Given r2cov a lifecycle plan exists for decisions When r2cov a decision is created for that plan Then r2cov the decision should be retrievable by ID @decision @error Scenario: DecisionRepository.create raises DuplicateDecisionError on duplicate Given r2cov a lifecycle plan exists for decisions And r2cov a decision "01HGZ6FE0AQDYTR4BX000DEC01" exists for that plan When r2cov creating a decision with the same ID is attempted Then r2cov a DuplicateDecisionError should be raised # --- get ------------------------------------------------------------------- @decision Scenario: DecisionRepository.get returns None for missing decision When r2cov getting decision "01HGZ6FE0AQDYTR4BX000MISS0" is attempted Then r2cov the result should be None # --- get_by_plan ----------------------------------------------------------- @decision Scenario: DecisionRepository.get_by_plan returns decisions ordered by sequence Given r2cov a lifecycle plan exists for decisions And r2cov decisions with sequence 0, 1, 2 exist for that plan When r2cov get_by_plan is called for that plan Then r2cov 3 decisions should be returned in sequence order # --- get_tree -------------------------------------------------------------- @decision Scenario: DecisionRepository.get_tree returns BFS-ordered tree Given r2cov a lifecycle plan exists for decisions And r2cov a decision tree with root and two children exists When r2cov get_tree is called with the root decision ID Then r2cov 3 decisions should be returned with root first @decision @error Scenario: DecisionRepository.get_tree raises DecisionNotFoundError for missing root When r2cov get_tree is called with a nonexistent root ID Then r2cov a DecisionNotFoundError should be raised # --- get_path_to_root ------------------------------------------------------ @decision Scenario: DecisionRepository.get_path_to_root walks from leaf to root Given r2cov a lifecycle plan exists for decisions And r2cov a decision chain root -> mid -> leaf exists When r2cov get_path_to_root is called with the leaf decision ID Then r2cov the path should contain 3 decisions starting with leaf ending with root @decision @error Scenario: DecisionRepository.get_path_to_root raises DecisionNotFoundError for missing start When r2cov get_path_to_root is called with a nonexistent ID Then r2cov a DecisionNotFoundError should be raised # --- get_superseded -------------------------------------------------------- @decision Scenario: DecisionRepository.get_superseded returns superseded decisions Given r2cov a lifecycle plan exists for decisions And r2cov a decision that has been superseded exists When r2cov get_superseded is called for that plan Then r2cov exactly 1 superseded decision should be returned # --- update_superseded_by -------------------------------------------------- @decision Scenario: DecisionRepository.update_superseded_by marks a decision as superseded Given r2cov a lifecycle plan exists for decisions And r2cov an original decision and a replacement decision exist When r2cov update_superseded_by is called on the original with the replacement ID Then r2cov the original decision should have superseded_by set @decision @error Scenario: DecisionRepository.update_superseded_by raises DecisionNotFoundError for missing When r2cov update_superseded_by is called with a nonexistent decision ID Then r2cov a DecisionNotFoundError should be raised # --- list_by_type ---------------------------------------------------------- @decision Scenario: DecisionRepository.list_by_type filters decisions by type Given r2cov a lifecycle plan exists for decisions And r2cov decisions of type "strategy_choice" and "implementation_choice" exist When r2cov list_by_type is called with "strategy_choice" Then r2cov only strategy_choice decisions should be returned # --- delete ---------------------------------------------------------------- @decision Scenario: DecisionRepository.delete removes a decision Given r2cov a lifecycle plan exists for decisions And r2cov a single decision exists for deletion When r2cov delete is called on the decision Then r2cov delete should return True And r2cov the decision should no longer be retrievable @decision Scenario: DecisionRepository.delete returns False for missing decision When r2cov delete is called with a nonexistent decision ID Then r2cov delete should return False # --- get_max_sequence_number ----------------------------------------------- @decision Scenario: DecisionRepository.get_max_sequence_number returns highest sequence Given r2cov a lifecycle plan exists for decisions And r2cov decisions with sequence 0, 1, 2 exist for that plan When r2cov get_max_sequence_number is called for that plan Then r2cov the result should be 2 # --- count ----------------------------------------------------------------- @decision Scenario: DecisionRepository.count returns number of decisions Given r2cov a lifecycle plan exists for decisions And r2cov decisions with sequence 0, 1, 2 exist for that plan When r2cov count is called for that plan Then r2cov the count result should be 3 # =========================================================================== # CheckpointRepository CRUD (lines 5279-5453) # =========================================================================== # --- create ---------------------------------------------------------------- @checkpoint Scenario: CheckpointRepository.create persists a new checkpoint Given r2cov a lifecycle plan exists for checkpoints When r2cov a checkpoint is created for that plan Then r2cov the checkpoint should be retrievable by ID # --- get_by_id ------------------------------------------------------------- @checkpoint @error Scenario: CheckpointRepository.get_by_id raises CheckpointNotFoundError for missing When r2cov getting checkpoint "01HGZ6FE0AQDYTR4BXCHK00000" is attempted Then r2cov a CheckpointNotFoundError should be raised # --- list_by_plan ---------------------------------------------------------- @checkpoint Scenario: CheckpointRepository.list_by_plan returns checkpoints ordered by creation time Given r2cov a lifecycle plan exists for checkpoints And r2cov 3 checkpoints exist for that plan When r2cov list_by_plan is called for that plan Then r2cov 3 checkpoints should be returned in creation order # --- delete ---------------------------------------------------------------- @checkpoint Scenario: CheckpointRepository.delete removes a checkpoint Given r2cov a lifecycle plan exists for checkpoints And r2cov a single checkpoint exists for deletion When r2cov delete_checkpoint is called on the checkpoint Then r2cov checkpoint delete should return True @checkpoint Scenario: CheckpointRepository.delete returns False for missing checkpoint When r2cov delete_checkpoint is called with a nonexistent ID Then r2cov checkpoint delete should return False # --- prune ----------------------------------------------------------------- @checkpoint Scenario: CheckpointRepository.prune removes excess interior checkpoints Given r2cov a lifecycle plan exists for checkpoints And r2cov 5 checkpoints exist for that plan When r2cov prune is called with max_checkpoints 3 Then r2cov 2 checkpoint IDs should be returned as pruned And r2cov only 3 checkpoints should remain for the plan @checkpoint Scenario: CheckpointRepository.prune does nothing when under limit Given r2cov a lifecycle plan exists for checkpoints And r2cov 2 checkpoints exist for that plan When r2cov prune is called with max_checkpoints 5 Then r2cov 0 checkpoint IDs should be returned as pruned # =========================================================================== # ToolRepository legacy wrapper error paths (lines 3568-3595) # =========================================================================== @tool @error Scenario: ToolRepository.get_by_name raises DatabaseError on OperationalError Given r2cov a tool repository with a broken session When r2cov ToolRepository.get_by_name is called with "local/broken" Then r2cov a DatabaseError should be raised containing "Failed to get tool" @tool @error Scenario: ToolRepository.remove raises ToolNotFoundError when tool does not exist Given r2cov a tool repository backed by an empty database When r2cov ToolRepository.remove is called with "local/nonexistent" Then r2cov a ToolNotFoundError should be raised