claude-code-conductor 2.22.0__tar.gz → 2.23.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 (194) hide show
  1. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/select_tier.py +145 -11
  2. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/CHANGELOG.md +26 -0
  3. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/PKG-INFO +1 -1
  4. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/__init__.py +1 -1
  5. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/cli_tier.py +2 -2
  6. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/db.py +33 -0
  7. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/pricing.py +41 -0
  8. claude_code_conductor-2.23.0/tests/hooks/test_select_tier.py +1005 -0
  9. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_cli_tier.py +1 -1
  10. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_db.py +146 -0
  11. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_pricing.py +46 -1
  12. claude_code_conductor-2.22.0/tests/hooks/test_select_tier.py +0 -495
  13. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/CLAUDE.md +0 -0
  14. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/architect.md +0 -0
  15. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/code-reviewer.md +0 -0
  16. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/developer.md +0 -0
  17. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/doc-writer.md +0 -0
  18. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/interviewer.md +0 -0
  19. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/planner.md +0 -0
  20. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/project-setup.md +0 -0
  21. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/security-reviewer.md +0 -0
  22. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/systematic-debugger.md +0 -0
  23. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/tester.md +0 -0
  24. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/wt_developer.md +0 -0
  25. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/wt_systematic-debugger.md +0 -0
  26. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/agents/wt_tester.md +0 -0
  27. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/breaking-changes.txt +0 -0
  28. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/deletions.txt +0 -0
  29. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/docs/config-policy.md +0 -0
  30. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/docs/parallel-agents-setup.md +0 -0
  31. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/docs/platform-adapters.md +0 -0
  32. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/docs/settings.json.md +0 -0
  33. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/_hook_utils.py +0 -0
  34. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/check_agent_invocation.py +0 -0
  35. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/consolidate_memory.py +0 -0
  36. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/permission_handler.py +0 -0
  37. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/permission_handler_toast.py +0 -0
  38. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/planner_check.py +0 -0
  39. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/post_tool.py +0 -0
  40. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/pre_compact.py +0 -0
  41. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/pre_tool.py +0 -0
  42. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/recall_inject.py +0 -0
  43. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/restore_session.py +0 -0
  44. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/session_start.py +0 -0
  45. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/session_stop.py +0 -0
  46. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/session_utils.py +0 -0
  47. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/statusline.py +0 -0
  48. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/stop.py +0 -0
  49. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/hooks/worktree_guard.py +0 -0
  50. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/memory/.gitkeep +0 -0
  51. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/permission_rules.json +0 -0
  52. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/rules/promoted/index.md +0 -0
  53. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/settings.json +0 -0
  54. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/codex-review/SKILL.md +0 -0
  55. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
  56. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
  57. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
  58. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
  59. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
  60. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/dev-workflow/scripts/record_tier_outcome.py +0 -0
  61. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
  62. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/develop/SKILL.md +0 -0
  63. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/doc/SKILL.md +0 -0
  64. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/extract-lib/SKILL.md +0 -0
  65. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/init-session/SKILL.md +0 -0
  66. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/mcp-config/SKILL.md +0 -0
  67. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
  68. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/pattern-status/SKILL.md +0 -0
  69. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
  70. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/recall/SKILL.md +0 -0
  71. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
  72. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
  73. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/review-phase/SKILL.md +0 -0
  74. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/setup/SKILL.md +0 -0
  75. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/setup/reference.md +0 -0
  76. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
  77. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
  78. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/skills/start/SKILL.md +0 -0
  79. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.claude/state/.gitkeep +0 -0
  80. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/.gitignore +0 -0
  81. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/LICENSE +0 -0
  82. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/LICENSES/chroma-hnswlib-LICENSE +0 -0
  83. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/LICENSES/chroma-hnswlib-NOTICE +0 -0
  84. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/LICENSES/fastembed-LICENSE +0 -0
  85. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/LICENSES/fastembed-NOTICE +0 -0
  86. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/LICENSES/onnxruntime-LICENSE +0 -0
  87. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
  88. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/README.md +0 -0
  89. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/hatch_build.py +0 -0
  90. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/pyproject.toml +0 -0
  91. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/__main__.py +0 -0
  92. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/_excludes.py +0 -0
  93. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/_terminal.py +0 -0
  94. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/adapters.py +0 -0
  95. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/cli.py +0 -0
  96. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/cli_ask.py +0 -0
  97. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/cli_doctor.py +0 -0
  98. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/cli_init.py +0 -0
  99. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/cli_list.py +0 -0
  100. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/cli_plan.py +0 -0
  101. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/cli_recall.py +0 -0
  102. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/cli_update.py +0 -0
  103. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/embedding.py +0 -0
  104. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/mcp_server.py +0 -0
  105. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/migrate.py +0 -0
  106. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/migrations/001_initial.sql +0 -0
  107. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
  108. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/migrations/003_tier_cost.sql +0 -0
  109. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/migrations/README.md +0 -0
  110. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/migrations/__init__.py +0 -0
  111. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/paths.py +0 -0
  112. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/plan_validator.py +0 -0
  113. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/platforms.py +0 -0
  114. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/question.py +0 -0
  115. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/recall_chunker.py +0 -0
  116. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/recall_index.py +0 -0
  117. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/src/c3/usage_ingester.py +0 -0
  118. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/__init__.py +0 -0
  119. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/conftest.py +0 -0
  120. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/fixtures/usage/README.md +0 -0
  121. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/fixtures/usage/mainline.jsonl +0 -0
  122. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
  123. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
  124. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/__init__.py +0 -0
  125. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_check_agent_invocation.py +0 -0
  126. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_consolidate_memory.py +0 -0
  127. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_hook_utils.py +0 -0
  128. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_permission_handler.py +0 -0
  129. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_permission_handler_toast.py +0 -0
  130. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
  131. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_planner_check.py +0 -0
  132. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_planner_check_dev.py +0 -0
  133. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_post_tool.py +0 -0
  134. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_pre_tool.py +0 -0
  135. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_recall_inject.py +0 -0
  136. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_record_review_decision.py +0 -0
  137. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_record_tier_outcome.py +0 -0
  138. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_restore_session.py +0 -0
  139. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_review_hint_inject.py +0 -0
  140. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_select_tier_escalation.py +0 -0
  141. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_session_start.py +0 -0
  142. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_session_stop.py +0 -0
  143. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_session_utils.py +0 -0
  144. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
  145. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_similarity_boost.py +0 -0
  146. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_statusline.py +0 -0
  147. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_statusline_template_sync.py +0 -0
  148. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_sync_check.py +0 -0
  149. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/hooks/test_template_guard.py +0 -0
  150. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/skills/__init__.py +0 -0
  151. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/skills/_skill_helpers.py +0 -0
  152. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
  153. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/skills/test_init_session_no_task_type.py +0 -0
  154. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/skills/test_planner_lightweight.py +0 -0
  155. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/skills/test_recall_skill.py +0 -0
  156. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
  157. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/skills/test_setup_templates.py +0 -0
  158. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
  159. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/skills/test_start_skill_new_flow.py +0 -0
  160. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
  161. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_adapters.py +0 -0
  162. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_cli_ask.py +0 -0
  163. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_cli_entry.py +0 -0
  164. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_cli_init.py +0 -0
  165. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_cli_list.py +0 -0
  166. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_cli_plan.py +0 -0
  167. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_cli_recall.py +0 -0
  168. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_cli_update_breaking_changes.py +0 -0
  169. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_cli_update_deletions.py +0 -0
  170. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_docstring_consistency.py +0 -0
  171. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_embedding.py +0 -0
  172. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_excludes.py +0 -0
  173. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_extract_breaking_changes.py +0 -0
  174. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_mcp_server_elicit.py +0 -0
  175. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_migrate.py +0 -0
  176. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_paths.py +0 -0
  177. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_plan_validator.py +0 -0
  178. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_pre_compact.py +0 -0
  179. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_pre_tool_hook.py +0 -0
  180. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_precompact_additional.py +0 -0
  181. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_precompact_toctou_fixes.py +0 -0
  182. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_recall_chunker.py +0 -0
  183. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_recall_index.py +0 -0
  184. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_references_migration.py +0 -0
  185. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_session_utils_additional.py +0 -0
  186. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_skill_no_builtin_conflict.py +0 -0
  187. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_statusline.py +0 -0
  188. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_stop_additional.py +0 -0
  189. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_stop_hook.py +0 -0
  190. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_stop_precompact_fixes.py +0 -0
  191. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_sync_template_stop.py +0 -0
  192. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_template_pre_tool_hook.py +0 -0
  193. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_usage_ingester.py +0 -0
  194. {claude_code_conductor-2.22.0 → claude_code_conductor-2.23.0}/tests/test_worktree_guard.py +0 -0
@@ -31,6 +31,7 @@ import os
31
31
  import random
32
32
  import re
33
33
  import sys
34
+ from typing import NamedTuple
34
35
  from pathlib import Path
35
36
 
36
37
  try:
@@ -110,6 +111,69 @@ _PROMPT_HISTORY_SCAN_LINES = 1000
110
111
 
111
112
  TIERS: tuple[str, ...] = ("haiku", "sonnet", "opus")
112
113
 
114
+ # cost-aware tie-break の拮抗判定閾値(v2.23.0)。
115
+ # Beta サンプルは 0〜1 スケール。成功率 5pt 以内=実質同等とみなす拮抗判定閾値。
116
+ # 過大は成功率犠牲リスク、過小は無発動。調整可能化は v2.24.0。
117
+ EPSILON: float = 0.05
118
+
119
+
120
+ class SelectionResult(NamedTuple):
121
+ """select_tier_detailed の戻り値(NamedTuple = immutable)。
122
+
123
+ tier: 選択された tier 名。
124
+ mode: "uniform" または "thompson"。
125
+ cost_tiebreak: Thompson 分岐で cost tie-break が発動した場合 True。
126
+ contenders: 拮抗判定に入った tier のタプル(observability/デバッグ用)。
127
+ frozen 安全のため list ではなく tuple を使用。
128
+ """
129
+
130
+ tier: str
131
+ mode: str
132
+ cost_tiebreak: bool = False
133
+ contenders: tuple[str, ...] = ()
134
+
135
+
136
+ def _cost_tiebreak(
137
+ samples: dict[str, float],
138
+ cost_map: dict[str, float] | None,
139
+ *,
140
+ epsilon: float = EPSILON,
141
+ ) -> tuple[str, bool, tuple[str, ...]]:
142
+ """Thompson サンプル拮抗群内で min-max 正規化コストが最安の tier を返す。
143
+
144
+ Args:
145
+ samples: {tier: beta_sample} の dict(Thompson Sampling 結果)。
146
+ cost_map: {tier: cost} の dict。None なら cost を見ず従来挙動。
147
+ cost は実測 avg_cost_usd または静的参照単価(ハイブリッド)。
148
+ 混在スケール(USD vs per-MTok)の厳密化は v2.24.0。
149
+ ``cost_map`` は None、または contenders 全件をキーとして含む dict を
150
+ 渡すこと。partial dict を渡すと ``cost_map[t]`` で KeyError が発生する。
151
+ ``select_tier_detailed`` 経由では呼び出し側(main)が全 TIERS 分を
152
+ 構築して保証する。
153
+ epsilon: 拮抗判定の閾値(デフォルト EPSILON=0.05)。
154
+
155
+ Returns:
156
+ (chosen, did_tiebreak, contenders) のタプル。
157
+ - chosen: 選択された tier 名。
158
+ - did_tiebreak: cost tie-break が発動した場合 True。
159
+ - contenders: 拮抗判定に入った tier のタプル。
160
+ """
161
+ max_sample = max(samples.values())
162
+ contenders = [t for t in samples if max_sample - samples[t] <= epsilon]
163
+
164
+ if len(contenders) <= 1 or cost_map is None:
165
+ # 従来挙動と完全一致: max(samples, key=lambda t: samples[t]) と同じ式
166
+ chosen = max(samples, key=lambda t: samples[t])
167
+ return chosen, False, tuple(contenders)
168
+
169
+ # 拮抗群内で min-max 正規化コストを計算し最安 tier を選ぶ
170
+ costs = {t: cost_map[t] for t in contenders}
171
+ lo, hi = min(costs.values()), max(costs.values())
172
+ norm = {t: ((costs[t] - lo) / (hi - lo) if hi > lo else 0.0) for t in contenders}
173
+ # 同値安定 tie-break: norm 同値時はサンプル大(=従来選好)を優先 → 決定論
174
+ chosen = min(contenders, key=lambda t: (norm[t], -samples[t]))
175
+ return chosen, True, tuple(contenders)
176
+
113
177
 
114
178
  def _prompt_prefix_and_hash(prompt: str) -> tuple[str, str]:
115
179
  """prompt から (prefix, hash) を抽出する。
@@ -226,34 +290,67 @@ def estimate_complexity(prompt: str) -> str:
226
290
  return "medium"
227
291
 
228
292
 
229
- def select_tier(
293
+ def select_tier_detailed(
230
294
  params: dict[str, tuple[float, float, int]],
231
295
  *,
232
296
  rng: random.Random | None = None,
233
- ) -> tuple[str, str]:
234
- """Beta サンプリングまたは uniform 選択で推奨 Tier を返す。
297
+ cost_map: dict[str, float] | None = None,
298
+ ) -> SelectionResult:
299
+ """Beta サンプリングまたは uniform 選択で推奨 Tier を SelectionResult で返す。
235
300
 
236
301
  Args:
237
302
  params: ``read_tier_params`` の戻り値。
238
303
  ``{"haiku": (alpha, beta, trials), ...}``
239
304
  rng: テスト用に決定論的にしたい場合は ``random.Random(seed)`` を渡す。
305
+ cost_map: {tier: cost} の dict、または None。
306
+ None(cost を見ない=従来 Thompson)または「params の全 tier キーを
307
+ 含む完全な dict」のいずれか。partial dict は渡されない前提
308
+ (呼び出し側が全 TIERS 分を構築して保証する)。
309
+ uniform 分岐では cost_map の有無に関わらず完全無視する(探索保護)。
240
310
 
241
311
  Returns:
242
- ``(tier, mode)`` のタプル。``mode`` は ``"thompson"`` / ``"uniform"`` で、
243
- プロンプトに「学習データ収集中」と表示するかの分岐に使う。
312
+ SelectionResult(tier, mode, cost_tiebreak, contenders)。
313
+ mode は ``"thompson"`` / ``"uniform"``。
244
314
  """
245
315
  rng = rng or random
246
316
  total_trials = sum(p[2] for p in params.values())
247
317
  if total_trials < LEARNING_THRESHOLD:
248
- return rng.choice(TIERS), "uniform"
318
+ # uniform: cost を完全無視・従来挙動完全維持
319
+ return SelectionResult(rng.choice(TIERS), "uniform", False, ())
249
320
 
250
- # Thompson Sampling: tier Beta(α, β) からサンプリング、最大値を選ぶ
321
+ # Thompson Sampling: rng の消費順序を従来 select_tier と完全一致させる
251
322
  samples = {
252
323
  tier: rng.betavariate(p[0], p[1])
253
324
  for tier, p in params.items()
254
325
  }
255
- chosen = max(samples, key=lambda t: samples[t])
256
- return chosen, "thompson"
326
+ chosen, did_tiebreak, contenders = _cost_tiebreak(samples, cost_map)
327
+ return SelectionResult(chosen, "thompson", did_tiebreak, contenders)
328
+
329
+
330
+ def select_tier(
331
+ params: dict[str, tuple[float, float, int]],
332
+ *,
333
+ rng: random.Random | None = None,
334
+ cost_map: dict[str, float] | None = None,
335
+ ) -> tuple[str, str]:
336
+ """Beta サンプリングまたは uniform 選択で推奨 Tier を返す。
337
+
338
+ Args:
339
+ params: ``read_tier_params`` の戻り値。
340
+ ``{"haiku": (alpha, beta, trials), ...}``
341
+ rng: テスト用に決定論的にしたい場合は ``random.Random(seed)`` を渡す。
342
+ cost_map: {tier: cost} の dict、または None。
343
+ None なら cost を見ず従来の Thompson Sampling と完全一致。
344
+ uniform 分岐では cost_map の有無に関わらず完全無視する。
345
+ 詳細は :func:`select_tier_detailed` を参照。
346
+
347
+ Returns:
348
+ ``(tier, mode)`` のタプル。``mode`` は ``"thompson"`` / ``"uniform"`` で、
349
+ プロンプトに「学習データ収集中」と表示するかの分岐に使う。
350
+ 戻り値型は v2.22.0 以前と完全に不変。
351
+ """
352
+ result = select_tier_detailed(params, rng=rng, cost_map=cost_map)
353
+ return result.tier, result.mode
257
354
 
258
355
 
259
356
  # Phase 2-B: 失敗率による昇格マッピング(haiku → sonnet, sonnet → opus)。
@@ -324,6 +421,7 @@ def write_tier_selection(
324
421
  prompt_prefix: str | None = None,
325
422
  prompt_hash: str | None = None,
326
423
  session_id: str | None = None,
424
+ cost_tiebreak: bool = False,
327
425
  ) -> None:
328
426
  """直近の選択結果を ``tier_selection.json`` に書く。
329
427
 
@@ -339,6 +437,10 @@ def write_tier_selection(
339
437
 
340
438
  ``session_id`` を任意で含める。UserPromptSubmit payload の session UUID。
341
439
  None のときは tier_selection.json のキー自体を省略する(後方互換)。
440
+
441
+ ``cost_tiebreak`` を任意で含める(v2.23.0)。
442
+ Thompson Sampling の拮抗群内で cost tie-break が発動した場合のみ True。
443
+ False のときはキー自体を省略する(escalated/session_id と同パターン)。
342
444
  """
343
445
  os.makedirs(os.path.dirname(TIER_SELECTION_PATH), exist_ok=True)
344
446
  payload: dict[str, object] = {
@@ -360,6 +462,8 @@ def write_tier_selection(
360
462
  payload["prompt_hash"] = prompt_hash
361
463
  if session_id is not None:
362
464
  payload["session_id"] = session_id
465
+ if cost_tiebreak:
466
+ payload["cost_tiebreak"] = True
363
467
  try:
364
468
  with open(TIER_SELECTION_PATH, "w", encoding="utf-8") as f:
365
469
  json.dump(payload, f, ensure_ascii=False)
@@ -376,8 +480,13 @@ def build_additional_context(
376
480
  *,
377
481
  escalation_reason: str | None = None,
378
482
  complexity_source: str | None = None,
483
+ cost_tiebreak: bool = False,
379
484
  ) -> str:
380
- """親 Claude に追加注入する文字列を組み立てる。"""
485
+ """親 Claude に追加注入する文字列を組み立てる。
486
+
487
+ ``cost_tiebreak`` が True のとき、suffix に cost-aware 発動を示す文言を追加する(v2.23.0)。
488
+ False のときは不変(既存文言と完全一致)。
489
+ """
381
490
  trials = sum(p[2] for p in params.values())
382
491
  if mode == "uniform":
383
492
  confidence = f"学習データ収集中(合計 {trials}/{LEARNING_THRESHOLD} 試行)"
@@ -390,6 +499,8 @@ def build_additional_context(
390
499
  suffix += f" [Phase 2-B 昇格: {escalation_reason}]"
391
500
  if complexity_source:
392
501
  suffix += f" [複雑度判定: {complexity_source}]"
502
+ if cost_tiebreak:
503
+ suffix += " [cost-aware: 成功率拮抗のため低コスト Tier を選択]"
393
504
 
394
505
  return (
395
506
  f"[tier-routing 推奨] 複雑度: {complexity} / 推奨 Tier: {tier}({confidence})。"
@@ -445,7 +556,28 @@ def main() -> int:
445
556
  else:
446
557
  params = c3_db.read_tier_params(complexity)
447
558
 
448
- tier, mode = select_tier(params)
559
+ # v2.23.0: cost_map をハイブリッド解決(実測 avg_cost を主に、欠損 tier は静的単価で補完)。
560
+ # c3_db が None または pricing import 失敗時は cost_map=None で従来 Thompson にデグレード。
561
+ # cost_map=None の場合、select_tier_detailed/_cost_tiebreak は従来 Thompson 挙動と完全一致(引数定義参照)。
562
+ cost_map = None
563
+ if c3_db is not None:
564
+ try:
565
+ from c3 import pricing # type: ignore[import-not-found]
566
+ measured = c3_db.read_tier_cost_for_complexity(complexity) # {tier: avg} 実測>0 のみ
567
+ cost_map = {}
568
+ for tier_name in TIERS:
569
+ if tier_name in measured and measured[tier_name] > 0:
570
+ cost_map[tier_name] = measured[tier_name]
571
+ else:
572
+ # tier_reference_cost は未知 tier に 0.0 を返すが、TIERS と _TIER_REFERENCE_KEY は
573
+ # 同期前提のため現行 3 tier(haiku/sonnet/opus)では 0.0 混入は起きない。
574
+ cost_map[tier_name] = pricing.tier_reference_cost(tier_name) # 静的 fallback
575
+ except ImportError:
576
+ cost_map = None
577
+
578
+ result = select_tier_detailed(params, cost_map=cost_map)
579
+ tier, mode = result.tier, result.mode
580
+ cost_tiebreak = result.cost_tiebreak
449
581
 
450
582
  # Phase 2-B: failure rate に基づく escalation
451
583
  effective_tier, escalation_reason = maybe_escalate(complexity, tier)
@@ -457,12 +589,14 @@ def main() -> int:
457
589
  prompt_prefix=prompt_prefix,
458
590
  prompt_hash=prompt_hash,
459
591
  session_id=session_id,
592
+ cost_tiebreak=cost_tiebreak,
460
593
  )
461
594
 
462
595
  context_text = build_additional_context(
463
596
  complexity, effective_tier, mode, params,
464
597
  escalation_reason=escalation_reason,
465
598
  complexity_source=complexity_source,
599
+ cost_tiebreak=cost_tiebreak,
466
600
  )
467
601
  output = {
468
602
  "hookSpecificOutput": {
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.23.0] - 2026-05-25
4
+
5
+ **tier-routing cost-aware tie-break**: `select_tier` の Thompson Sampling 分岐に「拮抗 tier 群内コスト tie-break」を追加する。サンプル最大から ε(=0.05) 以内の拮抗 tier が複数ある場合のみ、min-max 正規化コストが最安の tier を選ぶ。単独最大なら従来通り(挙動不変)。成功率を犠牲にしない最小スコープの cost 統合。
6
+
7
+ **スコープ注記**:
8
+ - **最小スコープ=tie-break のみ**。精度向上(model 一致集計・agent_id 単位紐づけ)と `tier_bandit.total_cost_usd`/`cost_samples` への書き込みは **v2.24.0**。
9
+ - cost は**ハイブリッド源**(実測 avg_cost を主・欠損 tier は静的参照単価で補完)。実測 USD と静的 per-MTok の混在スケールは min-max 正規化で**拮抗群内の概算順位**に畳む(厳密な単位整合は v2.24.0)。
10
+ - LEARNING_THRESHOLD(30) 未満は cost 完全無視(uniform のまま・探索保護)。escalation は不変。
11
+ - migration なし(読み出しのみ・書き込みなし)。**破壊的変更なし**(optional kw-only 引数追加・None/False 時従来動作・出力キー条件付き追加のみ)。
12
+
13
+ ### 機能追加
14
+
15
+ - **`src/c3/pricing.py`: `tier_reference_cost(tier) -> float`(新規)**: tier 名から input+output 静的参照単価和を返す純関数。haiku < sonnet < opus の単調性を保証。未知 tier は 0.0 を返す。`_TIER_REFERENCE_KEY` 定数で TIERS との同期チェックを明示。
16
+
17
+ - **`src/c3/db.py`: `read_tier_cost_for_complexity(complexity, *, db_path=None) -> dict[str, float]`(新規)**: `read_tier_cost_summary` を complexity 一致・avg_cost_usd > 0 でフィルタし `{tier: avg_cost_usd}` を返す薄いラッパー。テーブル不在・データ不在・DB 不在で `{}`。
18
+
19
+ - **`.claude/hooks/select_tier.py`**: `EPSILON=0.05` 定数 / `SelectionResult`(NamedTuple: tier/mode/cost_tiebreak/contenders)/ `_cost_tiebreak`(拮抗群 min-max 最安・同値はサンプル大優先で決定論)/ `select_tier_detailed(params, *, rng, cost_map)`(2 層 API の詳細版)を追加。
20
+
21
+ ### 変更
22
+
23
+ - **`.claude/hooks/select_tier.py`**: `select_tier` に kw-only `cost_map=None` を追加し `select_tier_detailed` への委譲に変更(**戻り値型 (tier, mode) 不変**)。`write_tier_selection`/`build_additional_context` に `cost_tiebreak: bool = False` を追加(True 時のみ json キー追加 / context suffix「[cost-aware: 成功率拮抗のため低コスト Tier を選択]」追記)。`main()` で cost_map をハイブリッド解決し `select_tier_detailed` を使用(c3_db/pricing import 失敗時は cost_map=None で従来 Thompson にデグレード)。
24
+
25
+ - **`src/c3/__init__.py`**: `__version__` を `"2.22.0"` から `"2.23.0"` に更新。
26
+
27
+ - **注記更新**: `c3 tier stats` の「精度向上は v2.23.0」を「精度向上は v2.24.0」へ、「cost-aware routing 本体は v2.23.0 予定」を「cost-aware routing 本体(tie-break)実装済み。精度向上は v2.24.0 予定」へ更新。
28
+
3
29
  ## [2.22.0] - 2026-05-25
4
30
 
5
31
  **tier-routing cost 紐づけデータ蓄積**: tier_recent_outcomes に session_id 列を追加し、agent_cost_runs と JOIN できるデータ基盤を整備する。`c3 tier stats` に complexity×tier 別の平均コストセクションを追加する。cost-aware routing 本体は v2.23.0。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-conductor
3
- Version: 2.22.0
3
+ Version: 2.23.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
@@ -1,3 +1,3 @@
1
1
  """Claude Code Conductor (C3) - multi-agent orchestration framework for Claude Code."""
2
2
 
3
- __version__ = "2.22.0"
3
+ __version__ = "2.23.0"
@@ -194,7 +194,7 @@ def _render_human(snapshot: dict[str, Any]) -> None:
194
194
  )
195
195
  print()
196
196
 
197
- print("== Tier 別平均コスト(粗い概算 / 精度向上は v2.23.0) ==")
197
+ print("== Tier 別平均コスト(粗い概算 / 精度向上は v2.24.0) ==")
198
198
  tier_cost = snapshot.get("tier_cost", [])
199
199
  if not tier_cost:
200
200
  print("(cost 紐づけデータ未収集)")
@@ -208,4 +208,4 @@ def _render_human(snapshot: dict[str, Any]) -> None:
208
208
  f"${row['total_cost_usd']:>9.4f}"
209
209
  )
210
210
  print()
211
- print("(注: データ紐づけ蓄積。cost-aware routing 本体は v2.23.0 予定)")
211
+ print("(注: cost-aware routing 本体(tie-break)実装済み。精度向上は v2.24.0 予定)")
@@ -759,6 +759,39 @@ def read_tier_cost_summary(
759
759
  ]
760
760
 
761
761
 
762
+ def read_tier_cost_for_complexity(
763
+ complexity: str,
764
+ *,
765
+ db_path: Path | None = None,
766
+ ) -> dict[str, float]:
767
+ """complexity 別の tier 平均コストを {tier: avg_cost_usd} で返す。
768
+
769
+ tie-break のハイブリッド cost 源(実測 avg_cost)。
770
+ complexity 一致 & avg_cost_usd > 0 のみ。
771
+ ``read_tier_cost_summary`` の薄いラッパー。
772
+
773
+ DB アクセスは ``read_tier_cost_summary`` に委譲するため、
774
+ DB 例外処理・busy_timeout・read 規約は同関数から継承される。
775
+ データ/DB 不在で ``read_tier_cost_summary`` が ``[]`` を返す場合、
776
+ 本関数は ``{}`` を返す。
777
+
778
+ Args:
779
+ complexity: フィルタ対象の complexity 値("simple" / "medium" / "complex")。
780
+ db_path: c3.db のパス。省略時は :func:`locate_c3_db` で探索
781
+ (``read_tier_cost_summary`` に委譲)。
782
+
783
+ Returns:
784
+ ``{tier: avg_cost_usd}`` の dict。
785
+ 該当データ不在・DB 不在・エラー時は ``{}``。
786
+ """
787
+ rows = read_tier_cost_summary(db_path=db_path)
788
+ return {
789
+ r["tier"]: r["avg_cost_usd"]
790
+ for r in rows
791
+ if r["complexity"] == complexity and r["avg_cost_usd"] > 0
792
+ }
793
+
794
+
762
795
  def get_ingest_offset(
763
796
  file_key: str,
764
797
  *,
@@ -10,6 +10,7 @@
10
10
  - resolve_tier(model) -> str | None
11
11
  - compute_cost_usd(...) -> tuple[float, bool]
12
12
  - known_models() -> tuple[str, ...]
13
+ - tier_reference_cost(tier) -> float
13
14
 
14
15
  設計判断(plan-report T1 §2):
15
16
  Opus は世代で単価が約 3 倍異なる(4.1/4 = $15 系、4.5/4.6/4.7 = $5 系)ため、
@@ -148,6 +149,46 @@ def compute_cost_usd(
148
149
  return (cost, True)
149
150
 
150
151
 
152
+ # ---------------------------------------------------------------------------
153
+ # tier → 参照単価マッピング(v2.23.0 T1)
154
+ # tie-break 静的 fallback 用。tier 名から _PRICING キーへの対応表。
155
+ # 注意: select_tier.py の TIERS と同期必須。新 tier を TIERS に追加する場合は
156
+ # 本 dict も更新すること(未更新だと tier_reference_cost が未知 tier に 0.0 を返し、
157
+ # cost_map に混入して min-max 正規化で最安誤判定につながる)。
158
+ # ---------------------------------------------------------------------------
159
+ _TIER_REFERENCE_KEY: dict[str, str] = {
160
+ "haiku": "haiku-4-5",
161
+ "sonnet": "sonnet-4-5",
162
+ "opus": "opus-4-5",
163
+ }
164
+
165
+
166
+ def tier_reference_cost(tier: str) -> float:
167
+ """tier 名から静的参照単価(input + output 単価和、USD/MTok)を返す。
168
+
169
+ tie-break の static fallback 用。実測 avg_cost が利用可能な場合は
170
+ 実測値を優先し、本関数はデータ不足/欠損 tier の補完にのみ使用すること。
171
+
172
+ min-max 正規化で使用するため、絶対値より haiku < sonnet < opus の
173
+ 順位が重要(_PRICING の現行世代単価で単調性が保証される)。
174
+
175
+ Args:
176
+ tier: "haiku" / "sonnet" / "opus" のいずれか。
177
+
178
+ Returns:
179
+ _PRICING[_TIER_REFERENCE_KEY[tier]] の input + output 単価和(USD/MTok)。
180
+ 未知 tier(_TIER_REFERENCE_KEY に存在しないキー)は 0.0 を返す。
181
+ """
182
+ key = _TIER_REFERENCE_KEY.get(tier)
183
+ if key is None:
184
+ return 0.0
185
+ pricing = _PRICING.get(key)
186
+ if pricing is None:
187
+ return 0.0
188
+ inp_price, out_price, _cache_write, _cache_read = pricing
189
+ return inp_price + out_price
190
+
191
+
151
192
  def known_models() -> tuple[str, ...]:
152
193
  """単価表のキー一覧を返す(テスト・デバッグ用)。
153
194