claude-code-conductor 2.24.0__tar.gz → 2.26.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.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/select_tier.py +231 -28
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/session_stop.py +2 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/CHANGELOG.md +46 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/PKG-INFO +2 -1
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/README.md +1 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/__init__.py +1 -1
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/cli_tier.py +17 -3
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/db.py +137 -7
- claude_code_conductor-2.26.0/tests/hooks/test_select_tier.py +2142 -0
- claude_code_conductor-2.26.0/tests/hooks/test_select_tier_escalation.py +464 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_session_stop.py +55 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_cli_tier.py +146 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_db.py +420 -0
- claude_code_conductor-2.24.0/tests/hooks/test_select_tier.py +0 -1082
- claude_code_conductor-2.24.0/tests/hooks/test_select_tier_escalation.py +0 -229
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/CLAUDE.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/code-reviewer.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/developer.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/security-reviewer.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/systematic-debugger.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/tester.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/wt_developer.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/wt_systematic-debugger.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/agents/wt_tester.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/breaking-changes.txt +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/deletions.txt +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/docs/config-policy.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/docs/parallel-agents-setup.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/docs/platform-adapters.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/docs/settings.json.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/_hook_utils.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/check_agent_invocation.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/consolidate_memory.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/permission_handler.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/permission_handler_toast.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/planner_check.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/post_tool.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/pre_compact.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/pre_tool.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/recall_inject.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/restore_session.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/session_start.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/session_utils.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/statusline.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/stop.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/hooks/worktree_guard.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/memory/.gitkeep +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/permission_rules.json +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/settings.json +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/codex-review/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/dev-workflow/scripts/record_tier_outcome.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/develop/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/doc/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/extract-lib/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/init-session/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/mcp-config/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/pattern-status/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/recall/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/review-phase/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/setup/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/setup/reference.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/skills/start/SKILL.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.claude/state/.gitkeep +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/.gitignore +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/LICENSE +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/LICENSES/chroma-hnswlib-LICENSE +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/LICENSES/chroma-hnswlib-NOTICE +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/LICENSES/fastembed-LICENSE +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/LICENSES/fastembed-NOTICE +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/LICENSES/onnxruntime-LICENSE +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/hatch_build.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/pyproject.toml +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/__main__.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/_excludes.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/_terminal.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/adapters.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/cli.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/cli_ask.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/cli_init.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/cli_list.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/cli_plan.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/cli_recall.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/embedding.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/mcp_server.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/migrate.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/migrations/001_initial.sql +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/migrations/003_tier_cost.sql +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/migrations/README.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/migrations/__init__.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/paths.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/plan_validator.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/platforms.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/pricing.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/question.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/recall_chunker.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/recall_index.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/src/c3/usage_ingester.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/__init__.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/conftest.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/fixtures/usage/README.md +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/fixtures/usage/mainline.jsonl +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/__init__.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_check_agent_invocation.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_consolidate_memory.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_hook_utils.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_permission_handler.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_permission_handler_toast.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_planner_check.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_planner_check_dev.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_post_tool.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_pre_tool.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_recall_inject.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_record_review_decision.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_record_tier_outcome.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_restore_session.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_review_hint_inject.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_session_start.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_session_utils.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_similarity_boost.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_statusline.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_statusline_template_sync.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_sync_check.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/hooks/test_template_guard.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/skills/__init__.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/skills/_skill_helpers.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/skills/test_init_session_no_task_type.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/skills/test_planner_lightweight.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/skills/test_recall_skill.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/skills/test_setup_templates.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/skills/test_start_skill_new_flow.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_adapters.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_cli_ask.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_cli_entry.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_cli_init.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_cli_list.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_cli_plan.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_cli_recall.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_cli_update_breaking_changes.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_cli_update_deletions.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_docstring_consistency.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_embedding.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_excludes.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_extract_breaking_changes.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_mcp_server_elicit.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_migrate.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_paths.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_plan_validator.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_pre_compact.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_precompact_additional.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_precompact_toctou_fixes.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_pricing.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_recall_chunker.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_recall_index.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_references_migration.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_session_utils_additional.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_skill_no_builtin_conflict.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_statusline.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_stop_additional.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_stop_hook.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_stop_precompact_fixes.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_sync_template_stop.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_template_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_usage_ingester.py +0 -0
- {claude_code_conductor-2.24.0 → claude_code_conductor-2.26.0}/tests/test_worktree_guard.py +0 -0
|
@@ -27,6 +27,7 @@ import collections
|
|
|
27
27
|
import difflib
|
|
28
28
|
import hashlib
|
|
29
29
|
import json
|
|
30
|
+
import math
|
|
30
31
|
import os
|
|
31
32
|
import random
|
|
32
33
|
import re
|
|
@@ -47,8 +48,14 @@ except AttributeError:
|
|
|
47
48
|
try:
|
|
48
49
|
from c3 import db as _c3_db_const # type: ignore[import-not-found]
|
|
49
50
|
LEARNING_THRESHOLD: int = _c3_db_const.LEARNING_THRESHOLD
|
|
51
|
+
EPSILON: float = _c3_db_const.EPSILON_TIEBREAK
|
|
52
|
+
ESCALATION_THRESHOLD: float = _c3_db_const.ESCALATION_THRESHOLD_DEFAULT
|
|
53
|
+
COST_LAMBDA_DEFAULT: float | None = _c3_db_const.COST_LAMBDA_DEFAULT
|
|
50
54
|
except ImportError:
|
|
51
55
|
LEARNING_THRESHOLD = 30
|
|
56
|
+
EPSILON = 0.05
|
|
57
|
+
ESCALATION_THRESHOLD = 0.5
|
|
58
|
+
COST_LAMBDA_DEFAULT = None
|
|
52
59
|
|
|
53
60
|
# 複雑度推定のキーワード
|
|
54
61
|
SIMPLE_KEYWORDS = frozenset({
|
|
@@ -111,10 +118,9 @@ _PROMPT_HISTORY_SCAN_LINES = 1000
|
|
|
111
118
|
|
|
112
119
|
TIERS: tuple[str, ...] = ("haiku", "sonnet", "opus")
|
|
113
120
|
|
|
114
|
-
# cost-aware tie-break の拮抗判定閾値(v2.23.0)。
|
|
121
|
+
# cost-aware tie-break の拮抗判定閾値(v2.23.0・SSOT は db.EPSILON_TIEBREAK)。
|
|
115
122
|
# Beta サンプルは 0〜1 スケール。成功率 5pt 以内=実質同等とみなす拮抗判定閾値。
|
|
116
|
-
#
|
|
117
|
-
EPSILON: float = 0.05
|
|
123
|
+
# 過大は成功率犠牲リスク、過小は無発動。C3_TIER_EPSILON env で上書き可(v2.25.0)。
|
|
118
124
|
|
|
119
125
|
|
|
120
126
|
class SelectionResult(NamedTuple):
|
|
@@ -125,12 +131,16 @@ class SelectionResult(NamedTuple):
|
|
|
125
131
|
cost_tiebreak: Thompson 分岐で cost tie-break が発動した場合 True。
|
|
126
132
|
contenders: 拮抗判定に入った tier のタプル(observability/デバッグ用)。
|
|
127
133
|
frozen 安全のため list ではなく tuple を使用。
|
|
134
|
+
cost_weighted: λ>0 全 tier weighting が適用された場合 True(v2.26.0)。
|
|
135
|
+
lam=None(env 未設定)または cost_map=None の場合は False のまま。
|
|
136
|
+
uniform 分岐では常に False(探索保護・不可侵)。
|
|
128
137
|
"""
|
|
129
138
|
|
|
130
139
|
tier: str
|
|
131
140
|
mode: str
|
|
132
141
|
cost_tiebreak: bool = False
|
|
133
142
|
contenders: tuple[str, ...] = ()
|
|
143
|
+
cost_weighted: bool = False
|
|
134
144
|
|
|
135
145
|
|
|
136
146
|
def _cost_tiebreak(
|
|
@@ -138,41 +148,79 @@ def _cost_tiebreak(
|
|
|
138
148
|
cost_map: dict[str, float] | None,
|
|
139
149
|
*,
|
|
140
150
|
epsilon: float = EPSILON,
|
|
151
|
+
lam: float | None = None,
|
|
141
152
|
) -> tuple[str, bool, tuple[str, ...]]:
|
|
142
|
-
"""Thompson
|
|
153
|
+
"""Thompson サンプルから cost を考慮して最適な tier を返す(3 経路)。
|
|
154
|
+
|
|
155
|
+
lam(λ)の値によって 3 つの経路に分岐する:
|
|
156
|
+
- 経路 0(cost_map is None or lam == 0): cost を見ない。argmax(sample) を返す。
|
|
157
|
+
lam=None を渡しても cost_map が None なら経路 0。lam=0.0 の明示オプトも経路 0。
|
|
158
|
+
- 経路 1(lam is None・既定): v2.25.0 の ε-gated min-max 最安(後方互換)。
|
|
159
|
+
contenders ≤ 1 は argmax、複数 contenders は min-max 正規化コスト最安を選ぶ。
|
|
160
|
+
lam=None(env 未設定センチネル)がデフォルト→ v2.25.0 挙動と完全一致。
|
|
161
|
+
- 経路 2(lam > 0): 全 tier weighting。
|
|
162
|
+
score[t] = sample[t] - lam * cost_norm[t] で全 tier を比較し最大を選ぶ。
|
|
163
|
+
cost_norm は全 tier の min-max 正規化(最安→0・最高→1)。
|
|
143
164
|
|
|
144
165
|
Args:
|
|
145
166
|
samples: {tier: beta_sample} の dict(Thompson Sampling 結果)。
|
|
146
|
-
cost_map: {tier: cost} の dict。None なら cost
|
|
167
|
+
cost_map: {tier: cost} の dict。None なら cost を見ず従来挙動(経路 0)。
|
|
147
168
|
cost は実測 rate_usd_per_mtok または静的参照単価(ハイブリッド)。
|
|
148
169
|
v2.24.0 で rate 化(USD/MTok)により実測・静的とも同次元で整合済み。
|
|
149
|
-
``cost_map`` は None、または
|
|
150
|
-
|
|
170
|
+
``cost_map`` は None、または samples の全 tier キーを含む dict を渡すこと。
|
|
171
|
+
partial dict を渡すと ``cost_map[t]`` で KeyError が発生する。
|
|
151
172
|
``select_tier_detailed`` 経由では呼び出し側(main)が全 TIERS 分を
|
|
152
173
|
構築して保証する。
|
|
153
|
-
epsilon: 拮抗判定の閾値(デフォルト EPSILON=0.05
|
|
174
|
+
epsilon: 拮抗判定の閾値(デフォルト EPSILON=0.05)。経路 0/1 で使用。
|
|
175
|
+
経路 2 では contenders 算出にのみ使用(選択自体は全 tier score 比較)。
|
|
176
|
+
lam: cost weighting 係数(λ)。None=センチネル(経路 1・後方互換)、
|
|
177
|
+
0.0=cost 無視明示(経路 0)、0 < lam <= 1=全 tier weighting(経路 2)。
|
|
178
|
+
デフォルト None で既存 2 引数呼び出しの挙動・シグネチャを完全不変にする。
|
|
154
179
|
|
|
155
180
|
Returns:
|
|
156
181
|
(chosen, did_tiebreak, contenders) のタプル。
|
|
157
182
|
- chosen: 選択された tier 名。
|
|
158
|
-
- did_tiebreak: cost
|
|
159
|
-
|
|
183
|
+
- did_tiebreak: cost が選択に影響した場合 True。
|
|
184
|
+
経路 1: contenders 内 min-max で安い方を選んだ場合 True。
|
|
185
|
+
経路 2: 全 tier weighting で argmax(sample) と異なる選択になった場合 True。
|
|
186
|
+
- contenders: ε 拮抗判定に入った tier のタプル(observability 用)。
|
|
160
187
|
"""
|
|
161
188
|
max_sample = max(samples.values())
|
|
162
189
|
contenders = [t for t in samples if max_sample - samples[t] <= epsilon]
|
|
163
190
|
|
|
164
|
-
|
|
165
|
-
|
|
191
|
+
# 経路 0: cost を見ない(cost_map なし or λ=0 明示)。
|
|
192
|
+
# None == 0 は Python では False のため lam == 0 は float 0.0 のみ真(意図通り)。
|
|
193
|
+
if cost_map is None or lam == 0:
|
|
166
194
|
chosen = max(samples, key=lambda t: samples[t])
|
|
167
195
|
return chosen, False, tuple(contenders)
|
|
168
196
|
|
|
169
|
-
#
|
|
170
|
-
|
|
197
|
+
# 経路 1(lam=None・既定): v2.25.0 の ε-gated min-max 最安(現行ロジック完全踏襲)。
|
|
198
|
+
if lam is None:
|
|
199
|
+
if len(contenders) <= 1:
|
|
200
|
+
chosen = max(samples, key=lambda t: samples[t])
|
|
201
|
+
return chosen, False, tuple(contenders)
|
|
202
|
+
# 拮抗群内で min-max 正規化コストを計算し最安 tier を選ぶ
|
|
203
|
+
costs = {t: cost_map[t] for t in contenders}
|
|
204
|
+
lo, hi = min(costs.values()), max(costs.values())
|
|
205
|
+
norm = {t: ((costs[t] - lo) / (hi - lo) if hi > lo else 0.0) for t in contenders}
|
|
206
|
+
# 同値安定 tie-break: norm 同値時はサンプル大(=従来選好)を優先 → 決定論
|
|
207
|
+
# 注: 全 tier コスト同値(hi == lo → norm 全 0.0)でも did_tiebreak=True を返す。
|
|
208
|
+
# これは v2.25.0 のバイト互換維持のため意図的(フラグ意味の精緻化=同値時 False は
|
|
209
|
+
# 後方互換を崩すため v2.27.0+ で扱う)。[CR-Q-001]
|
|
210
|
+
chosen = min(contenders, key=lambda t: (norm[t], -samples[t]))
|
|
211
|
+
return chosen, True, tuple(contenders)
|
|
212
|
+
|
|
213
|
+
# 経路 2(lam > 0): 全 tier weighting。
|
|
214
|
+
# cost_norm は全 tier で min-max 正規化(最安→0・最高→1)。
|
|
215
|
+
costs = {t: cost_map[t] for t in samples}
|
|
171
216
|
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
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
217
|
+
norm = {t: ((costs[t] - lo) / (hi - lo) if hi > lo else 0.0) for t in samples}
|
|
218
|
+
score = {t: samples[t] - lam * norm[t] for t in samples}
|
|
219
|
+
# 同点は sample 大優先(決定論)
|
|
220
|
+
chosen = max(samples, key=lambda t: (score[t], samples[t]))
|
|
221
|
+
pure_max = max(samples, key=lambda t: samples[t])
|
|
222
|
+
cost_tiebreak = chosen != pure_max
|
|
223
|
+
return chosen, cost_tiebreak, tuple(contenders)
|
|
176
224
|
|
|
177
225
|
|
|
178
226
|
def _prompt_prefix_and_hash(prompt: str) -> tuple[str, str]:
|
|
@@ -295,6 +343,8 @@ def select_tier_detailed(
|
|
|
295
343
|
*,
|
|
296
344
|
rng: random.Random | None = None,
|
|
297
345
|
cost_map: dict[str, float] | None = None,
|
|
346
|
+
epsilon: float | None = None,
|
|
347
|
+
lam: float | None = None,
|
|
298
348
|
) -> SelectionResult:
|
|
299
349
|
"""Beta サンプリングまたは uniform 選択で推奨 Tier を SelectionResult で返す。
|
|
300
350
|
|
|
@@ -307,15 +357,21 @@ def select_tier_detailed(
|
|
|
307
357
|
含む完全な dict」のいずれか。partial dict は渡されない前提
|
|
308
358
|
(呼び出し側が全 TIERS 分を構築して保証する)。
|
|
309
359
|
uniform 分岐では cost_map の有無に関わらず完全無視する(探索保護)。
|
|
360
|
+
epsilon: 拮抗判定閾値。None なら module 定数 EPSILON を使う(C3_TIER_EPSILON で上書き可)。
|
|
361
|
+
lam: cost weighting 係数(λ)。None=センチネル(v2.25.0 ε-gated 後方互換)、
|
|
362
|
+
0.0=cost 無視明示、0<lam<=1=全 tier weighting 発動(C3_TIER_COST_LAMBDA で上書き可)。
|
|
363
|
+
None(デフォルト)では env 未設定時と完全一致する(後方互換の核心)。
|
|
364
|
+
uniform 分岐では lam の値に関わらず完全無視する(探索保護・不可侵)。
|
|
310
365
|
|
|
311
366
|
Returns:
|
|
312
|
-
SelectionResult(tier, mode, cost_tiebreak, contenders)。
|
|
367
|
+
SelectionResult(tier, mode, cost_tiebreak, contenders, cost_weighted)。
|
|
313
368
|
mode は ``"thompson"`` / ``"uniform"``。
|
|
369
|
+
cost_weighted は lam>0 かつ cost_map が有効な場合のみ True。
|
|
314
370
|
"""
|
|
315
371
|
rng = rng or random
|
|
316
372
|
total_trials = sum(p[2] for p in params.values())
|
|
317
373
|
if total_trials < LEARNING_THRESHOLD:
|
|
318
|
-
# uniform: cost
|
|
374
|
+
# uniform: cost/λ を完全無視・従来挙動完全維持(不可侵)
|
|
319
375
|
return SelectionResult(rng.choice(TIERS), "uniform", False, ())
|
|
320
376
|
|
|
321
377
|
# Thompson Sampling: rng の消費順序を従来 select_tier と完全一致させる
|
|
@@ -323,8 +379,10 @@ def select_tier_detailed(
|
|
|
323
379
|
tier: rng.betavariate(p[0], p[1])
|
|
324
380
|
for tier, p in params.items()
|
|
325
381
|
}
|
|
326
|
-
|
|
327
|
-
|
|
382
|
+
eff_epsilon = epsilon if epsilon is not None else EPSILON
|
|
383
|
+
chosen, did_tiebreak, contenders = _cost_tiebreak(samples, cost_map, epsilon=eff_epsilon, lam=lam)
|
|
384
|
+
cost_weighted = (cost_map is not None and lam is not None and lam > 0)
|
|
385
|
+
return SelectionResult(chosen, "thompson", did_tiebreak, contenders, cost_weighted)
|
|
328
386
|
|
|
329
387
|
|
|
330
388
|
def select_tier(
|
|
@@ -332,6 +390,8 @@ def select_tier(
|
|
|
332
390
|
*,
|
|
333
391
|
rng: random.Random | None = None,
|
|
334
392
|
cost_map: dict[str, float] | None = None,
|
|
393
|
+
epsilon: float | None = None,
|
|
394
|
+
lam: float | None = None,
|
|
335
395
|
) -> tuple[str, str]:
|
|
336
396
|
"""Beta サンプリングまたは uniform 選択で推奨 Tier を返す。
|
|
337
397
|
|
|
@@ -343,13 +403,16 @@ def select_tier(
|
|
|
343
403
|
None なら cost を見ず従来の Thompson Sampling と完全一致。
|
|
344
404
|
uniform 分岐では cost_map の有無に関わらず完全無視する。
|
|
345
405
|
詳細は :func:`select_tier_detailed` を参照。
|
|
406
|
+
epsilon: 拮抗判定閾値。None なら module 定数 EPSILON を使う。
|
|
407
|
+
lam: cost weighting 係数(λ)。select_tier_detailed に委譲する。
|
|
408
|
+
None=センチネル(v2.25.0 後方互換)、0.0=cost 無視、0<lam<=1=全 tier weighting。
|
|
346
409
|
|
|
347
410
|
Returns:
|
|
348
411
|
``(tier, mode)`` のタプル。``mode`` は ``"thompson"`` / ``"uniform"`` で、
|
|
349
412
|
プロンプトに「学習データ収集中」と表示するかの分岐に使う。
|
|
350
413
|
戻り値型は v2.22.0 以前と完全に不変。
|
|
351
414
|
"""
|
|
352
|
-
result = select_tier_detailed(params, rng=rng, cost_map=cost_map)
|
|
415
|
+
result = select_tier_detailed(params, rng=rng, cost_map=cost_map, epsilon=epsilon, lam=lam)
|
|
353
416
|
return result.tier, result.mode
|
|
354
417
|
|
|
355
418
|
|
|
@@ -361,7 +424,7 @@ _ESCALATION_MAP: dict[str, str] = {
|
|
|
361
424
|
}
|
|
362
425
|
|
|
363
426
|
# Phase 2-B: failure rate がこの値以上で escalation 判定。
|
|
364
|
-
|
|
427
|
+
# SSOT: db.ESCALATION_THRESHOLD_DEFAULT 由来(import 部で取得)。C3_ESCALATION_THRESHOLD env で上書き可(v2.26.0)。
|
|
365
428
|
|
|
366
429
|
|
|
367
430
|
def _db_failure_rate(complexity: str, tier: str) -> tuple:
|
|
@@ -380,6 +443,7 @@ def maybe_escalate(
|
|
|
380
443
|
chosen_tier: str,
|
|
381
444
|
*,
|
|
382
445
|
failure_rate_fn=None,
|
|
446
|
+
threshold: float | None = None,
|
|
383
447
|
) -> tuple[str, str | None]:
|
|
384
448
|
"""Phase 2-B: failure rate が高ければ 1 段昇格する。
|
|
385
449
|
|
|
@@ -389,6 +453,9 @@ def maybe_escalate(
|
|
|
389
453
|
failure_rate_fn: テスト用に注入可能な
|
|
390
454
|
``(complexity, tier) -> (rate_or_None, sample_count)``。
|
|
391
455
|
省略時は :func:`_db_failure_rate` を使う。
|
|
456
|
+
threshold: escalation 閾値(failure rate がこの値以上で昇格)。
|
|
457
|
+
None のとき module 定数 ``ESCALATION_THRESHOLD`` を使う。
|
|
458
|
+
``main()`` は ``_resolve_escalation_threshold()`` で解決した値を渡す。
|
|
392
459
|
|
|
393
460
|
Returns:
|
|
394
461
|
``(effective_tier, escalation_reason)``。
|
|
@@ -400,7 +467,8 @@ def maybe_escalate(
|
|
|
400
467
|
|
|
401
468
|
effective_fn = failure_rate_fn or _db_failure_rate
|
|
402
469
|
rate, samples = effective_fn(complexity, chosen_tier)
|
|
403
|
-
if
|
|
470
|
+
eff_threshold = threshold if threshold is not None else ESCALATION_THRESHOLD
|
|
471
|
+
if rate is None or rate < eff_threshold:
|
|
404
472
|
return chosen_tier, None
|
|
405
473
|
|
|
406
474
|
escalated = _ESCALATION_MAP[chosen_tier]
|
|
@@ -422,6 +490,8 @@ def write_tier_selection(
|
|
|
422
490
|
prompt_hash: str | None = None,
|
|
423
491
|
session_id: str | None = None,
|
|
424
492
|
cost_tiebreak: bool = False,
|
|
493
|
+
cost_weighted: bool = False,
|
|
494
|
+
cost_lambda: float | None = None,
|
|
425
495
|
) -> None:
|
|
426
496
|
"""直近の選択結果を ``tier_selection.json`` に書く。
|
|
427
497
|
|
|
@@ -441,6 +511,14 @@ def write_tier_selection(
|
|
|
441
511
|
``cost_tiebreak`` を任意で含める(v2.23.0)。
|
|
442
512
|
Thompson Sampling の拮抗群内で cost tie-break が発動した場合のみ True。
|
|
443
513
|
False のときはキー自体を省略する(escalated/session_id と同パターン)。
|
|
514
|
+
|
|
515
|
+
``cost_weighted`` を任意で含める(v2.26.0)。
|
|
516
|
+
λ>0 の全 tier weighting が適用された場合のみ True。
|
|
517
|
+
False のときはキー自体を省略する(cost_tiebreak と同パターン)。
|
|
518
|
+
|
|
519
|
+
``cost_lambda`` を任意で含める(v2.26.0)。
|
|
520
|
+
``_resolve_cost_lambda()`` で解決した λ 値(None 以外のとき出力)。
|
|
521
|
+
None のときはキー自体を省略する(env 未設定時の後方互換)。
|
|
444
522
|
"""
|
|
445
523
|
os.makedirs(os.path.dirname(TIER_SELECTION_PATH), exist_ok=True)
|
|
446
524
|
payload: dict[str, object] = {
|
|
@@ -464,6 +542,10 @@ def write_tier_selection(
|
|
|
464
542
|
payload["session_id"] = session_id
|
|
465
543
|
if cost_tiebreak:
|
|
466
544
|
payload["cost_tiebreak"] = True
|
|
545
|
+
if cost_weighted:
|
|
546
|
+
payload["cost_weighted"] = True
|
|
547
|
+
if cost_lambda is not None:
|
|
548
|
+
payload["cost_lambda"] = cost_lambda
|
|
467
549
|
try:
|
|
468
550
|
with open(TIER_SELECTION_PATH, "w", encoding="utf-8") as f:
|
|
469
551
|
json.dump(payload, f, ensure_ascii=False)
|
|
@@ -481,11 +563,16 @@ def build_additional_context(
|
|
|
481
563
|
escalation_reason: str | None = None,
|
|
482
564
|
complexity_source: str | None = None,
|
|
483
565
|
cost_tiebreak: bool = False,
|
|
566
|
+
cost_weighted: bool = False,
|
|
484
567
|
) -> str:
|
|
485
568
|
"""親 Claude に追加注入する文字列を組み立てる。
|
|
486
569
|
|
|
487
570
|
``cost_tiebreak`` が True のとき、suffix に cost-aware 発動を示す文言を追加する(v2.23.0)。
|
|
488
571
|
False のときは不変(既存文言と完全一致)。
|
|
572
|
+
|
|
573
|
+
``cost_weighted`` が True のとき、cost-weighted 文言を suffix に追加する(v2.26.0)。
|
|
574
|
+
True のときは cost_tiebreak の文言より優先される(λ>0 全 tier weighting を明示)。
|
|
575
|
+
False のときは cost_tiebreak による既存文言のみ(v2.25.0 以前と完全一致)。
|
|
489
576
|
"""
|
|
490
577
|
trials = sum(p[2] for p in params.values())
|
|
491
578
|
if mode == "uniform":
|
|
@@ -499,7 +586,9 @@ def build_additional_context(
|
|
|
499
586
|
suffix += f" [Phase 2-B 昇格: {escalation_reason}]"
|
|
500
587
|
if complexity_source:
|
|
501
588
|
suffix += f" [複雑度判定: {complexity_source}]"
|
|
502
|
-
if
|
|
589
|
+
if cost_weighted:
|
|
590
|
+
suffix += " [cost-weighted: 成功率とコストを加重して選択]"
|
|
591
|
+
elif cost_tiebreak:
|
|
503
592
|
suffix += " [cost-aware: 成功率拮抗のため低コスト Tier を選択]"
|
|
504
593
|
|
|
505
594
|
return (
|
|
@@ -522,6 +611,114 @@ def _load_c3_db_module():
|
|
|
522
611
|
return None
|
|
523
612
|
|
|
524
613
|
|
|
614
|
+
def _resolve_epsilon() -> float:
|
|
615
|
+
"""``C3_TIER_EPSILON`` を安全に解決する。
|
|
616
|
+
|
|
617
|
+
不正値(非数値 / 0 以下 / 1 超 / NaN)は受け付けず、stderr 警告 + デフォルト(EPSILON)に戻す。
|
|
618
|
+
未設定 / 空文字は無警告でデフォルトを返す([SR-V-001])。
|
|
619
|
+
"""
|
|
620
|
+
raw = os.environ.get("C3_TIER_EPSILON")
|
|
621
|
+
if raw is None or raw == "":
|
|
622
|
+
return EPSILON
|
|
623
|
+
try:
|
|
624
|
+
x = float(raw)
|
|
625
|
+
except ValueError:
|
|
626
|
+
print(
|
|
627
|
+
f"[select_tier:epsilon] invalid C3_TIER_EPSILON={raw!r}, "
|
|
628
|
+
f"using default {EPSILON}",
|
|
629
|
+
file=sys.stderr,
|
|
630
|
+
)
|
|
631
|
+
return EPSILON
|
|
632
|
+
if math.isnan(x):
|
|
633
|
+
print(
|
|
634
|
+
f"[select_tier:epsilon] C3_TIER_EPSILON={raw!r} is NaN, "
|
|
635
|
+
f"using default {EPSILON}",
|
|
636
|
+
file=sys.stderr,
|
|
637
|
+
)
|
|
638
|
+
return EPSILON
|
|
639
|
+
if x <= 0 or x > 1:
|
|
640
|
+
print(
|
|
641
|
+
f"[select_tier:epsilon] C3_TIER_EPSILON={x} out of range (0, 1], "
|
|
642
|
+
f"using default {EPSILON}",
|
|
643
|
+
file=sys.stderr,
|
|
644
|
+
)
|
|
645
|
+
return EPSILON
|
|
646
|
+
return x
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def _resolve_escalation_threshold() -> float:
|
|
650
|
+
"""``C3_ESCALATION_THRESHOLD`` を安全に解決する。
|
|
651
|
+
|
|
652
|
+
不正値(非数値 / 0 以下 / 1 超 / NaN)は受け付けず、stderr 警告 + デフォルト(ESCALATION_THRESHOLD)に戻す。
|
|
653
|
+
未設定 / 空文字は無警告でデフォルトを返す。
|
|
654
|
+
妥当域: 0 < x <= 1(_resolve_epsilon と同じ範囲)。区間表記: (0, 1](x=0 拒否のため半開区間)。
|
|
655
|
+
"""
|
|
656
|
+
raw = os.environ.get("C3_ESCALATION_THRESHOLD")
|
|
657
|
+
if raw is None or raw == "":
|
|
658
|
+
return ESCALATION_THRESHOLD
|
|
659
|
+
try:
|
|
660
|
+
x = float(raw)
|
|
661
|
+
except ValueError:
|
|
662
|
+
print(
|
|
663
|
+
f"[select_tier:escalation] invalid C3_ESCALATION_THRESHOLD={raw!r}, "
|
|
664
|
+
f"using default {ESCALATION_THRESHOLD}",
|
|
665
|
+
file=sys.stderr,
|
|
666
|
+
)
|
|
667
|
+
return ESCALATION_THRESHOLD
|
|
668
|
+
if math.isnan(x):
|
|
669
|
+
print(
|
|
670
|
+
f"[select_tier:escalation] C3_ESCALATION_THRESHOLD={raw!r} is NaN, "
|
|
671
|
+
f"using default {ESCALATION_THRESHOLD}",
|
|
672
|
+
file=sys.stderr,
|
|
673
|
+
)
|
|
674
|
+
return ESCALATION_THRESHOLD
|
|
675
|
+
if x <= 0 or x > 1:
|
|
676
|
+
print(
|
|
677
|
+
f"[select_tier:escalation] C3_ESCALATION_THRESHOLD={x!r} out of range (0, 1], "
|
|
678
|
+
f"using default {ESCALATION_THRESHOLD}",
|
|
679
|
+
file=sys.stderr,
|
|
680
|
+
)
|
|
681
|
+
return ESCALATION_THRESHOLD
|
|
682
|
+
return x
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def _resolve_cost_lambda() -> float | None:
|
|
686
|
+
"""``C3_TIER_COST_LAMBDA`` を安全に解決する。
|
|
687
|
+
|
|
688
|
+
不正値(非数値 / 0 未満 / 1 超 / NaN)は受け付けず、stderr 警告 + デフォルト(COST_LAMBDA_DEFAULT)に戻す。
|
|
689
|
+
未設定 / 空文字は無警告でデフォルト(None)を返す。
|
|
690
|
+
妥当域: 0 <= x <= 1(x == 0 は許容=cost 無視の明示オプト・_resolve_epsilon と異なり下限を含む)。区間表記: [0, 1](x=0 許容のため閉区間)。
|
|
691
|
+
戻り値が None の場合は v2.25.0 互換の ε tie-break 経路を維持する(センチネル)。
|
|
692
|
+
"""
|
|
693
|
+
raw = os.environ.get("C3_TIER_COST_LAMBDA")
|
|
694
|
+
if raw is None or raw == "":
|
|
695
|
+
return COST_LAMBDA_DEFAULT
|
|
696
|
+
try:
|
|
697
|
+
x = float(raw)
|
|
698
|
+
except ValueError:
|
|
699
|
+
print(
|
|
700
|
+
f"[select_tier:cost_lambda] invalid C3_TIER_COST_LAMBDA={raw!r}, "
|
|
701
|
+
f"using default {COST_LAMBDA_DEFAULT}",
|
|
702
|
+
file=sys.stderr,
|
|
703
|
+
)
|
|
704
|
+
return COST_LAMBDA_DEFAULT
|
|
705
|
+
if math.isnan(x):
|
|
706
|
+
print(
|
|
707
|
+
f"[select_tier:cost_lambda] C3_TIER_COST_LAMBDA={raw!r} is NaN, "
|
|
708
|
+
f"using default {COST_LAMBDA_DEFAULT}",
|
|
709
|
+
file=sys.stderr,
|
|
710
|
+
)
|
|
711
|
+
return COST_LAMBDA_DEFAULT
|
|
712
|
+
if x < 0 or x > 1:
|
|
713
|
+
print(
|
|
714
|
+
f"[select_tier:cost_lambda] C3_TIER_COST_LAMBDA={x!r} out of range [0, 1], "
|
|
715
|
+
f"using default {COST_LAMBDA_DEFAULT}",
|
|
716
|
+
file=sys.stderr,
|
|
717
|
+
)
|
|
718
|
+
return COST_LAMBDA_DEFAULT
|
|
719
|
+
return x
|
|
720
|
+
|
|
721
|
+
|
|
525
722
|
def main() -> int:
|
|
526
723
|
try:
|
|
527
724
|
payload = json.loads(sys.stdin.read())
|
|
@@ -576,12 +773,15 @@ def main() -> int:
|
|
|
576
773
|
except ImportError:
|
|
577
774
|
cost_map = None
|
|
578
775
|
|
|
579
|
-
|
|
776
|
+
eps = _resolve_epsilon()
|
|
777
|
+
lam = _resolve_cost_lambda()
|
|
778
|
+
result = select_tier_detailed(params, cost_map=cost_map, epsilon=eps, lam=lam)
|
|
580
779
|
tier, mode = result.tier, result.mode
|
|
581
780
|
cost_tiebreak = result.cost_tiebreak
|
|
582
781
|
|
|
583
782
|
# Phase 2-B: failure rate に基づく escalation
|
|
584
|
-
|
|
783
|
+
esc_thr = _resolve_escalation_threshold()
|
|
784
|
+
effective_tier, escalation_reason = maybe_escalate(complexity, tier, threshold=esc_thr)
|
|
585
785
|
escalated = effective_tier != tier
|
|
586
786
|
|
|
587
787
|
write_tier_selection(
|
|
@@ -591,6 +791,8 @@ def main() -> int:
|
|
|
591
791
|
prompt_hash=prompt_hash,
|
|
592
792
|
session_id=session_id,
|
|
593
793
|
cost_tiebreak=cost_tiebreak,
|
|
794
|
+
cost_weighted=result.cost_weighted,
|
|
795
|
+
cost_lambda=lam,
|
|
594
796
|
)
|
|
595
797
|
|
|
596
798
|
context_text = build_additional_context(
|
|
@@ -598,6 +800,7 @@ def main() -> int:
|
|
|
598
800
|
escalation_reason=escalation_reason,
|
|
599
801
|
complexity_source=complexity_source,
|
|
600
802
|
cost_tiebreak=cost_tiebreak,
|
|
803
|
+
cost_weighted=result.cost_weighted,
|
|
601
804
|
)
|
|
602
805
|
output = {
|
|
603
806
|
"hookSpecificOutput": {
|
|
@@ -103,6 +103,8 @@ def main() -> int:
|
|
|
103
103
|
from c3.usage_ingester import ingest_session # noqa: PLC0415
|
|
104
104
|
project_dir = _Path(transcript_path).parent
|
|
105
105
|
ingest_session(session_id=session_id, project_dir=project_dir)
|
|
106
|
+
from c3.db import sync_tier_bandit_cost # noqa: PLC0415
|
|
107
|
+
sync_tier_bandit_cost()
|
|
106
108
|
except Exception as e:
|
|
107
109
|
print(f"[session_stop:usage_ingester] failed: {type(e).__name__}", file=sys.stderr)
|
|
108
110
|
|
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.26.0] - 2026-05-26
|
|
4
|
+
|
|
5
|
+
**cost-weighted Thompson 本格統合(全 tier)・ESCALATION_THRESHOLD 調整可能化**: Thompson Sampling のサンプル値を全 tier でコスト重み付けして routing する機能を導入。failure-rate escalation 閾値を環境変数で調整可能にする。環境変数 3 種すべて未設定で v2.25.0 と完全一致。**破壊的変更なし**。
|
|
6
|
+
|
|
7
|
+
### 機能追加
|
|
8
|
+
|
|
9
|
+
- **`.claude/hooks/select_tier.py`: cost-weighted Thompson Sampling(全 tier 対象)**: スコアを `score = sample − λ·cost_norm`(cost_norm は全 tier の min-max 正規化)とし最大 tier を選ぶ routing を導入。λ は環境変数 `C3_TIER_COST_LAMBDA`(0〜1)で設定。λ>0 で発動し、安い tier が成功率優位な tier に勝ちうる(成功率 vs コストのトレードオフ)。λ=0 明示でコスト無視(純 Thompson)。`db.COST_LAMBDA_DEFAULT`(None)が SSOT。
|
|
10
|
+
|
|
11
|
+
- **`.claude/hooks/select_tier.py`: `C3_ESCALATION_THRESHOLD` 対応**: failure-rate escalation の閾値を環境変数 `C3_ESCALATION_THRESHOLD`(0 < x ≤ 1)で設定可能に。`db.ESCALATION_THRESHOLD_DEFAULT`(0.5)が SSOT。NaN・範囲外・非数値は default 値に fallback。
|
|
12
|
+
|
|
13
|
+
### 変更
|
|
14
|
+
|
|
15
|
+
- **`.claude/hooks/select_tier.py`**: `SelectionResult` に `cost_weighted: bool` / `cost_lambda: float | None` を末尾追加(既存フィールド不変)。`write_tier_selection`/`build_additional_context` に cost-weighted 発動時 `cost_weighted` / `cost_lambda` を記録(既存 `cost_tiebreak` キーは不変)。
|
|
16
|
+
|
|
17
|
+
- **`src/c3/db.py`**: `COST_LAMBDA_DEFAULT = None`・`ESCALATION_THRESHOLD_DEFAULT = 0.5` 定数を追加(環境変数オーバーライドの SSOT)。
|
|
18
|
+
|
|
19
|
+
### 後方互換
|
|
20
|
+
|
|
21
|
+
- `C3_TIER_COST_LAMBDA` 未設定(デフォルト None)時は v2.25.0 の ε tie-break 挙動を完全維持(routing 出力はバイト互換)。
|
|
22
|
+
- `C3_TIER_EPSILON`・`C3_ESCALATION_THRESHOLD` 未設定時も v2.25.0 と完全一致。
|
|
23
|
+
- 既存関数シグネチャ不変(`select_tier_detailed`/`select_tier` の λ は optional kwarg・`SelectionResult` は末尾フィールド追加)。
|
|
24
|
+
- migration 不要(v2.22.0 の 003 で列確保済み)。**破壊的変更なし**。
|
|
25
|
+
|
|
26
|
+
## [2.25.0] - 2026-05-26
|
|
27
|
+
|
|
28
|
+
**tier_bandit cost 蓄積・EPSILON 調整可能化・例外ログ統一**: v2.22.0 で列確保済みの `tier_bandit.total_cost_usd`/`cost_samples` へ実測値を materialize する同期関数を追加。cost-aware tie-break の拮抗判定閾値を定数 SSOT 化し環境変数で上書き可能にする。db.py 既存 6 関数の例外ログを型名統一(SR-R-001)。routing 挙動は不変。cost-weighted Thompson 本格統合は v2.26.0。
|
|
29
|
+
|
|
30
|
+
### 機能追加
|
|
31
|
+
|
|
32
|
+
- **`src/c3/db.py`: `sync_tier_bandit_cost(*, db_path=None) -> None`(新規)**: `read_tier_cost_rate_summary` 由来の model 一致集計値を `tier_bandit` テーブルへ materialize する冪等な同期関数。「全クリア(total_cost_usd=0, cost_samples=0) → 集計 SET」の UPDATE-only 実装(INSERT なし)。session_stop の usage ingest 直後に実行。tier_bandit 行が存在しない複合キー (complexity, tier) は無視(INSERT は行わない)。
|
|
33
|
+
|
|
34
|
+
- **`.claude/hooks/select_tier.py`: `db.EPSILON_TIEBREAK`(0.05)定数 SSOT 化・環境変数 `C3_TIER_EPSILON` 対応**: 従来 `EPSILON=0.05` をモジュールローカルで持っていた値を `c3.db.EPSILON_TIEBREAK` に移し Single Source of Truth 化。`select_tier.py` は `db.EPSILON_TIEBREAK` を参照するよう変更。環境変数 `C3_TIER_EPSILON` に数値が設定されている場合は実行時にその値で上書き可能。NaN・範囲外(0 < x <= 1 の外)・非数値は default 値(0.05)に fallback。env 未設定時の routing 挙動は v2.24.0 と完全一致。
|
|
35
|
+
|
|
36
|
+
### 変更
|
|
37
|
+
|
|
38
|
+
- **`src/c3/cli_tier.py`**: `c3 tier stats` の tier_bandit セクションに `total_cost_usd`(合計コスト USD)および `cost_samples`(計上セッション数)列を追加。値は `sync_tier_bandit_cost` 蓄積値を表示。データ未蓄積(0/0)時はセルを「-」表示。
|
|
39
|
+
|
|
40
|
+
- **`src/c3/db.py`: 例外ログ型名統一(SR-R-001)**: 既存 6 関数 7 箇所の `except Exception as exc` ログを `logger.warning("...: %s", exc)` から `logger.warning("...: %s", type(exc).__name__)` に統一。生 exc message(外部 path・SQL 文・行データ等)の意図しない流出を防止。関数シグネチャ・戻り値・呼び出し元への影響なし。
|
|
41
|
+
|
|
42
|
+
### 後方互換
|
|
43
|
+
|
|
44
|
+
- 既存関数シグネチャ不変(`read_tier_params`・`read_tier_cost_rate_summary` 等)。
|
|
45
|
+
- `select_tier_detailed`/`select_tier` の `epsilon` は optional kwarg(省略時 `db.EPSILON_TIEBREAK` を参照)。
|
|
46
|
+
- migration 不要(`tier_bandit` の cost 列は v2.22.0 の 003 migration で確保済み)。
|
|
47
|
+
- **破壊的変更なし**。
|
|
48
|
+
|
|
3
49
|
## [2.24.0] - 2026-05-25
|
|
4
50
|
|
|
5
51
|
**tier-routing cost 精度向上**: tie-break が使う cost データを model 一致集計・USD/MTok レート化により信頼できるものにする。新関数 2 つを追加し、`select_tier` の cost_map 源を rate 関数へ切替。既存関数・tie-break ロジックは完全不変。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-conductor
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.26.0
|
|
4
4
|
Summary: Multi-agent orchestration framework for Claude Code with Codex/Cursor adapters (C3)
|
|
5
5
|
Project-URL: Homepage, https://github.com/satoh-y-0323/claude-code-conductor
|
|
6
6
|
Project-URL: Repository, https://github.com/satoh-y-0323/claude-code-conductor
|
|
@@ -201,6 +201,7 @@ C3 のスラッシュコマンドはすべてスキル(`skills/{name}/SKILL.md
|
|
|
201
201
|
| `c3 recall search "<query>"` または `c3 recall "<query>"` | `.claude/memory/sessions/` 等から類似チャンクを意味検索 |
|
|
202
202
|
| `c3 recall rebuild [--force]` | HNSW インデックスを再構築(初回は fastembed が ~220MB のモデルを取得) |
|
|
203
203
|
| `c3 recall stats` | チャンク数・モデル名・最終 rebuild 日時を表示 |
|
|
204
|
+
| `c3 tier stats` | tier-routing(複雑度に応じた Tier 自動ルーティング)の学習データ・Tier 別コストを表形式で表示(`--json` で機械可読出力・`--recent N` で直近 outcome 件数指定)。ルーティング挙動は環境変数 `C3_TIER_COST_LAMBDA`(cost-weighted の重み・v2.26.0〜)/ `C3_TIER_EPSILON` / `C3_ESCALATION_THRESHOLD` で調整可([CLI リファレンス](https://satoh-y-0323.github.io/claude-code-conductor/cli-reference/)参照) |
|
|
204
205
|
|
|
205
206
|
### 基本的な使い方
|
|
206
207
|
|
|
@@ -154,6 +154,7 @@ C3 のスラッシュコマンドはすべてスキル(`skills/{name}/SKILL.md
|
|
|
154
154
|
| `c3 recall search "<query>"` または `c3 recall "<query>"` | `.claude/memory/sessions/` 等から類似チャンクを意味検索 |
|
|
155
155
|
| `c3 recall rebuild [--force]` | HNSW インデックスを再構築(初回は fastembed が ~220MB のモデルを取得) |
|
|
156
156
|
| `c3 recall stats` | チャンク数・モデル名・最終 rebuild 日時を表示 |
|
|
157
|
+
| `c3 tier stats` | tier-routing(複雑度に応じた Tier 自動ルーティング)の学習データ・Tier 別コストを表形式で表示(`--json` で機械可読出力・`--recent N` で直近 outcome 件数指定)。ルーティング挙動は環境変数 `C3_TIER_COST_LAMBDA`(cost-weighted の重み・v2.26.0〜)/ `C3_TIER_EPSILON` / `C3_ESCALATION_THRESHOLD` で調整可([CLI リファレンス](https://satoh-y-0323.github.io/claude-code-conductor/cli-reference/)参照) |
|
|
157
158
|
|
|
158
159
|
### 基本的な使い方
|
|
159
160
|
|
|
@@ -94,6 +94,11 @@ def _collect_snapshot(db_path, recent_limit: int) -> dict[str, Any]:
|
|
|
94
94
|
bandit_rows: list[dict[str, Any]] = []
|
|
95
95
|
total_trials = 0
|
|
96
96
|
|
|
97
|
+
# cost 列は別 SELECT(read_tier_params は alpha/beta/trials 専用を維持)
|
|
98
|
+
bandit_cost: dict[tuple[str, str], tuple[float, int]] = c3_db.read_tier_bandit_cost(
|
|
99
|
+
db_path=db_path,
|
|
100
|
+
)
|
|
101
|
+
|
|
97
102
|
for complexity in _COMPLEXITIES:
|
|
98
103
|
params = c3_db.read_tier_params(complexity, db_path=db_path)
|
|
99
104
|
for tier in _TIERS:
|
|
@@ -101,6 +106,7 @@ def _collect_snapshot(db_path, recent_limit: int) -> dict[str, Any]:
|
|
|
101
106
|
total_trials += trials
|
|
102
107
|
denom = alpha + beta
|
|
103
108
|
expected = alpha / denom if denom > 0 else 0.5
|
|
109
|
+
cost_usd, cost_samples = bandit_cost.get((complexity, tier), (0.0, 0))
|
|
104
110
|
bandit_rows.append({
|
|
105
111
|
"complexity": complexity,
|
|
106
112
|
"tier": tier,
|
|
@@ -108,6 +114,8 @@ def _collect_snapshot(db_path, recent_limit: int) -> dict[str, Any]:
|
|
|
108
114
|
"beta": beta,
|
|
109
115
|
"trials": trials,
|
|
110
116
|
"expected_success_rate": expected,
|
|
117
|
+
"total_cost_usd": cost_usd,
|
|
118
|
+
"cost_samples": cost_samples,
|
|
111
119
|
})
|
|
112
120
|
|
|
113
121
|
recent_outcomes: list[dict[str, Any]] = c3_db.read_recent_outcomes(
|
|
@@ -154,12 +162,18 @@ def _render_human(snapshot: dict[str, Any]) -> None:
|
|
|
154
162
|
print()
|
|
155
163
|
|
|
156
164
|
print("== Tier 別累積(tier_bandit) ==")
|
|
157
|
-
print(
|
|
165
|
+
print(
|
|
166
|
+
f"{'complexity':<12} {'tier':<8} {'trials':>6} {'alpha':>5} {'beta':>5} "
|
|
167
|
+
f"{'期待成功率':>10} {'cost_usd':>10} {'cost_samples':>12}"
|
|
168
|
+
)
|
|
158
169
|
for row in snapshot["tier_bandit"]:
|
|
170
|
+
complexity_safe = sanitize_terminal_text(str(row["complexity"]))
|
|
171
|
+
tier_safe = sanitize_terminal_text(str(row["tier"]))
|
|
159
172
|
print(
|
|
160
|
-
f"{
|
|
173
|
+
f"{complexity_safe:<12} {tier_safe:<8} "
|
|
161
174
|
f"{row['trials']:>6} {row['alpha']:>5.2f} {row['beta']:>5.2f} "
|
|
162
|
-
f"{row['expected_success_rate'] * 100:>9.2f}%"
|
|
175
|
+
f"{row['expected_success_rate'] * 100:>9.2f}% "
|
|
176
|
+
f"${row['total_cost_usd']:>9.4f} {row['cost_samples']:>12}"
|
|
163
177
|
)
|
|
164
178
|
print()
|
|
165
179
|
|