diff --git a/.github/coverage-baseline.json b/.github/coverage-baseline.json new file mode 100644 index 00000000..3df575a6 --- /dev/null +++ b/.github/coverage-baseline.json @@ -0,0 +1,3576 @@ +{ + "files": { + "raven/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 13, + "line_percent": 76.470588, + "statements": 17 + }, + "raven/agent/__init__.py": { + "branch_percent": 0.0, + "branches": 2, + "covered_branches": 0, + "covered_lines": 5, + "line_percent": 45.454545, + "statements": 11 + }, + "raven/agent/context/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/agent/context/builder.py": { + "branch_percent": 50.0, + "branches": 28, + "covered_branches": 14, + "covered_lines": 72, + "line_percent": 72.0, + "statements": 100 + }, + "raven/agent/hook/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 100.0, + "statements": 4 + }, + "raven/agent/hook/adapters.py": { + "branch_percent": 100.0, + "branches": 10, + "covered_branches": 10, + "covered_lines": 61, + "line_percent": 100.0, + "statements": 61 + }, + "raven/agent/hook/base.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 35, + "line_percent": 100.0, + "statements": 35 + }, + "raven/agent/hook/composite.py": { + "branch_percent": 100.0, + "branches": 10, + "covered_branches": 10, + "covered_lines": 50, + "line_percent": 100.0, + "statements": 50 + }, + "raven/agent/loop/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/agent/loop/checkpoint.py": { + "branch_percent": 90.909091, + "branches": 22, + "covered_branches": 20, + "covered_lines": 81, + "line_percent": 90.0, + "statements": 90 + }, + "raven/agent/loop/main.py": { + "branch_percent": 81.981982, + "branches": 444, + "covered_branches": 364, + "covered_lines": 1036, + "line_percent": 86.985726, + "statements": 1191 + }, + "raven/agent/loop/recovery.py": { + "branch_percent": 100.0, + "branches": 8, + "covered_branches": 8, + "covered_lines": 36, + "line_percent": 100.0, + "statements": 36 + }, + "raven/agent/personalizer/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/agent/personalizer/personalizer.py": { + "branch_percent": 0.0, + "branches": 24, + "covered_branches": 0, + "covered_lines": 26, + "line_percent": 21.138211, + "statements": 123 + }, + "raven/agent/spine_runner.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 9, + "line_percent": 100.0, + "statements": 9 + }, + "raven/agent/subagent/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/agent/subagent/manager.py": { + "branch_percent": 85.714286, + "branches": 28, + "covered_branches": 24, + "covered_lines": 146, + "line_percent": 94.805195, + "statements": 154 + }, + "raven/agent/tools/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 3, + "line_percent": 100.0, + "statements": 3 + }, + "raven/agent/tools/ask_user.py": { + "branch_percent": 94.444444, + "branches": 18, + "covered_branches": 17, + "covered_lines": 52, + "line_percent": 96.296296, + "statements": 54 + }, + "raven/agent/tools/base.py": { + "branch_percent": 62.857143, + "branches": 70, + "covered_branches": 44, + "covered_lines": 100, + "line_percent": 73.529412, + "statements": 136 + }, + "raven/agent/tools/deep_research.py": { + "branch_percent": 89.655172, + "branches": 58, + "covered_branches": 52, + "covered_lines": 248, + "line_percent": 93.939394, + "statements": 264 + }, + "raven/agent/tools/file_search.py": { + "branch_percent": 64.285714, + "branches": 70, + "covered_branches": 45, + "covered_lines": 148, + "line_percent": 72.195122, + "statements": 205 + }, + "raven/agent/tools/filesystem.py": { + "branch_percent": 38.157895, + "branches": 76, + "covered_branches": 29, + "covered_lines": 137, + "line_percent": 61.160714, + "statements": 224 + }, + "raven/agent/tools/mcp.py": { + "branch_percent": 45.833333, + "branches": 24, + "covered_branches": 11, + "covered_lines": 40, + "line_percent": 45.977011, + "statements": 87 + }, + "raven/agent/tools/media.py": { + "branch_percent": 80.0, + "branches": 20, + "covered_branches": 16, + "covered_lines": 73, + "line_percent": 94.805195, + "statements": 77 + }, + "raven/agent/tools/media_gen.py": { + "branch_percent": 0.0, + "branches": 82, + "covered_branches": 0, + "covered_lines": 59, + "line_percent": 22.43346, + "statements": 263 + }, + "raven/agent/tools/message.py": { + "branch_percent": 66.666667, + "branches": 6, + "covered_branches": 4, + "covered_lines": 48, + "line_percent": 94.117647, + "statements": 51 + }, + "raven/agent/tools/registry.py": { + "branch_percent": 90.0, + "branches": 10, + "covered_branches": 9, + "covered_lines": 52, + "line_percent": 94.545455, + "statements": 55 + }, + "raven/agent/tools/shell.py": { + "branch_percent": 75.0, + "branches": 44, + "covered_branches": 33, + "covered_lines": 108, + "line_percent": 84.375, + "statements": 128 + }, + "raven/agent/tools/shell_policy.py": { + "branch_percent": 86.842105, + "branches": 76, + "covered_branches": 66, + "covered_lines": 121, + "line_percent": 93.79845, + "statements": 129 + }, + "raven/agent/tools/skill_hub.py": { + "branch_percent": 96.428571, + "branches": 28, + "covered_branches": 27, + "covered_lines": 92, + "line_percent": 94.845361, + "statements": 97 + }, + "raven/agent/tools/spawn.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 29, + "line_percent": 93.548387, + "statements": 31 + }, + "raven/agent/tools/tool_index.py": { + "branch_percent": 100.0, + "branches": 20, + "covered_branches": 20, + "covered_lines": 71, + "line_percent": 100.0, + "statements": 71 + }, + "raven/agent/tools/tool_search.py": { + "branch_percent": 91.666667, + "branches": 24, + "covered_branches": 22, + "covered_lines": 99, + "line_percent": 99.0, + "statements": 100 + }, + "raven/agent/tools/web.py": { + "branch_percent": 4.166667, + "branches": 24, + "covered_branches": 1, + "covered_lines": 33, + "line_percent": 37.078652, + "statements": 89 + }, + "raven/auth/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 100.0, + "statements": 4 + }, + "raven/auth/allowlist.py": { + "branch_percent": 100.0, + "branches": 8, + "covered_branches": 8, + "covered_lines": 24, + "line_percent": 100.0, + "statements": 24 + }, + "raven/auth/capability_token.py": { + "branch_percent": 100.0, + "branches": 8, + "covered_branches": 8, + "covered_lines": 52, + "line_percent": 96.296296, + "statements": 54 + }, + "raven/auth/managed_settings.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 9, + "line_percent": 100.0, + "statements": 9 + }, + "raven/channels/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 100.0, + "statements": 4 + }, + "raven/channels/adapters/dingtalk/api.py": { + "branch_percent": 70.454545, + "branches": 44, + "covered_branches": 31, + "covered_lines": 110, + "line_percent": 72.847682, + "statements": 151 + }, + "raven/channels/adapters/dingtalk/channel.py": { + "branch_percent": 62.5, + "branches": 56, + "covered_branches": 35, + "covered_lines": 112, + "line_percent": 71.794872, + "statements": 156 + }, + "raven/channels/adapters/dingtalk/parsing.py": { + "branch_percent": 88.235294, + "branches": 34, + "covered_branches": 30, + "covered_lines": 81, + "line_percent": 100.0, + "statements": 81 + }, + "raven/channels/adapters/dingtalk/spec.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 66.666667, + "statements": 6 + }, + "raven/channels/adapters/discord/channel.py": { + "branch_percent": 63.265306, + "branches": 98, + "covered_branches": 62, + "covered_lines": 199, + "line_percent": 75.09434, + "statements": 265 + }, + "raven/channels/adapters/discord/spec.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 66.666667, + "statements": 6 + }, + "raven/channels/adapters/email/channel.py": { + "branch_percent": 81.818182, + "branches": 44, + "covered_branches": 36, + "covered_lines": 110, + "line_percent": 92.436975, + "statements": 119 + }, + "raven/channels/adapters/email/mailbox.py": { + "branch_percent": 77.777778, + "branches": 18, + "covered_branches": 14, + "covered_lines": 44, + "line_percent": 89.795918, + "statements": 49 + }, + "raven/channels/adapters/email/parsing.py": { + "branch_percent": 86.111111, + "branches": 36, + "covered_branches": 31, + "covered_lines": 75, + "line_percent": 91.463415, + "statements": 82 + }, + "raven/channels/adapters/email/spec.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 66.666667, + "statements": 6 + }, + "raven/channels/adapters/feishu/cards.py": { + "branch_percent": 70.37037, + "branches": 54, + "covered_branches": 38, + "covered_lines": 97, + "line_percent": 89.814815, + "statements": 108 + }, + "raven/channels/adapters/feishu/channel.py": { + "branch_percent": 25.423729, + "branches": 118, + "covered_branches": 30, + "covered_lines": 123, + "line_percent": 40.728477, + "statements": 302 + }, + "raven/channels/adapters/feishu/content.py": { + "branch_percent": 51.111111, + "branches": 90, + "covered_branches": 46, + "covered_lines": 75, + "line_percent": 64.655172, + "statements": 116 + }, + "raven/channels/adapters/feishu/spec.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 66.666667, + "statements": 6 + }, + "raven/channels/adapters/matrix/channel.py": { + "branch_percent": 19.166667, + "branches": 120, + "covered_branches": 23, + "covered_lines": 98, + "line_percent": 28.571429, + "statements": 343 + }, + "raven/channels/adapters/matrix/content.py": { + "branch_percent": 86.0, + "branches": 50, + "covered_branches": 43, + "covered_lines": 130, + "line_percent": 94.890511, + "statements": 137 + }, + "raven/channels/adapters/matrix/spec.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 66.666667, + "statements": 6 + }, + "raven/channels/adapters/mochat/api.py": { + "branch_percent": 68.75, + "branches": 16, + "covered_branches": 11, + "covered_lines": 45, + "line_percent": 86.538462, + "statements": 52 + }, + "raven/channels/adapters/mochat/channel.py": { + "branch_percent": 38.157895, + "branches": 152, + "covered_branches": 58, + "covered_lines": 188, + "line_percent": 52.661064, + "statements": 357 + }, + "raven/channels/adapters/mochat/cursors.py": { + "branch_percent": 78.571429, + "branches": 14, + "covered_branches": 11, + "covered_lines": 48, + "line_percent": 87.272727, + "statements": 55 + }, + "raven/channels/adapters/mochat/parsing.py": { + "branch_percent": 89.0625, + "branches": 64, + "covered_branches": 57, + "covered_lines": 114, + "line_percent": 95.798319, + "statements": 119 + }, + "raven/channels/adapters/mochat/pipeline.py": { + "branch_percent": 87.5, + "branches": 16, + "covered_branches": 14, + "covered_lines": 68, + "line_percent": 100.0, + "statements": 68 + }, + "raven/channels/adapters/mochat/spec.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 66.666667, + "statements": 6 + }, + "raven/channels/adapters/mochat/transport.py": { + "branch_percent": 100.0, + "branches": 12, + "covered_branches": 12, + "covered_lines": 55, + "line_percent": 85.9375, + "statements": 64 + }, + "raven/channels/adapters/qq/channel.py": { + "branch_percent": 60.0, + "branches": 20, + "covered_branches": 12, + "covered_lines": 53, + "line_percent": 59.550562, + "statements": 89 + }, + "raven/channels/adapters/qq/parsing.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 11, + "line_percent": 100.0, + "statements": 11 + }, + "raven/channels/adapters/qq/spec.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 66.666667, + "statements": 6 + }, + "raven/channels/adapters/slack/channel.py": { + "branch_percent": 80.952381, + "branches": 42, + "covered_branches": 34, + "covered_lines": 91, + "line_percent": 75.206612, + "statements": 121 + }, + "raven/channels/adapters/slack/parsing.py": { + "branch_percent": 89.285714, + "branches": 28, + "covered_branches": 25, + "covered_lines": 67, + "line_percent": 97.101449, + "statements": 69 + }, + "raven/channels/adapters/slack/spec.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 66.666667, + "statements": 6 + }, + "raven/channels/adapters/telegram/channel.py": { + "branch_percent": 50.847458, + "branches": 118, + "covered_branches": 60, + "covered_lines": 225, + "line_percent": 64.102564, + "statements": 351 + }, + "raven/channels/adapters/telegram/spec.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 66.666667, + "statements": 6 + }, + "raven/channels/adapters/wecom/channel.py": { + "branch_percent": 76.666667, + "branches": 60, + "covered_branches": 46, + "covered_lines": 120, + "line_percent": 75.949367, + "statements": 158 + }, + "raven/channels/adapters/wecom/spec.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 66.666667, + "statements": 6 + }, + "raven/channels/adapters/weixin/channel.py": { + "branch_percent": 33.333333, + "branches": 180, + "covered_branches": 60, + "covered_lines": 203, + "line_percent": 42.647059, + "statements": 476 + }, + "raven/channels/adapters/weixin/crypto.py": { + "branch_percent": 90.0, + "branches": 10, + "covered_branches": 9, + "covered_lines": 36, + "line_percent": 76.595745, + "statements": 47 + }, + "raven/channels/adapters/weixin/protocol.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 58, + "line_percent": 96.666667, + "statements": 60 + }, + "raven/channels/adapters/weixin/spec.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 6, + "line_percent": 100.0, + "statements": 6 + }, + "raven/channels/adapters/weixin/typing_state.py": { + "branch_percent": 70.0, + "branches": 20, + "covered_branches": 14, + "covered_lines": 66, + "line_percent": 90.410959, + "statements": 73 + }, + "raven/channels/adapters/whatsapp/bridge.py": { + "branch_percent": 50.0, + "branches": 14, + "covered_branches": 7, + "covered_lines": 40, + "line_percent": 66.666667, + "statements": 60 + }, + "raven/channels/adapters/whatsapp/channel.py": { + "branch_percent": 57.142857, + "branches": 42, + "covered_branches": 24, + "covered_lines": 91, + "line_percent": 71.09375, + "statements": 128 + }, + "raven/channels/adapters/whatsapp/parsing.py": { + "branch_percent": 100.0, + "branches": 12, + "covered_branches": 12, + "covered_lines": 32, + "line_percent": 100.0, + "statements": 32 + }, + "raven/channels/adapters/whatsapp/spec.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 6, + "line_percent": 100.0, + "statements": 6 + }, + "raven/channels/base.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 19, + "line_percent": 100.0, + "statements": 19 + }, + "raven/channels/contract.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 28, + "line_percent": 100.0, + "statements": 28 + }, + "raven/channels/errors.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 22, + "line_percent": 78.571429, + "statements": 28 + }, + "raven/channels/intake.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 27, + "line_percent": 100.0, + "statements": 27 + }, + "raven/channels/manager.py": { + "branch_percent": 70.0, + "branches": 20, + "covered_branches": 14, + "covered_lines": 57, + "line_percent": 75.0, + "statements": 76 + }, + "raven/channels/media.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 12, + "line_percent": 100.0, + "statements": 12 + }, + "raven/channels/outlet.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 14, + "line_percent": 100.0, + "statements": 14 + }, + "raven/channels/registry.py": { + "branch_percent": 66.666667, + "branches": 6, + "covered_branches": 4, + "covered_lines": 18, + "line_percent": 85.714286, + "statements": 21 + }, + "raven/channels/transcribe.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 11, + "line_percent": 100.0, + "statements": 11 + }, + "raven/cli/_cron_handler.py": { + "branch_percent": 70.0, + "branches": 30, + "covered_branches": 21, + "covered_lines": 78, + "line_percent": 88.636364, + "statements": 88 + }, + "raven/cli/_eval_stack.py": { + "branch_percent": 100.0, + "branches": 2, + "covered_branches": 2, + "covered_lines": 11, + "line_percent": 100.0, + "statements": 11 + }, + "raven/cli/_exit.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 16 + }, + "raven/cli/_gateway_lock.py": { + "branch_percent": 100.0, + "branches": 2, + "covered_branches": 2, + "covered_lines": 52, + "line_percent": 100.0, + "statements": 52 + }, + "raven/cli/_gateway_spine.py": { + "branch_percent": 100.0, + "branches": 12, + "covered_branches": 12, + "covered_lines": 51, + "line_percent": 100.0, + "statements": 51 + }, + "raven/cli/_helpers.py": { + "branch_percent": 75.0, + "branches": 40, + "covered_branches": 30, + "covered_lines": 119, + "line_percent": 87.5, + "statements": 136 + }, + "raven/cli/_hooks_stack.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 16, + "line_percent": 100.0, + "statements": 16 + }, + "raven/cli/_log_file.py": { + "branch_percent": 87.5, + "branches": 16, + "covered_branches": 14, + "covered_lines": 58, + "line_percent": 92.063492, + "statements": 63 + }, + "raven/cli/_log_silence.py": { + "branch_percent": 50.0, + "branches": 2, + "covered_branches": 1, + "covered_lines": 9, + "line_percent": 90.0, + "statements": 10 + }, + "raven/cli/_plugin_stack.py": { + "branch_percent": 91.666667, + "branches": 24, + "covered_branches": 22, + "covered_lines": 65, + "line_percent": 89.041096, + "statements": 73 + }, + "raven/cli/_proactive_stack.py": { + "branch_percent": 26.0, + "branches": 50, + "covered_branches": 13, + "covered_lines": 44, + "line_percent": 26.993865, + "statements": 163 + }, + "raven/cli/_repl_slash.py": { + "branch_percent": 67.741935, + "branches": 62, + "covered_branches": 42, + "covered_lines": 107, + "line_percent": 71.333333, + "statements": 150 + }, + "raven/cli/_repl_spine.py": { + "branch_percent": 90.0, + "branches": 20, + "covered_branches": 18, + "covered_lines": 53, + "line_percent": 96.363636, + "statements": 55 + }, + "raven/cli/_styles.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 100.0, + "statements": 4 + }, + "raven/cli/_theme.py": { + "branch_percent": 76.315789, + "branches": 38, + "covered_branches": 29, + "covered_lines": 80, + "line_percent": 69.565217, + "statements": 115 + }, + "raven/cli/_token_wise_stack.py": { + "branch_percent": 66.666667, + "branches": 6, + "covered_branches": 4, + "covered_lines": 16, + "line_percent": 100.0, + "statements": 16 + }, + "raven/cli/agent_commands.py": { + "branch_percent": 64.583333, + "branches": 48, + "covered_branches": 31, + "covered_lines": 168, + "line_percent": 81.95122, + "statements": 205 + }, + "raven/cli/channel_commands.py": { + "branch_percent": 82.142857, + "branches": 56, + "covered_branches": 46, + "covered_lines": 201, + "line_percent": 83.057851, + "statements": 242 + }, + "raven/cli/commands.py": { + "branch_percent": 40.0, + "branches": 10, + "covered_branches": 4, + "covered_lines": 46, + "line_percent": 66.666667, + "statements": 69 + }, + "raven/cli/cron_commands.py": { + "branch_percent": 78.125, + "branches": 128, + "covered_branches": 100, + "covered_lines": 337, + "line_percent": 90.106952, + "statements": 374 + }, + "raven/cli/deep_research_commands.py": { + "branch_percent": 89.583333, + "branches": 48, + "covered_branches": 43, + "covered_lines": 132, + "line_percent": 98.507463, + "statements": 134 + }, + "raven/cli/doctor_commands.py": { + "branch_percent": 80.681818, + "branches": 88, + "covered_branches": 71, + "covered_lines": 238, + "line_percent": 94.820717, + "statements": 251 + }, + "raven/cli/gateway_commands.py": { + "branch_percent": 9.090909, + "branches": 66, + "covered_branches": 6, + "covered_lines": 72, + "line_percent": 30.508475, + "statements": 236 + }, + "raven/cli/import_commands.py": { + "branch_percent": 79.054054, + "branches": 148, + "covered_branches": 117, + "covered_lines": 406, + "line_percent": 87.124464, + "statements": 466 + }, + "raven/cli/onboard_channels.py": { + "branch_percent": 34.615385, + "branches": 78, + "covered_branches": 27, + "covered_lines": 122, + "line_percent": 54.464286, + "statements": 224 + }, + "raven/cli/onboard_commands.py": { + "branch_percent": 66.326531, + "branches": 392, + "covered_branches": 260, + "covered_lines": 791, + "line_percent": 78.162055, + "statements": 1012 + }, + "raven/cli/onboard_everos.py": { + "branch_percent": 43.032787, + "branches": 244, + "covered_branches": 105, + "covered_lines": 300, + "line_percent": 54.644809, + "statements": 549 + }, + "raven/cli/plugin_commands.py": { + "branch_percent": 67.857143, + "branches": 28, + "covered_branches": 19, + "covered_lines": 77, + "line_percent": 92.771084, + "statements": 83 + }, + "raven/cli/provider_commands.py": { + "branch_percent": 83.333333, + "branches": 96, + "covered_branches": 80, + "covered_lines": 368, + "line_percent": 89.104116, + "statements": 413 + }, + "raven/cli/sandbox_commands.py": { + "branch_percent": 50.0, + "branches": 40, + "covered_branches": 20, + "covered_lines": 147, + "line_percent": 53.068592, + "statements": 277 + }, + "raven/cli/sentinel_commands.py": { + "branch_percent": 55.769231, + "branches": 156, + "covered_branches": 87, + "covered_lines": 298, + "line_percent": 59.719439, + "statements": 499 + }, + "raven/cli/session_commands.py": { + "branch_percent": 94.117647, + "branches": 34, + "covered_branches": 32, + "covered_lines": 133, + "line_percent": 96.376812, + "statements": 138 + }, + "raven/cli/skill_commands.py": { + "branch_percent": 92.857143, + "branches": 14, + "covered_branches": 13, + "covered_lines": 42, + "line_percent": 87.5, + "statements": 48 + }, + "raven/cli/status_commands.py": { + "branch_percent": 100.0, + "branches": 8, + "covered_branches": 8, + "covered_lines": 31, + "line_percent": 100.0, + "statements": 31 + }, + "raven/cli/tracing_commands.py": { + "branch_percent": 18.181818, + "branches": 22, + "covered_branches": 4, + "covered_lines": 38, + "line_percent": 41.758242, + "statements": 91 + }, + "raven/cli/tui_commands.py": { + "branch_percent": 75.362319, + "branches": 138, + "covered_branches": 104, + "covered_lines": 363, + "line_percent": 80.666667, + "statements": 450 + }, + "raven/cli/update_notice.py": { + "branch_percent": 92.307692, + "branches": 26, + "covered_branches": 24, + "covered_lines": 86, + "line_percent": 94.505495, + "statements": 91 + }, + "raven/cli/upgrade_commands.py": { + "branch_percent": 87.254902, + "branches": 102, + "covered_branches": 89, + "covered_lines": 274, + "line_percent": 93.197279, + "statements": 294 + }, + "raven/config/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 5, + "line_percent": 100.0, + "statements": 5 + }, + "raven/config/loader.py": { + "branch_percent": 88.709677, + "branches": 62, + "covered_branches": 55, + "covered_lines": 110, + "line_percent": 94.017094, + "statements": 117 + }, + "raven/config/paths.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 35, + "line_percent": 87.5, + "statements": 40 + }, + "raven/config/raven.py": { + "branch_percent": 77.272727, + "branches": 44, + "covered_branches": 34, + "covered_lines": 432, + "line_percent": 97.737557, + "statements": 442 + }, + "raven/config/schema.py": { + "branch_percent": 86.111111, + "branches": 72, + "covered_branches": 62, + "covered_lines": 461, + "line_percent": 97.876858, + "statements": 471 + }, + "raven/config/update.py": { + "branch_percent": 87.5, + "branches": 24, + "covered_branches": 21, + "covered_lines": 141, + "line_percent": 99.295775, + "statements": 142 + }, + "raven/config/update_channels.py": { + "branch_percent": 84.52381, + "branches": 84, + "covered_branches": 71, + "covered_lines": 206, + "line_percent": 87.288136, + "statements": 236 + }, + "raven/config/update_everos.py": { + "branch_percent": 92.857143, + "branches": 14, + "covered_branches": 13, + "covered_lines": 63, + "line_percent": 94.029851, + "statements": 67 + }, + "raven/config/update_providers.py": { + "branch_percent": 82.038835, + "branches": 206, + "covered_branches": 169, + "covered_lines": 538, + "line_percent": 87.479675, + "statements": 615 + }, + "raven/config/update_tools.py": { + "branch_percent": 100.0, + "branches": 2, + "covered_branches": 2, + "covered_lines": 46, + "line_percent": 100.0, + "statements": 46 + }, + "raven/context_engine/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 6, + "line_percent": 100.0, + "statements": 6 + }, + "raven/context_engine/assembler.py": { + "branch_percent": 91.176471, + "branches": 34, + "covered_branches": 31, + "covered_lines": 85, + "line_percent": 97.701149, + "statements": 87 + }, + "raven/context_engine/base.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 46, + "line_percent": 95.833333, + "statements": 48 + }, + "raven/context_engine/curator.py": { + "branch_percent": 39.285714, + "branches": 84, + "covered_branches": 33, + "covered_lines": 349, + "line_percent": 75.215517, + "statements": 464 + }, + "raven/context_engine/factory.py": { + "branch_percent": 88.888889, + "branches": 18, + "covered_branches": 16, + "covered_lines": 51, + "line_percent": 100.0, + "statements": 51 + }, + "raven/context_engine/history_trimmer.py": { + "branch_percent": 34.615385, + "branches": 52, + "covered_branches": 18, + "covered_lines": 75, + "line_percent": 68.807339, + "statements": 109 + }, + "raven/context_engine/segments/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 6, + "line_percent": 100.0, + "statements": 6 + }, + "raven/context_engine/segments/active_skills.py": { + "branch_percent": 25.0, + "branches": 4, + "covered_branches": 1, + "covered_lines": 15, + "line_percent": 71.428571, + "statements": 21 + }, + "raven/context_engine/segments/bootstrap.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 14, + "line_percent": 100.0, + "statements": 14 + }, + "raven/context_engine/segments/curator.py": { + "branch_percent": 76.666667, + "branches": 30, + "covered_branches": 23, + "covered_lines": 114, + "line_percent": 92.682927, + "statements": 123 + }, + "raven/context_engine/segments/identity.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 12, + "line_percent": 100.0, + "statements": 12 + }, + "raven/context_engine/segments/memory.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 28, + "line_percent": 100.0, + "statements": 28 + }, + "raven/context_engine/segments/render.py": { + "branch_percent": 91.666667, + "branches": 48, + "covered_branches": 44, + "covered_lines": 132, + "line_percent": 96.350365, + "statements": 137 + }, + "raven/context_engine/segments/skills.py": { + "branch_percent": 81.578947, + "branches": 38, + "covered_branches": 31, + "covered_lines": 98, + "line_percent": 90.740741, + "statements": 108 + }, + "raven/eval_engine/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 7, + "line_percent": 100.0, + "statements": 7 + }, + "raven/eval_engine/adapter/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/eval_engine/adapter/adapter.py": { + "branch_percent": 100.0, + "branches": 2, + "covered_branches": 2, + "covered_lines": 22, + "line_percent": 100.0, + "statements": 22 + }, + "raven/eval_engine/config.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 22, + "line_percent": 100.0, + "statements": 22 + }, + "raven/eval_engine/engine.py": { + "branch_percent": 50.0, + "branches": 2, + "covered_branches": 1, + "covered_lines": 32, + "line_percent": 91.428571, + "statements": 35 + }, + "raven/eval_engine/hooks/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 100.0, + "statements": 4 + }, + "raven/eval_engine/hooks/after_iteration_hook.py": { + "branch_percent": 81.818182, + "branches": 22, + "covered_branches": 18, + "covered_lines": 50, + "line_percent": 90.909091, + "statements": 55 + }, + "raven/eval_engine/hooks/before_iteration_hook.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 27, + "line_percent": 90.0, + "statements": 30 + }, + "raven/eval_engine/hooks/tool_audit_hook.py": { + "branch_percent": 87.5, + "branches": 16, + "covered_branches": 14, + "covered_lines": 36, + "line_percent": 94.736842, + "statements": 38 + }, + "raven/eval_engine/judge/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/eval_engine/judge/judge.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 32, + "line_percent": 100.0, + "statements": 32 + }, + "raven/eval_engine/prompts/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 3, + "line_percent": 100.0, + "statements": 3 + }, + "raven/eval_engine/prompts/task_completion.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/eval_engine/prompts/tool_safety.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/evolver/activation/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 6, + "line_percent": 100.0, + "statements": 6 + }, + "raven/evolver/activation/audit.py": { + "branch_percent": 0.0, + "branches": 18, + "covered_branches": 0, + "covered_lines": 6, + "line_percent": 17.647059, + "statements": 34 + }, + "raven/evolver/activation/chamber.py": { + "branch_percent": 0.0, + "branches": 8, + "covered_branches": 0, + "covered_lines": 22, + "line_percent": 51.162791, + "statements": 43 + }, + "raven/evolver/activation/ledger.py": { + "branch_percent": 0.0, + "branches": 18, + "covered_branches": 0, + "covered_lines": 20, + "line_percent": 33.898305, + "statements": 59 + }, + "raven/evolver/activation/predicates.py": { + "branch_percent": 0.0, + "branches": 14, + "covered_branches": 0, + "covered_lines": 10, + "line_percent": 27.027027, + "statements": 37 + }, + "raven/evolver/activation/routing_query.py": { + "branch_percent": 0.0, + "branches": 6, + "covered_branches": 0, + "covered_lines": 6, + "line_percent": 22.222222, + "statements": 27 + }, + "raven/evolver/activation/spec.py": { + "branch_percent": 0.0, + "branches": 94, + "covered_branches": 0, + "covered_lines": 22, + "line_percent": 13.414634, + "statements": 164 + }, + "raven/evolver/analysis/failure_map_builder.py": { + "branch_percent": 0.0, + "branches": 10, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 59 + }, + "raven/evolver/analysis/proxy_features.py": { + "branch_percent": 0.0, + "branches": 52, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 157 + }, + "raven/evolver/analysis/stability_bucket.py": { + "branch_percent": 0.0, + "branches": 30, + "covered_branches": 0, + "covered_lines": 26, + "line_percent": 29.885057, + "statements": 87 + }, + "raven/evolver/analysis/trial_pool.py": { + "branch_percent": 0.0, + "branches": 10, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 28 + }, + "raven/evolver/applier/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 3, + "line_percent": 100.0, + "statements": 3 + }, + "raven/evolver/applier/beacon_guard.py": { + "branch_percent": 0.0, + "branches": 4, + "covered_branches": 0, + "covered_lines": 6, + "line_percent": 60.0, + "statements": 10 + }, + "raven/evolver/applier/path_guard.py": { + "branch_percent": 57.142857, + "branches": 14, + "covered_branches": 8, + "covered_lines": 24, + "line_percent": 75.0, + "statements": 32 + }, + "raven/evolver/cli.py": { + "branch_percent": 0.0, + "branches": 8, + "covered_branches": 0, + "covered_lines": 21, + "line_percent": 65.625, + "statements": 32 + }, + "raven/evolver/compressor/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 2 + }, + "raven/evolver/compressor/trajectory.py": { + "branch_percent": 0.0, + "branches": 108, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 232 + }, + "raven/evolver/judge/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 5, + "line_percent": 100.0, + "statements": 5 + }, + "raven/evolver/judge/llm_client.py": { + "branch_percent": 0.0, + "branches": 46, + "covered_branches": 0, + "covered_lines": 51, + "line_percent": 29.651163, + "statements": 172 + }, + "raven/evolver/judge/parser.py": { + "branch_percent": 0.0, + "branches": 86, + "covered_branches": 0, + "covered_lines": 15, + "line_percent": 9.868421, + "statements": 152 + }, + "raven/evolver/judge/prompts.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 14, + "line_percent": 77.777778, + "statements": 18 + }, + "raven/evolver/judge/schema.py": { + "branch_percent": 0.0, + "branches": 32, + "covered_branches": 0, + "covered_lines": 80, + "line_percent": 68.376068, + "statements": 117 + }, + "raven/evolver/launch/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 3, + "line_percent": 100.0, + "statements": 3 + }, + "raven/evolver/launch/config.py": { + "branch_percent": 79.545455, + "branches": 44, + "covered_branches": 35, + "covered_lines": 95, + "line_percent": 87.962963, + "statements": 108 + }, + "raven/evolver/launch/contract.py": { + "branch_percent": 83.333333, + "branches": 6, + "covered_branches": 5, + "covered_lines": 34, + "line_percent": 94.444444, + "statements": 36 + }, + "raven/evolver/launch/models.py": { + "branch_percent": 21.428571, + "branches": 28, + "covered_branches": 6, + "covered_lines": 35, + "line_percent": 45.454545, + "statements": 77 + }, + "raven/evolver/launch/registry.py": { + "branch_percent": 66.666667, + "branches": 6, + "covered_branches": 4, + "covered_lines": 16, + "line_percent": 94.117647, + "statements": 17 + }, + "raven/evolver/launch/runner.py": { + "branch_percent": 12.121212, + "branches": 66, + "covered_branches": 8, + "covered_lines": 62, + "line_percent": 27.678571, + "statements": 224 + }, + "raven/evolver/launch/state.py": { + "branch_percent": 100.0, + "branches": 2, + "covered_branches": 2, + "covered_lines": 59, + "line_percent": 90.769231, + "statements": 65 + }, + "raven/evolver/orchestrator/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 1, + "line_percent": 100.0, + "statements": 1 + }, + "raven/evolver/orchestrator/archive.py": { + "branch_percent": 0.0, + "branches": 50, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 166 + }, + "raven/evolver/orchestrator/config.py": { + "branch_percent": 66.666667, + "branches": 6, + "covered_branches": 4, + "covered_lines": 50, + "line_percent": 89.285714, + "statements": 56 + }, + "raven/evolver/orchestrator/gates/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 100.0, + "statements": 4 + }, + "raven/evolver/orchestrator/gates/fisher.py": { + "branch_percent": 91.666667, + "branches": 12, + "covered_branches": 11, + "covered_lines": 32, + "line_percent": 96.969697, + "statements": 33 + }, + "raven/evolver/orchestrator/gates/paired.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 35, + "line_percent": 100.0, + "statements": 35 + }, + "raven/evolver/orchestrator/gates/pipeline.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 25, + "line_percent": 100.0, + "statements": 25 + }, + "raven/evolver/orchestrator/gates/policy.py": { + "branch_percent": 0.0, + "branches": 6, + "covered_branches": 0, + "covered_lines": 55, + "line_percent": 63.218391, + "statements": 87 + }, + "raven/evolver/orchestrator/gates/strategies.py": { + "branch_percent": 83.333333, + "branches": 18, + "covered_branches": 15, + "covered_lines": 87, + "line_percent": 97.752809, + "statements": 89 + }, + "raven/evolver/orchestrator/loop.py": { + "branch_percent": 0.0, + "branches": 102, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 316 + }, + "raven/evolver/orchestrator/nodes/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 1, + "line_percent": 100.0, + "statements": 1 + }, + "raven/evolver/orchestrator/nodes/design.py": { + "branch_percent": 0.0, + "branches": 18, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 62 + }, + "raven/evolver/orchestrator/nodes/diagnose.py": { + "branch_percent": 0.0, + "branches": 20, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 54 + }, + "raven/evolver/orchestrator/nodes/screen.py": { + "branch_percent": 83.333333, + "branches": 6, + "covered_branches": 5, + "covered_lines": 31, + "line_percent": 93.939394, + "statements": 33 + }, + "raven/evolver/orchestrator/nodes/semantic.py": { + "branch_percent": 0.0, + "branches": 4, + "covered_branches": 0, + "covered_lines": 20, + "line_percent": 51.282051, + "statements": 39 + }, + "raven/evolver/orchestrator/nodes/taxonomy.py": { + "branch_percent": 0.0, + "branches": 82, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 229 + }, + "raven/evolver/orchestrator/nodes/verdict.py": { + "branch_percent": 0.0, + "branches": 6, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 28 + }, + "raven/evolver/orchestrator/production.py": { + "branch_percent": 0.0, + "branches": 46, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 234 + }, + "raven/evolver/orchestrator/providers/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 1, + "line_percent": 100.0, + "statements": 1 + }, + "raven/evolver/orchestrator/providers/claude_agentic.py": { + "branch_percent": 0.0, + "branches": 14, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 37 + }, + "raven/evolver/orchestrator/providers/claude_cli.py": { + "branch_percent": 93.75, + "branches": 16, + "covered_branches": 15, + "covered_lines": 56, + "line_percent": 100.0, + "statements": 56 + }, + "raven/evolver/orchestrator/providers/openai_compat.py": { + "branch_percent": 0.0, + "branches": 6, + "covered_branches": 0, + "covered_lines": 13, + "line_percent": 33.333333, + "statements": 39 + }, + "raven/evolver/orchestrator/scoring.py": { + "branch_percent": 100.0, + "branches": 24, + "covered_branches": 24, + "covered_lines": 71, + "line_percent": 100.0, + "statements": 71 + }, + "raven/evolver/orchestrator/sealed/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 1 + }, + "raven/evolver/orchestrator/sealed/runner.py": { + "branch_percent": 0.0, + "branches": 20, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 100 + }, + "raven/evolver/orchestrator/state/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 1 + }, + "raven/evolver/orchestrator/state/journal.py": { + "branch_percent": 0.0, + "branches": 6, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 32 + }, + "raven/evolver/orchestrator/termination.py": { + "branch_percent": 0.0, + "branches": 16, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 35 + }, + "raven/evolver/scheduler/anchor_selection.py": { + "branch_percent": 0.0, + "branches": 18, + "covered_branches": 0, + "covered_lines": 25, + "line_percent": 35.211268, + "statements": 71 + }, + "raven/evolver/scheduler/bandit_tasks.py": { + "branch_percent": 0.0, + "branches": 34, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 86 + }, + "raven/evolver/scheduler/branching_policy.py": { + "branch_percent": 0.0, + "branches": 34, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 79 + }, + "raven/evolver/scheduler/cold_start_bandit.py": { + "branch_percent": 0.0, + "branches": 60, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 157 + }, + "raven/evolver/scheduler/tree_aware_bandit.py": { + "branch_percent": 0.0, + "branches": 60, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 134 + }, + "raven/evolver/scheduler/tree_loader.py": { + "branch_percent": 0.0, + "branches": 74, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 143 + }, + "raven/evolver/tree/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 100.0, + "statements": 4 + }, + "raven/evolver/tree/git_ops.py": { + "branch_percent": 50.0, + "branches": 26, + "covered_branches": 13, + "covered_lines": 89, + "line_percent": 70.634921, + "statements": 126 + }, + "raven/evolver/tree/node.py": { + "branch_percent": 8.333333, + "branches": 60, + "covered_branches": 5, + "covered_lines": 137, + "line_percent": 56.61157, + "statements": 242 + }, + "raven/evolver/tree/store.py": { + "branch_percent": 0.0, + "branches": 32, + "covered_branches": 0, + "covered_lines": 29, + "line_percent": 28.431373, + "statements": 102 + }, + "raven/importer/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 6, + "line_percent": 100.0, + "statements": 6 + }, + "raven/importer/hermes_user_md.py": { + "branch_percent": 100.0, + "branches": 16, + "covered_branches": 16, + "covered_lines": 60, + "line_percent": 100.0, + "statements": 60 + }, + "raven/importer/orchestrator.py": { + "branch_percent": 96.875, + "branches": 32, + "covered_branches": 31, + "covered_lines": 112, + "line_percent": 100.0, + "statements": 112 + }, + "raven/importer/scanners/__init__.py": { + "branch_percent": 91.666667, + "branches": 12, + "covered_branches": 11, + "covered_lines": 32, + "line_percent": 96.969697, + "statements": 33 + }, + "raven/importer/scanners/claude_code.py": { + "branch_percent": 86.27451, + "branches": 102, + "covered_branches": 88, + "covered_lines": 244, + "line_percent": 89.051095, + "statements": 274 + }, + "raven/importer/scanners/hermes.py": { + "branch_percent": 91.860465, + "branches": 86, + "covered_branches": 79, + "covered_lines": 267, + "line_percent": 96.043165, + "statements": 278 + }, + "raven/importer/skills/__init__.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 34, + "line_percent": 100.0, + "statements": 34 + }, + "raven/importer/skills/hermes.py": { + "branch_percent": 96.875, + "branches": 32, + "covered_branches": 31, + "covered_lines": 100, + "line_percent": 97.087379, + "statements": 103 + }, + "raven/importer/skills/installer.py": { + "branch_percent": 83.333333, + "branches": 18, + "covered_branches": 15, + "covered_lines": 75, + "line_percent": 92.592593, + "statements": 81 + }, + "raven/importer/state.py": { + "branch_percent": 83.333333, + "branches": 6, + "covered_branches": 5, + "covered_lines": 63, + "line_percent": 98.4375, + "statements": 64 + }, + "raven/importer/types.py": { + "branch_percent": 100.0, + "branches": 2, + "covered_branches": 2, + "covered_lines": 46, + "line_percent": 100.0, + "statements": 46 + }, + "raven/memory_engine/__init__.py": { + "branch_percent": 100.0, + "branches": 2, + "covered_branches": 2, + "covered_lines": 9, + "line_percent": 100.0, + "statements": 9 + }, + "raven/memory_engine/backend.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 19, + "line_percent": 100.0, + "statements": 19 + }, + "raven/memory_engine/base.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 23, + "line_percent": 100.0, + "statements": 23 + }, + "raven/memory_engine/consolidate/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/memory_engine/consolidate/attention.py": { + "branch_percent": 90.0, + "branches": 20, + "covered_branches": 18, + "covered_lines": 48, + "line_percent": 97.959184, + "statements": 49 + }, + "raven/memory_engine/consolidate/behaviors.py": { + "branch_percent": 77.777778, + "branches": 54, + "covered_branches": 42, + "covered_lines": 127, + "line_percent": 90.714286, + "statements": 140 + }, + "raven/memory_engine/consolidate/behaviors_extractor.py": { + "branch_percent": 64.772727, + "branches": 88, + "covered_branches": 57, + "covered_lines": 216, + "line_percent": 81.203008, + "statements": 266 + }, + "raven/memory_engine/consolidate/consolidator.py": { + "branch_percent": 80.859375, + "branches": 256, + "covered_branches": 207, + "covered_lines": 573, + "line_percent": 88.69969, + "statements": 646 + }, + "raven/memory_engine/contract_test.py": { + "branch_percent": 50.0, + "branches": 2, + "covered_branches": 1, + "covered_lines": 46, + "line_percent": 88.461538, + "statements": 52 + }, + "raven/memory_engine/skill_forge/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 12, + "line_percent": 100.0, + "statements": 12 + }, + "raven/memory_engine/skill_forge/catalog.py": { + "branch_percent": 61.111111, + "branches": 72, + "covered_branches": 44, + "covered_lines": 131, + "line_percent": 75.287356, + "statements": 174 + }, + "raven/memory_engine/skill_forge/everos_source.py": { + "branch_percent": 66.666667, + "branches": 6, + "covered_branches": 4, + "covered_lines": 29, + "line_percent": 96.666667, + "statements": 30 + }, + "raven/memory_engine/skill_forge/fusion.py": { + "branch_percent": 100.0, + "branches": 8, + "covered_branches": 8, + "covered_lines": 25, + "line_percent": 100.0, + "statements": 25 + }, + "raven/memory_engine/skill_forge/gate.py": { + "branch_percent": 73.529412, + "branches": 34, + "covered_branches": 25, + "covered_lines": 100, + "line_percent": 87.719298, + "statements": 114 + }, + "raven/memory_engine/skill_forge/hub_source.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 28, + "line_percent": 100.0, + "statements": 28 + }, + "raven/memory_engine/skill_forge/local_source.py": { + "branch_percent": 83.333333, + "branches": 6, + "covered_branches": 5, + "covered_lines": 25, + "line_percent": 100.0, + "statements": 25 + }, + "raven/memory_engine/skill_forge/refs.py": { + "branch_percent": 100.0, + "branches": 14, + "covered_branches": 14, + "covered_lines": 43, + "line_percent": 100.0, + "statements": 43 + }, + "raven/memory_engine/skill_forge/rewriter.py": { + "branch_percent": 78.571429, + "branches": 14, + "covered_branches": 11, + "covered_lines": 61, + "line_percent": 96.825397, + "statements": 63 + }, + "raven/memory_engine/skill_forge/router.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 23, + "line_percent": 100.0, + "statements": 23 + }, + "raven/memory_engine/skill_forge/types.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 23, + "line_percent": 100.0, + "statements": 23 + }, + "raven/memory_engine/skill_local/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 100.0, + "statements": 4 + }, + "raven/memory_engine/skill_local/local_pool.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 41, + "line_percent": 100.0, + "statements": 41 + }, + "raven/memory_engine/skill_local/registry.py": { + "branch_percent": 75.0, + "branches": 128, + "covered_branches": 96, + "covered_lines": 237, + "line_percent": 85.559567, + "statements": 277 + }, + "raven/memory_engine/skill_local/types.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 38, + "line_percent": 100.0, + "statements": 38 + }, + "raven/memory_engine/skill_local/watcher.py": { + "branch_percent": 14.285714, + "branches": 14, + "covered_branches": 2, + "covered_lines": 30, + "line_percent": 56.603774, + "statements": 53 + }, + "raven/plugin/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 7, + "line_percent": 100.0, + "statements": 7 + }, + "raven/plugin/bootstrap.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 10, + "line_percent": 100.0, + "statements": 10 + }, + "raven/plugin/context.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 19, + "line_percent": 100.0, + "statements": 19 + }, + "raven/plugin/discover.py": { + "branch_percent": 83.333333, + "branches": 30, + "covered_branches": 25, + "covered_lines": 75, + "line_percent": 82.417582, + "statements": 91 + }, + "raven/plugin/manifest.py": { + "branch_percent": 100.0, + "branches": 10, + "covered_branches": 10, + "covered_lines": 62, + "line_percent": 100.0, + "statements": 62 + }, + "raven/plugin/memory/everos/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 1, + "line_percent": 100.0, + "statements": 1 + }, + "raven/plugin/memory/everos/_health.py": { + "branch_percent": 83.333333, + "branches": 6, + "covered_branches": 5, + "covered_lines": 34, + "line_percent": 79.069767, + "statements": 43 + }, + "raven/plugin/memory/everos/_server.py": { + "branch_percent": 75.0, + "branches": 8, + "covered_branches": 6, + "covered_lines": 34, + "line_percent": 59.649123, + "statements": 57 + }, + "raven/plugin/memory/everos/backend.py": { + "branch_percent": 86.111111, + "branches": 108, + "covered_branches": 93, + "covered_lines": 253, + "line_percent": 91.007194, + "statements": 278 + }, + "raven/plugin/memory/everos/multimodal.py": { + "branch_percent": 85.714286, + "branches": 14, + "covered_branches": 12, + "covered_lines": 65, + "line_percent": 90.277778, + "statements": 72 + }, + "raven/plugin/memory/everos/tools.py": { + "branch_percent": 90.0, + "branches": 10, + "covered_branches": 9, + "covered_lines": 45, + "line_percent": 91.836735, + "statements": 49 + }, + "raven/plugin/registry.py": { + "branch_percent": 90.909091, + "branches": 22, + "covered_branches": 20, + "covered_lines": 112, + "line_percent": 99.115044, + "statements": 113 + }, + "raven/proactive_engine/schedulers/cron/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 3, + "line_percent": 100.0, + "statements": 3 + }, + "raven/proactive_engine/schedulers/cron/service.py": { + "branch_percent": 67.613636, + "branches": 176, + "covered_branches": 119, + "covered_lines": 332, + "line_percent": 78.859857, + "statements": 421 + }, + "raven/proactive_engine/schedulers/cron/tool.py": { + "branch_percent": 62.5, + "branches": 40, + "covered_branches": 25, + "covered_lines": 83, + "line_percent": 76.851852, + "statements": 108 + }, + "raven/proactive_engine/schedulers/cron/types.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 42, + "line_percent": 100.0, + "statements": 42 + }, + "raven/proactive_engine/schedulers/heartbeat/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/proactive_engine/schedulers/heartbeat/service.py": { + "branch_percent": 72.222222, + "branches": 36, + "covered_branches": 26, + "covered_lines": 106, + "line_percent": 87.603306, + "statements": 121 + }, + "raven/proactive_engine/sentinel/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 13, + "line_percent": 100.0, + "statements": 13 + }, + "raven/proactive_engine/sentinel/attention_producers/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 15, + "line_percent": 100.0, + "statements": 15 + }, + "raven/proactive_engine/sentinel/attention_producers/_base.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 12, + "line_percent": 100.0, + "statements": 12 + }, + "raven/proactive_engine/sentinel/attention_producers/active_threads.py": { + "branch_percent": 66.666667, + "branches": 6, + "covered_branches": 4, + "covered_lines": 24, + "line_percent": 100.0, + "statements": 24 + }, + "raven/proactive_engine/sentinel/attention_producers/archived_patterns.py": { + "branch_percent": 83.333333, + "branches": 6, + "covered_branches": 5, + "covered_lines": 21, + "line_percent": 100.0, + "statements": 21 + }, + "raven/proactive_engine/sentinel/attention_producers/behavior_patterns.py": { + "branch_percent": 83.333333, + "branches": 12, + "covered_branches": 10, + "covered_lines": 29, + "line_percent": 93.548387, + "statements": 31 + }, + "raven/proactive_engine/sentinel/attention_producers/currently_focused.py": { + "branch_percent": 87.5, + "branches": 48, + "covered_branches": 42, + "covered_lines": 99, + "line_percent": 86.842105, + "statements": 114 + }, + "raven/proactive_engine/sentinel/attention_producers/daily_plan.py": { + "branch_percent": 47.916667, + "branches": 48, + "covered_branches": 23, + "covered_lines": 95, + "line_percent": 63.758389, + "statements": 149 + }, + "raven/proactive_engine/sentinel/attention_producers/pending_proposals.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 23, + "line_percent": 100.0, + "statements": 23 + }, + "raven/proactive_engine/sentinel/attention_producers/predicted_3d.py": { + "branch_percent": 83.333333, + "branches": 12, + "covered_branches": 10, + "covered_lines": 32, + "line_percent": 84.210526, + "statements": 38 + }, + "raven/proactive_engine/sentinel/attention_producers/project_rhythm.py": { + "branch_percent": 88.888889, + "branches": 18, + "covered_branches": 16, + "covered_lines": 47, + "line_percent": 92.156863, + "statements": 51 + }, + "raven/proactive_engine/sentinel/attention_producers/recent_decisions.py": { + "branch_percent": 75.0, + "branches": 20, + "covered_branches": 15, + "covered_lines": 50, + "line_percent": 81.967213, + "statements": 61 + }, + "raven/proactive_engine/sentinel/attention_producers/recently_abandoned.py": { + "branch_percent": 75.0, + "branches": 12, + "covered_branches": 9, + "covered_lines": 29, + "line_percent": 85.294118, + "statements": 34 + }, + "raven/proactive_engine/sentinel/attention_producers/rejected_cooldown.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 27, + "line_percent": 100.0, + "statements": 27 + }, + "raven/proactive_engine/sentinel/attention_producers/sentinel_observations.py": { + "branch_percent": 79.6875, + "branches": 64, + "covered_branches": 51, + "covered_lines": 129, + "line_percent": 88.356164, + "statements": 146 + }, + "raven/proactive_engine/sentinel/attention_producers/stance_log.py": { + "branch_percent": 70.588235, + "branches": 34, + "covered_branches": 24, + "covered_lines": 84, + "line_percent": 85.714286, + "statements": 98 + }, + "raven/proactive_engine/sentinel/attention_updater.py": { + "branch_percent": 80.0, + "branches": 10, + "covered_branches": 8, + "covered_lines": 46, + "line_percent": 80.701754, + "statements": 57 + }, + "raven/proactive_engine/sentinel/discover_triggers.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 57, + "line_percent": 91.935484, + "statements": 62 + }, + "raven/proactive_engine/sentinel/executor/action_executor.py": { + "branch_percent": 96.875, + "branches": 32, + "covered_branches": 31, + "covered_lines": 98, + "line_percent": 95.145631, + "statements": 103 + }, + "raven/proactive_engine/sentinel/executor/decision_consumer.py": { + "branch_percent": 60.416667, + "branches": 48, + "covered_branches": 29, + "covered_lines": 91, + "line_percent": 74.590164, + "statements": 122 + }, + "raven/proactive_engine/sentinel/executor/decision_router.py": { + "branch_percent": 76.5625, + "branches": 64, + "covered_branches": 49, + "covered_lines": 138, + "line_percent": 89.032258, + "statements": 155 + }, + "raven/proactive_engine/sentinel/executor/defer_manager.py": { + "branch_percent": 83.333333, + "branches": 48, + "covered_branches": 40, + "covered_lines": 175, + "line_percent": 91.623037, + "statements": 191 + }, + "raven/proactive_engine/sentinel/executor/dispatcher.py": { + "branch_percent": 95.454545, + "branches": 22, + "covered_branches": 21, + "covered_lines": 68, + "line_percent": 97.142857, + "statements": 70 + }, + "raven/proactive_engine/sentinel/executor/injector.py": { + "branch_percent": 87.5, + "branches": 32, + "covered_branches": 28, + "covered_lines": 103, + "line_percent": 96.261682, + "statements": 107 + }, + "raven/proactive_engine/sentinel/executor/pending_decision.py": { + "branch_percent": 94.0, + "branches": 50, + "covered_branches": 47, + "covered_lines": 166, + "line_percent": 91.208791, + "statements": 182 + }, + "raven/proactive_engine/sentinel/executor/runner.py": { + "branch_percent": 65.151515, + "branches": 264, + "covered_branches": 172, + "covered_lines": 525, + "line_percent": 74.89301, + "statements": 701 + }, + "raven/proactive_engine/sentinel/executor/spawn.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 38, + "line_percent": 100.0, + "statements": 38 + }, + "raven/proactive_engine/sentinel/feedback/persistence.py": { + "branch_percent": 50.0, + "branches": 4, + "covered_branches": 2, + "covered_lines": 35, + "line_percent": 92.105263, + "statements": 38 + }, + "raven/proactive_engine/sentinel/feedback/tracker.py": { + "branch_percent": 82.727273, + "branches": 110, + "covered_branches": 91, + "covered_lines": 226, + "line_percent": 88.28125, + "statements": 256 + }, + "raven/proactive_engine/sentinel/planner.py": { + "branch_percent": 95.0, + "branches": 20, + "covered_branches": 19, + "covered_lines": 69, + "line_percent": 94.520548, + "statements": 73 + }, + "raven/proactive_engine/sentinel/predictor/context_assembler.py": { + "branch_percent": 84.782609, + "branches": 92, + "covered_branches": 78, + "covered_lines": 226, + "line_percent": 85.931559, + "statements": 263 + }, + "raven/proactive_engine/sentinel/predictor/daily_analysis.py": { + "branch_percent": 72.826087, + "branches": 92, + "covered_branches": 67, + "covered_lines": 199, + "line_percent": 82.916667, + "statements": 240 + }, + "raven/proactive_engine/sentinel/predictor/prompts.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 11, + "line_percent": 100.0, + "statements": 11 + }, + "raven/proactive_engine/sentinel/predictor/routine_aggregator.py": { + "branch_percent": 79.166667, + "branches": 24, + "covered_branches": 19, + "covered_lines": 71, + "line_percent": 95.945946, + "statements": 74 + }, + "raven/proactive_engine/sentinel/predictor/routine_learner.py": { + "branch_percent": 95.833333, + "branches": 72, + "covered_branches": 69, + "covered_lines": 195, + "line_percent": 97.5, + "statements": 200 + }, + "raven/proactive_engine/sentinel/predictor/routine_store.py": { + "branch_percent": 89.655172, + "branches": 58, + "covered_branches": 52, + "covered_lines": 162, + "line_percent": 94.736842, + "statements": 171 + }, + "raven/proactive_engine/sentinel/predictor/routine_validator.py": { + "branch_percent": 85.714286, + "branches": 14, + "covered_branches": 12, + "covered_lines": 55, + "line_percent": 100.0, + "statements": 55 + }, + "raven/proactive_engine/sentinel/predictor/task_discoverer.py": { + "branch_percent": 75.0, + "branches": 100, + "covered_branches": 75, + "covered_lines": 236, + "line_percent": 78.666667, + "statements": 300 + }, + "raven/proactive_engine/sentinel/tools/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 2 + }, + "raven/proactive_engine/sentinel/tools/nudge_feedback_tool.py": { + "branch_percent": 0.0, + "branches": 4, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 31 + }, + "raven/proactive_engine/sentinel/trigger_policy/derive_dnd.py": { + "branch_percent": 90.0, + "branches": 50, + "covered_branches": 45, + "covered_lines": 107, + "line_percent": 92.241379, + "statements": 116 + }, + "raven/proactive_engine/sentinel/trigger_policy/policy.py": { + "branch_percent": 78.571429, + "branches": 168, + "covered_branches": 132, + "covered_lines": 296, + "line_percent": 87.833828, + "statements": 337 + }, + "raven/proactive_engine/sentinel/trigger_policy/prefs.py": { + "branch_percent": 90.0, + "branches": 20, + "covered_branches": 18, + "covered_lines": 62, + "line_percent": 93.939394, + "statements": 66 + }, + "raven/proactive_engine/sentinel/trigger_policy/prompts.py": { + "branch_percent": 50.0, + "branches": 58, + "covered_branches": 29, + "covered_lines": 58, + "line_percent": 56.31068, + "statements": 103 + }, + "raven/proactive_engine/sentinel/types.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 115, + "line_percent": 100.0, + "statements": 115 + }, + "raven/proactive_engine/system_events.py": { + "branch_percent": 83.333333, + "branches": 6, + "covered_branches": 5, + "covered_lines": 38, + "line_percent": 97.435897, + "statements": 39 + }, + "raven/proactive_engine/wake.py": { + "branch_percent": 91.666667, + "branches": 12, + "covered_branches": 11, + "covered_lines": 44, + "line_percent": 95.652174, + "statements": 46 + }, + "raven/providers/__init__.py": { + "branch_percent": 50.0, + "branches": 2, + "covered_branches": 1, + "covered_lines": 8, + "line_percent": 72.727273, + "statements": 11 + }, + "raven/providers/auth.py": { + "branch_percent": 92.5, + "branches": 40, + "covered_branches": 37, + "covered_lines": 121, + "line_percent": 97.580645, + "statements": 124 + }, + "raven/providers/azure_openai_provider.py": { + "branch_percent": 34.615385, + "branches": 26, + "covered_branches": 9, + "covered_lines": 57, + "line_percent": 67.058824, + "statements": 85 + }, + "raven/providers/base.py": { + "branch_percent": 83.75, + "branches": 80, + "covered_branches": 67, + "covered_lines": 220, + "line_percent": 89.068826, + "statements": 247 + }, + "raven/providers/capabilities.py": { + "branch_percent": 90.625, + "branches": 32, + "covered_branches": 29, + "covered_lines": 80, + "line_percent": 97.560976, + "statements": 82 + }, + "raven/providers/catalog.py": { + "branch_percent": 91.666667, + "branches": 12, + "covered_branches": 11, + "covered_lines": 61, + "line_percent": 98.387097, + "statements": 62 + }, + "raven/providers/chatgpt_token.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 47, + "line_percent": 100.0, + "statements": 47 + }, + "raven/providers/codex_catalog.py": { + "branch_percent": 81.25, + "branches": 16, + "covered_branches": 13, + "covered_lines": 56, + "line_percent": 98.245614, + "statements": 57 + }, + "raven/providers/common_models.py": { + "branch_percent": 95.0, + "branches": 20, + "covered_branches": 19, + "covered_lines": 55, + "line_percent": 100.0, + "statements": 55 + }, + "raven/providers/endpoint_rotor.py": { + "branch_percent": 67.857143, + "branches": 28, + "covered_branches": 19, + "covered_lines": 122, + "line_percent": 89.051095, + "statements": 137 + }, + "raven/providers/endpoints.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 23, + "line_percent": 100.0, + "statements": 23 + }, + "raven/providers/lazy.py": { + "branch_percent": 100.0, + "branches": 12, + "covered_branches": 12, + "covered_lines": 67, + "line_percent": 100.0, + "statements": 67 + }, + "raven/providers/litellm_provider.py": { + "branch_percent": 63.970588, + "branches": 136, + "covered_branches": 87, + "covered_lines": 250, + "line_percent": 76.687117, + "statements": 326 + }, + "raven/providers/litellm_provider_names.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/providers/litellm_setup.py": { + "branch_percent": 100.0, + "branches": 10, + "covered_branches": 10, + "covered_lines": 28, + "line_percent": 100.0, + "statements": 28 + }, + "raven/providers/minimax_oauth.py": { + "branch_percent": 75.0, + "branches": 48, + "covered_branches": 36, + "covered_lines": 187, + "line_percent": 86.175115, + "statements": 217 + }, + "raven/providers/minimax_oauth_provider.py": { + "branch_percent": 0.0, + "branches": 2, + "covered_branches": 0, + "covered_lines": 24, + "line_percent": 88.888889, + "statements": 27 + }, + "raven/providers/model_catalog_cache.py": { + "branch_percent": 80.0, + "branches": 10, + "covered_branches": 8, + "covered_lines": 38, + "line_percent": 86.363636, + "statements": 44 + }, + "raven/providers/openai_codex_provider.py": { + "branch_percent": 46.153846, + "branches": 104, + "covered_branches": 48, + "covered_lines": 150, + "line_percent": 66.666667, + "statements": 225 + }, + "raven/providers/per_model_provider.py": { + "branch_percent": 93.75, + "branches": 16, + "covered_branches": 15, + "covered_lines": 49, + "line_percent": 98.0, + "statements": 50 + }, + "raven/providers/pin.py": { + "branch_percent": 100.0, + "branches": 14, + "covered_branches": 14, + "covered_lines": 35, + "line_percent": 100.0, + "statements": 35 + }, + "raven/providers/prompt_cache.py": { + "branch_percent": 94.444444, + "branches": 18, + "covered_branches": 17, + "covered_lines": 57, + "line_percent": 100.0, + "statements": 57 + }, + "raven/providers/rates.py": { + "branch_percent": 85.897436, + "branches": 78, + "covered_branches": 67, + "covered_lines": 202, + "line_percent": 88.986784, + "statements": 227 + }, + "raven/providers/reasoning.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 15, + "line_percent": 100.0, + "statements": 15 + }, + "raven/providers/registry.py": { + "branch_percent": 98.214286, + "branches": 56, + "covered_branches": 55, + "covered_lines": 140, + "line_percent": 99.29078, + "statements": 141 + }, + "raven/providers/transcription.py": { + "branch_percent": 0.0, + "branches": 4, + "covered_branches": 0, + "covered_lines": 7, + "line_percent": 25.0, + "statements": 28 + }, + "raven/providers/wire.py": { + "branch_percent": 97.222222, + "branches": 36, + "covered_branches": 35, + "covered_lines": 68, + "line_percent": 98.550725, + "statements": 69 + }, + "raven/routing/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/routing/cache.py": { + "branch_percent": 0.0, + "branches": 20, + "covered_branches": 0, + "covered_lines": 26, + "line_percent": 26.262626, + "statements": 99 + }, + "raven/routing/classifier.py": { + "branch_percent": 0.0, + "branches": 12, + "covered_branches": 0, + "covered_lines": 22, + "line_percent": 33.333333, + "statements": 66 + }, + "raven/routing/fetcher.py": { + "branch_percent": 0.0, + "branches": 16, + "covered_branches": 0, + "covered_lines": 15, + "line_percent": 20.27027, + "statements": 74 + }, + "raven/routing/generate_embeddings.py": { + "branch_percent": 0.0, + "branches": 2, + "covered_branches": 0, + "covered_lines": 0, + "line_percent": 0.0, + "statements": 19 + }, + "raven/routing/knn_router.py": { + "branch_percent": 81.25, + "branches": 32, + "covered_branches": 26, + "covered_lines": 115, + "line_percent": 83.333333, + "statements": 138 + }, + "raven/routing/profiles.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/routing/router.py": { + "branch_percent": 60.0, + "branches": 10, + "covered_branches": 6, + "covered_lines": 31, + "line_percent": 54.385965, + "statements": 57 + }, + "raven/routing/selector.py": { + "branch_percent": 0.0, + "branches": 14, + "covered_branches": 0, + "covered_lines": 12, + "line_percent": 24.489796, + "statements": 49 + }, + "raven/routing/types.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 40, + "line_percent": 100.0, + "statements": 40 + }, + "raven/sandbox/__init__.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 21, + "line_percent": 91.304348, + "statements": 23 + }, + "raven/sandbox/_async_utils.py": { + "branch_percent": 50.0, + "branches": 2, + "covered_branches": 1, + "covered_lines": 8, + "line_percent": 72.727273, + "statements": 11 + }, + "raven/sandbox/_runtime.py": { + "branch_percent": 50.0, + "branches": 2, + "covered_branches": 1, + "covered_lines": 13, + "line_percent": 100.0, + "statements": 13 + }, + "raven/sandbox/boxlite_executor.py": { + "branch_percent": 60.0, + "branches": 50, + "covered_branches": 30, + "covered_lines": 183, + "line_percent": 75.0, + "statements": 244 + }, + "raven/sandbox/config.py": { + "branch_percent": 91.666667, + "branches": 12, + "covered_branches": 11, + "covered_lines": 45, + "line_percent": 97.826087, + "statements": 46 + }, + "raven/sandbox/debug_server.py": { + "branch_percent": 78.04878, + "branches": 82, + "covered_branches": 64, + "covered_lines": 258, + "line_percent": 77.710843, + "statements": 332 + }, + "raven/sandbox/direct_executor.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 26, + "line_percent": 100.0, + "statements": 26 + }, + "raven/sandbox/interfaces.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 39, + "line_percent": 97.5, + "statements": 40 + }, + "raven/security/network.py": { + "branch_percent": 88.888889, + "branches": 18, + "covered_branches": 16, + "covered_lines": 46, + "line_percent": 82.142857, + "statements": 56 + }, + "raven/security/trust.py": { + "branch_percent": 100.0, + "branches": 8, + "covered_branches": 8, + "covered_lines": 18, + "line_percent": 100.0, + "statements": 18 + }, + "raven/session/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/session/export.py": { + "branch_percent": 52.941176, + "branches": 34, + "covered_branches": 18, + "covered_lines": 64, + "line_percent": 79.012346, + "statements": 81 + }, + "raven/session/manager.py": { + "branch_percent": 87.837838, + "branches": 74, + "covered_branches": 65, + "covered_lines": 229, + "line_percent": 93.469388, + "statements": 245 + }, + "raven/skill_hub/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/skill_hub/client.py": { + "branch_percent": 64.285714, + "branches": 28, + "covered_branches": 18, + "covered_lines": 75, + "line_percent": 75.0, + "statements": 100 + }, + "raven/spine/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 6, + "line_percent": 100.0, + "statements": 6 + }, + "raven/spine/delivery.py": { + "branch_percent": 92.5, + "branches": 40, + "covered_branches": 37, + "covered_lines": 122, + "line_percent": 100.0, + "statements": 122 + }, + "raven/spine/events.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 78, + "line_percent": 100.0, + "statements": 78 + }, + "raven/spine/message.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 19, + "line_percent": 100.0, + "statements": 19 + }, + "raven/spine/runner.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 13, + "line_percent": 100.0, + "statements": 13 + }, + "raven/spine/scheduler.py": { + "branch_percent": 93.902439, + "branches": 82, + "covered_branches": 77, + "covered_lines": 233, + "line_percent": 98.312236, + "statements": 237 + }, + "raven/spine/turn.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 27, + "line_percent": 100.0, + "statements": 27 + }, + "raven/token_wise/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 5, + "line_percent": 100.0, + "statements": 5 + }, + "raven/token_wise/base.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 22, + "line_percent": 100.0, + "statements": 22 + }, + "raven/token_wise/cache_optimizer.py": { + "branch_percent": 90.909091, + "branches": 22, + "covered_branches": 20, + "covered_lines": 58, + "line_percent": 96.666667, + "statements": 60 + }, + "raven/token_wise/pricing.py": { + "branch_percent": 100.0, + "branches": 6, + "covered_branches": 6, + "covered_lines": 18, + "line_percent": 100.0, + "statements": 18 + }, + "raven/token_wise/registry.py": { + "branch_percent": 100.0, + "branches": 10, + "covered_branches": 10, + "covered_lines": 32, + "line_percent": 96.969697, + "statements": 33 + }, + "raven/token_wise/system_and_tail_cache.py": { + "branch_percent": 0.0, + "branches": 14, + "covered_branches": 0, + "covered_lines": 14, + "line_percent": 35.897436, + "statements": 39 + }, + "raven/token_wise/usage_tracker.py": { + "branch_percent": 93.75, + "branches": 16, + "covered_branches": 15, + "covered_lines": 73, + "line_percent": 97.333333, + "statements": 75 + }, + "raven/tracing/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 4, + "line_percent": 80.0, + "statements": 5 + }, + "raven/tracing/config.py": { + "branch_percent": 70.0, + "branches": 10, + "covered_branches": 7, + "covered_lines": 32, + "line_percent": 64.0, + "statements": 50 + }, + "raven/tracing/context.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 33, + "line_percent": 76.744186, + "statements": 43 + }, + "raven/tracing/semconv.py": { + "branch_percent": 73.75, + "branches": 80, + "covered_branches": 59, + "covered_lines": 281, + "line_percent": 88.924051, + "statements": 316 + }, + "raven/tracing/spans.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 27, + "line_percent": 87.096774, + "statements": 31 + }, + "raven/tracing/store.py": { + "branch_percent": 72.222222, + "branches": 18, + "covered_branches": 13, + "covered_lines": 81, + "line_percent": 80.19802, + "statements": 101 + }, + "raven/tracing/trace.py": { + "branch_percent": 87.5, + "branches": 24, + "covered_branches": 21, + "covered_lines": 163, + "line_percent": 85.789474, + "statements": 190 + }, + "raven/tracing/usage.py": { + "branch_percent": 50.0, + "branches": 4, + "covered_branches": 2, + "covered_lines": 17, + "line_percent": 80.952381, + "statements": 21 + }, + "raven/tui_rpc/_ansi_filter.py": { + "branch_percent": 100.0, + "branches": 2, + "covered_branches": 2, + "covered_lines": 15, + "line_percent": 100.0, + "statements": 15 + }, + "raven/tui_rpc/_confirm_injection.py": { + "branch_percent": 50.0, + "branches": 2, + "covered_branches": 1, + "covered_lines": 23, + "line_percent": 95.833333, + "statements": 24 + }, + "raven/tui_rpc/_console_injection.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 27, + "line_percent": 100.0, + "statements": 27 + }, + "raven/tui_rpc/approval_broker.py": { + "branch_percent": 80.0, + "branches": 10, + "covered_branches": 8, + "covered_lines": 57, + "line_percent": 95.0, + "statements": 60 + }, + "raven/tui_rpc/confirm_broker.py": { + "branch_percent": 83.333333, + "branches": 6, + "covered_branches": 5, + "covered_lines": 39, + "line_percent": 92.857143, + "statements": 42 + }, + "raven/tui_rpc/dispatcher.py": { + "branch_percent": 65.384615, + "branches": 26, + "covered_branches": 17, + "covered_lines": 57, + "line_percent": 83.823529, + "statements": 68 + }, + "raven/tui_rpc/errors.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 73, + "line_percent": 100.0, + "statements": 73 + }, + "raven/tui_rpc/methods/__init__.py": { + "branch_percent": 100.0, + "branches": 8, + "covered_branches": 8, + "covered_lines": 40, + "line_percent": 100.0, + "statements": 40 + }, + "raven/tui_rpc/methods/_stubs.py": { + "branch_percent": 100.0, + "branches": 2, + "covered_branches": 2, + "covered_lines": 11, + "line_percent": 100.0, + "statements": 11 + }, + "raven/tui_rpc/methods/_typer_reflect.py": { + "branch_percent": 70.0, + "branches": 10, + "covered_branches": 7, + "covered_lines": 16, + "line_percent": 88.888889, + "statements": 18 + }, + "raven/tui_rpc/methods/approval.py": { + "branch_percent": 100.0, + "branches": 2, + "covered_branches": 2, + "covered_lines": 13, + "line_percent": 92.857143, + "statements": 14 + }, + "raven/tui_rpc/methods/cli_dispatch.py": { + "branch_percent": 96.153846, + "branches": 26, + "covered_branches": 25, + "covered_lines": 103, + "line_percent": 95.37037, + "statements": 108 + }, + "raven/tui_rpc/methods/commands.py": { + "branch_percent": 78.26087, + "branches": 46, + "covered_branches": 36, + "covered_lines": 107, + "line_percent": 87.704918, + "statements": 122 + }, + "raven/tui_rpc/methods/config.py": { + "branch_percent": 77.419355, + "branches": 62, + "covered_branches": 48, + "covered_lines": 134, + "line_percent": 90.540541, + "statements": 148 + }, + "raven/tui_rpc/methods/confirm.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 11, + "line_percent": 91.666667, + "statements": 12 + }, + "raven/tui_rpc/methods/model.py": { + "branch_percent": 96.666667, + "branches": 30, + "covered_branches": 29, + "covered_lines": 175, + "line_percent": 91.623037, + "statements": 191 + }, + "raven/tui_rpc/methods/question.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 11, + "line_percent": 91.666667, + "statements": 12 + }, + "raven/tui_rpc/methods/reload.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 7, + "line_percent": 100.0, + "statements": 7 + }, + "raven/tui_rpc/methods/session.py": { + "branch_percent": 87.837838, + "branches": 74, + "covered_branches": 65, + "covered_lines": 247, + "line_percent": 90.145985, + "statements": 274 + }, + "raven/tui_rpc/methods/setup.py": { + "branch_percent": 75.0, + "branches": 16, + "covered_branches": 12, + "covered_lines": 55, + "line_percent": 87.301587, + "statements": 63 + }, + "raven/tui_rpc/methods/slash_routing.py": { + "branch_percent": 85.714286, + "branches": 14, + "covered_branches": 12, + "covered_lines": 55, + "line_percent": 84.615385, + "statements": 65 + }, + "raven/tui_rpc/methods/system.py": { + "branch_percent": 100.0, + "branches": 4, + "covered_branches": 4, + "covered_lines": 33, + "line_percent": 94.285714, + "statements": 35 + }, + "raven/tui_rpc/methods/terminal.py": { + "branch_percent": 90.0, + "branches": 10, + "covered_branches": 9, + "covered_lines": 26, + "line_percent": 100.0, + "statements": 26 + }, + "raven/tui_rpc/methods/turn.py": { + "branch_percent": 76.666667, + "branches": 30, + "covered_branches": 23, + "covered_lines": 108, + "line_percent": 96.428571, + "statements": 112 + }, + "raven/tui_rpc/models.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 389, + "line_percent": 100.0, + "statements": 389 + }, + "raven/tui_rpc/question_broker.py": { + "branch_percent": 78.571429, + "branches": 14, + "covered_branches": 11, + "covered_lines": 55, + "line_percent": 93.220339, + "statements": 59 + }, + "raven/tui_rpc/server.py": { + "branch_percent": 60.714286, + "branches": 28, + "covered_branches": 17, + "covered_lines": 96, + "line_percent": 76.190476, + "statements": 126 + }, + "raven/tui_rpc/spine.py": { + "branch_percent": 92.5, + "branches": 40, + "covered_branches": 37, + "covered_lines": 112, + "line_percent": 99.115044, + "statements": 113 + }, + "raven/tui_rpc/subscriptions.py": { + "branch_percent": 82.352941, + "branches": 34, + "covered_branches": 28, + "covered_lines": 90, + "line_percent": 92.783505, + "statements": 97 + }, + "raven/utils/__init__.py": { + "branch_percent": 100.0, + "branches": 0, + "covered_branches": 0, + "covered_lines": 2, + "line_percent": 100.0, + "statements": 2 + }, + "raven/utils/atomic_io.py": { + "branch_percent": 83.333333, + "branches": 6, + "covered_branches": 5, + "covered_lines": 31, + "line_percent": 96.875, + "statements": 32 + }, + "raven/utils/bm25.py": { + "branch_percent": 100.0, + "branches": 16, + "covered_branches": 16, + "covered_lines": 40, + "line_percent": 100.0, + "statements": 40 + }, + "raven/utils/helpers.py": { + "branch_percent": 75.0, + "branches": 128, + "covered_branches": 96, + "covered_lines": 220, + "line_percent": 83.018868, + "statements": 265 + }, + "raven/utils/portable_lock.py": { + "branch_percent": 50.0, + "branches": 2, + "covered_branches": 1, + "covered_lines": 22, + "line_percent": 81.481481, + "statements": 27 + }, + "raven/utils/text.py": { + "branch_percent": 80.0, + "branches": 10, + "covered_branches": 8, + "covered_lines": 33, + "line_percent": 94.285714, + "statements": 35 + } + }, + "python": "3.12", + "reference_commit": "840d05101b393354e24bba0191b6d9ae40790df6", + "schema_version": 1, + "source": "raven", + "totals": { + "branch_percent": 64.038959, + "branches": 13142, + "covered_branches": 8416, + "covered_lines": 32494, + "line_percent": 75.56217, + "statements": 43003 + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a90301b1..54acb2de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,6 +73,8 @@ jobs: name: unit (py${{ matrix.python }} / ${{ matrix.os }}) runs-on: ${{ matrix.os }} timeout-minutes: 20 + env: + PYTHON_VERSION: ${{ matrix.python }} strategy: fail-fast: false matrix: @@ -80,6 +82,8 @@ jobs: python: ["3.12"] steps: - uses: actions/checkout@v7 + with: + fetch-depth: 0 - name: Install uv uses: astral-sh/setup-uv@v8.2.0 @@ -91,15 +95,54 @@ jobs: run: uv python install ${{ matrix.python }} - name: Install dependencies - run: uv sync --locked --all-extras --dev + run: uv sync --locked --all-extras --dev --python ${{ matrix.python }} - - name: Unit tests (integration + e2e deselected by norecursedirs + addopts) + - name: Unit tests with line and branch coverage # TERM=dumb: typer's rich_utils force-enables colour when GITHUB_ACTIONS is # set, and the highlighter then splits option names with escape codes, so # `"--flag" in result.stdout` assertions fail only on CI. + run: make coverage + + - name: Coverage summary and lowest-covered files + if: ${{ !cancelled() && hashFiles('coverage.json') != '' }} + run: make coverage-summary + + - name: Overall coverage ratchet + if: ${{ !cancelled() && hashFiles('coverage.json') != '' }} + run: make coverage-ratchet + + - name: Coverage baseline cannot decrease + if: ${{ !cancelled() && github.event_name == 'pull_request' }} env: - TERM: dumb - run: uv run --all-extras pytest -q + COVERAGE_BASE_REF: origin/${{ github.base_ref }} + run: make coverage-baseline-check + + - name: PR diff coverage + if: ${{ !cancelled() && github.event_name == 'pull_request' && hashFiles('coverage.json') != '' }} + env: + COVERAGE_BASE_REF: origin/${{ github.base_ref }} + run: make coverage-diff + + - name: Explain skipped diff coverage + if: ${{ !cancelled() && github.event_name != 'pull_request' }} + run: echo "Diff coverage is a PR-only gate; push builds still enforce the overall ratchet." + + - name: Create auditable baseline candidate + if: ${{ !cancelled() && hashFiles('coverage.json') != '' }} + run: make coverage-baseline-candidate + + - name: Upload coverage reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: python-coverage-py${{ matrix.python }}-${{ matrix.os }} + path: | + coverage.xml + coverage.json + htmlcov/ + coverage-baseline-candidate.json + if-no-files-found: warn + retention-days: 30 windows-upgrade: name: Windows self-upgrade diff --git a/.gitignore b/.gitignore index 04df3378..3ca133ab 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ poetry.lock .coverage .coverage.* coverage.xml +coverage.json +coverage-baseline-candidate.json htmlcov/ botpy.log nano.*.save diff --git a/AGENTS.md b/AGENTS.md index 09d1efa6..aea6dd75 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,6 +22,9 @@ Hard constraints only (violations get reverted / rejected). Soft suggestions and ### ยง1.1 Top rule: don't add comments unless necessary +- Every new code file must document its purpose in English with an appropriate + module docstring or equivalent file-level documentation. Inline comments + still follow the necessity rules below. - Match the style of surrounding lines. If neighboring code has no comments, **don't** add one to your new line. - Comment **only** when: - the logic is non-obvious; diff --git a/Makefile b/Makefile index aa501989..8b916740 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,15 @@ -.PHONY: help install install-deps lint lint-python lint-tui lint-bridge test test-python test-tui build build-tui build-bridge check-commits check-pr-title check-large-files ci clean +.PHONY: help install install-deps lint lint-python lint-tui lint-bridge test test-python test-tui coverage coverage-summary coverage-diff coverage-ratchet coverage-baseline-check coverage-baseline-candidate build build-tui build-bridge check-commits check-pr-title check-large-files ci clean PYTHON ?= python3 -PYTHON_LINT_TARGETS ?= scripts/check_commit_file.py scripts/check_commit_messages.py scripts/check_pr_title.py scripts/check_large_files.py scripts/commit_lint.py tests/test_commit_lint.py tests/test_large_file_check.py +PYTHON_VERSION ?= 3.12 +PYTHON_LINT_TARGETS ?= scripts/check_commit_file.py scripts/check_commit_messages.py scripts/check_pr_title.py scripts/check_large_files.py scripts/commit_lint.py scripts/coverage_gate.py tests/test_commit_lint.py tests/test_coverage_gate.py tests/test_large_file_check.py COMMIT_RANGE ?= origin/main..HEAD +COVERAGE_BASE_REF ?= origin/main +# Required coverage percentage for executable lines changed by a PR. +COVERAGE_DIFF_THRESHOLD ?= 90 +# Allowed line or branch regression in percentage points to absorb rounding noise. +COVERAGE_RATCHET_TOLERANCE ?= 0.05 +COVERAGE_REPORT_ARGS = --cov=raven --cov-branch --cov-report=term-missing:skip-covered --cov-report=xml --cov-report=json --cov-report=html help: @echo "Targets:" @@ -13,6 +20,10 @@ help: @echo " lint-tui TypeScript lint + RPC drift check" @echo " lint-bridge Bridge package build check" @echo " test Run focused Python checks and TUI tests" + @echo " coverage Run the default Python suite with line and branch coverage" + @echo " coverage-diff Check changed executable lines against COVERAGE_BASE_REF" + @echo " coverage-ratchet Check total line and branch coverage against the baseline" + @echo " coverage-baseline-check Ensure a proposed baseline never lowers the target branch" @echo " check-commits Validate Conventional Commit subjects" @echo " check-pr-title Validate the PR title in PR_TITLE" @echo " check-large-files Validate PR files avoid blocked assets and size bloat" @@ -20,11 +31,11 @@ help: @echo " clean Remove generated caches and build output" install-deps: - uv sync --frozen --extra dev --dev + uv sync --frozen --python $(PYTHON_VERSION) --extra dev --dev install: install-deps - uv run pre-commit install - uv run pre-commit install --hook-type commit-msg + uv run --frozen --python $(PYTHON_VERSION) pre-commit install + uv run --frozen --python $(PYTHON_VERSION) pre-commit install --hook-type commit-msg npm ci npm ci --prefix ui-tui npm ci --prefix bridge @@ -32,8 +43,8 @@ install: install-deps lint: lint-python lint-tui lint-bridge lint-python: - uv run --extra dev ruff check $(PYTHON_LINT_TARGETS) - uv run --extra dev ruff format --check $(PYTHON_LINT_TARGETS) + uv run --frozen --python $(PYTHON_VERSION) --extra dev ruff check $(PYTHON_LINT_TARGETS) + uv run --frozen --python $(PYTHON_VERSION) --extra dev ruff format --check $(PYTHON_LINT_TARGETS) lint-tui: npm run lint --prefix ui-tui @@ -46,7 +57,25 @@ lint-bridge: test: test-python test-tui test-python: - uv run --all-extras pytest -q + uv run --frozen --python $(PYTHON_VERSION) --all-extras pytest -q + +coverage: + TERM=dumb uv run --frozen --python $(PYTHON_VERSION) --all-extras pytest -q $(COVERAGE_REPORT_ARGS) + +coverage-summary: + uv run --frozen --python $(PYTHON_VERSION) python scripts/coverage_gate.py summary + +coverage-diff: + uv run --frozen --python $(PYTHON_VERSION) python scripts/coverage_gate.py diff --base-ref $(COVERAGE_BASE_REF) --threshold $(COVERAGE_DIFF_THRESHOLD) + +coverage-ratchet: + uv run --frozen --python $(PYTHON_VERSION) python scripts/coverage_gate.py ratchet --tolerance $(COVERAGE_RATCHET_TOLERANCE) + +coverage-baseline-check: + uv run --frozen --python $(PYTHON_VERSION) python scripts/coverage_gate.py baseline-check --base-ref $(COVERAGE_BASE_REF) + +coverage-baseline-candidate: + uv run --frozen --python $(PYTHON_VERSION) python scripts/coverage_gate.py baseline test-tui: npm test --prefix ui-tui @@ -61,18 +90,18 @@ build-bridge: check-commits: npx commitlint --from origin/main --to HEAD --config commitlint.config.cjs - PYTHONPATH=. uv run --extra dev python scripts/check_commit_messages.py $(COMMIT_RANGE) + PYTHONPATH=. uv run --frozen --python $(PYTHON_VERSION) --extra dev python scripts/check_commit_messages.py $(COMMIT_RANGE) check-pr-title: - PYTHONPATH=. uv run --extra dev python scripts/check_pr_title.py + PYTHONPATH=. uv run --frozen --python $(PYTHON_VERSION) --extra dev python scripts/check_pr_title.py check-large-files: - PYTHONPATH=. uv run --extra dev python scripts/check_large_files.py $(COMMIT_RANGE) + PYTHONPATH=. uv run --frozen --python $(PYTHON_VERSION) --extra dev python scripts/check_large_files.py $(COMMIT_RANGE) ci: lint test build clean: - rm -rf .pytest_cache .ruff_cache .uv-cache .mypy_cache htmlcov dist build + rm -rf .pytest_cache .ruff_cache .uv-cache .mypy_cache htmlcov coverage.xml coverage.json coverage-baseline-candidate.json dist build rm -rf ui-tui/dist ui-tui/coverage ui-tui/.vitest-cache ui-tui/packages/hermes-ink/dist rm -rf bridge/dist find . -type d -name __pycache__ -prune -exec rm -rf {} + diff --git a/pyproject.toml b/pyproject.toml index 279a57a0..de4eaf70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -270,6 +270,37 @@ markers = [ "llm_judge: uses an LLM as a grader for extraction quality (extra cost / non-determinism); opt-in", ] +[tool.coverage.run] +branch = true +source = ["raven"] +omit = [ + "raven/__main__.py", + "raven/evolver/__main__.py", + "raven/utils/win_fcntl_shim.py", +] + +[tool.coverage.report] +precision = 2 +show_missing = true +omit = [ + "raven/__main__.py", + "raven/evolver/__main__.py", + "raven/utils/win_fcntl_shim.py", +] +exclude_also = [ + "if TYPE_CHECKING:", + "if __name__ == .__main__.:", +] + +[tool.coverage.html] +directory = "htmlcov" + +[tool.coverage.xml] +output = "coverage.xml" + +[tool.coverage.json] +output = "coverage.json" + [dependency-groups] dev = [ "json-repair>=0.59.5", diff --git a/scripts/coverage_gate.py b/scripts/coverage_gate.py new file mode 100644 index 00000000..52e7d8d7 --- /dev/null +++ b/scripts/coverage_gate.py @@ -0,0 +1,355 @@ +"""Coverage reporting and governance gates shared by CI and local development.""" + +from __future__ import annotations + +import argparse +import json +import re +import shutil +import subprocess +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Iterable + +SCHEMA_VERSION = 1 +DEFAULT_TOLERANCE = 0.05 +# Glob magic so ** matches zero-or-more path components; the default pathspec +# raven/**/*.py drops top-level files like raven/__init__.py because plain * spans +# slashes and the middle separator then has nothing to match. +PRODUCTION_PATHSPEC = ":(glob)raven/**/*.py" +DEFAULT_DIFF_THRESHOLD = 90.0 +OMITTED_PATHS = { + "raven/__main__.py", + "raven/evolver/__main__.py", + "raven/utils/win_fcntl_shim.py", +} + + +@dataclass(frozen=True) +class Metrics: + statements: int + covered_lines: int + branches: int + covered_branches: int + + @property + def line_percent(self) -> float: + return _percent(self.covered_lines, self.statements) + + @property + def branch_percent(self) -> float: + return _percent(self.covered_branches, self.branches) + + +@dataclass(frozen=True) +class DiffCoverage: + executable: frozenset[tuple[str, int]] + uncovered: frozenset[tuple[str, int]] + zero_coverage_files: tuple[str, ...] + unmeasured_files: tuple[str, ...] + + @property + def percent(self) -> float: + return _percent(len(self.executable) - len(self.uncovered), len(self.executable)) + + +def _percent(covered: int, total: int) -> float: + return 100.0 if total == 0 else covered * 100.0 / total + + +def _metrics(summary: dict[str, Any]) -> Metrics: + return Metrics( + statements=int(summary["num_statements"]), + covered_lines=int(summary["covered_lines"]), + branches=int(summary["num_branches"]), + covered_branches=int(summary["covered_branches"]), + ) + + +def load_coverage(path: Path) -> dict[str, Any]: + with path.open(encoding="utf-8") as handle: + report = json.load(handle) + if not isinstance(report.get("files"), dict) or not isinstance(report.get("totals"), dict): + raise ValueError(f"{path} is not a coverage.py JSON report") + return report + + +def print_summary(report: dict[str, Any], limit: int) -> None: + totals = _metrics(report["totals"]) + print(f"Line coverage: {totals.line_percent:.2f}% ({totals.covered_lines}/{totals.statements})") + print(f"Branch coverage: {totals.branch_percent:.2f}% ({totals.covered_branches}/{totals.branches})") + print(f"Lowest {limit} production files by combined coverage:") + ranked = sorted( + ( + (float(data["summary"]["percent_covered"]), name, _metrics(data["summary"])) + for name, data in report["files"].items() + if int(data["summary"]["num_statements"]) > 0 + ), + key=lambda item: (item[0], item[1]), + ) + for combined, name, metrics in ranked[:limit]: + print(f" {combined:6.2f}% line {metrics.line_percent:6.2f}% branch {metrics.branch_percent:6.2f}% {name}") + + +def create_baseline(report: dict[str, Any], commit: str) -> dict[str, Any]: + files = { + name: _metrics_payload(_metrics(data["summary"])) + for name, data in sorted(report["files"].items()) + if int(data["summary"]["num_statements"]) > 0 + } + return { + "schema_version": SCHEMA_VERSION, + "source": "raven", + "python": f"{sys.version_info.major}.{sys.version_info.minor}", + "reference_commit": commit, + "totals": _metrics_payload(_metrics(report["totals"])), + "files": files, + } + + +def _metrics_payload(metrics: Metrics) -> dict[str, int | float]: + return { + "statements": metrics.statements, + "covered_lines": metrics.covered_lines, + "line_percent": round(metrics.line_percent, 6), + "branches": metrics.branches, + "covered_branches": metrics.covered_branches, + "branch_percent": round(metrics.branch_percent, 6), + } + + +def write_baseline(report: dict[str, Any], output: Path, commit: str) -> None: + output.write_text(json.dumps(create_baseline(report, commit), indent=2, sort_keys=True) + "\n", encoding="utf-8") + print(f"Wrote coverage baseline candidate to {output}") + + +def check_baseline_update(current: dict[str, Any], previous: dict[str, Any]) -> bool: + if current.get("schema_version") != SCHEMA_VERSION or previous.get("schema_version") != SCHEMA_VERSION: + raise ValueError("current and target-branch baselines must use the supported schema") + passed = True + for metric in ("line_percent", "branch_percent"): + value = float(current["totals"][metric]) + target = float(previous["totals"][metric]) + delta = value - target + print(f"Baseline {metric}: proposed={value:.6f}% target={target:.6f}% delta={delta:+.6f}pp") + if value < target: + passed = False + if passed: + print("Coverage baseline update is monotonic.") + else: + print("Coverage baseline files may not lower line or branch coverage.") + return passed + + +def check_ratchet(report: dict[str, Any], baseline: dict[str, Any], tolerance: float, limit: int) -> bool: + if baseline.get("schema_version") != SCHEMA_VERSION: + raise ValueError(f"unsupported baseline schema: {baseline.get('schema_version')!r}") + current = _metrics(report["totals"]) + expected = baseline["totals"] + checks = ( + ("line", current.line_percent, float(expected["line_percent"])), + ("branch", current.branch_percent, float(expected["branch_percent"])), + ) + failed = [(name, value, target) for name, value, target in checks if value + tolerance < target] + for name, value, target in checks: + delta = value - target + print(f"Ratchet {name}: current={value:.2f}% baseline={target:.2f}% delta={delta:+.2f}pp") + if not failed: + print(f"Coverage ratchet passed with {tolerance:.2f}pp tolerance.") + return True + + print("Largest per-file coverage declines:") + declines = _file_declines(report, baseline) + for decline, name, line_delta, branch_delta in declines[:limit]: + print(f" {decline:6.2f}pp line {line_delta:+7.2f}pp branch {branch_delta:+7.2f}pp {name}") + return False + + +def _file_declines(report: dict[str, Any], baseline: dict[str, Any]) -> list[tuple[float, str, float, float]]: + declines: list[tuple[float, str, float, float]] = [] + current_files = report["files"] + for name, expected in baseline["files"].items(): + data = current_files.get(name) + if data is None: + continue + current = _metrics(data["summary"]) + line_delta = current.line_percent - float(expected["line_percent"]) + branch_delta = current.branch_percent - float(expected["branch_percent"]) + decline = max(-line_delta, -branch_delta, 0.0) + if decline > 0: + declines.append((decline, name, line_delta, branch_delta)) + return sorted(declines, key=lambda item: (-item[0], item[1])) + + +_HUNK_RE = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@") + + +def parse_changed_lines(diff: str) -> dict[str, set[int]]: + """Return new-side hunk lines; deleted lines never enter the denominator.""" + changed: dict[str, set[int]] = {} + path: str | None = None + for line in diff.splitlines(): + if line.startswith("+++ "): + # Reset on a non-b/ target (e.g. +++ /dev/null) so a deleted file's + # hunks are not misattributed to the previously seen path. + path = line[6:] if line.startswith("+++ b/") else None + if path is not None: + changed.setdefault(path, set()) + continue + match = _HUNK_RE.match(line) + if path is None or match is None: + continue + start = int(match.group(1)) + count = int(match.group(2) or "1") + changed[path].update(range(start, start + count)) + return changed + + +def calculate_diff_coverage( + report: dict[str, Any], changed: dict[str, set[int]], added_files: Iterable[str] = () +) -> DiffCoverage: + """Intersect changed lines with coverage.py statements to ignore comments and blanks.""" + executable: set[tuple[str, int]] = set() + uncovered: set[tuple[str, int]] = set() + zero_coverage_files: list[str] = [] + unmeasured_files: list[str] = [] + added = set(added_files) + for name, lines in changed.items(): + data = report["files"].get(name) + if data is None: + if name not in OMITTED_PATHS: + unmeasured_files.append(name) + continue + executed = set(data["executed_lines"]) + missing = set(data["missing_lines"]) + statements = executed | missing + for line in lines & statements: + item = (name, line) + executable.add(item) + if line in missing: + uncovered.add(item) + if name in added and statements and not executed: + zero_coverage_files.append(name) + return DiffCoverage( + frozenset(executable), + frozenset(uncovered), + tuple(sorted(zero_coverage_files)), + tuple(sorted(unmeasured_files)), + ) + + +def _git(*args: str) -> str: + executable = shutil.which("git") + if executable is None: + raise OSError("git is required for coverage diff checks") + completed = subprocess.run([executable, *args], check=True, capture_output=True, text=True) + return completed.stdout + + +def load_baseline_from_ref(base_ref: str, path: Path) -> dict[str, Any] | None: + _git("rev-parse", "--verify", f"{base_ref}^{{commit}}") + try: + content = _git("show", f"{base_ref}:{path.as_posix()}") + except subprocess.CalledProcessError: + return None + return json.loads(content) + + +def git_diff_inputs(base_ref: str, head: str | None) -> tuple[dict[str, set[int]], set[str]]: + """Resolve the merge base and collect Python changes for CI or the local worktree.""" + merge_base = _git("merge-base", base_ref, "HEAD").strip() + end = head or "" + range_args = [merge_base, end] if end else [merge_base] + diff = _git("diff", "--unified=0", "--no-color", "--diff-filter=ACMR", *range_args, "--", PRODUCTION_PATHSPEC) + names = _git("diff", "--name-only", "--diff-filter=A", *range_args, "--", PRODUCTION_PATHSPEC) + return parse_changed_lines(diff), {name for name in names.splitlines() if name} + + +def check_diff(report: dict[str, Any], base_ref: str, head: str | None, threshold: float) -> bool: + changed, added = git_diff_inputs(base_ref, head) + result = calculate_diff_coverage(report, changed, added) + covered = len(result.executable) - len(result.uncovered) + print(f"Diff coverage: {result.percent:.2f}% ({covered}/{len(result.executable)} executable changed lines)") + if result.uncovered: + print("Uncovered changed lines:") + for name, line in sorted(result.uncovered): + print(f" {name}:{line}") + if result.zero_coverage_files: + print("New production files with 0% line coverage:") + for name in result.zero_coverage_files: + print(f" {name}") + if result.unmeasured_files: + print("Changed production files missing from the coverage report:") + for name in result.unmeasured_files: + print(f" {name}") + passed = result.percent + 1e-9 >= threshold and not result.zero_coverage_files and not result.unmeasured_files + if passed: + print(f"Diff coverage passed the {threshold:.2f}% threshold.") + else: + print(f"Diff coverage failed the {threshold:.2f}% threshold.") + return passed + + +def _parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--coverage-json", type=Path, default=Path("coverage.json")) + subparsers = parser.add_subparsers(dest="command", required=True) + + summary = subparsers.add_parser("summary") + summary.add_argument("--limit", type=int, default=10) + + baseline = subparsers.add_parser("baseline") + baseline.add_argument("--output", type=Path, default=Path("coverage-baseline-candidate.json")) + baseline.add_argument("--commit", default="HEAD") + + baseline_check = subparsers.add_parser("baseline-check") + baseline_check.add_argument("--baseline", type=Path, default=Path(".github/coverage-baseline.json")) + baseline_check.add_argument("--base-ref", required=True) + + ratchet = subparsers.add_parser("ratchet") + ratchet.add_argument("--baseline", type=Path, default=Path(".github/coverage-baseline.json")) + ratchet.add_argument("--tolerance", type=float, default=DEFAULT_TOLERANCE) + ratchet.add_argument("--limit", type=int, default=10) + + diff = subparsers.add_parser("diff") + diff.add_argument("--base-ref", required=True) + diff.add_argument("--head", default=None) + diff.add_argument("--threshold", type=float, default=DEFAULT_DIFF_THRESHOLD) + return parser + + +def main(argv: list[str] | None = None) -> int: + args = _parser().parse_args(argv) + try: + if args.command == "baseline-check": + with args.baseline.open(encoding="utf-8") as handle: + current_baseline = json.load(handle) + previous_baseline = load_baseline_from_ref(args.base_ref, args.baseline) + if previous_baseline is None: + print(f"No coverage baseline exists on {args.base_ref}; accepting the initial bootstrap baseline.") + return 0 + return 0 if check_baseline_update(current_baseline, previous_baseline) else 1 + + report = load_coverage(args.coverage_json) + if args.command == "summary": + print_summary(report, args.limit) + return 0 + if args.command == "baseline": + commit = _git("rev-parse", args.commit).strip() + write_baseline(report, args.output, commit) + return 0 + if args.command == "ratchet": + with args.baseline.open(encoding="utf-8") as handle: + baseline = json.load(handle) + return 0 if check_ratchet(report, baseline, args.tolerance, args.limit) else 1 + if args.command == "diff": + return 0 if check_diff(report, args.base_ref, args.head, args.threshold) else 1 + except (OSError, ValueError, KeyError, json.JSONDecodeError, subprocess.CalledProcessError) as exc: + print(f"coverage gate error: {exc}", file=sys.stderr) + return 2 + raise AssertionError(f"unhandled command: {args.command}") + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/README.md b/tests/README.md index 08e79263..97997d7c 100644 --- a/tests/README.md +++ b/tests/README.md @@ -21,3 +21,80 @@ uv run pytest -m "not real_llm" Tests should avoid live network calls by default. When a test needs external services or a real model, guard it behind an explicit marker or environment variable so CI and local contributors get deterministic results. + +## Coverage Governance + +Raven uses three related checks: + +- **Coverage** measures line and branch execution across the default Python + suite. It is the broad health signal for the production `raven/` package. +- **Diff coverage** measures executable lines added or modified relative to the + PR target branch. Comments, deleted lines, and non-executable lines are not + part of its denominator. The initial threshold is 90%. +- **The ratchet** compares total line and branch coverage with the audited + baseline in `.github/coverage-baseline.json`. Neither metric may decline by + more than 0.05 percentage points. This protects the established level without + pretending the current project is already near 100%. + +Run the same coverage command used by CI: + +```bash +make coverage +``` + +`PYTHON_VERSION` defaults to the CI version (`3.12`) and is shared by all +Python Make targets. It can be overridden for exploratory compatibility runs, +for example `make coverage PYTHON_VERSION=3.13`; only the configured CI matrix +version may produce an authoritative baseline. + +The command prints missing lines and creates `coverage.xml`, `coverage.json`, +and `htmlcov/`. Open `htmlcov/index.html` in a browser for line-by-line detail. +Generated reports are ignored by git. + +After running coverage, use the local gates: + +```bash +make coverage-summary +make coverage-diff COVERAGE_BASE_REF=origin/main +make coverage-ratchet +make coverage-baseline-check COVERAGE_BASE_REF=origin/main +``` + +The summary prints separate total line and branch percentages plus the ten +lowest-covered production files. A diff failure lists every uncovered changed +line as `path:line`. A ratchet failure prints the current value, baseline, +change in percentage points, and the files with the largest declines. + +The CI unit job runs the default suite once and reuses its data for all gates. +It uploads XML, JSON, and HTML reports even when tests or a gate fail. Diff +coverage runs only for pull requests because a push has no PR target branch; +push builds still enforce the overall ratchet. + +### Baseline updates + +`make coverage-baseline-candidate` creates +`coverage-baseline-candidate.json` from the current report. The tracked +bootstrap baseline is accepted only after the introducing PR reproduces it in +Python 3.12 Ubuntu CI within the ratchet tolerance. After bootstrap, the +baseline must only be replaced with a candidate downloaded from a successful +`main` CI run. Review the reference commit and totals in the candidate before +replacement. Never edit the percentages by hand or lower the baseline to make +a change pass. PR CI compares a proposed baseline with the target branch and +rejects any decrease in either line or branch coverage. + +### Scope and exclusions + +The default coverage suite excludes tests marked `integration` or `e2e`, plus +`tests/integration/`, because they require real services, credentials, +subprocess stacks, VMs, or network access and are not deterministic unit-CI +inputs. Provider cases that require live credentials remain skipped. + +All production Python under `raven/` is measured except: + +- `raven/__main__.py` and `raven/evolver/__main__.py`, which only forward their + module entry points; +- `raven/utils/win_fcntl_shim.py`, which is executable only on Windows while the + canonical coverage job runs on Ubuntu. + +Type-checking-only blocks and `if __name__ == "__main__"` launcher blocks are +excluded as non-runtime paths. No low-coverage feature module is omitted. diff --git a/tests/test_coverage_gate.py b/tests/test_coverage_gate.py new file mode 100644 index 00000000..865d0d09 --- /dev/null +++ b/tests/test_coverage_gate.py @@ -0,0 +1,234 @@ +"""Unit tests for coverage summaries, diff selection, and ratchet enforcement.""" + +from __future__ import annotations + +import json +import subprocess +import sys +import tomllib +from pathlib import Path + +import pytest + +from scripts.coverage_gate import ( + OMITTED_PATHS, + calculate_diff_coverage, + check_baseline_update, + check_ratchet, + create_baseline, + git_diff_inputs, + load_coverage, + parse_changed_lines, +) + + +def _file_summary( + *, statements: int, covered_lines: int, branches: int = 0, covered_branches: int = 0 +) -> dict[str, int | float]: + denominator = statements + branches + numerator = covered_lines + covered_branches + return { + "covered_lines": covered_lines, + "num_statements": statements, + "percent_covered": 100.0 if denominator == 0 else numerator * 100.0 / denominator, + "percent_covered_display": "0", + "missing_lines": statements - covered_lines, + "excluded_lines": 0, + "num_branches": branches, + "num_partial_branches": 0, + "covered_branches": covered_branches, + "missing_branches": branches - covered_branches, + } + + +def _report( + *, + statements: int = 10, + covered_lines: int = 8, + branches: int = 4, + covered_branches: int = 3, +) -> dict: + summary = _file_summary( + statements=statements, + covered_lines=covered_lines, + branches=branches, + covered_branches=covered_branches, + ) + return { + "meta": {"branch_coverage": True}, + "files": { + "raven/example.py": { + "executed_lines": list(range(1, covered_lines + 1)), + "missing_lines": list(range(covered_lines + 1, statements + 1)), + "excluded_lines": [], + "executed_branches": [], + "missing_branches": [], + "summary": summary, + } + }, + "totals": summary, + } + + +def test_load_coverage_rejects_non_coverage_json(tmp_path: Path) -> None: + path = tmp_path / "report.json" + path.write_text(json.dumps({"files": []}), encoding="utf-8") + + with pytest.raises(ValueError, match="not a coverage.py JSON report"): + load_coverage(path) + + +def test_documented_omissions_match_run_and_report_configuration() -> None: + with Path("pyproject.toml").open("rb") as handle: + coverage_config = tomllib.load(handle)["tool"]["coverage"] + + assert set(coverage_config["run"]["omit"]) == OMITTED_PATHS + assert set(coverage_config["report"]["omit"]) == OMITTED_PATHS + + +def test_parse_changed_lines_uses_only_new_hunk_ranges() -> None: + diff = """diff --git a/raven/example.py b/raven/example.py +--- a/raven/example.py ++++ b/raven/example.py +@@ -2,3 +2,4 @@ ++replacement +@@ -20,0 +22,2 @@ ++first ++second +diff --git a/tests/test_example.py b/tests/test_example.py +--- a/tests/test_example.py ++++ b/tests/test_example.py +@@ -1 +1 @@ +-old ++new +""" + + assert parse_changed_lines(diff) == { + "raven/example.py": {2, 3, 4, 5, 22, 23}, + "tests/test_example.py": {1}, + } + + +def test_parse_changed_lines_ignores_deleted_file_hunks() -> None: + diff = """diff --git a/raven/removed.py b/raven/removed.py +--- a/raven/removed.py ++++ /dev/null +@@ -1,2 +0,0 @@ +-gone +-also gone +diff --git a/raven/kept.py b/raven/kept.py +--- a/raven/kept.py ++++ b/raven/kept.py +@@ -3 +3 @@ ++changed +""" + + assert parse_changed_lines(diff) == {"raven/kept.py": {3}} + + +def test_diff_coverage_ignores_comments_deletions_and_non_executable_lines() -> None: + report = _report() + changed = {"raven/example.py": {2, 9, 10, 11, 12}} + + result = calculate_diff_coverage(report, changed) + + assert result.executable == {("raven/example.py", 2), ("raven/example.py", 9), ("raven/example.py", 10)} + assert result.uncovered == {("raven/example.py", 9), ("raven/example.py", 10)} + assert result.percent == pytest.approx(100 / 3) + + +def test_diff_coverage_flags_new_production_file_at_zero_percent() -> None: + report = _report(covered_lines=0) + + result = calculate_diff_coverage(report, {"raven/example.py": set(range(1, 11))}, ["raven/example.py"]) + + assert result.zero_coverage_files == ("raven/example.py",) + assert result.percent == 0.0 + + +def test_diff_coverage_fails_closed_for_unmeasured_production_file() -> None: + result = calculate_diff_coverage(_report(), {"raven/new_module.py": {1}}) + + assert result.unmeasured_files == ("raven/new_module.py",) + + +def test_diff_coverage_allows_documented_omitted_entry_point() -> None: + result = calculate_diff_coverage(_report(), {"raven/__main__.py": {1}}) + + assert result.unmeasured_files == () + + +def test_git_diff_inputs_includes_top_level_raven_files(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + def git(*args: str) -> None: + subprocess.run(["git", *args], cwd=tmp_path, check=True, capture_output=True, text=True) + + git("init") + git("config", "user.email", "test@example.com") + git("config", "user.name", "test") + (tmp_path / "README.md").write_text("base\n", encoding="utf-8") + git("add", "README.md") + git("commit", "-m", "base") + base = subprocess.run( + ["git", "rev-parse", "HEAD"], cwd=tmp_path, check=True, capture_output=True, text=True + ).stdout.strip() + + (tmp_path / "raven").mkdir() + (tmp_path / "raven" / "__init__.py").write_text("value = 1\n", encoding="utf-8") + (tmp_path / "raven" / "pkg").mkdir() + (tmp_path / "raven" / "pkg" / "mod.py").write_text("def run():\n return 1\n", encoding="utf-8") + git("add", "-A") + git("commit", "-m", "add raven sources") + + monkeypatch.chdir(tmp_path) + changed, added = git_diff_inputs(base, "HEAD") + + assert "raven/__init__.py" in changed + assert "raven/pkg/mod.py" in changed + assert "raven/__init__.py" in added + + +def test_create_baseline_records_separate_line_and_branch_metrics() -> None: + baseline = create_baseline(_report(), "abc123") + + assert baseline["reference_commit"] == "abc123" + assert baseline["python"] == f"{sys.version_info.major}.{sys.version_info.minor}" + assert baseline["totals"]["line_percent"] == 80.0 + assert baseline["totals"]["branch_percent"] == 75.0 + assert baseline["files"]["raven/example.py"]["covered_lines"] == 8 + + +def test_baseline_update_cannot_lower_either_metric(capsys: pytest.CaptureFixture[str]) -> None: + previous = create_baseline(_report(), "base") + proposed = create_baseline(_report(covered_lines=9, covered_branches=2), "head") + + assert check_baseline_update(proposed, previous) is False + output = capsys.readouterr().out + assert "line_percent: proposed=90.000000% target=80.000000%" in output + assert "branch_percent: proposed=50.000000% target=75.000000%" in output + assert "may not lower" in output + + +def test_baseline_update_accepts_equal_or_higher_metrics() -> None: + previous = create_baseline(_report(), "base") + proposed = create_baseline(_report(covered_lines=9, covered_branches=4), "head") + + assert check_baseline_update(proposed, previous) is True + + +def test_ratchet_allows_small_tolerance(capsys: pytest.CaptureFixture[str]) -> None: + baseline = create_baseline(_report(statements=10_000, covered_lines=8_001), "abc123") + current = _report(statements=10_000, covered_lines=8_000) + + assert check_ratchet(current, baseline, tolerance=0.05, limit=10) is True + assert "delta=-0.01pp" in capsys.readouterr().out + + +def test_ratchet_reports_total_and_largest_file_decline(capsys: pytest.CaptureFixture[str]) -> None: + baseline = create_baseline(_report(), "abc123") + current = _report(covered_lines=7, covered_branches=2) + + assert check_ratchet(current, baseline, tolerance=0.05, limit=10) is False + output = capsys.readouterr().out + assert "Ratchet line: current=70.00% baseline=80.00% delta=-10.00pp" in output + assert "Ratchet branch: current=50.00% baseline=75.00% delta=-25.00pp" in output + assert "raven/example.py" in output