claude-code-conductor 2.21.0__tar.gz → 2.22.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.
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/select_tier.py +9 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/dev-workflow/scripts/record_tier_outcome.py +5 -1
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/CHANGELOG.md +26 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/PKG-INFO +1 -1
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/__init__.py +1 -1
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/cli_tier.py +20 -2
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/db.py +105 -3
- claude_code_conductor-2.22.0/src/c3/migrations/003_tier_cost.sql +25 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_record_tier_outcome.py +115 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_select_tier.py +82 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_cli_tier.py +164 -5
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_db.py +198 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_migrate.py +151 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/CLAUDE.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/code-reviewer.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/developer.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/security-reviewer.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/systematic-debugger.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/tester.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/wt_developer.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/wt_systematic-debugger.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/agents/wt_tester.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/breaking-changes.txt +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/deletions.txt +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/docs/config-policy.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/docs/parallel-agents-setup.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/docs/platform-adapters.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/docs/settings.json.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/_hook_utils.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/check_agent_invocation.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/consolidate_memory.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/permission_handler.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/permission_handler_toast.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/planner_check.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/post_tool.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/pre_compact.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/pre_tool.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/recall_inject.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/restore_session.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/session_start.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/session_stop.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/session_utils.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/statusline.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/stop.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/hooks/worktree_guard.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/memory/.gitkeep +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/permission_rules.json +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/settings.json +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/codex-review/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/develop/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/doc/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/extract-lib/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/init-session/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/mcp-config/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/pattern-status/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/recall/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/review-phase/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/setup/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/setup/reference.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/skills/start/SKILL.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.claude/state/.gitkeep +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/.gitignore +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/LICENSE +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/LICENSES/chroma-hnswlib-LICENSE +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/LICENSES/chroma-hnswlib-NOTICE +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/LICENSES/fastembed-LICENSE +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/LICENSES/fastembed-NOTICE +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/LICENSES/onnxruntime-LICENSE +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/README.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/hatch_build.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/pyproject.toml +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/__main__.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/_excludes.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/_terminal.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/adapters.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/cli.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/cli_ask.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/cli_init.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/cli_list.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/cli_plan.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/cli_recall.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/embedding.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/mcp_server.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/migrate.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/migrations/001_initial.sql +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/migrations/README.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/migrations/__init__.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/paths.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/plan_validator.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/platforms.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/pricing.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/question.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/recall_chunker.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/recall_index.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/src/c3/usage_ingester.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/__init__.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/conftest.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/fixtures/usage/README.md +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/fixtures/usage/mainline.jsonl +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/__init__.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_check_agent_invocation.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_consolidate_memory.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_hook_utils.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_permission_handler.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_permission_handler_toast.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_planner_check.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_planner_check_dev.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_post_tool.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_pre_tool.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_recall_inject.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_record_review_decision.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_restore_session.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_review_hint_inject.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_select_tier_escalation.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_session_start.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_session_stop.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_session_utils.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_similarity_boost.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_statusline.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_statusline_template_sync.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_sync_check.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_template_guard.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/skills/__init__.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/skills/_skill_helpers.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/skills/test_init_session_no_task_type.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/skills/test_planner_lightweight.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/skills/test_recall_skill.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/skills/test_setup_templates.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/skills/test_start_skill_new_flow.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_adapters.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_cli_ask.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_cli_entry.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_cli_init.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_cli_list.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_cli_plan.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_cli_recall.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_cli_update_breaking_changes.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_cli_update_deletions.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_docstring_consistency.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_embedding.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_excludes.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_extract_breaking_changes.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_mcp_server_elicit.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_paths.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_plan_validator.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_pre_compact.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_precompact_additional.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_precompact_toctou_fixes.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_pricing.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_recall_chunker.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_recall_index.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_references_migration.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_session_utils_additional.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_skill_no_builtin_conflict.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_statusline.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_stop_additional.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_stop_hook.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_stop_precompact_fixes.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_sync_template_stop.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_template_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_usage_ingester.py +0 -0
- {claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/test_worktree_guard.py +0 -0
|
@@ -323,6 +323,7 @@ def write_tier_selection(
|
|
|
323
323
|
escalation_reason: str | None = None,
|
|
324
324
|
prompt_prefix: str | None = None,
|
|
325
325
|
prompt_hash: str | None = None,
|
|
326
|
+
session_id: str | None = None,
|
|
326
327
|
) -> None:
|
|
327
328
|
"""直近の選択結果を ``tier_selection.json`` に書く。
|
|
328
329
|
|
|
@@ -335,6 +336,9 @@ def write_tier_selection(
|
|
|
335
336
|
|
|
336
337
|
``escalated`` / ``escalation_reason`` を任意で含める。
|
|
337
338
|
failure rate に基づく昇格が起きた場合のみ True / 文字列が入る。
|
|
339
|
+
|
|
340
|
+
``session_id`` を任意で含める。UserPromptSubmit payload の session UUID。
|
|
341
|
+
None のときは tier_selection.json のキー自体を省略する(後方互換)。
|
|
338
342
|
"""
|
|
339
343
|
os.makedirs(os.path.dirname(TIER_SELECTION_PATH), exist_ok=True)
|
|
340
344
|
payload: dict[str, object] = {
|
|
@@ -354,6 +358,8 @@ def write_tier_selection(
|
|
|
354
358
|
payload["prompt_prefix"] = prompt_prefix
|
|
355
359
|
if prompt_hash is not None:
|
|
356
360
|
payload["prompt_hash"] = prompt_hash
|
|
361
|
+
if session_id is not None:
|
|
362
|
+
payload["session_id"] = session_id
|
|
357
363
|
try:
|
|
358
364
|
with open(TIER_SELECTION_PATH, "w", encoding="utf-8") as f:
|
|
359
365
|
json.dump(payload, f, ensure_ascii=False)
|
|
@@ -415,6 +421,8 @@ def main() -> int:
|
|
|
415
421
|
if not isinstance(prompt, str) or not prompt.strip():
|
|
416
422
|
return 0
|
|
417
423
|
|
|
424
|
+
session_id = payload.get("session_id")
|
|
425
|
+
|
|
418
426
|
# Phase 2-C: 類似度推定で complexity を補強
|
|
419
427
|
heuristic_complexity = estimate_complexity(prompt)
|
|
420
428
|
strong_complexity, weak_matches = similarity_boost(prompt)
|
|
@@ -448,6 +456,7 @@ def main() -> int:
|
|
|
448
456
|
escalated=escalated, escalation_reason=escalation_reason,
|
|
449
457
|
prompt_prefix=prompt_prefix,
|
|
450
458
|
prompt_hash=prompt_hash,
|
|
459
|
+
session_id=session_id,
|
|
451
460
|
)
|
|
452
461
|
|
|
453
462
|
context_text = build_additional_context(
|
|
@@ -210,15 +210,19 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
210
210
|
# Phase 2-B: tier_recent_outcomes にも 1 件記録(escalation 判定の母数)。
|
|
211
211
|
# tier_bandit 更新が失敗してもこちらは試みる(DB が一時的に詰まる程度なら
|
|
212
212
|
# 片方だけ通る可能性もある)。
|
|
213
|
+
# v2.22.0: session_id を渡して cost 紐づけを可能にする。
|
|
214
|
+
# session_id キーが無い古い tier_selection.json の場合は None になる(後方互換)。
|
|
215
|
+
session_id = selection.get("session_id")
|
|
213
216
|
try:
|
|
214
217
|
c3_db.record_tier_recent_outcome(
|
|
215
218
|
complexity=selection["complexity"],
|
|
216
219
|
tier=selection["tier"],
|
|
217
220
|
success=success,
|
|
221
|
+
session_id=session_id,
|
|
218
222
|
)
|
|
219
223
|
except Exception as exc: # noqa: BLE001
|
|
220
224
|
print(
|
|
221
|
-
f"[record_tier_outcome] tier_recent_outcomes record skipped: {exc}",
|
|
225
|
+
f"[record_tier_outcome] tier_recent_outcomes record skipped: {type(exc).__name__}",
|
|
222
226
|
file=sys.stderr,
|
|
223
227
|
)
|
|
224
228
|
|
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.22.0] - 2026-05-25
|
|
4
|
+
|
|
5
|
+
**tier-routing cost 紐づけデータ蓄積**: tier_recent_outcomes に session_id 列を追加し、agent_cost_runs と JOIN できるデータ基盤を整備する。`c3 tier stats` に complexity×tier 別の平均コストセクションを追加する。cost-aware routing 本体は v2.23.0。
|
|
6
|
+
|
|
7
|
+
**スコープ注記**: 単価テーブル自動更新は公式 price API が非提供のため pricing.py の手動メンテ継続。tier_bandit の total_cost_usd / cost_samples 列は v2.23.0 用に確保のみ(書き込み・読み出しなし)。
|
|
8
|
+
|
|
9
|
+
### 機能追加
|
|
10
|
+
|
|
11
|
+
- **`src/c3/migrations/003_tier_cost.sql`(新規)**: `tier_recent_outcomes` に `session_id TEXT` 列と `idx_tier_recent_session` インデックスを追加。`tier_bandit` に `total_cost_usd REAL DEFAULT 0.0` / `cost_samples INTEGER DEFAULT 0` 列を追加(v2.23.0 用確保のみ)。既存データは ADD COLUMN DEFAULT で保持(破壊的変更なし)。
|
|
12
|
+
|
|
13
|
+
- **`src/c3/db.py`: `record_tier_recent_outcome` に `session_id` 追加**: kw-only 引数 `session_id: str | None = None` を追加。既存呼び出しは後方互換(省略時 NULL 保存)。
|
|
14
|
+
|
|
15
|
+
- **`src/c3/db.py`: `read_tier_cost_summary` 追加**: tier_recent_outcomes × agent_cost_runs を session_id で JOIN し、complexity×tier 別の sessions / total_cost_usd / avg_cost_usd を返す。2 段 CTE で session コストの 1 session 内重複計上を防ぐ(mainline 除外・session 単位 SUM → DISTINCT JOIN)。テーブル不在・データ不在・session_id 全 NULL で `[]`。
|
|
16
|
+
|
|
17
|
+
- **`.claude/hooks/select_tier.py`: session_id 記録**: UserPromptSubmit payload から session_id を取得し tier_selection.json に追記。session_id が None のときはキーを省略(既存テスト互換)。
|
|
18
|
+
|
|
19
|
+
- **`.claude/skills/dev-workflow/scripts/record_tier_outcome.py`: session_id 受け渡し**: tier_selection.json から session_id を読み `record_tier_recent_outcome` に渡す。session_id キーが無い古い json でも動作(None として扱う)。
|
|
20
|
+
|
|
21
|
+
- **`c3 tier stats` に Tier 別平均コストセクション追加**: `_collect_snapshot()` に `read_tier_cost_summary()` の結果を `tier_cost` キーで追加。human 表示に「Tier 別平均コスト(粗い概算 / 精度向上は v2.23.0)」セクションを追加。tier_cost が空のときは「(cost 紐づけデータ未収集)」と表示。`--json` 出力の `tier_cost` キーにも自動反映。
|
|
22
|
+
|
|
23
|
+
### 変更
|
|
24
|
+
|
|
25
|
+
- **`src/c3/__init__.py`**: `__version__` を `"2.21.0"` から `"2.22.0"` に更新。
|
|
26
|
+
|
|
27
|
+
- **注記更新**: `c3 tier stats` の「cost-aware routing は v2.22.0 予定」を「データ紐づけ蓄積。cost-aware routing 本体は v2.23.0 予定」へ更新。
|
|
28
|
+
|
|
3
29
|
## [2.21.0] - 2026-05-25
|
|
4
30
|
|
|
5
31
|
**tier-routing コスト統合(データ収集基盤)**: Claude Code セッションログ(`~/.claude/projects/<slug>/<session>.jsonl` + subagent jsonl)を読み込み、モデル単価で USD 換算して c3.db に蓄積するデータ収集基盤を整備する。将来の cost-aware routing(v2.22.0)の土台。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-conductor
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.22.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
|
|
@@ -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 / agent_cost を読み snapshot dict を返す。"""
|
|
92
|
+
"""DB から tier_bandit / tier_recent_outcomes / agent_cost / tier_cost を読み snapshot dict を返す。"""
|
|
93
93
|
bandit_rows: list[dict[str, Any]] = []
|
|
94
94
|
total_trials = 0
|
|
95
95
|
|
|
@@ -116,6 +116,8 @@ def _collect_snapshot(db_path, recent_limit: int) -> dict[str, Any]:
|
|
|
116
116
|
|
|
117
117
|
agent_cost: list[dict[str, Any]] = c3_db.read_agent_cost_summary(db_path=db_path)
|
|
118
118
|
|
|
119
|
+
tier_cost: list[dict[str, Any]] = c3_db.read_tier_cost_summary(db_path=db_path)
|
|
120
|
+
|
|
119
121
|
if total_trials < _LEARNING_THRESHOLD:
|
|
120
122
|
mode = "uniform"
|
|
121
123
|
else:
|
|
@@ -130,6 +132,7 @@ def _collect_snapshot(db_path, recent_limit: int) -> dict[str, Any]:
|
|
|
130
132
|
"tier_bandit": bandit_rows,
|
|
131
133
|
"recent_outcomes": recent_outcomes,
|
|
132
134
|
"agent_cost": agent_cost,
|
|
135
|
+
"tier_cost": tier_cost,
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
|
|
@@ -190,4 +193,19 @@ def _render_human(snapshot: dict[str, Any]) -> None:
|
|
|
190
193
|
f"{note}"
|
|
191
194
|
)
|
|
192
195
|
print()
|
|
193
|
-
|
|
196
|
+
|
|
197
|
+
print("== Tier 別平均コスト(粗い概算 / 精度向上は v2.23.0) ==")
|
|
198
|
+
tier_cost = snapshot.get("tier_cost", [])
|
|
199
|
+
if not tier_cost:
|
|
200
|
+
print("(cost 紐づけデータ未収集)")
|
|
201
|
+
else:
|
|
202
|
+
print(f"{'complexity':<12} {'tier':<8} {'sessions':>8} {'avg_usd':>10} {'total_usd':>10}")
|
|
203
|
+
for row in tier_cost:
|
|
204
|
+
print(
|
|
205
|
+
f"{row['complexity']:<12} {row['tier']:<8} "
|
|
206
|
+
f"{row['sessions']:>8} "
|
|
207
|
+
f"${row['avg_cost_usd']:>9.4f} "
|
|
208
|
+
f"${row['total_cost_usd']:>9.4f}"
|
|
209
|
+
)
|
|
210
|
+
print()
|
|
211
|
+
print("(注: データ紐づけ蓄積。cost-aware routing 本体は v2.23.0 予定)")
|
|
@@ -358,12 +358,21 @@ def record_tier_recent_outcome(
|
|
|
358
358
|
tier: str,
|
|
359
359
|
success: bool,
|
|
360
360
|
db_path: Path | None = None,
|
|
361
|
+
session_id: str | None = None,
|
|
361
362
|
) -> bool:
|
|
362
363
|
"""``tier_recent_outcomes`` に 1 件 INSERT する。
|
|
363
364
|
|
|
364
365
|
Phase 2-B のエスカレーション判定用。tier_bandit の累積 α/β とは別に、
|
|
365
366
|
直近 N 件の event を時系列で保持する。
|
|
366
367
|
|
|
368
|
+
Args:
|
|
369
|
+
complexity: 'simple' | 'medium' | 'complex'。
|
|
370
|
+
tier: 'haiku' | 'sonnet' | 'opus'。
|
|
371
|
+
success: True なら成功、False なら失敗。
|
|
372
|
+
db_path: c3.db のパス。省略時は :func:`locate_c3_db` で探索。
|
|
373
|
+
session_id: セッション UUID(v2.22.0+)。省略時は NULL で保存。
|
|
374
|
+
cost 紐づけに使用。None の場合は既存行との後方互換を維持する。
|
|
375
|
+
|
|
367
376
|
Returns:
|
|
368
377
|
INSERT 成功時 True、DB 不在 / エラー時 False。
|
|
369
378
|
"""
|
|
@@ -382,15 +391,15 @@ def record_tier_recent_outcome(
|
|
|
382
391
|
_apply_busy_timeout(conn)
|
|
383
392
|
conn.execute(
|
|
384
393
|
"INSERT INTO tier_recent_outcomes "
|
|
385
|
-
"(task_complexity, tier, success, ts) VALUES (?, ?, ?, ?)",
|
|
386
|
-
(complexity, tier, 1 if success else 0, now_iso),
|
|
394
|
+
"(task_complexity, tier, success, ts, session_id) VALUES (?, ?, ?, ?, ?)",
|
|
395
|
+
(complexity, tier, 1 if success else 0, now_iso, session_id),
|
|
387
396
|
)
|
|
388
397
|
conn.commit()
|
|
389
398
|
finally:
|
|
390
399
|
conn.close()
|
|
391
400
|
return True
|
|
392
401
|
except Exception as exc: # noqa: BLE001
|
|
393
|
-
logger.warning("failed to record tier_recent_outcome: %s", exc)
|
|
402
|
+
logger.warning("failed to record tier_recent_outcome: %s", type(exc).__name__)
|
|
394
403
|
return False
|
|
395
404
|
|
|
396
405
|
|
|
@@ -657,6 +666,99 @@ def read_agent_cost_summary(
|
|
|
657
666
|
]
|
|
658
667
|
|
|
659
668
|
|
|
669
|
+
def read_tier_cost_summary(
|
|
670
|
+
*,
|
|
671
|
+
db_path: Path | None = None,
|
|
672
|
+
) -> list[dict]:
|
|
673
|
+
"""tier_recent_outcomes × agent_cost_runs を session_id で JOIN し、
|
|
674
|
+
complexity×tier 別コストを返す(粗い概算 / 精度向上は v2.23.0)。
|
|
675
|
+
|
|
676
|
+
2 段 CTE で重複計上を防ぐ:
|
|
677
|
+
- ``session_cost`` CTE: agent_cost_runs を mainline 除外しつつ
|
|
678
|
+
session_id 単位で SUM(1 session = 1 行)→ cost の重複計上を構造的に防ぐ
|
|
679
|
+
- ``outcome_sessions`` CTE: tier_recent_outcomes を
|
|
680
|
+
(session_id, task_complexity, tier) で DISTINCT 化。
|
|
681
|
+
session_id が NULL(v2.22.0 移行前行)は除外
|
|
682
|
+
- JOIN して complexity×tier 別に sessions / total_cost_usd / avg_cost_usd を算出
|
|
683
|
+
|
|
684
|
+
既知の不正確性(v2.22.0 許容・精度向上は v2.23.0):
|
|
685
|
+
- session が複数の (complexity, tier) outcome を持つ場合、同一 session の cost が
|
|
686
|
+
複数の (complexity,tier) 行に重複加算されうる(1 session 内での cost 重複は防ぐが、
|
|
687
|
+
複数 outcome を持つ session の cross-outcome 重複は未解決)
|
|
688
|
+
- cost は session 全体の non-mainline 合計であり、特定 tier の model 行に限定しない
|
|
689
|
+
- agent_id 単位の紐づけはしていない
|
|
690
|
+
|
|
691
|
+
Args:
|
|
692
|
+
db_path: c3.db のパス。省略時は :func:`locate_c3_db` で探索。
|
|
693
|
+
|
|
694
|
+
Returns:
|
|
695
|
+
各行を dict にしたリスト。キー:
|
|
696
|
+
``complexity``(str) / ``tier``(str) / ``sessions``(int) /
|
|
697
|
+
``total_cost_usd``(float) / ``avg_cost_usd``(float)。
|
|
698
|
+
ORDER BY total_cost_usd DESC。
|
|
699
|
+
テーブル不在 / データ不在 / session_id 全 NULL / JOIN 0 行 /
|
|
700
|
+
DB 不在 / エラー時は空リストを返す。
|
|
701
|
+
"""
|
|
702
|
+
if db_path is None:
|
|
703
|
+
db_path = locate_c3_db()
|
|
704
|
+
if db_path is None:
|
|
705
|
+
return []
|
|
706
|
+
|
|
707
|
+
try:
|
|
708
|
+
conn = sqlite3.connect(str(db_path))
|
|
709
|
+
try:
|
|
710
|
+
# WAL は書き込みヘルパー呼び出し時または migrate 時に設定済みの前提
|
|
711
|
+
# (read_agent_cost_summary 等の read ヘルパーと同方針 / CR-M-001)
|
|
712
|
+
_apply_busy_timeout(conn)
|
|
713
|
+
rows = conn.execute(
|
|
714
|
+
"WITH session_cost AS ("
|
|
715
|
+
" SELECT session_id,"
|
|
716
|
+
" SUM(total_cost_usd) AS session_cost_usd"
|
|
717
|
+
" FROM agent_cost_runs"
|
|
718
|
+
" WHERE agent_type <> 'mainline'"
|
|
719
|
+
" GROUP BY session_id"
|
|
720
|
+
"),"
|
|
721
|
+
"outcome_sessions AS ("
|
|
722
|
+
" SELECT DISTINCT session_id, task_complexity, tier"
|
|
723
|
+
" FROM tier_recent_outcomes"
|
|
724
|
+
" WHERE session_id IS NOT NULL"
|
|
725
|
+
") "
|
|
726
|
+
"SELECT o.task_complexity AS complexity,"
|
|
727
|
+
" o.tier AS tier,"
|
|
728
|
+
" COUNT(DISTINCT o.session_id) AS sessions,"
|
|
729
|
+
" SUM(sc.session_cost_usd) AS total_cost_usd,"
|
|
730
|
+
" SUM(sc.session_cost_usd) / COUNT(DISTINCT o.session_id)"
|
|
731
|
+
" AS avg_cost_usd "
|
|
732
|
+
"FROM outcome_sessions o "
|
|
733
|
+
"JOIN session_cost sc ON sc.session_id = o.session_id "
|
|
734
|
+
"GROUP BY o.task_complexity, o.tier "
|
|
735
|
+
"ORDER BY total_cost_usd DESC"
|
|
736
|
+
).fetchall()
|
|
737
|
+
finally:
|
|
738
|
+
conn.close()
|
|
739
|
+
except sqlite3.OperationalError as exc:
|
|
740
|
+
# テーブル不在(no such table)は [] を返す(DB 未初期化でも止めない)
|
|
741
|
+
logger.debug(
|
|
742
|
+
"read_tier_cost_summary: table not found or inaccessible: %s",
|
|
743
|
+
type(exc).__name__,
|
|
744
|
+
)
|
|
745
|
+
return []
|
|
746
|
+
except Exception as exc: # noqa: BLE001
|
|
747
|
+
logger.warning("read_tier_cost_summary: unexpected error: %s", type(exc).__name__)
|
|
748
|
+
return []
|
|
749
|
+
|
|
750
|
+
return [
|
|
751
|
+
{
|
|
752
|
+
"complexity": row[0],
|
|
753
|
+
"tier": row[1],
|
|
754
|
+
"sessions": int(row[2]),
|
|
755
|
+
"total_cost_usd": float(row[3]) if row[3] is not None else 0.0,
|
|
756
|
+
"avg_cost_usd": float(row[4]) if row[4] is not None else 0.0,
|
|
757
|
+
}
|
|
758
|
+
for row in rows
|
|
759
|
+
]
|
|
760
|
+
|
|
761
|
+
|
|
660
762
|
def get_ingest_offset(
|
|
661
763
|
file_key: str,
|
|
662
764
|
*,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-- C3 SQLite migration 003: tier_recent_outcomes に session_id 列・INDEX を追加し、
|
|
2
|
+
-- tier_bandit にコスト集計用列(v2.23.0 用)を確保する。
|
|
3
|
+
--
|
|
4
|
+
-- 変更内容:
|
|
5
|
+
-- - tier_recent_outcomes.session_id TEXT(既存行は NULL)
|
|
6
|
+
-- - idx_tier_recent_session ON tier_recent_outcomes(session_id)
|
|
7
|
+
-- - tier_bandit.total_cost_usd REAL NOT NULL DEFAULT 0.0 (v2.23.0 用確保のみ)
|
|
8
|
+
-- - tier_bandit.cost_samples INTEGER NOT NULL DEFAULT 0 (v2.23.0 用確保のみ)
|
|
9
|
+
--
|
|
10
|
+
-- 後方互換: ADD COLUMN DEFAULT により既存行は NULL / 0.0 / 0 で埋まる。
|
|
11
|
+
|
|
12
|
+
BEGIN;
|
|
13
|
+
|
|
14
|
+
ALTER TABLE tier_recent_outcomes ADD COLUMN session_id TEXT;
|
|
15
|
+
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_tier_recent_session
|
|
17
|
+
ON tier_recent_outcomes(session_id);
|
|
18
|
+
|
|
19
|
+
ALTER TABLE tier_bandit ADD COLUMN total_cost_usd REAL NOT NULL DEFAULT 0.0;
|
|
20
|
+
|
|
21
|
+
ALTER TABLE tier_bandit ADD COLUMN cost_samples INTEGER NOT NULL DEFAULT 0;
|
|
22
|
+
|
|
23
|
+
INSERT OR IGNORE INTO schema_migrations (version) VALUES ('003');
|
|
24
|
+
|
|
25
|
+
COMMIT;
|
|
@@ -499,3 +499,118 @@ class TestClaudeDirGuard:
|
|
|
499
499
|
|
|
500
500
|
with pytest.raises(RuntimeError, match=r"_CLAUDE_DIR resolution broke"):
|
|
501
501
|
self._load_from_path(wrong_hook)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
# ---------------------------------------------------------------------------
|
|
505
|
+
# T4 (v2.22.0): session_id 受け渡し検証
|
|
506
|
+
# ---------------------------------------------------------------------------
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
class TestSessionIdPropagation:
|
|
510
|
+
"""v2.22.0 T4: record_tier_outcome が tier_selection.json の session_id を
|
|
511
|
+
record_tier_recent_outcome に渡すことを検証する。"""
|
|
512
|
+
|
|
513
|
+
def _write_selection_with_session(
|
|
514
|
+
self,
|
|
515
|
+
path: Path,
|
|
516
|
+
*,
|
|
517
|
+
complexity: str,
|
|
518
|
+
tier: str,
|
|
519
|
+
session_id: str,
|
|
520
|
+
) -> None:
|
|
521
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
522
|
+
path.write_text(
|
|
523
|
+
json.dumps({
|
|
524
|
+
"complexity": complexity,
|
|
525
|
+
"tier": tier,
|
|
526
|
+
"mode": "thompson",
|
|
527
|
+
"session_id": session_id,
|
|
528
|
+
}),
|
|
529
|
+
encoding="utf-8",
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
def test_session_id_passed_to_record_tier_recent_outcome(
|
|
533
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
534
|
+
) -> None:
|
|
535
|
+
"""tier_selection.json に session_id があるとき、
|
|
536
|
+
record_tier_recent_outcome に session_id が渡されること。
|
|
537
|
+
monkeypatch で呼び出し引数をキャプチャして検証する。
|
|
538
|
+
"""
|
|
539
|
+
db_path = tmp_path / "c3.db"
|
|
540
|
+
_create_c3_db(db_path)
|
|
541
|
+
sel_path = tmp_path / "state" / "tier_selection.json"
|
|
542
|
+
self._write_selection_with_session(
|
|
543
|
+
sel_path,
|
|
544
|
+
complexity="simple",
|
|
545
|
+
tier="haiku",
|
|
546
|
+
session_id="test-session-abc123",
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
mod = _load_hook_module("record_tier_outcome_t4_a")
|
|
550
|
+
monkeypatch.setattr(mod, "TIER_SELECTION_PATH", str(sel_path))
|
|
551
|
+
|
|
552
|
+
from c3 import db as c3_db
|
|
553
|
+
monkeypatch.setattr(c3_db, "locate_c3_db", lambda start=None: db_path)
|
|
554
|
+
|
|
555
|
+
# record_tier_recent_outcome の呼び出し引数をキャプチャ
|
|
556
|
+
captured: list[dict] = []
|
|
557
|
+
original_fn = c3_db.record_tier_recent_outcome
|
|
558
|
+
|
|
559
|
+
def _capture(**kwargs): # type: ignore[no-untyped-def]
|
|
560
|
+
captured.append(kwargs)
|
|
561
|
+
return original_fn(**kwargs)
|
|
562
|
+
|
|
563
|
+
monkeypatch.setattr(c3_db, "record_tier_recent_outcome", _capture)
|
|
564
|
+
|
|
565
|
+
rc = mod.main(["--outcome", "success"])
|
|
566
|
+
assert rc == 0
|
|
567
|
+
|
|
568
|
+
assert len(captured) == 1
|
|
569
|
+
assert captured[0]["session_id"] == "test-session-abc123"
|
|
570
|
+
assert captured[0]["complexity"] == "simple"
|
|
571
|
+
assert captured[0]["tier"] == "haiku"
|
|
572
|
+
assert captured[0]["success"] is True
|
|
573
|
+
|
|
574
|
+
def test_no_session_id_in_json_passes_none(
|
|
575
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
576
|
+
) -> None:
|
|
577
|
+
"""session_id キーが無い古い tier_selection.json(旧フォーマット)でも
|
|
578
|
+
crash せず、record_tier_recent_outcome に session_id=None が渡ること。
|
|
579
|
+
既存の tier_bandit 更新・recent_outcomes 記録が従来通り行われること。
|
|
580
|
+
"""
|
|
581
|
+
db_path = tmp_path / "c3.db"
|
|
582
|
+
_create_c3_db(db_path)
|
|
583
|
+
sel_path = tmp_path / "state" / "tier_selection.json"
|
|
584
|
+
# session_id キーを含まない旧フォーマット
|
|
585
|
+
_write_selection(sel_path, "medium", "sonnet")
|
|
586
|
+
|
|
587
|
+
mod = _load_hook_module("record_tier_outcome_t4_b")
|
|
588
|
+
monkeypatch.setattr(mod, "TIER_SELECTION_PATH", str(sel_path))
|
|
589
|
+
|
|
590
|
+
from c3 import db as c3_db
|
|
591
|
+
monkeypatch.setattr(c3_db, "locate_c3_db", lambda start=None: db_path)
|
|
592
|
+
|
|
593
|
+
# record_tier_recent_outcome の呼び出し引数をキャプチャ
|
|
594
|
+
captured: list[dict] = []
|
|
595
|
+
original_fn = c3_db.record_tier_recent_outcome
|
|
596
|
+
|
|
597
|
+
def _capture(**kwargs): # type: ignore[no-untyped-def]
|
|
598
|
+
captured.append(kwargs)
|
|
599
|
+
return original_fn(**kwargs)
|
|
600
|
+
|
|
601
|
+
monkeypatch.setattr(c3_db, "record_tier_recent_outcome", _capture)
|
|
602
|
+
|
|
603
|
+
rc = mod.main(["--outcome", "success"])
|
|
604
|
+
assert rc == 0
|
|
605
|
+
|
|
606
|
+
# crash していない & session_id=None が渡されている
|
|
607
|
+
assert len(captured) == 1
|
|
608
|
+
assert captured[0]["session_id"] is None
|
|
609
|
+
assert captured[0]["complexity"] == "medium"
|
|
610
|
+
assert captured[0]["tier"] == "sonnet"
|
|
611
|
+
|
|
612
|
+
# tier_bandit が従来通り更新されている
|
|
613
|
+
params = c3_db.read_tier_params("medium", db_path=db_path)
|
|
614
|
+
assert params["sonnet"] == (2.0, 1.0, 1)
|
|
615
|
+
# tier_selection.json も削除されている
|
|
616
|
+
assert not sel_path.exists()
|
{claude_code_conductor-2.21.0 → claude_code_conductor-2.22.0}/tests/hooks/test_select_tier.py
RENAMED
|
@@ -411,3 +411,85 @@ class TestMaskSecrets:
|
|
|
411
411
|
data = json.loads((tmp_path / "tier_selection.json").read_text(encoding="utf-8"))
|
|
412
412
|
assert "ghp_12345678abcdef" not in data.get("prompt_prefix", "")
|
|
413
413
|
assert "token=***" in data.get("prompt_prefix", "")
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
# ---------------------------------------------------------------------------
|
|
417
|
+
# T3: session_id 記録(AC-3 / AC-9)
|
|
418
|
+
# ---------------------------------------------------------------------------
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class TestSessionIdRecording:
|
|
422
|
+
"""write_tier_selection / main の session_id 記録を検証する。"""
|
|
423
|
+
|
|
424
|
+
def test_write_tier_selection_with_session_id(
|
|
425
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
426
|
+
) -> None:
|
|
427
|
+
"""session_id が渡されると tier_selection.json に session_id キーが入る。"""
|
|
428
|
+
mod = _load_hook_module()
|
|
429
|
+
target = tmp_path / "tier_selection.json"
|
|
430
|
+
monkeypatch.setattr(mod, "TIER_SELECTION_PATH", str(target))
|
|
431
|
+
|
|
432
|
+
mod.write_tier_selection("medium", "sonnet", "thompson", session_id="sess-abc123")
|
|
433
|
+
data = json.loads(target.read_text(encoding="utf-8"))
|
|
434
|
+
assert "session_id" in data
|
|
435
|
+
assert data["session_id"] == "sess-abc123"
|
|
436
|
+
|
|
437
|
+
def test_write_tier_selection_without_session_id_no_key(
|
|
438
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
439
|
+
) -> None:
|
|
440
|
+
"""session_id が渡されない(None)ときは tier_selection.json に session_id キーが入らない。
|
|
441
|
+
|
|
442
|
+
既存の dict 完全一致テスト(test_write_tier_selection)と同形の確認。
|
|
443
|
+
"""
|
|
444
|
+
mod = _load_hook_module()
|
|
445
|
+
target = tmp_path / "tier_selection.json"
|
|
446
|
+
monkeypatch.setattr(mod, "TIER_SELECTION_PATH", str(target))
|
|
447
|
+
|
|
448
|
+
mod.write_tier_selection("complex", "opus", "thompson")
|
|
449
|
+
data = json.loads(target.read_text(encoding="utf-8"))
|
|
450
|
+
# session_id キーが存在しないことを確認
|
|
451
|
+
assert "session_id" not in data
|
|
452
|
+
# 既存テストと同一の期待 dict と一致する
|
|
453
|
+
assert data == {
|
|
454
|
+
"complexity": "complex",
|
|
455
|
+
"tier": "opus",
|
|
456
|
+
"mode": "thompson",
|
|
457
|
+
"suggested_model": "opus",
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
def test_main_records_session_id_when_present(
|
|
461
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture
|
|
462
|
+
) -> None:
|
|
463
|
+
"""payload に session_id があるとき tier_selection.json に session_id が入る。"""
|
|
464
|
+
mod = _load_hook_module()
|
|
465
|
+
monkeypatch.setattr(
|
|
466
|
+
mod, "TIER_SELECTION_PATH",
|
|
467
|
+
str(tmp_path / "tier_selection.json"),
|
|
468
|
+
)
|
|
469
|
+
payload = {"prompt": "新しい機能を追加してください", "session_id": "sess-xyz-999"}
|
|
470
|
+
monkeypatch.setattr(sys, "stdin", io.StringIO(json.dumps(payload)))
|
|
471
|
+
|
|
472
|
+
rc = mod.main()
|
|
473
|
+
assert rc == 0
|
|
474
|
+
|
|
475
|
+
data = json.loads((tmp_path / "tier_selection.json").read_text(encoding="utf-8"))
|
|
476
|
+
assert "session_id" in data
|
|
477
|
+
assert data["session_id"] == "sess-xyz-999"
|
|
478
|
+
|
|
479
|
+
def test_main_no_session_id_in_payload_no_key_in_json(
|
|
480
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture
|
|
481
|
+
) -> None:
|
|
482
|
+
"""payload に session_id が無いとき tier_selection.json に session_id キーが入らず crash しない。"""
|
|
483
|
+
mod = _load_hook_module()
|
|
484
|
+
monkeypatch.setattr(
|
|
485
|
+
mod, "TIER_SELECTION_PATH",
|
|
486
|
+
str(tmp_path / "tier_selection.json"),
|
|
487
|
+
)
|
|
488
|
+
payload = {"prompt": "テストを書いてください"}
|
|
489
|
+
monkeypatch.setattr(sys, "stdin", io.StringIO(json.dumps(payload)))
|
|
490
|
+
|
|
491
|
+
rc = mod.main()
|
|
492
|
+
assert rc == 0
|
|
493
|
+
|
|
494
|
+
data = json.loads((tmp_path / "tier_selection.json").read_text(encoding="utf-8"))
|
|
495
|
+
assert "session_id" not in data
|