claude-code-conductor 2.24.0__tar.gz → 2.25.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/select_tier.py +49 -6
  2. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/session_stop.py +2 -0
  3. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/CHANGELOG.md +23 -0
  4. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/PKG-INFO +2 -1
  5. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/README.md +1 -0
  6. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/__init__.py +1 -1
  7. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/cli_tier.py +17 -3
  8. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/db.py +127 -7
  9. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_select_tier.py +229 -0
  10. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_session_stop.py +55 -0
  11. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_cli_tier.py +146 -0
  12. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_db.py +401 -0
  13. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/CLAUDE.md +0 -0
  14. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/architect.md +0 -0
  15. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/code-reviewer.md +0 -0
  16. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/developer.md +0 -0
  17. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/doc-writer.md +0 -0
  18. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/interviewer.md +0 -0
  19. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/planner.md +0 -0
  20. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/project-setup.md +0 -0
  21. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/security-reviewer.md +0 -0
  22. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/systematic-debugger.md +0 -0
  23. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/tester.md +0 -0
  24. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/wt_developer.md +0 -0
  25. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/wt_systematic-debugger.md +0 -0
  26. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/agents/wt_tester.md +0 -0
  27. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/breaking-changes.txt +0 -0
  28. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/deletions.txt +0 -0
  29. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/docs/config-policy.md +0 -0
  30. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/docs/parallel-agents-setup.md +0 -0
  31. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/docs/platform-adapters.md +0 -0
  32. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/docs/settings.json.md +0 -0
  33. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/_hook_utils.py +0 -0
  34. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/check_agent_invocation.py +0 -0
  35. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/consolidate_memory.py +0 -0
  36. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/permission_handler.py +0 -0
  37. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/permission_handler_toast.py +0 -0
  38. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/planner_check.py +0 -0
  39. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/post_tool.py +0 -0
  40. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/pre_compact.py +0 -0
  41. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/pre_tool.py +0 -0
  42. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/recall_inject.py +0 -0
  43. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/restore_session.py +0 -0
  44. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/session_start.py +0 -0
  45. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/session_utils.py +0 -0
  46. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/statusline.py +0 -0
  47. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/stop.py +0 -0
  48. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/hooks/worktree_guard.py +0 -0
  49. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/memory/.gitkeep +0 -0
  50. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/permission_rules.json +0 -0
  51. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/rules/promoted/index.md +0 -0
  52. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/settings.json +0 -0
  53. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/codex-review/SKILL.md +0 -0
  54. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
  55. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
  56. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
  57. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
  58. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
  59. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/dev-workflow/scripts/record_tier_outcome.py +0 -0
  60. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
  61. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/develop/SKILL.md +0 -0
  62. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/doc/SKILL.md +0 -0
  63. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/extract-lib/SKILL.md +0 -0
  64. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/init-session/SKILL.md +0 -0
  65. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/mcp-config/SKILL.md +0 -0
  66. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
  67. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/pattern-status/SKILL.md +0 -0
  68. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
  69. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/recall/SKILL.md +0 -0
  70. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
  71. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
  72. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/review-phase/SKILL.md +0 -0
  73. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/setup/SKILL.md +0 -0
  74. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/setup/reference.md +0 -0
  75. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
  76. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
  77. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/skills/start/SKILL.md +0 -0
  78. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.claude/state/.gitkeep +0 -0
  79. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/.gitignore +0 -0
  80. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/LICENSE +0 -0
  81. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/LICENSES/chroma-hnswlib-LICENSE +0 -0
  82. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/LICENSES/chroma-hnswlib-NOTICE +0 -0
  83. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/LICENSES/fastembed-LICENSE +0 -0
  84. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/LICENSES/fastembed-NOTICE +0 -0
  85. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/LICENSES/onnxruntime-LICENSE +0 -0
  86. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
  87. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/hatch_build.py +0 -0
  88. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/pyproject.toml +0 -0
  89. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/__main__.py +0 -0
  90. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/_excludes.py +0 -0
  91. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/_terminal.py +0 -0
  92. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/adapters.py +0 -0
  93. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/cli.py +0 -0
  94. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/cli_ask.py +0 -0
  95. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/cli_doctor.py +0 -0
  96. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/cli_init.py +0 -0
  97. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/cli_list.py +0 -0
  98. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/cli_plan.py +0 -0
  99. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/cli_recall.py +0 -0
  100. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/cli_update.py +0 -0
  101. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/embedding.py +0 -0
  102. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/mcp_server.py +0 -0
  103. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/migrate.py +0 -0
  104. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/migrations/001_initial.sql +0 -0
  105. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
  106. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/migrations/003_tier_cost.sql +0 -0
  107. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/migrations/README.md +0 -0
  108. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/migrations/__init__.py +0 -0
  109. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/paths.py +0 -0
  110. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/plan_validator.py +0 -0
  111. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/platforms.py +0 -0
  112. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/pricing.py +0 -0
  113. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/question.py +0 -0
  114. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/recall_chunker.py +0 -0
  115. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/recall_index.py +0 -0
  116. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/src/c3/usage_ingester.py +0 -0
  117. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/__init__.py +0 -0
  118. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/conftest.py +0 -0
  119. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/fixtures/usage/README.md +0 -0
  120. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/fixtures/usage/mainline.jsonl +0 -0
  121. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
  122. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
  123. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/__init__.py +0 -0
  124. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_check_agent_invocation.py +0 -0
  125. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_consolidate_memory.py +0 -0
  126. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_hook_utils.py +0 -0
  127. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_permission_handler.py +0 -0
  128. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_permission_handler_toast.py +0 -0
  129. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
  130. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_planner_check.py +0 -0
  131. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_planner_check_dev.py +0 -0
  132. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_post_tool.py +0 -0
  133. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_pre_tool.py +0 -0
  134. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_recall_inject.py +0 -0
  135. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_record_review_decision.py +0 -0
  136. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_record_tier_outcome.py +0 -0
  137. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_restore_session.py +0 -0
  138. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_review_hint_inject.py +0 -0
  139. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_select_tier_escalation.py +0 -0
  140. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_session_start.py +0 -0
  141. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_session_utils.py +0 -0
  142. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
  143. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_similarity_boost.py +0 -0
  144. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_statusline.py +0 -0
  145. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_statusline_template_sync.py +0 -0
  146. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_sync_check.py +0 -0
  147. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/hooks/test_template_guard.py +0 -0
  148. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/skills/__init__.py +0 -0
  149. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/skills/_skill_helpers.py +0 -0
  150. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
  151. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/skills/test_init_session_no_task_type.py +0 -0
  152. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/skills/test_planner_lightweight.py +0 -0
  153. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/skills/test_recall_skill.py +0 -0
  154. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
  155. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/skills/test_setup_templates.py +0 -0
  156. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
  157. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/skills/test_start_skill_new_flow.py +0 -0
  158. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
  159. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_adapters.py +0 -0
  160. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_cli_ask.py +0 -0
  161. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_cli_entry.py +0 -0
  162. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_cli_init.py +0 -0
  163. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_cli_list.py +0 -0
  164. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_cli_plan.py +0 -0
  165. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_cli_recall.py +0 -0
  166. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_cli_update_breaking_changes.py +0 -0
  167. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_cli_update_deletions.py +0 -0
  168. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_docstring_consistency.py +0 -0
  169. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_embedding.py +0 -0
  170. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_excludes.py +0 -0
  171. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_extract_breaking_changes.py +0 -0
  172. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_mcp_server_elicit.py +0 -0
  173. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_migrate.py +0 -0
  174. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_paths.py +0 -0
  175. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_plan_validator.py +0 -0
  176. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_pre_compact.py +0 -0
  177. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_pre_tool_hook.py +0 -0
  178. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_precompact_additional.py +0 -0
  179. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_precompact_toctou_fixes.py +0 -0
  180. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_pricing.py +0 -0
  181. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_recall_chunker.py +0 -0
  182. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_recall_index.py +0 -0
  183. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_references_migration.py +0 -0
  184. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_session_utils_additional.py +0 -0
  185. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_skill_no_builtin_conflict.py +0 -0
  186. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_statusline.py +0 -0
  187. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_stop_additional.py +0 -0
  188. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_stop_hook.py +0 -0
  189. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_stop_precompact_fixes.py +0 -0
  190. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_sync_template_stop.py +0 -0
  191. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_template_pre_tool_hook.py +0 -0
  192. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_usage_ingester.py +0 -0
  193. {claude_code_conductor-2.24.0 → claude_code_conductor-2.25.0}/tests/test_worktree_guard.py +0 -0
@@ -27,6 +27,7 @@ import collections
27
27
  import difflib
28
28
  import hashlib
29
29
  import json
30
+ import math
30
31
  import os
31
32
  import random
32
33
  import re
@@ -47,8 +48,10 @@ except AttributeError:
47
48
  try:
48
49
  from c3 import db as _c3_db_const # type: ignore[import-not-found]
49
50
  LEARNING_THRESHOLD: int = _c3_db_const.LEARNING_THRESHOLD
51
+ EPSILON: float = _c3_db_const.EPSILON_TIEBREAK
50
52
  except ImportError:
51
53
  LEARNING_THRESHOLD = 30
54
+ EPSILON = 0.05
52
55
 
53
56
  # 複雑度推定のキーワード
54
57
  SIMPLE_KEYWORDS = frozenset({
@@ -111,10 +114,9 @@ _PROMPT_HISTORY_SCAN_LINES = 1000
111
114
 
112
115
  TIERS: tuple[str, ...] = ("haiku", "sonnet", "opus")
113
116
 
114
- # cost-aware tie-break の拮抗判定閾値(v2.23.0)。
117
+ # cost-aware tie-break の拮抗判定閾値(v2.23.0・SSOT は db.EPSILON_TIEBREAK)。
115
118
  # Beta サンプルは 0〜1 スケール。成功率 5pt 以内=実質同等とみなす拮抗判定閾値。
116
- # 過大は成功率犠牲リスク、過小は無発動。調整可能化は v2.24.0
117
- EPSILON: float = 0.05
119
+ # 過大は成功率犠牲リスク、過小は無発動。C3_TIER_EPSILON env で上書き可(v2.25.0)。
118
120
 
119
121
 
120
122
  class SelectionResult(NamedTuple):
@@ -295,6 +297,7 @@ def select_tier_detailed(
295
297
  *,
296
298
  rng: random.Random | None = None,
297
299
  cost_map: dict[str, float] | None = None,
300
+ epsilon: float | None = None,
298
301
  ) -> SelectionResult:
299
302
  """Beta サンプリングまたは uniform 選択で推奨 Tier を SelectionResult で返す。
300
303
 
@@ -307,6 +310,7 @@ def select_tier_detailed(
307
310
  含む完全な dict」のいずれか。partial dict は渡されない前提
308
311
  (呼び出し側が全 TIERS 分を構築して保証する)。
309
312
  uniform 分岐では cost_map の有無に関わらず完全無視する(探索保護)。
313
+ epsilon: 拮抗判定閾値。None なら module 定数 EPSILON を使う(C3_TIER_EPSILON で上書き可)。
310
314
 
311
315
  Returns:
312
316
  SelectionResult(tier, mode, cost_tiebreak, contenders)。
@@ -323,7 +327,8 @@ def select_tier_detailed(
323
327
  tier: rng.betavariate(p[0], p[1])
324
328
  for tier, p in params.items()
325
329
  }
326
- chosen, did_tiebreak, contenders = _cost_tiebreak(samples, cost_map)
330
+ eff_epsilon = epsilon if epsilon is not None else EPSILON
331
+ chosen, did_tiebreak, contenders = _cost_tiebreak(samples, cost_map, epsilon=eff_epsilon)
327
332
  return SelectionResult(chosen, "thompson", did_tiebreak, contenders)
328
333
 
329
334
 
@@ -332,6 +337,7 @@ def select_tier(
332
337
  *,
333
338
  rng: random.Random | None = None,
334
339
  cost_map: dict[str, float] | None = None,
340
+ epsilon: float | None = None,
335
341
  ) -> tuple[str, str]:
336
342
  """Beta サンプリングまたは uniform 選択で推奨 Tier を返す。
337
343
 
@@ -343,13 +349,14 @@ def select_tier(
343
349
  None なら cost を見ず従来の Thompson Sampling と完全一致。
344
350
  uniform 分岐では cost_map の有無に関わらず完全無視する。
345
351
  詳細は :func:`select_tier_detailed` を参照。
352
+ epsilon: 拮抗判定閾値。None なら module 定数 EPSILON を使う。
346
353
 
347
354
  Returns:
348
355
  ``(tier, mode)`` のタプル。``mode`` は ``"thompson"`` / ``"uniform"`` で、
349
356
  プロンプトに「学習データ収集中」と表示するかの分岐に使う。
350
357
  戻り値型は v2.22.0 以前と完全に不変。
351
358
  """
352
- result = select_tier_detailed(params, rng=rng, cost_map=cost_map)
359
+ result = select_tier_detailed(params, rng=rng, cost_map=cost_map, epsilon=epsilon)
353
360
  return result.tier, result.mode
354
361
 
355
362
 
@@ -522,6 +529,41 @@ def _load_c3_db_module():
522
529
  return None
523
530
 
524
531
 
532
+ def _resolve_epsilon() -> float:
533
+ """``C3_TIER_EPSILON`` を安全に解決する。
534
+
535
+ 不正値(非数値 / 0 以下 / 1 超 / NaN)は受け付けず、stderr 警告 + デフォルト(EPSILON)に戻す。
536
+ 未設定 / 空文字は無警告でデフォルトを返す([SR-V-001])。
537
+ """
538
+ raw = os.environ.get("C3_TIER_EPSILON")
539
+ if raw is None or raw == "":
540
+ return EPSILON
541
+ try:
542
+ x = float(raw)
543
+ except ValueError:
544
+ print(
545
+ f"[select_tier:epsilon] invalid C3_TIER_EPSILON={raw!r}, "
546
+ f"using default {EPSILON}",
547
+ file=sys.stderr,
548
+ )
549
+ return EPSILON
550
+ if math.isnan(x):
551
+ print(
552
+ f"[select_tier:epsilon] C3_TIER_EPSILON={raw!r} is NaN, "
553
+ f"using default {EPSILON}",
554
+ file=sys.stderr,
555
+ )
556
+ return EPSILON
557
+ if x <= 0 or x > 1:
558
+ print(
559
+ f"[select_tier:epsilon] C3_TIER_EPSILON={x} out of range (0, 1], "
560
+ f"using default {EPSILON}",
561
+ file=sys.stderr,
562
+ )
563
+ return EPSILON
564
+ return x
565
+
566
+
525
567
  def main() -> int:
526
568
  try:
527
569
  payload = json.loads(sys.stdin.read())
@@ -576,7 +618,8 @@ def main() -> int:
576
618
  except ImportError:
577
619
  cost_map = None
578
620
 
579
- result = select_tier_detailed(params, cost_map=cost_map)
621
+ eps = _resolve_epsilon()
622
+ result = select_tier_detailed(params, cost_map=cost_map, epsilon=eps)
580
623
  tier, mode = result.tier, result.mode
581
624
  cost_tiebreak = result.cost_tiebreak
582
625
 
@@ -103,6 +103,8 @@ def main() -> int:
103
103
  from c3.usage_ingester import ingest_session # noqa: PLC0415
104
104
  project_dir = _Path(transcript_path).parent
105
105
  ingest_session(session_id=session_id, project_dir=project_dir)
106
+ from c3.db import sync_tier_bandit_cost # noqa: PLC0415
107
+ sync_tier_bandit_cost()
106
108
  except Exception as e:
107
109
  print(f"[session_stop:usage_ingester] failed: {type(e).__name__}", file=sys.stderr)
108
110
 
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.25.0] - 2026-05-26
4
+
5
+ **tier_bandit cost 蓄積・EPSILON 調整可能化・例外ログ統一**: v2.22.0 で列確保済みの `tier_bandit.total_cost_usd`/`cost_samples` へ実測値を materialize する同期関数を追加。cost-aware tie-break の拮抗判定閾値を定数 SSOT 化し環境変数で上書き可能にする。db.py 既存 6 関数の例外ログを型名統一(SR-R-001)。routing 挙動は不変。cost-weighted Thompson 本格統合は v2.26.0。
6
+
7
+ ### 機能追加
8
+
9
+ - **`src/c3/db.py`: `sync_tier_bandit_cost(*, db_path=None) -> None`(新規)**: `read_tier_cost_rate_summary` 由来の model 一致集計値を `tier_bandit` テーブルへ materialize する冪等な同期関数。「全クリア(total_cost_usd=0, cost_samples=0) → 集計 SET」の UPDATE-only 実装(INSERT なし)。session_stop の usage ingest 直後に実行。tier_bandit 行が存在しない複合キー (complexity, tier) は無視(INSERT は行わない)。
10
+
11
+ - **`.claude/hooks/select_tier.py`: `db.EPSILON_TIEBREAK`(0.05)定数 SSOT 化・環境変数 `C3_TIER_EPSILON` 対応**: 従来 `EPSILON=0.05` をモジュールローカルで持っていた値を `c3.db.EPSILON_TIEBREAK` に移し Single Source of Truth 化。`select_tier.py` は `db.EPSILON_TIEBREAK` を参照するよう変更。環境変数 `C3_TIER_EPSILON` に数値が設定されている場合は実行時にその値で上書き可能。NaN・範囲外(0 < x <= 1 の外)・非数値は default 値(0.05)に fallback。env 未設定時の routing 挙動は v2.24.0 と完全一致。
12
+
13
+ ### 変更
14
+
15
+ - **`src/c3/cli_tier.py`**: `c3 tier stats` の tier_bandit セクションに `total_cost_usd`(合計コスト USD)および `cost_samples`(計上セッション数)列を追加。値は `sync_tier_bandit_cost` 蓄積値を表示。データ未蓄積(0/0)時はセルを「-」表示。
16
+
17
+ - **`src/c3/db.py`: 例外ログ型名統一(SR-R-001)**: 既存 6 関数 7 箇所の `except Exception as exc` ログを `logger.warning("...: %s", exc)` から `logger.warning("...: %s", type(exc).__name__)` に統一。生 exc message(外部 path・SQL 文・行データ等)の意図しない流出を防止。関数シグネチャ・戻り値・呼び出し元への影響なし。
18
+
19
+ ### 後方互換
20
+
21
+ - 既存関数シグネチャ不変(`read_tier_params`・`read_tier_cost_rate_summary` 等)。
22
+ - `select_tier_detailed`/`select_tier` の `epsilon` は optional kwarg(省略時 `db.EPSILON_TIEBREAK` を参照)。
23
+ - migration 不要(`tier_bandit` の cost 列は v2.22.0 の 003 migration で確保済み)。
24
+ - **破壊的変更なし**。
25
+
3
26
  ## [2.24.0] - 2026-05-25
4
27
 
5
28
  **tier-routing cost 精度向上**: tie-break が使う cost データを model 一致集計・USD/MTok レート化により信頼できるものにする。新関数 2 つを追加し、`select_tier` の cost_map 源を rate 関数へ切替。既存関数・tie-break ロジックは完全不変。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-conductor
3
- Version: 2.24.0
3
+ Version: 2.25.0
4
4
  Summary: Multi-agent orchestration framework for Claude Code with Codex/Cursor adapters (C3)
5
5
  Project-URL: Homepage, https://github.com/satoh-y-0323/claude-code-conductor
6
6
  Project-URL: Repository, https://github.com/satoh-y-0323/claude-code-conductor
@@ -201,6 +201,7 @@ C3 のスラッシュコマンドはすべてスキル(`skills/{name}/SKILL.md
201
201
  | `c3 recall search "<query>"` または `c3 recall "<query>"` | `.claude/memory/sessions/` 等から類似チャンクを意味検索 |
202
202
  | `c3 recall rebuild [--force]` | HNSW インデックスを再構築(初回は fastembed が ~220MB のモデルを取得) |
203
203
  | `c3 recall stats` | チャンク数・モデル名・最終 rebuild 日時を表示 |
204
+ | `c3 tier stats` | tier-routing(複雑度に応じた Tier 自動ルーティング)の学習データ・Tier 別コストを表形式で表示(`--json` で機械可読出力・`--recent N` で直近 outcome 件数指定) |
204
205
 
205
206
  ### 基本的な使い方
206
207
 
@@ -154,6 +154,7 @@ C3 のスラッシュコマンドはすべてスキル(`skills/{name}/SKILL.md
154
154
  | `c3 recall search "<query>"` または `c3 recall "<query>"` | `.claude/memory/sessions/` 等から類似チャンクを意味検索 |
155
155
  | `c3 recall rebuild [--force]` | HNSW インデックスを再構築(初回は fastembed が ~220MB のモデルを取得) |
156
156
  | `c3 recall stats` | チャンク数・モデル名・最終 rebuild 日時を表示 |
157
+ | `c3 tier stats` | tier-routing(複雑度に応じた Tier 自動ルーティング)の学習データ・Tier 別コストを表形式で表示(`--json` で機械可読出力・`--recent N` で直近 outcome 件数指定) |
157
158
 
158
159
  ### 基本的な使い方
159
160
 
@@ -1,3 +1,3 @@
1
1
  """Claude Code Conductor (C3) - multi-agent orchestration framework for Claude Code."""
2
2
 
3
- __version__ = "2.24.0"
3
+ __version__ = "2.25.0"
@@ -94,6 +94,11 @@ def _collect_snapshot(db_path, recent_limit: int) -> dict[str, Any]:
94
94
  bandit_rows: list[dict[str, Any]] = []
95
95
  total_trials = 0
96
96
 
97
+ # cost 列は別 SELECT(read_tier_params は alpha/beta/trials 専用を維持)
98
+ bandit_cost: dict[tuple[str, str], tuple[float, int]] = c3_db.read_tier_bandit_cost(
99
+ db_path=db_path,
100
+ )
101
+
97
102
  for complexity in _COMPLEXITIES:
98
103
  params = c3_db.read_tier_params(complexity, db_path=db_path)
99
104
  for tier in _TIERS:
@@ -101,6 +106,7 @@ def _collect_snapshot(db_path, recent_limit: int) -> dict[str, Any]:
101
106
  total_trials += trials
102
107
  denom = alpha + beta
103
108
  expected = alpha / denom if denom > 0 else 0.5
109
+ cost_usd, cost_samples = bandit_cost.get((complexity, tier), (0.0, 0))
104
110
  bandit_rows.append({
105
111
  "complexity": complexity,
106
112
  "tier": tier,
@@ -108,6 +114,8 @@ def _collect_snapshot(db_path, recent_limit: int) -> dict[str, Any]:
108
114
  "beta": beta,
109
115
  "trials": trials,
110
116
  "expected_success_rate": expected,
117
+ "total_cost_usd": cost_usd,
118
+ "cost_samples": cost_samples,
111
119
  })
112
120
 
113
121
  recent_outcomes: list[dict[str, Any]] = c3_db.read_recent_outcomes(
@@ -154,12 +162,18 @@ def _render_human(snapshot: dict[str, Any]) -> None:
154
162
  print()
155
163
 
156
164
  print("== Tier 別累積(tier_bandit) ==")
157
- print(f"{'complexity':<12} {'tier':<8} {'trials':>6} {'alpha':>5} {'beta':>5} {'期待成功率':>10}")
165
+ print(
166
+ f"{'complexity':<12} {'tier':<8} {'trials':>6} {'alpha':>5} {'beta':>5} "
167
+ f"{'期待成功率':>10} {'cost_usd':>10} {'cost_samples':>12}"
168
+ )
158
169
  for row in snapshot["tier_bandit"]:
170
+ complexity_safe = sanitize_terminal_text(str(row["complexity"]))
171
+ tier_safe = sanitize_terminal_text(str(row["tier"]))
159
172
  print(
160
- f"{row['complexity']:<12} {row['tier']:<8} "
173
+ f"{complexity_safe:<12} {tier_safe:<8} "
161
174
  f"{row['trials']:>6} {row['alpha']:>5.2f} {row['beta']:>5.2f} "
162
- f"{row['expected_success_rate'] * 100:>9.2f}%"
175
+ f"{row['expected_success_rate'] * 100:>9.2f}% "
176
+ f"${row['total_cost_usd']:>9.4f} {row['cost_samples']:>12}"
163
177
  )
164
178
  print()
165
179
 
@@ -35,6 +35,11 @@ _BUSY_TIMEOUT_MS = BUSY_TIMEOUT_MS # 内部互換エイリアス(既存コー
35
35
  # tier-routing: 学習データ収集期の閾値(合計試行数がこの値未満なら uniform 選択)。
36
36
  # SSOT: cli_tier.py / select_tier.py はここから参照する(CR-M-002)。
37
37
  LEARNING_THRESHOLD = 30
38
+ # cost-aware tie-break の拮抗判定閾値。Beta サンプルは 0〜1 スケールで、
39
+ # 成功率 5pt(=0.05)以内を拮抗とみなす。本定数が SSOT。
40
+ # 過大にすると成功率を犠牲にするリスク、過小にすると無発動になる。
41
+ # C3_TIER_EPSILON 環境変数で上書き可(v2.25.0)。
42
+ EPSILON_TIEBREAK = 0.05
38
43
 
39
44
 
40
45
  def _apply_busy_timeout(conn: sqlite3.Connection) -> None:
@@ -139,7 +144,7 @@ def fetch_review_decisions(
139
144
  finally:
140
145
  conn.close()
141
146
  except Exception as exc: # noqa: BLE001
142
- logger.warning("failed to fetch review_decisions: %s", exc)
147
+ logger.warning("failed to fetch review_decisions: %s", type(exc).__name__)
143
148
  return []
144
149
 
145
150
 
@@ -204,7 +209,7 @@ def insert_review_decision(
204
209
  conn.close()
205
210
  return True
206
211
  except Exception as exc: # noqa: BLE001
207
- logger.warning("failed to insert review_decision: %s", exc)
212
+ logger.warning("failed to insert review_decision: %s", type(exc).__name__)
208
213
  return False
209
214
 
210
215
 
@@ -270,7 +275,7 @@ def read_tier_params(
270
275
  finally:
271
276
  conn.close()
272
277
  except Exception as exc: # noqa: BLE001
273
- logger.warning("failed to read tier_params: %s", exc)
278
+ logger.warning("failed to read tier_params: %s", type(exc).__name__)
274
279
  return defaults
275
280
 
276
281
  result = dict(defaults)
@@ -342,7 +347,7 @@ def update_tier_params(
342
347
  conn.close()
343
348
  return True
344
349
  except Exception as exc: # noqa: BLE001
345
- logger.warning("failed to update tier_params: %s", exc)
350
+ logger.warning("failed to update tier_params: %s", type(exc).__name__)
346
351
  return False
347
352
 
348
353
 
@@ -439,10 +444,10 @@ def read_recent_outcomes(
439
444
  finally:
440
445
  conn.close()
441
446
  except sqlite3.OperationalError as exc:
442
- logger.debug("read_recent_outcomes: table not found or inaccessible: %s", exc)
447
+ logger.debug("read_recent_outcomes: table not found or inaccessible: %s", type(exc).__name__)
443
448
  return []
444
449
  except Exception as exc: # noqa: BLE001
445
- logger.warning("read_recent_outcomes: unexpected error: %s", exc)
450
+ logger.warning("read_recent_outcomes: unexpected error: %s", type(exc).__name__)
446
451
  return []
447
452
 
448
453
  return [
@@ -493,7 +498,7 @@ def read_tier_failure_rate(
493
498
  finally:
494
499
  conn.close()
495
500
  except Exception as exc: # noqa: BLE001
496
- logger.warning("failed to read tier_failure_rate: %s", exc)
501
+ logger.warning("failed to read tier_failure_rate: %s", type(exc).__name__)
497
502
  return None, 0
498
503
 
499
504
  sample_count = len(rows)
@@ -1110,3 +1115,118 @@ def set_ingest_offset(
1110
1115
  except Exception as exc: # noqa: BLE001
1111
1116
  logger.warning("failed to set_ingest_offset: %s", type(exc).__name__)
1112
1117
  return False
1118
+
1119
+
1120
+ def read_tier_bandit_cost(
1121
+ *,
1122
+ db_path: Path | None = None,
1123
+ ) -> dict[tuple[str, str], tuple[float, int]]:
1124
+ """tier_bandit テーブルから cost 列を読む。
1125
+
1126
+ cli_tier.py の _collect_snapshot が alpha/beta/trials(read_tier_params 由来)と
1127
+ 別 SELECT で cost を取得するために使う。
1128
+
1129
+ Args:
1130
+ db_path: c3.db のパス。省略時は locate_c3_db() で探索。
1131
+
1132
+ Returns:
1133
+ ``{(task_complexity, tier): (total_cost_usd, cost_samples)}`` の dict。
1134
+ テーブル不在 / DB 不在 / エラー時は空 dict を返す。
1135
+ """
1136
+ if db_path is None:
1137
+ db_path = locate_c3_db()
1138
+ if db_path is None:
1139
+ return {}
1140
+
1141
+ try:
1142
+ conn = sqlite3.connect(str(db_path))
1143
+ try:
1144
+ _apply_busy_timeout(conn)
1145
+ rows = conn.execute(
1146
+ "SELECT task_complexity, tier, total_cost_usd, cost_samples "
1147
+ "FROM tier_bandit"
1148
+ ).fetchall()
1149
+ finally:
1150
+ conn.close()
1151
+ except Exception as exc: # noqa: BLE001
1152
+ logger.warning("failed to read tier_bandit_cost: %s", type(exc).__name__)
1153
+ return {}
1154
+
1155
+ return {
1156
+ (row[0], row[1]): (float(row[2]), int(row[3]))
1157
+ for row in rows
1158
+ }
1159
+
1160
+
1161
+ def sync_tier_bandit_cost(
1162
+ *,
1163
+ db_path: Path | None = None,
1164
+ ) -> int:
1165
+ """tier_bandit テーブルの cost 列を rate_summary 集計値で同期する(冪等 SET 同期)。
1166
+
1167
+ 冪等性: 全行の cost 列を 0.0/0 にクリアしてから SET するため、
1168
+ 何度呼び出しても結果が同じになる(全クリア→SET の SET 同期)。
1169
+
1170
+ 動作:
1171
+ 1. ``read_tier_cost_rate_summary`` で (complexity, tier) 別集計を取得。
1172
+ 2. 1 トランザクション内で:
1173
+ a. 全行 cost 列クリア: ``UPDATE tier_bandit SET total_cost_usd=0.0, cost_samples=0``
1174
+ (alpha/beta/trials/last_updated は一切変更しない)。
1175
+ b. 各集計行を ``UPDATE tier_bandit SET total_cost_usd=?, cost_samples=?
1176
+ WHERE task_complexity=? AND tier=?`` で SET。
1177
+ ``cost_samples`` は DISTINCT session 数(``read_tier_cost_rate_summary``
1178
+ の ``sessions`` フィールド)を格納する。
1179
+ c. ``commit()``。クリア後 SET 前に別プロセスが読む瞬間を作らないため
1180
+ 途中で commit しない(R1 冪等性保証)。
1181
+ 3. UPDATE-only: tier_bandit に行が存在しない (complexity, tier) は INSERT しない。
1182
+ alpha/beta のない半端な bandit 行の生成を防ぐ(R4 回避)。
1183
+
1184
+ 制限:
1185
+ USD/MTok レートの再現には billable_tokens が必要であり、本テーブル単独では
1186
+ 再現不可。レートが必要な箇所は ``read_tier_cost_rate_summary`` /
1187
+ ``read_tier_cost_rate_for_complexity`` を使うこと。
1188
+
1189
+ Args:
1190
+ db_path: c3.db のパス。省略時は locate_c3_db() で探索。
1191
+ None(locate_c3_db が None を返す)の場合は 0 を返す。
1192
+
1193
+ Returns:
1194
+ cost を SET できた行数(rowcount > 0 の UPDATE 件数合計)。
1195
+ DB 不在 / エラー時は 0 を返す(例外を投げない)。
1196
+ 例外発生時は ``finally: conn.close()`` 経由で rollback され、
1197
+ cost 列がクリアされた状態で残る可能性がある。
1198
+ 戻り値 0 を受け取った呼び出し元は次回 session_stop での再試行に委ねる。
1199
+ """
1200
+ if db_path is None:
1201
+ db_path = locate_c3_db()
1202
+ if db_path is None:
1203
+ return 0
1204
+
1205
+ rows = read_tier_cost_rate_summary(db_path=db_path)
1206
+
1207
+ set_count = 0
1208
+ try:
1209
+ conn = sqlite3.connect(str(db_path))
1210
+ try:
1211
+ conn.execute("PRAGMA journal_mode=WAL")
1212
+ _apply_busy_timeout(conn)
1213
+ # Step 1: 全行 cost 列クリア(alpha/beta/trials/last_updated は触らない)
1214
+ conn.execute("UPDATE tier_bandit SET total_cost_usd = 0.0, cost_samples = 0")
1215
+ # Step 2: 各集計行を SET(UPDATE-only: tier_bandit に行が無い場合は rowcount=0)
1216
+ for row in rows:
1217
+ cur = conn.execute(
1218
+ "UPDATE tier_bandit "
1219
+ "SET total_cost_usd = ?, cost_samples = ? "
1220
+ "WHERE task_complexity = ? AND tier = ?",
1221
+ (row["total_cost_usd"], row["sessions"], row["complexity"], row["tier"]),
1222
+ )
1223
+ set_count += cur.rowcount
1224
+ # Step 3: 全 SET が完了してから一括 commit(途中 commit しない)
1225
+ conn.commit()
1226
+ finally:
1227
+ conn.close()
1228
+ except Exception as exc: # noqa: BLE001
1229
+ logger.warning("failed to sync_tier_bandit_cost: %s", type(exc).__name__)
1230
+ return 0
1231
+
1232
+ return set_count