claude-code-conductor 2.19.0__tar.gz → 2.21.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 (192) hide show
  1. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/breaking-changes.txt +1 -0
  2. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/deletions.txt +3 -0
  3. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/docs/config-policy.md +2 -1
  4. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/session_start.py +49 -39
  5. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/session_stop.py +17 -0
  6. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/CHANGELOG.md +98 -0
  7. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/PKG-INFO +1 -1
  8. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/__init__.py +1 -1
  9. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/cli_tier.py +26 -1
  10. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/db.py +247 -0
  11. claude_code_conductor-2.21.0/src/c3/migrate.py +260 -0
  12. claude_code_conductor-2.19.0/.claude/hooks/schema.sql → claude_code_conductor-2.21.0/src/c3/migrations/001_initial.sql +30 -12
  13. claude_code_conductor-2.21.0/src/c3/migrations/002_agent_cost_runs.sql +41 -0
  14. claude_code_conductor-2.21.0/src/c3/migrations/README.md +90 -0
  15. claude_code_conductor-2.21.0/src/c3/migrations/__init__.py +1 -0
  16. claude_code_conductor-2.21.0/src/c3/pricing.py +157 -0
  17. claude_code_conductor-2.21.0/src/c3/usage_ingester.py +388 -0
  18. claude_code_conductor-2.21.0/tests/fixtures/usage/README.md +18 -0
  19. claude_code_conductor-2.21.0/tests/fixtures/usage/mainline.jsonl +5 -0
  20. claude_code_conductor-2.21.0/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +3 -0
  21. claude_code_conductor-2.21.0/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +1 -0
  22. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_record_tier_outcome.py +2 -6
  23. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_review_hint_inject.py +2 -7
  24. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_select_tier.py +2 -6
  25. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_select_tier_escalation.py +2 -6
  26. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_session_start.py +185 -47
  27. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_cli_tier.py +92 -6
  28. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_db.py +158 -4
  29. claude_code_conductor-2.21.0/tests/test_migrate.py +521 -0
  30. claude_code_conductor-2.21.0/tests/test_pricing.py +225 -0
  31. claude_code_conductor-2.21.0/tests/test_usage_ingester.py +529 -0
  32. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/CLAUDE.md +0 -0
  33. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/architect.md +0 -0
  34. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/code-reviewer.md +0 -0
  35. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/developer.md +0 -0
  36. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/doc-writer.md +0 -0
  37. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/interviewer.md +0 -0
  38. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/planner.md +0 -0
  39. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/project-setup.md +0 -0
  40. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/security-reviewer.md +0 -0
  41. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/systematic-debugger.md +0 -0
  42. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/tester.md +0 -0
  43. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/wt_developer.md +0 -0
  44. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/wt_systematic-debugger.md +0 -0
  45. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/agents/wt_tester.md +0 -0
  46. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/docs/parallel-agents-setup.md +0 -0
  47. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/docs/platform-adapters.md +0 -0
  48. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/docs/settings.json.md +0 -0
  49. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/_hook_utils.py +0 -0
  50. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/check_agent_invocation.py +0 -0
  51. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/consolidate_memory.py +0 -0
  52. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/permission_handler.py +0 -0
  53. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/permission_handler_toast.py +0 -0
  54. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/planner_check.py +0 -0
  55. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/post_tool.py +0 -0
  56. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/pre_compact.py +0 -0
  57. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/pre_tool.py +0 -0
  58. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/recall_inject.py +0 -0
  59. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/restore_session.py +0 -0
  60. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/select_tier.py +0 -0
  61. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/session_utils.py +0 -0
  62. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/statusline.py +0 -0
  63. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/stop.py +0 -0
  64. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/hooks/worktree_guard.py +0 -0
  65. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/memory/.gitkeep +0 -0
  66. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/permission_rules.json +0 -0
  67. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/rules/promoted/index.md +0 -0
  68. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/settings.json +0 -0
  69. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/codex-review/SKILL.md +0 -0
  70. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
  71. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
  72. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
  73. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
  74. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
  75. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/dev-workflow/scripts/record_tier_outcome.py +0 -0
  76. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
  77. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/develop/SKILL.md +0 -0
  78. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/doc/SKILL.md +0 -0
  79. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/extract-lib/SKILL.md +0 -0
  80. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/init-session/SKILL.md +0 -0
  81. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/mcp-config/SKILL.md +0 -0
  82. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
  83. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/pattern-status/SKILL.md +0 -0
  84. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
  85. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/recall/SKILL.md +0 -0
  86. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
  87. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
  88. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/review-phase/SKILL.md +0 -0
  89. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/setup/SKILL.md +0 -0
  90. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/setup/reference.md +0 -0
  91. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
  92. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
  93. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/skills/start/SKILL.md +0 -0
  94. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.claude/state/.gitkeep +0 -0
  95. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/.gitignore +0 -0
  96. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/LICENSE +0 -0
  97. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/LICENSES/chroma-hnswlib-LICENSE +0 -0
  98. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/LICENSES/chroma-hnswlib-NOTICE +0 -0
  99. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/LICENSES/fastembed-LICENSE +0 -0
  100. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/LICENSES/fastembed-NOTICE +0 -0
  101. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/LICENSES/onnxruntime-LICENSE +0 -0
  102. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
  103. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/README.md +0 -0
  104. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/hatch_build.py +0 -0
  105. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/pyproject.toml +0 -0
  106. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/__main__.py +0 -0
  107. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/_excludes.py +0 -0
  108. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/_terminal.py +0 -0
  109. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/adapters.py +0 -0
  110. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/cli.py +0 -0
  111. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/cli_ask.py +0 -0
  112. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/cli_doctor.py +0 -0
  113. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/cli_init.py +0 -0
  114. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/cli_list.py +0 -0
  115. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/cli_plan.py +0 -0
  116. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/cli_recall.py +0 -0
  117. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/cli_update.py +0 -0
  118. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/embedding.py +0 -0
  119. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/mcp_server.py +0 -0
  120. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/paths.py +0 -0
  121. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/plan_validator.py +0 -0
  122. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/platforms.py +0 -0
  123. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/question.py +0 -0
  124. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/recall_chunker.py +0 -0
  125. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/src/c3/recall_index.py +0 -0
  126. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/__init__.py +0 -0
  127. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/conftest.py +0 -0
  128. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/__init__.py +0 -0
  129. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_check_agent_invocation.py +0 -0
  130. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_consolidate_memory.py +0 -0
  131. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_hook_utils.py +0 -0
  132. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_permission_handler.py +0 -0
  133. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_permission_handler_toast.py +0 -0
  134. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
  135. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_planner_check.py +0 -0
  136. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_planner_check_dev.py +0 -0
  137. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_post_tool.py +0 -0
  138. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_pre_tool.py +0 -0
  139. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_recall_inject.py +0 -0
  140. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_record_review_decision.py +0 -0
  141. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_restore_session.py +0 -0
  142. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_session_stop.py +0 -0
  143. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_session_utils.py +0 -0
  144. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
  145. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_similarity_boost.py +0 -0
  146. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_statusline.py +0 -0
  147. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_statusline_template_sync.py +0 -0
  148. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_sync_check.py +0 -0
  149. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/hooks/test_template_guard.py +0 -0
  150. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/skills/__init__.py +0 -0
  151. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/skills/_skill_helpers.py +0 -0
  152. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
  153. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/skills/test_init_session_no_task_type.py +0 -0
  154. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/skills/test_planner_lightweight.py +0 -0
  155. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/skills/test_recall_skill.py +0 -0
  156. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
  157. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/skills/test_setup_templates.py +0 -0
  158. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
  159. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/skills/test_start_skill_new_flow.py +0 -0
  160. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
  161. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_adapters.py +0 -0
  162. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_cli_ask.py +0 -0
  163. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_cli_entry.py +0 -0
  164. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_cli_init.py +0 -0
  165. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_cli_list.py +0 -0
  166. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_cli_plan.py +0 -0
  167. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_cli_recall.py +0 -0
  168. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_cli_update_breaking_changes.py +0 -0
  169. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_cli_update_deletions.py +0 -0
  170. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_docstring_consistency.py +0 -0
  171. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_embedding.py +0 -0
  172. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_excludes.py +0 -0
  173. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_extract_breaking_changes.py +0 -0
  174. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_mcp_server_elicit.py +0 -0
  175. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_paths.py +0 -0
  176. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_plan_validator.py +0 -0
  177. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_pre_compact.py +0 -0
  178. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_pre_tool_hook.py +0 -0
  179. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_precompact_additional.py +0 -0
  180. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_precompact_toctou_fixes.py +0 -0
  181. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_recall_chunker.py +0 -0
  182. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_recall_index.py +0 -0
  183. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_references_migration.py +0 -0
  184. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_session_utils_additional.py +0 -0
  185. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_skill_no_builtin_conflict.py +0 -0
  186. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_statusline.py +0 -0
  187. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_stop_additional.py +0 -0
  188. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_stop_hook.py +0 -0
  189. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_stop_precompact_fixes.py +0 -0
  190. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_sync_template_stop.py +0 -0
  191. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_template_pre_tool_hook.py +0 -0
  192. {claude_code_conductor-2.19.0 → claude_code_conductor-2.21.0}/tests/test_worktree_guard.py +0 -0
@@ -5,3 +5,4 @@
5
5
  # Lines starting with '#' are comments. Blank lines ignored.
6
6
 
7
7
  v2.11.0|summarize-memory agent + skill removed; replaced by `c3 recall`|summarize-memory エージェント / skill を完全廃止。代替は `c3 recall` の意味検索。
8
+ v2.20.0|hooks/schema.sql removed; SQLite schema is now managed by c3/migrations/ (auto-applied at session_start)|hooks/schema.sql を削除しました。SQLite スキーマは c3/migrations/ (wheel 内) で管理され session_start で自動適用されます。
@@ -37,3 +37,6 @@ skills/phase-c-plan/SKILL.md
37
37
  skills/phase-d-implement/SKILL.md
38
38
  skills/phase-debug/SKILL.md
39
39
  skills/phase-e-review/SKILL.md
40
+
41
+ # ---- v2.20.0: schema.sql を c3/migrations/ へ移行 ----
42
+ hooks/schema.sql
@@ -24,6 +24,7 @@
24
24
  .dev/hooks/ 配布元専用 hook(配布されない)
25
25
  src/c3/_excludes.py wheel 除外パターン(配布元のビルド制御)
26
26
  hatch_build.py _excludes.py の重複定義(ビルド時専用)
27
+ src/c3/migrations/ SQLite schema migration SQL ファイル群(Python package・wheel 同梱・.claude/ 配下ではないため 3 ファイル同期対象外)
27
28
  ```
28
29
 
29
30
  ### 1-2. 利用先プロジェクト(C3 ユーザー向け)
@@ -111,7 +112,7 @@ hatch_build.py _excludes.py の重複定義(ビルド
111
112
 
112
113
  | # | カテゴリ | 配布 | c3 update が更新 | 理由 |
113
114
  |---|---|---|---|---|
114
- | 1 | `.claude/hooks/*.py` | ○ | ○ | Claude Code lifecycle hook の実体。配布先で動作する。例外: `subagent_log.py` のみ除外(個人デバッグ用) |
115
+ | 1 | `.claude/hooks/*.py` | ○ | ○ | Claude Code lifecycle hook の実体。配布先で動作する。例外: `subagent_log.py` のみ除外(個人デバッグ用)。v2.20.0 で `hooks/schema.sql` を削除し SQLite スキーマは `src/c3/migrations/` に移管 |
115
116
  | 2 | `.claude/agents/*.md` | ○ | ○ | ペルソナ定義。配布先で読まれる。例外: `tdd-develop.md` のみ除外(v2.1.0 廃止) |
116
117
  | 3 | `.claude/skills/*/` | ○ | ○ | オーケストレーション/ユーティリティ skill 定義(`scripts/` / `templates/` 等サブディレクトリ含む)。例外: `worktree-tdd-workflow/*` のみ除外(v2.1.0 廃止) |
117
118
  | 4 | `.claude/rules/*.md` | ○ | ○ | C3 配布デフォルトルール(常時注入対象) |
@@ -12,22 +12,23 @@
12
12
  設計判断:
13
13
  - 旧 3 ファイルを統合することで SessionStart hook は本ファイル 1 本のみで完結
14
14
  - init-session SKILL.md からの手動 2 回呼び出しが不要になる
15
- - 旧 init_c3_db.py `apply_schema()` / `SCHEMA_VERSION` は test 互換性のため
16
- module レベルでそのまま公開する
15
+ - v2.20.0 apply_schema() c3.migrate.apply_pending_migrations() に委譲。
16
+ SCHEMA_VERSION / SCHEMA_PATH 定数および schema.sql は廃止。
17
17
  """
18
18
 
19
19
  from __future__ import annotations
20
20
 
21
21
  import json
22
+ import logging
22
23
  import os
23
24
  import shutil
24
- import sqlite3
25
25
  import sys
26
26
  import tempfile
27
- from datetime import datetime, timezone
28
27
 
29
28
  from session_utils import is_worktree
30
29
 
30
+ logger = logging.getLogger(__name__)
31
+
31
32
  try:
32
33
  sys.stdin.reconfigure(encoding="utf-8")
33
34
  sys.stdout.reconfigure(encoding="utf-8")
@@ -71,7 +72,11 @@ def _run_clear_file_history() -> None:
71
72
  except FileNotFoundError:
72
73
  pass # already deleted by another process between listdir and unlink/rmtree
73
74
  except Exception as e:
74
- print(f'[clear-file-history] 削除に失敗: {name} ({e})', file=sys.stderr)
75
+ # [SR-R-001] shutil.rmtree / os.unlink のエラーメッセージには失敗したパス
76
+ # (FILE_HISTORY_DIR はホームディレクトリ配下のためユーザー名を含む)が
77
+ # 含まれる可能性がある。main() の SR M-1 修正と一貫して例外型名のみ出力する。
78
+ logger.debug('[clear-file-history] delete failed: %s', name, exc_info=True)
79
+ print(f'[clear-file-history] 削除に失敗: {name} ({type(e).__name__})', file=sys.stderr)
75
80
 
76
81
  print(f'[clear-file-history] {deleted} 件削除しました。')
77
82
 
@@ -113,7 +118,9 @@ def _run_enable_sandbox() -> None:
113
118
  try:
114
119
  settings = json.load(f)
115
120
  except json.JSONDecodeError as e:
116
- print(f'[enable-sandbox] settings.json の JSON 解析に失敗しました: {e}')
121
+ # [SR-R-001] 一貫性のため例外型名のみ出力(JSONDecodeError 自体はパスを含まない
122
+ # が、SR M-1 / _run_clear_file_history と方針を統一する)。
123
+ print(f'[enable-sandbox] settings.json の JSON 解析に失敗しました: {type(e).__name__}')
117
124
  return
118
125
 
119
126
  if settings.get('sandbox', {}).get('enabled') is True:
@@ -132,7 +139,14 @@ def _run_enable_sandbox() -> None:
132
139
  json.dump(settings, tmp_f, ensure_ascii=False, indent=2)
133
140
  tmp_f.write('\n')
134
141
  except Exception:
135
- os.close(fd)
142
+ # [CR-E-001] os.fdopen 成功後の書き込み例外では with __exit__ が既に
143
+ # fd を閉じているため、ここでの os.close(fd) は OSError(Bad file
144
+ # descriptor) を raise し元の例外を上書きしてしまう。os.fdopen 自体が
145
+ # 失敗して with に入らなかった場合のみ fd 解放が必要なので OSError を無視する。
146
+ try:
147
+ os.close(fd)
148
+ except OSError:
149
+ pass
136
150
  raise
137
151
  os.replace(tmp_path, settings_path)
138
152
  tmp_path = None # os.replace が成功したので finally でのクリーンアップ不要
@@ -147,19 +161,10 @@ def _run_enable_sandbox() -> None:
147
161
  # 3. C3 SQLite DB 初期化(旧 init_c3_db.py)
148
162
  # =============================================================================
149
163
 
150
- # 現行スキーマバージョン。schema.sql に破壊的変更を入れたら +1 して
151
- # マイグレーションロジックを apply_schema() に追加する。
152
- SCHEMA_VERSION = 3 # v2.0.0 で PO 廃止に伴い po_results / po_status を削除
153
-
154
164
  _HOOKS_DIR = os.path.dirname(os.path.abspath(__file__))
155
165
  _CLAUDE_DIR = os.path.dirname(_HOOKS_DIR)
156
166
  STATE_DIR = os.path.join(_CLAUDE_DIR, 'state')
157
167
  DB_PATH = os.path.join(STATE_DIR, 'c3.db')
158
- SCHEMA_PATH = os.path.join(_HOOKS_DIR, 'schema.sql')
159
-
160
-
161
- def _now_iso() -> str:
162
- return datetime.now(timezone.utc).astimezone().isoformat(timespec='seconds')
163
168
 
164
169
 
165
170
  def _ensure_state_dir() -> None:
@@ -167,38 +172,36 @@ def _ensure_state_dir() -> None:
167
172
  os.makedirs(STATE_DIR, exist_ok=True)
168
173
 
169
174
 
170
- def apply_schema(db_path: str = DB_PATH, schema_path: str = SCHEMA_PATH) -> None:
171
- """schema_path DDL db_path の SQLite に適用する。
175
+ def apply_schema(db_path: str = DB_PATH) -> list[str]:
176
+ """SQLite DB にスキーマ migration を適用する。
172
177
 
173
- - WAL モードに切り替える
174
- - schema.sql CREATE TABLE IF NOT EXISTS 等を実行
175
- - schema_version テーブルに現行バージョンを INSERT OR IGNORE
178
+ v2.20.0+: 実体は c3.migrate.apply_pending_migrations() に委譲。
179
+ schema.sql は廃止され、src/c3/migrations/ の連番 SQL ファイルで管理される。
176
180
 
177
- 冪等: 既存 DB に何度呼んでもエラーにならない。
181
+ 戻り値: 今回新たに適用した migration version のリスト(例: ['001'])
178
182
  """
179
- with open(schema_path, 'r', encoding='utf-8') as f:
180
- ddl = f.read()
181
-
182
- conn = sqlite3.connect(db_path, timeout=30)
183
+ from c3.migrate import apply_pending_migrations
183
184
  try:
184
- # WAL モードを有効化(reader が writer をブロックしない)
185
- conn.execute('PRAGMA journal_mode=WAL')
186
- # DDL を一括実行(CREATE TABLE IF NOT EXISTS なので冪等)
187
- conn.executescript(ddl)
188
- # スキーマバージョンを記録(既存なら無視)
189
- conn.execute(
190
- 'INSERT OR IGNORE INTO schema_version (version, applied_at) VALUES (?, ?)',
191
- (SCHEMA_VERSION, _now_iso()),
185
+ return apply_pending_migrations(db_path)
186
+ except FileNotFoundError:
187
+ # migrations ディレクトリ不在(wheel が壊れている等)でもセッションは続行。
188
+ # [SR-R-001] 例外 e にはインストールパス(ユーザー名含む)が含まれるため
189
+ # stderr には固定文言のみ出力する。詳細は内部 logger(DEBUG)に残す。
190
+ logger.debug('apply_schema: migrations directory not found', exc_info=True)
191
+ print(
192
+ 'warning: c3 migrations directory not found (wheel may be corrupted)',
193
+ file=sys.stderr,
192
194
  )
193
- conn.commit()
194
- finally:
195
- conn.close()
195
+ return []
196
196
 
197
197
 
198
198
  def _run_init_c3_db() -> None:
199
199
  """C3 SQLite DB を初期化する."""
200
200
  _ensure_state_dir()
201
- apply_schema()
201
+ # [CR-M-003] apply_schema() の戻り値(適用した migration version の list[str])は
202
+ # 現状ログ・利用しない。セッション開始を妨げないことを最優先し、適用結果の通知は
203
+ # 行わない設計(将来 welcome メッセージ等で利用する場合はここで受ける)。
204
+ _ = apply_schema()
202
205
 
203
206
 
204
207
  # =============================================================================
@@ -222,7 +225,14 @@ def main() -> int:
222
225
  handler()
223
226
  except Exception as e:
224
227
  # 各ハンドラ失敗時は警告のみ(次のハンドラを継続)
225
- print(f'[session_start:{label}] failed: {e}', file=sys.stderr)
228
+ # [SR-R-001] 例外メッセージには db_path 等のプロジェクトパスが含まれる
229
+ # 可能性がある(例: sqlite3.OperationalError)。stderr には例外型名のみ
230
+ # 出力し、詳細は内部 logger(DEBUG)に残す。
231
+ # NOTE(SR Info-2): exc_info=True は現状ハンドラ未設定で出力されないが、
232
+ # 将来 logging ハンドラを追加する場合はスタックトレースにインストールパスが
233
+ # 含まれる点に留意する。
234
+ logger.debug('[session_start:%s] handler failed', label, exc_info=True)
235
+ print(f'[session_start:{label}] failed: {type(e).__name__}', file=sys.stderr)
226
236
  return 0
227
237
 
228
238
 
@@ -89,6 +89,23 @@ def main() -> int:
89
89
  except Exception as e:
90
90
  print(f"[session_stop:consolidate_memory] failed: {e}", file=sys.stderr)
91
91
 
92
+ # Phase 3: usage_ingester — セッションログからコスト集計データを ingestion
93
+ # worktree session では起動しない(stop.run の is_worktree 判定と整合)
94
+ # 安全側動作: session_utils ロード失敗時や is_worktree 判定失敗時は ingester をスキップする。
95
+ # worktree 判定不能で誤起動するより skip を優先(except Exception で捕捉 → 早期終了)。
96
+ try:
97
+ session_utils_module = _load_module("session_utils")
98
+ if not session_utils_module.is_worktree(os.getcwd()):
99
+ session_id = payload.get("session_id")
100
+ transcript_path = payload.get("transcript_path")
101
+ if session_id and transcript_path:
102
+ from pathlib import Path as _Path # noqa: PLC0415
103
+ from c3.usage_ingester import ingest_session # noqa: PLC0415
104
+ project_dir = _Path(transcript_path).parent
105
+ ingest_session(session_id=session_id, project_dir=project_dir)
106
+ except Exception as e:
107
+ print(f"[session_stop:usage_ingester] failed: {type(e).__name__}", file=sys.stderr)
108
+
92
109
  return 0
93
110
 
94
111
 
@@ -1,5 +1,103 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.21.0] - 2026-05-25
4
+
5
+ **tier-routing コスト統合(データ収集基盤)**: Claude Code セッションログ(`~/.claude/projects/<slug>/<session>.jsonl` + subagent jsonl)を読み込み、モデル単価で USD 換算して c3.db に蓄積するデータ収集基盤を整備する。将来の cost-aware routing(v2.22.0)の土台。
6
+
7
+ **スコープ注記**: 本リリースはデータ収集基盤のみ。cost-aware routing と tier_bandit のコスト列は v2.22.0 予定。
8
+
9
+ ### 機能追加
10
+
11
+ - **`src/c3/pricing.py`(新規)**: Claude API モデルの USD/MTok 単価から token コストを計算する純関数モジュール。
12
+ `resolve_tier(model)`・`compute_cost_usd(...) -> tuple[float, bool]`・`known_models()` を提供。
13
+ Opus は世代で単価が 3 倍異なる(4.1/4=$15 系、4.5/4.6/4.7=$5 系)ため、具体パターン優先マッチ → tier 部分一致 fallback の 2 段構成を採用。
14
+ 単価は 2026-05-25 公式取得値(出典 URL を docstring に明記)。
15
+
16
+ - **`src/c3/migrations/002_agent_cost_runs.sql`(新規)**: `agent_cost_runs` テーブル・`usage_ingest_state` テーブル・インデックスを追加する migration。
17
+ PK=(session_id, agent_id, model) で「1 エージェント × 1 モデル = 1 行」の集約設計。
18
+ 既存 event-based `agent_runs`(001)は一切変更しない。
19
+
20
+ - **`src/c3/usage_ingester.py`(新規)**: セッションログ取り込みモジュール。
21
+ 公開 API `ingest_session(*, session_id, project_dir, db_path=None) -> IngestResult`。
22
+ mainline / subagent jsonl を走査し、model 単位でトークンを合算して `insert_agent_cost_run` で upsert する。
23
+ session_id UUID validate・パストラバーサル防止・symlink スキップ・例外 type 名のみログ(SR-R-001 準拠)。
24
+
25
+ - **`.claude/hooks/session_stop.py` Phase 3 追加**: セッション終了時に `ingest_session` を呼ぶ Phase 3 を追加。
26
+ worktree session では起動しない。例外握りつぶしで exit 0 を維持する。
27
+
28
+ - **`c3 tier stats` の Agent 別コスト集計セクション追加**: `_collect_snapshot()` に `read_agent_cost_summary()` を追加し、human / JSON 両出力に `agent_cost` セクションを表示する。
29
+ mainline 行には「(マクロ集計・tier 学習対象外)」を明示。0 件のときは「(コストデータ未収集)」を表示。
30
+
31
+ ### 変更
32
+
33
+ - **`src/c3/db.py`**: 4 ヘルパー追加(`insert_agent_cost_run` / `read_agent_cost_summary` / `get_ingest_offset` / `set_ingest_offset`)。既存 tier ヘルパー規約(DB 不在で静かに False/0/[]・WAL・busy_timeout)に準拠。
34
+
35
+ - **`src/c3/__init__.py`**: `__version__` を `"2.20.0"` から `"2.21.0"` に更新。
36
+
37
+ ## [2.20.0] - 2026-05-25
38
+
39
+ **SQLite schema migration 枠組み導入**: `.claude/hooks/schema.sql` の「冪等 DDL 一発実行」から、`src/c3/migrations/` の「連番 NNN_xxx.sql migration runner」へ移行する基盤を整備する。
40
+ v2.18.0 の `deletions.txt` 機構・v2.19.0 の `breaking-changes.txt` 機構の初の実運用ドッグフーディングでもある。
41
+
42
+ ### 機能追加
43
+
44
+ - **`src/c3/migrate.py`(新規)**: SQLite migration runner。
45
+ 公開 API `apply_pending_migrations(db_path, migrations_dir=None) -> list[str]` および例外クラス `MigrationError(RuntimeError)` を提供。
46
+ `src/c3/migrations/` 配下の連番 SQL ファイルを昇順に適用し、適用済み migration を `schema_migrations` テーブルで管理する。
47
+ WAL モード / busy_timeout=5000ms を冒頭で設定(既存 `c3.db.BUSY_TIMEOUT_MS` を SSOT として参照)。
48
+
49
+ - **`src/c3/migrations/001_initial.sql`(新規)**: 既存 `hooks/schema.sql` の DDL を逐語移植 + bootstrap。
50
+ `schema_migrations` テーブル新設、旧 `schema_version` テーブル DROP、`BEGIN;` / `COMMIT;` 明示記述による transaction 境界保証を含む。
51
+
52
+ - **`src/c3/migrations/__init__.py`(新規)**: Python package marker(wheel 自動同梱のため)。
53
+
54
+ - **`src/c3/migrations/README.md`(新規)**: 命名規約(`NNN_xxx.sql`)・`BEGIN;`/`COMMIT;` 必須運用ルール・002 以降の追加手順を記載。
55
+
56
+ - **`schema_migrations` テーブル(新規)**: 適用済み migration の一覧を保持(`version TEXT PK`, `applied_at TIMESTAMP`)。
57
+ 旧 `schema_version (version INTEGER PK, applied_at TEXT)` を置換。
58
+
59
+ ### 変更
60
+
61
+ - **`.claude/hooks/session_start.py`**: `apply_schema(db_path)` の実体を `c3.migrate.apply_pending_migrations()` に委譲。
62
+ 戻り値が `None` → `list[str]`(適用した migration version のリスト)に変更。
63
+ migrations_dir 不在時は `FileNotFoundError` を warning として stderr に出力し、セッションは続行する(exit 0 維持の既存方針踏襲)。
64
+
65
+ - **`src/c3/__init__.py`**: `__version__` を `"2.19.0"` から `"2.20.0"` に更新。
66
+
67
+ ### 破壊的変更
68
+
69
+ - **`hooks/schema.sql` 削除**: `.claude/hooks/schema.sql` をリポジトリから削除。
70
+ SQLite スキーマは `src/c3/migrations/` (wheel 内) で管理され、`session_start` で自動適用される。
71
+ 利用先からの削除は `deletions.txt` 機構(v2.18.0)によって `c3 update` 実行時に処理される。
72
+
73
+ - **`apply_schema` の `schema_path` 引数削除**: `session_start.py::apply_schema()` から `schema_path` 引数を完全削除(deprecation 期間なし)。
74
+ 呼び出し側で `schema_path=` を渡している場合は削除が必要(`c3 update` 実行後のテストコード等)。
75
+
76
+ ### ドキュメント
77
+
78
+ - **`.claude/docs/config-policy.md`**:
79
+ - §1-1 配布元ディレクトリ表に `src/c3/migrations/` を追加(Python package・wheel 同梱・3 ファイル同期対象外)
80
+ - §3 カテゴリ #1 hooks 備考に「v2.20.0 で `hooks/schema.sql` を削除、SQLite スキーマは `src/c3/migrations/` に移管」を追記
81
+
82
+ - **`.claude/breaking-changes.txt`**: v2.20.0 エントリ追記(`hooks/schema.sql` 削除)
83
+
84
+ - **`.claude/deletions.txt`**: `hooks/schema.sql` 追記(利用先からの自動削除を `c3 update` 経由で実現)
85
+
86
+ ### ドッグフーディング
87
+
88
+ - **v2.18.0 `deletions.txt` 機構**: `hooks/` 配下の非テスト SQL ファイル削除の初の実運用事例(過去は `agents/` / `skills/` の `.md` のみ)。
89
+ - **v2.19.0 `breaking-changes.txt` 機構**: v2.20.0 エントリを新規追加して MINOR bump 表示路を初通過させる。
90
+ - **`scripts/extract_breaking_changes.py`**: `--dry-run` / 通常実行 / `--check` のフルライフサイクル運用。
91
+
92
+ ### 影響
93
+
94
+ - **既存利用先**: `c3 update` 実行時に v2.20.0 breaking change が表示される(v2.19.0 以降の利用先)。
95
+ `.claude/hooks/schema.sql` は `c3 update` 実行時に自動削除される。
96
+ 次回セッション開始時に `apply_pending_migrations` が `schema_migrations` テーブルを新設し 001 を適用する。
97
+ 既存データ(`review_decisions` 等)は保持される。`schema_version` テーブルは削除される。
98
+
99
+ ---
100
+
3
101
  ## [2.19.0] - 2026-05-24
4
102
 
5
103
  **基盤整備リリース第 3 弾**: `c3 update` 実行時に breaking changes 表示 + MAJOR 承認プロンプトを導入する。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-conductor
3
- Version: 2.19.0
3
+ Version: 2.21.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.19.0"
3
+ __version__ = "2.21.0"
@@ -89,7 +89,7 @@ def handle_stats(args: argparse.Namespace) -> int:
89
89
 
90
90
 
91
91
  def _collect_snapshot(db_path, recent_limit: int) -> dict[str, Any]:
92
- """DB から tier_bandit / tier_recent_outcomes を読み snapshot dict を返す。"""
92
+ """DB から tier_bandit / tier_recent_outcomes / agent_cost を読み snapshot dict を返す。"""
93
93
  bandit_rows: list[dict[str, Any]] = []
94
94
  total_trials = 0
95
95
 
@@ -114,6 +114,8 @@ def _collect_snapshot(db_path, recent_limit: int) -> dict[str, Any]:
114
114
  db_path=db_path,
115
115
  )
116
116
 
117
+ agent_cost: list[dict[str, Any]] = c3_db.read_agent_cost_summary(db_path=db_path)
118
+
117
119
  if total_trials < _LEARNING_THRESHOLD:
118
120
  mode = "uniform"
119
121
  else:
@@ -127,6 +129,7 @@ def _collect_snapshot(db_path, recent_limit: int) -> dict[str, Any]:
127
129
  },
128
130
  "tier_bandit": bandit_rows,
129
131
  "recent_outcomes": recent_outcomes,
132
+ "agent_cost": agent_cost,
130
133
  }
131
134
 
132
135
 
@@ -166,3 +169,25 @@ def _render_human(snapshot: dict[str, Any]) -> None:
166
169
  print("== 学習データ記録チャネル ==")
167
170
  print("記録元: dev-workflow フェーズ E の最終承認時のみ(record_tier_outcome.py)")
168
171
  print("直接指示作業ではデータが溜まりません(設計通り)")
172
+ print()
173
+
174
+ print("== Agent 別コスト集計(agent_cost_runs) ==")
175
+ agent_cost = snapshot.get("agent_cost", [])
176
+ if not agent_cost:
177
+ print("(コストデータ未収集)")
178
+ else:
179
+ print(
180
+ f"{'agent_type':<16} {'runs':>5} {'total_usd':>10} "
181
+ f"{'in_tok':>9} {'out_tok':>9} {'cache_r':>9} {'cache_w':>9}"
182
+ )
183
+ for row in agent_cost:
184
+ note = " (マクロ集計・tier 学習対象外)" if row["agent_type"] == "mainline" else ""
185
+ print(
186
+ f"{row['agent_type']:<16} {row['runs']:>5} "
187
+ f"${row['total_cost_usd']:>9.4f} "
188
+ f"{row['input_tokens']:>9} {row['output_tokens']:>9} "
189
+ f"{row['cache_read_tokens']:>9} {row['cache_create_tokens']:>9}"
190
+ f"{note}"
191
+ )
192
+ print()
193
+ print("(注: 本リリースはデータ収集基盤のみ。cost-aware routing は v2.22.0 予定)")
@@ -493,3 +493,250 @@ def read_tier_failure_rate(
493
493
 
494
494
  failures = sum(1 for r in rows if r[0] == 0)
495
495
  return failures / sample_count, sample_count
496
+
497
+
498
+ # ---------------------------------------------------------------------------
499
+ # usage-ingester: agent_cost_runs / usage_ingest_state ヘルパー(v2.21.0)
500
+ # ---------------------------------------------------------------------------
501
+
502
+
503
+ def insert_agent_cost_run(
504
+ *,
505
+ session_id: str,
506
+ agent_id: str,
507
+ agent_type: str,
508
+ description: str | None,
509
+ model: str,
510
+ attribution_skill: str | None,
511
+ input_tokens: int,
512
+ output_tokens: int,
513
+ cache_read_tokens: int,
514
+ cache_create_tokens: int,
515
+ total_cost_usd: float,
516
+ db_path: Path | None = None,
517
+ ) -> bool:
518
+ """agent_cost_runs に 1 行 upsert する。
519
+
520
+ PK=(session_id, agent_id, model) で「1 エージェント × 1 モデル = 1 行」。
521
+ 同一 PK が既に存在する場合は全数値列・total_cost_usd・recorded_at を最新合算値に上書き。
522
+
523
+ Args:
524
+ session_id: セッション UUID。
525
+ agent_id: 'agent-<id>' または 'mainline'。
526
+ agent_type: meta.json の agentType / 'mainline'。
527
+ description: meta.json の description(任意)。
528
+ model: message.model 文字列。
529
+ attribution_skill: assistant レコードの attributionSkill(任意)。
530
+ input_tokens: 入力トークン数(jsonl 内合算値)。
531
+ output_tokens: 出力トークン数(jsonl 内合算値)。
532
+ cache_read_tokens: キャッシュ読み込みトークン数(jsonl 内合算値)。
533
+ cache_create_tokens: キャッシュ書き込みトークン数(jsonl 内合算値)。
534
+ total_cost_usd: compute_cost_usd が返した USD コスト。
535
+ db_path: c3.db のパス。省略時は locate_c3_db() で探索。
536
+
537
+ Returns:
538
+ upsert 成功時 True、DB 不在 / sqlite3.Error 時は静かに False。
539
+ """
540
+ if db_path is None:
541
+ db_path = locate_c3_db()
542
+ if db_path is None:
543
+ return False
544
+
545
+ from datetime import timezone as _tz # noqa: PLC0415
546
+ now_iso = datetime.now(_tz.utc).isoformat(timespec="seconds")
547
+
548
+ try:
549
+ conn = sqlite3.connect(str(db_path))
550
+ try:
551
+ conn.execute("PRAGMA journal_mode=WAL")
552
+ _apply_busy_timeout(conn)
553
+ conn.execute(
554
+ "INSERT INTO agent_cost_runs "
555
+ "(session_id, agent_id, agent_type, description, model, "
556
+ " attribution_skill, input_tokens, output_tokens, "
557
+ " cache_read_tokens, cache_create_tokens, "
558
+ " total_cost_usd, recorded_at) "
559
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "
560
+ "ON CONFLICT(session_id, agent_id, model) DO UPDATE SET "
561
+ " agent_type = excluded.agent_type, "
562
+ " description = excluded.description, "
563
+ " attribution_skill = excluded.attribution_skill, "
564
+ " input_tokens = excluded.input_tokens, "
565
+ " output_tokens = excluded.output_tokens, "
566
+ " cache_read_tokens = excluded.cache_read_tokens, "
567
+ " cache_create_tokens = excluded.cache_create_tokens, "
568
+ " total_cost_usd = excluded.total_cost_usd, "
569
+ " recorded_at = excluded.recorded_at",
570
+ (
571
+ session_id, agent_id, agent_type, description, model,
572
+ attribution_skill, input_tokens, output_tokens,
573
+ cache_read_tokens, cache_create_tokens,
574
+ total_cost_usd, now_iso,
575
+ ),
576
+ )
577
+ conn.commit()
578
+ finally:
579
+ conn.close()
580
+ return True
581
+ except Exception as exc: # noqa: BLE001
582
+ logger.warning("failed to insert_agent_cost_run: %s", type(exc).__name__)
583
+ return False
584
+
585
+
586
+ def read_agent_cost_summary(
587
+ *,
588
+ db_path: Path | None = None,
589
+ limit: int = 50,
590
+ ) -> list[dict]:
591
+ """agent_cost_runs を agent_type 別に集計した結果を返す。
592
+
593
+ SELECT agent_type, COUNT(*) runs, SUM(total_cost_usd), SUM(input_tokens),
594
+ SUM(output_tokens), SUM(cache_read_tokens), SUM(cache_create_tokens)
595
+ FROM agent_cost_runs GROUP BY agent_type ORDER BY total_cost_usd DESC LIMIT ?
596
+
597
+ Args:
598
+ db_path: c3.db のパス。省略時は locate_c3_db() で探索。
599
+ limit: 返す最大件数(デフォルト 50)。
600
+
601
+ Returns:
602
+ 各行を dict にしたリスト。キー:
603
+ ``agent_type`` / ``runs`` / ``total_cost_usd`` /
604
+ ``input_tokens`` / ``output_tokens`` /
605
+ ``cache_read_tokens`` / ``cache_create_tokens``。
606
+ DB 不在 / テーブル不在 / エラー時は空リスト。
607
+ """
608
+ if db_path is None:
609
+ db_path = locate_c3_db()
610
+ if db_path is None:
611
+ return []
612
+
613
+ try:
614
+ conn = sqlite3.connect(str(db_path))
615
+ try:
616
+ # WAL は書き込みヘルパー呼び出し時または migrate 時に設定済みの前提
617
+ # (既存 read_recent_outcomes 等の read ヘルパーと同方針 / CR-M-001)
618
+ _apply_busy_timeout(conn)
619
+ rows = conn.execute(
620
+ "SELECT agent_type, "
621
+ " COUNT(*) AS runs, "
622
+ " SUM(total_cost_usd) AS total_cost_usd, "
623
+ " SUM(input_tokens) AS input_tokens, "
624
+ " SUM(output_tokens) AS output_tokens, "
625
+ " SUM(cache_read_tokens) AS cache_read_tokens, "
626
+ " SUM(cache_create_tokens) AS cache_create_tokens "
627
+ "FROM agent_cost_runs "
628
+ "GROUP BY agent_type "
629
+ "ORDER BY total_cost_usd DESC "
630
+ "LIMIT ?",
631
+ (limit,),
632
+ ).fetchall()
633
+ finally:
634
+ conn.close()
635
+ except sqlite3.OperationalError as exc:
636
+ # テーブル不在(no such table)は [] を返す(DB 未初期化でも止めない)
637
+ logger.debug(
638
+ "read_agent_cost_summary: table not found or inaccessible: %s",
639
+ type(exc).__name__,
640
+ )
641
+ return []
642
+ except Exception as exc: # noqa: BLE001
643
+ logger.warning("read_agent_cost_summary: unexpected error: %s", type(exc).__name__)
644
+ return []
645
+
646
+ return [
647
+ {
648
+ "agent_type": row[0],
649
+ "runs": row[1],
650
+ "total_cost_usd": row[2],
651
+ "input_tokens": row[3],
652
+ "output_tokens": row[4],
653
+ "cache_read_tokens": row[5],
654
+ "cache_create_tokens": row[6],
655
+ }
656
+ for row in rows
657
+ ]
658
+
659
+
660
+ def get_ingest_offset(
661
+ file_key: str,
662
+ *,
663
+ db_path: Path | None = None,
664
+ ) -> int:
665
+ """usage_ingest_state から処理済み行数(offset)を返す。
666
+
667
+ Args:
668
+ file_key: '<session>:mainline' / '<session>:agent-<id>'。
669
+ db_path: c3.db のパス。省略時は locate_c3_db() で探索。
670
+
671
+ Returns:
672
+ 処理済み行数。行・テーブル不在・DB 不在は 0 を返す。
673
+ """
674
+ if db_path is None:
675
+ db_path = locate_c3_db()
676
+ if db_path is None:
677
+ return 0
678
+
679
+ try:
680
+ conn = sqlite3.connect(str(db_path))
681
+ try:
682
+ # WAL は書き込みヘルパー呼び出し時または migrate 時に設定済みの前提
683
+ # (既存 read_recent_outcomes 等の read ヘルパーと同方針 / CR-M-001)
684
+ _apply_busy_timeout(conn)
685
+ row = conn.execute(
686
+ "SELECT last_offset FROM usage_ingest_state WHERE file_key = ?",
687
+ (file_key,),
688
+ ).fetchone()
689
+ finally:
690
+ conn.close()
691
+ except Exception as exc: # noqa: BLE001
692
+ logger.debug("get_ingest_offset: %s", type(exc).__name__)
693
+ return 0
694
+
695
+ return row[0] if row is not None else 0
696
+
697
+
698
+ def set_ingest_offset(
699
+ file_key: str,
700
+ offset: int,
701
+ *,
702
+ db_path: Path | None = None,
703
+ ) -> bool:
704
+ """usage_ingest_state に offset を upsert する。
705
+
706
+ Args:
707
+ file_key: '<session>:mainline' / '<session>:agent-<id>'。
708
+ offset: 処理済み行数(= 次回開始行)。
709
+ db_path: c3.db のパス。省略時は locate_c3_db() で探索。
710
+
711
+ Returns:
712
+ upsert 成功時 True、DB 不在 / エラー時は静かに False。
713
+ """
714
+ if db_path is None:
715
+ db_path = locate_c3_db()
716
+ if db_path is None:
717
+ return False
718
+
719
+ from datetime import timezone as _tz # noqa: PLC0415
720
+ now_iso = datetime.now(_tz.utc).isoformat(timespec="seconds")
721
+
722
+ try:
723
+ conn = sqlite3.connect(str(db_path))
724
+ try:
725
+ conn.execute("PRAGMA journal_mode=WAL")
726
+ _apply_busy_timeout(conn)
727
+ conn.execute(
728
+ "INSERT INTO usage_ingest_state "
729
+ "(file_key, last_offset, last_processed_at) "
730
+ "VALUES (?, ?, ?) "
731
+ "ON CONFLICT(file_key) DO UPDATE SET "
732
+ " last_offset = excluded.last_offset, "
733
+ " last_processed_at = excluded.last_processed_at",
734
+ (file_key, offset, now_iso),
735
+ )
736
+ conn.commit()
737
+ finally:
738
+ conn.close()
739
+ return True
740
+ except Exception as exc: # noqa: BLE001
741
+ logger.warning("failed to set_ingest_offset: %s", type(exc).__name__)
742
+ return False