claude-code-conductor 2.26.0__tar.gz → 2.27.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.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/select_tier.py +17 -12
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/CHANGELOG.md +22 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/PKG-INFO +2 -2
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/README.md +1 -1
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/__init__.py +1 -1
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/cli_tier.py +20 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/db.py +121 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_select_tier.py +159 -6
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_cli_tier.py +106 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_db.py +210 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/CLAUDE.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/code-reviewer.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/developer.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/security-reviewer.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/systematic-debugger.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/tester.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/wt_developer.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/wt_systematic-debugger.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/agents/wt_tester.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/breaking-changes.txt +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/deletions.txt +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/docs/config-policy.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/docs/parallel-agents-setup.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/docs/platform-adapters.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/docs/settings.json.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/_hook_utils.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/check_agent_invocation.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/consolidate_memory.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/permission_handler.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/permission_handler_toast.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/planner_check.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/post_tool.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/pre_compact.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/pre_tool.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/recall_inject.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/restore_session.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/session_start.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/session_stop.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/session_utils.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/statusline.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/stop.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/hooks/worktree_guard.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/memory/.gitkeep +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/permission_rules.json +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/settings.json +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/codex-review/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/dev-workflow/scripts/record_tier_outcome.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/develop/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/doc/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/extract-lib/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/init-session/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/mcp-config/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/pattern-status/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/recall/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/review-phase/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/setup/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/setup/reference.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/skills/start/SKILL.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.claude/state/.gitkeep +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/.gitignore +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/LICENSE +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/LICENSES/chroma-hnswlib-LICENSE +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/LICENSES/chroma-hnswlib-NOTICE +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/LICENSES/fastembed-LICENSE +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/LICENSES/fastembed-NOTICE +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/LICENSES/onnxruntime-LICENSE +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/hatch_build.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/pyproject.toml +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/__main__.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/_excludes.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/_terminal.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/adapters.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/cli.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/cli_ask.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/cli_init.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/cli_list.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/cli_plan.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/cli_recall.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/embedding.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/mcp_server.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/migrate.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/migrations/001_initial.sql +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/migrations/003_tier_cost.sql +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/migrations/README.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/migrations/__init__.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/paths.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/plan_validator.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/platforms.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/pricing.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/question.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/recall_chunker.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/recall_index.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/src/c3/usage_ingester.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/__init__.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/conftest.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/fixtures/usage/README.md +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/fixtures/usage/mainline.jsonl +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/__init__.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_check_agent_invocation.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_consolidate_memory.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_hook_utils.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_permission_handler.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_permission_handler_toast.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_planner_check.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_planner_check_dev.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_post_tool.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_pre_tool.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_recall_inject.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_record_review_decision.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_record_tier_outcome.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_restore_session.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_review_hint_inject.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_select_tier_escalation.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_session_start.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_session_stop.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_session_utils.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_similarity_boost.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_statusline.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_statusline_template_sync.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_sync_check.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_template_guard.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/skills/__init__.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/skills/_skill_helpers.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/skills/test_init_session_no_task_type.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/skills/test_planner_lightweight.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/skills/test_recall_skill.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/skills/test_setup_templates.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/skills/test_start_skill_new_flow.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_adapters.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_cli_ask.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_cli_entry.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_cli_init.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_cli_list.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_cli_plan.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_cli_recall.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_cli_update_breaking_changes.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_cli_update_deletions.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_docstring_consistency.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_embedding.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_excludes.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_extract_breaking_changes.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_mcp_server_elicit.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_migrate.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_paths.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_plan_validator.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_pre_compact.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_precompact_additional.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_precompact_toctou_fixes.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_pricing.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_recall_chunker.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_recall_index.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_references_migration.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_session_utils_additional.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_skill_no_builtin_conflict.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_statusline.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_stop_additional.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_stop_hook.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_stop_precompact_fixes.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_sync_template_stop.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_template_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_usage_ingester.py +0 -0
- {claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/test_worktree_guard.py +0 -0
|
@@ -51,11 +51,15 @@ try:
|
|
|
51
51
|
EPSILON: float = _c3_db_const.EPSILON_TIEBREAK
|
|
52
52
|
ESCALATION_THRESHOLD: float = _c3_db_const.ESCALATION_THRESHOLD_DEFAULT
|
|
53
53
|
COST_LAMBDA_DEFAULT: float | None = _c3_db_const.COST_LAMBDA_DEFAULT
|
|
54
|
+
COST_LAMBDA_MIN: float = _c3_db_const.COST_LAMBDA_MIN
|
|
55
|
+
COST_LAMBDA_MAX: float = _c3_db_const.COST_LAMBDA_MAX
|
|
54
56
|
except ImportError:
|
|
55
57
|
LEARNING_THRESHOLD = 30
|
|
56
58
|
EPSILON = 0.05
|
|
57
59
|
ESCALATION_THRESHOLD = 0.5
|
|
58
60
|
COST_LAMBDA_DEFAULT = None
|
|
61
|
+
COST_LAMBDA_MIN = 0.0
|
|
62
|
+
COST_LAMBDA_MAX = 5.0
|
|
59
63
|
|
|
60
64
|
# 複雑度推定のキーワード
|
|
61
65
|
SIMPLE_KEYWORDS = frozenset({
|
|
@@ -174,14 +178,14 @@ def _cost_tiebreak(
|
|
|
174
178
|
epsilon: 拮抗判定の閾値(デフォルト EPSILON=0.05)。経路 0/1 で使用。
|
|
175
179
|
経路 2 では contenders 算出にのみ使用(選択自体は全 tier score 比較)。
|
|
176
180
|
lam: cost weighting 係数(λ)。None=センチネル(経路 1・後方互換)、
|
|
177
|
-
0.0=cost 無視明示(経路 0)、0 < lam <=
|
|
181
|
+
0.0=cost 無視明示(経路 0)、0 < lam <= COST_LAMBDA_MAX=全 tier weighting(経路 2)。
|
|
178
182
|
デフォルト None で既存 2 引数呼び出しの挙動・シグネチャを完全不変にする。
|
|
179
183
|
|
|
180
184
|
Returns:
|
|
181
185
|
(chosen, did_tiebreak, contenders) のタプル。
|
|
182
186
|
- chosen: 選択された tier 名。
|
|
183
187
|
- did_tiebreak: cost が選択に影響した場合 True。
|
|
184
|
-
経路 1: contenders 内 min-max で安い方を選んだ場合 True
|
|
188
|
+
経路 1: contenders 内 min-max で安い方を選んだ場合 True(全 tier コスト同値時は False)。
|
|
185
189
|
経路 2: 全 tier weighting で argmax(sample) と異なる選択になった場合 True。
|
|
186
190
|
- contenders: ε 拮抗判定に入った tier のタプル(observability 用)。
|
|
187
191
|
"""
|
|
@@ -202,11 +206,12 @@ def _cost_tiebreak(
|
|
|
202
206
|
# 拮抗群内で min-max 正規化コストを計算し最安 tier を選ぶ
|
|
203
207
|
costs = {t: cost_map[t] for t in contenders}
|
|
204
208
|
lo, hi = min(costs.values()), max(costs.values())
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
209
|
+
if hi == lo:
|
|
210
|
+
# [CR-Q-001] 全 tier コスト同値: cost は選択に無関与。
|
|
211
|
+
# argmax(sample) を返し did_tiebreak=False(observability 精緻化・v2.27.0 で精緻化済み)。
|
|
212
|
+
chosen = max(samples, key=lambda t: samples[t])
|
|
213
|
+
return chosen, False, tuple(contenders)
|
|
214
|
+
norm = {t: (costs[t] - lo) / (hi - lo) for t in contenders}
|
|
210
215
|
chosen = min(contenders, key=lambda t: (norm[t], -samples[t]))
|
|
211
216
|
return chosen, True, tuple(contenders)
|
|
212
217
|
|
|
@@ -359,7 +364,7 @@ def select_tier_detailed(
|
|
|
359
364
|
uniform 分岐では cost_map の有無に関わらず完全無視する(探索保護)。
|
|
360
365
|
epsilon: 拮抗判定閾値。None なら module 定数 EPSILON を使う(C3_TIER_EPSILON で上書き可)。
|
|
361
366
|
lam: cost weighting 係数(λ)。None=センチネル(v2.25.0 ε-gated 後方互換)、
|
|
362
|
-
0.0=cost 無視明示、0<lam<=
|
|
367
|
+
0.0=cost 無視明示、0<lam<=COST_LAMBDA_MAX=全 tier weighting 発動(C3_TIER_COST_LAMBDA で上書き可)。
|
|
363
368
|
None(デフォルト)では env 未設定時と完全一致する(後方互換の核心)。
|
|
364
369
|
uniform 分岐では lam の値に関わらず完全無視する(探索保護・不可侵)。
|
|
365
370
|
|
|
@@ -685,9 +690,9 @@ def _resolve_escalation_threshold() -> float:
|
|
|
685
690
|
def _resolve_cost_lambda() -> float | None:
|
|
686
691
|
"""``C3_TIER_COST_LAMBDA`` を安全に解決する。
|
|
687
692
|
|
|
688
|
-
不正値(非数値 / 0 未満 /
|
|
693
|
+
不正値(非数値 / 0 未満 / COST_LAMBDA_MAX 超 / NaN)は受け付けず、stderr 警告 + デフォルト(COST_LAMBDA_DEFAULT)に戻す。
|
|
689
694
|
未設定 / 空文字は無警告でデフォルト(None)を返す。
|
|
690
|
-
妥当域: 0 <= x <=
|
|
695
|
+
妥当域: 0 <= x <= COST_LAMBDA_MAX(x == 0 は許容=cost 無視の明示オプト・_resolve_epsilon と異なり下限を含む)。区間表記: [0, COST_LAMBDA_MAX](x=0 許容のため閉区間)。
|
|
691
696
|
戻り値が None の場合は v2.25.0 互換の ε tie-break 経路を維持する(センチネル)。
|
|
692
697
|
"""
|
|
693
698
|
raw = os.environ.get("C3_TIER_COST_LAMBDA")
|
|
@@ -709,9 +714,9 @@ def _resolve_cost_lambda() -> float | None:
|
|
|
709
714
|
file=sys.stderr,
|
|
710
715
|
)
|
|
711
716
|
return COST_LAMBDA_DEFAULT
|
|
712
|
-
if x <
|
|
717
|
+
if x < COST_LAMBDA_MIN or x > COST_LAMBDA_MAX:
|
|
713
718
|
print(
|
|
714
|
-
f"[select_tier:cost_lambda] C3_TIER_COST_LAMBDA={x!r} out of range [0,
|
|
719
|
+
f"[select_tier:cost_lambda] C3_TIER_COST_LAMBDA={x!r} out of range [0, {COST_LAMBDA_MAX}], "
|
|
715
720
|
f"using default {COST_LAMBDA_DEFAULT}",
|
|
716
721
|
file=sys.stderr,
|
|
717
722
|
)
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.27.0] - 2026-05-26
|
|
4
|
+
|
|
5
|
+
**tier-routing λ 機能拡張(CR-Q-001 精緻化・λ 上限 5.0・cli_tier routing パラメータ表示)**: v2.26.0 で繰り越した 3 項目を解消。λ の上限を 1.0 から 5.0 に拡張、cost-aware tie-break の observability フラグを精緻化、`c3 tier stats` に現在の routing パラメータ(λ/ε/escalation)を表示。環境変数未設定時の routing 出力は v2.26.0 と一致。**破壊的変更なし**。
|
|
6
|
+
|
|
7
|
+
### 機能追加
|
|
8
|
+
|
|
9
|
+
- **`src/c3/db.py`: `COST_LAMBDA_MIN`(0.0)/ `COST_LAMBDA_MAX`(5.0)定数を追加(SSOT)**: cost-weighted Thompson の λ 妥当域の上限を 1.0 から 5.0 に拡張。これにより最高コスト tier の sample をより強く減点でき、cost を成功率より優先させる調整が可能になる。既存の `[0, 1]` の λ 値は引き続き有効(後方互換)。
|
|
10
|
+
- **`src/c3/db.py`: 公開 `resolve_cost_lambda` / `resolve_epsilon` / `resolve_escalation_threshold` を追加**: 環境変数(`C3_TIER_COST_LAMBDA` / `C3_TIER_EPSILON` / `C3_ESCALATION_THRESHOLD`)の解決ロジックを db.py に SSOT として新設。`cli_tier.py` が現在有効な routing パラメータを表示するために参照する。挙動は hook(`select_tier.py`)の既存 `_resolve_*` と一致し、parity テストで戻り値の一致を担保。
|
|
11
|
+
- **`src/c3/cli_tier.py`: `c3 tier stats` に「routing パラメータ」セクションを追加**: 現在有効な λ(`C3_TIER_COST_LAMBDA`)・ε(`C3_TIER_EPSILON`)・escalation threshold(`C3_ESCALATION_THRESHOLD`)を表示。λ は未設定(v2.25.0 互換)/ 0.0(cost 無視)/ 0 < x ≤ 5(全 tier weighting)で文言を分岐。`--json` 出力にも `routing_params` キーを追加。
|
|
12
|
+
|
|
13
|
+
### 変更
|
|
14
|
+
|
|
15
|
+
- **`.claude/hooks/select_tier.py`: `_resolve_cost_lambda` の上限を `COST_LAMBDA_MAX`(5.0)参照に変更**: 従来ハードコードの上限 `1` を db.py の SSOT 定数参照に変更(import 失敗時フォールバック 5.0)。下限も `COST_LAMBDA_MIN`(0.0)参照に統一。
|
|
16
|
+
- **`.claude/hooks/select_tier.py`: CR-Q-001 — `_cost_tiebreak` 経路1 の observability フラグ精緻化**: v2.25.0 互換の ε tie-break 経路で、拮抗群の全 tier コストが同値(`hi == lo`)の場合に `did_tiebreak=False` を返すよう変更。**選ばれる tier は不変**(`argmax(sample)`)で、変わるのは observability のみ。
|
|
17
|
+
|
|
18
|
+
### 後方互換
|
|
19
|
+
|
|
20
|
+
- 環境変数未設定時の routing 挙動・選ばれる tier は v2.26.0 と完全一致。
|
|
21
|
+
- λ の既存値(`[0, 1]`)は引き続き有効。上限拡張は許容域の拡大のみ。
|
|
22
|
+
- **observability 出力の差分(CR-Q-001)**: cost-aware tie-break で**全 tier コストが同値**の特定ケースに限り、`tier_selection.json` の `cost_tiebreak: true` キーが**省略**されるようになる(従来は `true` を出力)。routing 決定(選ばれる tier)には影響しない。
|
|
23
|
+
- migration 不要。**破壊的変更なし**。
|
|
24
|
+
|
|
3
25
|
## [2.26.0] - 2026-05-26
|
|
4
26
|
|
|
5
27
|
**cost-weighted Thompson 本格統合(全 tier)・ESCALATION_THRESHOLD 調整可能化**: Thompson Sampling のサンプル値を全 tier でコスト重み付けして routing する機能を導入。failure-rate escalation 閾値を環境変数で調整可能にする。環境変数 3 種すべて未設定で v2.25.0 と完全一致。**破壊的変更なし**。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-conductor
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.27.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,7 +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
|
|
204
|
+
| `c3 tier stats` | tier-routing(複雑度に応じた Tier 自動ルーティング)の学習データ・Tier 別コスト・現在の routing パラメータ(λ/ε/escalation・v2.27.0〜)を表形式で表示(`--json` で機械可読出力・`--recent N` で直近 outcome 件数指定)。ルーティング挙動は環境変数 `C3_TIER_COST_LAMBDA`(cost-weighted の重み・`0 ≤ λ ≤ 5`・v2.26.0〜、上限拡張 v2.27.0)/ `C3_TIER_EPSILON` / `C3_ESCALATION_THRESHOLD` で調整可([CLI リファレンス](https://satoh-y-0323.github.io/claude-code-conductor/cli-reference/)参照) |
|
|
205
205
|
|
|
206
206
|
### 基本的な使い方
|
|
207
207
|
|
|
@@ -154,7 +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
|
|
157
|
+
| `c3 tier stats` | tier-routing(複雑度に応じた Tier 自動ルーティング)の学習データ・Tier 別コスト・現在の routing パラメータ(λ/ε/escalation・v2.27.0〜)を表形式で表示(`--json` で機械可読出力・`--recent N` で直近 outcome 件数指定)。ルーティング挙動は環境変数 `C3_TIER_COST_LAMBDA`(cost-weighted の重み・`0 ≤ λ ≤ 5`・v2.26.0〜、上限拡張 v2.27.0)/ `C3_TIER_EPSILON` / `C3_ESCALATION_THRESHOLD` で調整可([CLI リファレンス](https://satoh-y-0323.github.io/claude-code-conductor/cli-reference/)参照) |
|
|
158
158
|
|
|
159
159
|
### 基本的な使い方
|
|
160
160
|
|
|
@@ -145,6 +145,11 @@ def _collect_snapshot(db_path, recent_limit: int) -> dict[str, Any]:
|
|
|
145
145
|
"agent_cost": agent_cost,
|
|
146
146
|
"tier_cost": tier_cost,
|
|
147
147
|
"tier_cost_rate": tier_cost_rate,
|
|
148
|
+
"routing_params": {
|
|
149
|
+
"cost_lambda": c3_db.resolve_cost_lambda(),
|
|
150
|
+
"epsilon": c3_db.resolve_epsilon(),
|
|
151
|
+
"escalation_threshold": c3_db.resolve_escalation_threshold(),
|
|
152
|
+
},
|
|
148
153
|
}
|
|
149
154
|
|
|
150
155
|
|
|
@@ -247,3 +252,18 @@ def _render_human(snapshot: dict[str, Any]) -> None:
|
|
|
247
252
|
f"{row['rate_usd_per_mtok']:>18.4f}"
|
|
248
253
|
)
|
|
249
254
|
print()
|
|
255
|
+
|
|
256
|
+
print("== routing パラメータ(環境変数で調整可) ==")
|
|
257
|
+
rp = snapshot.get("routing_params", {})
|
|
258
|
+
cost_lambda = rp.get("cost_lambda")
|
|
259
|
+
epsilon = rp.get("epsilon", c3_db.EPSILON_TIEBREAK)
|
|
260
|
+
escalation_threshold = rp.get("escalation_threshold", c3_db.ESCALATION_THRESHOLD_DEFAULT)
|
|
261
|
+
if cost_lambda is None:
|
|
262
|
+
print("λ (C3_TIER_COST_LAMBDA): 未設定 → v2.25.0 互換(ε tie-break のみ)")
|
|
263
|
+
elif cost_lambda == 0.0:
|
|
264
|
+
print("λ: 0.0(cost 無視・純 Thompson)")
|
|
265
|
+
else:
|
|
266
|
+
print(f"λ: {cost_lambda}(全 tier weighting 有効)")
|
|
267
|
+
print(f"ε (C3_TIER_EPSILON): {epsilon}")
|
|
268
|
+
print(f"escalation threshold (C3_ESCALATION_THRESHOLD): {escalation_threshold}")
|
|
269
|
+
print()
|
|
@@ -18,8 +18,10 @@ upsert_po_status / fetch_po_status)も同時に削除した。
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
|
+
import math
|
|
21
22
|
import os
|
|
22
23
|
import sqlite3
|
|
24
|
+
import sys
|
|
23
25
|
from datetime import datetime
|
|
24
26
|
from pathlib import Path
|
|
25
27
|
|
|
@@ -51,6 +53,125 @@ COST_LAMBDA_DEFAULT = None
|
|
|
51
53
|
# 本定数が SSOT(select_tier.py はここから参照)。
|
|
52
54
|
ESCALATION_THRESHOLD_DEFAULT = 0.5
|
|
53
55
|
|
|
56
|
+
# cost-weighted Thompson の λ 有効範囲(v2.27.0: 上限を 1.0→5.0 に拡張)。
|
|
57
|
+
# cost を成功率より強く効かせる余地を確保するため上限を 5.0 に設定。
|
|
58
|
+
# select_tier.py の _resolve_cost_lambda はここを SSOT として参照する。
|
|
59
|
+
COST_LAMBDA_MIN = 0.0
|
|
60
|
+
COST_LAMBDA_MAX = 5.0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def resolve_cost_lambda() -> float | None:
|
|
64
|
+
"""``C3_TIER_COST_LAMBDA`` を安全に解決する(cli_tier 用 SSOT)。
|
|
65
|
+
|
|
66
|
+
不正値(非数値 / 0 未満 / COST_LAMBDA_MAX 超 / NaN)は受け付けず、
|
|
67
|
+
stderr 警告 + デフォルト(COST_LAMBDA_DEFAULT = None)に戻す。
|
|
68
|
+
未設定 / 空文字は無警告でデフォルト(None)を返す。
|
|
69
|
+
妥当域: [COST_LAMBDA_MIN, COST_LAMBDA_MAX](x=0 許容の閉区間)。
|
|
70
|
+
戻り値が None の場合は v2.25.0 互換の ε tie-break 経路を維持する(センチネル)。
|
|
71
|
+
"""
|
|
72
|
+
raw = os.environ.get("C3_TIER_COST_LAMBDA")
|
|
73
|
+
if raw is None or raw == "":
|
|
74
|
+
return COST_LAMBDA_DEFAULT
|
|
75
|
+
try:
|
|
76
|
+
x = float(raw)
|
|
77
|
+
except ValueError:
|
|
78
|
+
print(
|
|
79
|
+
f"[c3:cost_lambda] invalid C3_TIER_COST_LAMBDA={raw!r}, "
|
|
80
|
+
f"using default {COST_LAMBDA_DEFAULT}",
|
|
81
|
+
file=sys.stderr,
|
|
82
|
+
)
|
|
83
|
+
return COST_LAMBDA_DEFAULT
|
|
84
|
+
if math.isnan(x):
|
|
85
|
+
print(
|
|
86
|
+
f"[c3:cost_lambda] C3_TIER_COST_LAMBDA={raw!r} is NaN, "
|
|
87
|
+
f"using default {COST_LAMBDA_DEFAULT}",
|
|
88
|
+
file=sys.stderr,
|
|
89
|
+
)
|
|
90
|
+
return COST_LAMBDA_DEFAULT
|
|
91
|
+
if x < COST_LAMBDA_MIN or x > COST_LAMBDA_MAX:
|
|
92
|
+
print(
|
|
93
|
+
f"[c3:cost_lambda] C3_TIER_COST_LAMBDA={x!r} out of range "
|
|
94
|
+
f"[{COST_LAMBDA_MIN}, {COST_LAMBDA_MAX}], "
|
|
95
|
+
f"using default {COST_LAMBDA_DEFAULT}",
|
|
96
|
+
file=sys.stderr,
|
|
97
|
+
)
|
|
98
|
+
return COST_LAMBDA_DEFAULT
|
|
99
|
+
return x
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def resolve_epsilon() -> float:
|
|
103
|
+
"""``C3_TIER_EPSILON`` を安全に解決する(cli_tier 用 SSOT)。
|
|
104
|
+
|
|
105
|
+
不正値(非数値 / 0 以下 / 1 超 / NaN)は受け付けず、
|
|
106
|
+
stderr 警告 + デフォルト(EPSILON_TIEBREAK)に戻す。
|
|
107
|
+
未設定 / 空文字は無警告でデフォルトを返す。
|
|
108
|
+
妥当域: (0, 1](x=0 拒否の半開区間)。
|
|
109
|
+
"""
|
|
110
|
+
raw = os.environ.get("C3_TIER_EPSILON")
|
|
111
|
+
if raw is None or raw == "":
|
|
112
|
+
return EPSILON_TIEBREAK
|
|
113
|
+
try:
|
|
114
|
+
x = float(raw)
|
|
115
|
+
except ValueError:
|
|
116
|
+
print(
|
|
117
|
+
f"[c3:epsilon] invalid C3_TIER_EPSILON={raw!r}, "
|
|
118
|
+
f"using default {EPSILON_TIEBREAK}",
|
|
119
|
+
file=sys.stderr,
|
|
120
|
+
)
|
|
121
|
+
return EPSILON_TIEBREAK
|
|
122
|
+
if math.isnan(x):
|
|
123
|
+
print(
|
|
124
|
+
f"[c3:epsilon] C3_TIER_EPSILON={raw!r} is NaN, "
|
|
125
|
+
f"using default {EPSILON_TIEBREAK}",
|
|
126
|
+
file=sys.stderr,
|
|
127
|
+
)
|
|
128
|
+
return EPSILON_TIEBREAK
|
|
129
|
+
if x <= 0 or x > 1:
|
|
130
|
+
print(
|
|
131
|
+
f"[c3:epsilon] C3_TIER_EPSILON={x!r} out of range (0, 1], "
|
|
132
|
+
f"using default {EPSILON_TIEBREAK}",
|
|
133
|
+
file=sys.stderr,
|
|
134
|
+
)
|
|
135
|
+
return EPSILON_TIEBREAK
|
|
136
|
+
return x
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def resolve_escalation_threshold() -> float:
|
|
140
|
+
"""``C3_ESCALATION_THRESHOLD`` を安全に解決する(cli_tier 用 SSOT)。
|
|
141
|
+
|
|
142
|
+
不正値(非数値 / 0 以下 / 1 超 / NaN)は受け付けず、
|
|
143
|
+
stderr 警告 + デフォルト(ESCALATION_THRESHOLD_DEFAULT)に戻す。
|
|
144
|
+
未設定 / 空文字は無警告でデフォルトを返す。
|
|
145
|
+
妥当域: (0, 1](x=0 拒否の半開区間)。
|
|
146
|
+
"""
|
|
147
|
+
raw = os.environ.get("C3_ESCALATION_THRESHOLD")
|
|
148
|
+
if raw is None or raw == "":
|
|
149
|
+
return ESCALATION_THRESHOLD_DEFAULT
|
|
150
|
+
try:
|
|
151
|
+
x = float(raw)
|
|
152
|
+
except ValueError:
|
|
153
|
+
print(
|
|
154
|
+
f"[c3:escalation] invalid C3_ESCALATION_THRESHOLD={raw!r}, "
|
|
155
|
+
f"using default {ESCALATION_THRESHOLD_DEFAULT}",
|
|
156
|
+
file=sys.stderr,
|
|
157
|
+
)
|
|
158
|
+
return ESCALATION_THRESHOLD_DEFAULT
|
|
159
|
+
if math.isnan(x):
|
|
160
|
+
print(
|
|
161
|
+
f"[c3:escalation] C3_ESCALATION_THRESHOLD={raw!r} is NaN, "
|
|
162
|
+
f"using default {ESCALATION_THRESHOLD_DEFAULT}",
|
|
163
|
+
file=sys.stderr,
|
|
164
|
+
)
|
|
165
|
+
return ESCALATION_THRESHOLD_DEFAULT
|
|
166
|
+
if x <= 0 or x > 1:
|
|
167
|
+
print(
|
|
168
|
+
f"[c3:escalation] C3_ESCALATION_THRESHOLD={x!r} out of range (0, 1], "
|
|
169
|
+
f"using default {ESCALATION_THRESHOLD_DEFAULT}",
|
|
170
|
+
file=sys.stderr,
|
|
171
|
+
)
|
|
172
|
+
return ESCALATION_THRESHOLD_DEFAULT
|
|
173
|
+
return x
|
|
174
|
+
|
|
54
175
|
|
|
55
176
|
def _apply_busy_timeout(conn: sqlite3.Connection) -> None:
|
|
56
177
|
# PRAGMA はパラメータバインドできないため値が整数であることを int() で強制する。
|
{claude_code_conductor-2.26.0 → claude_code_conductor-2.27.0}/tests/hooks/test_select_tier.py
RENAMED
|
@@ -525,15 +525,19 @@ class TestCostTiebreak:
|
|
|
525
525
|
assert did_tiebreak is False
|
|
526
526
|
|
|
527
527
|
def test_hi_lo_equal_all_zero_norm_picks_max_sample(self) -> None:
|
|
528
|
-
"""全 contender が同コスト(hi==lo
|
|
528
|
+
"""全 contender が同コスト(hi==lo)なら samples 最大を選び did_tiebreak=False。
|
|
529
|
+
|
|
530
|
+
CR-Q-001(v2.27.0): 全 tier コスト同値時は cost が選択に無関与なため
|
|
531
|
+
did_tiebreak=False を返すよう精緻化した。chosen は argmax(sample) で不変。
|
|
532
|
+
"""
|
|
529
533
|
mod = _load_hook_module()
|
|
530
534
|
# haiku と sonnet が拮抗・同コスト
|
|
531
535
|
samples = {"haiku": 0.82, "sonnet": 0.80, "opus": 0.30}
|
|
532
536
|
cost_map = {"haiku": 10.0, "sonnet": 10.0, "opus": 10.0}
|
|
533
537
|
chosen, did_tiebreak, contenders = mod._cost_tiebreak(samples, cost_map)
|
|
534
|
-
#
|
|
538
|
+
# 全コスト同値(hi == lo)→ did_tiebreak=False(CR-Q-001・v2.27.0 精緻化済み)
|
|
535
539
|
assert chosen == "haiku"
|
|
536
|
-
assert did_tiebreak is
|
|
540
|
+
assert did_tiebreak is False
|
|
537
541
|
|
|
538
542
|
def test_tiebreak_picks_cheapest_among_contenders(self) -> None:
|
|
539
543
|
"""拮抗群の中で最安 tier が選ばれる。"""
|
|
@@ -1392,12 +1396,12 @@ class TestResolveCostLambda:
|
|
|
1392
1396
|
err = capsys.readouterr().err
|
|
1393
1397
|
assert "C3_TIER_COST_LAMBDA" in err
|
|
1394
1398
|
|
|
1395
|
-
def
|
|
1399
|
+
def test_above_max_returns_none_with_warning(
|
|
1396
1400
|
self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture
|
|
1397
1401
|
) -> None:
|
|
1398
|
-
""""1
|
|
1402
|
+
""""5.1" → None + stderr 警告(x > COST_LAMBDA_MAX は拒否)。v2.27.0 λ 上限 5.0 化に伴い "1.5" → "5.1" に更新。"""
|
|
1399
1403
|
mod = _load_hook_module()
|
|
1400
|
-
monkeypatch.setenv("C3_TIER_COST_LAMBDA", "1
|
|
1404
|
+
monkeypatch.setenv("C3_TIER_COST_LAMBDA", "5.1")
|
|
1401
1405
|
result = mod._resolve_cost_lambda()
|
|
1402
1406
|
assert result is None
|
|
1403
1407
|
err = capsys.readouterr().err
|
|
@@ -1415,6 +1419,155 @@ class TestResolveCostLambda:
|
|
|
1415
1419
|
assert "C3_TIER_COST_LAMBDA" in err
|
|
1416
1420
|
assert "NaN" in err
|
|
1417
1421
|
|
|
1422
|
+
def test_new_upper_boundary_5_0_valid(
|
|
1423
|
+
self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture
|
|
1424
|
+
) -> None:
|
|
1425
|
+
""""5.0" → 5.0(v2.27.0 新上限境界・許容)・警告なし。"""
|
|
1426
|
+
mod = _load_hook_module()
|
|
1427
|
+
monkeypatch.setenv("C3_TIER_COST_LAMBDA", "5.0")
|
|
1428
|
+
result = mod._resolve_cost_lambda()
|
|
1429
|
+
assert result == pytest.approx(5.0)
|
|
1430
|
+
assert capsys.readouterr().err == ""
|
|
1431
|
+
|
|
1432
|
+
def test_value_2_5_valid_in_extended_range(
|
|
1433
|
+
self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture
|
|
1434
|
+
) -> None:
|
|
1435
|
+
""""2.5" → 2.5(旧上限 1.0 超だが新範囲 [0, 5.0] 内)・警告なし。"""
|
|
1436
|
+
mod = _load_hook_module()
|
|
1437
|
+
monkeypatch.setenv("C3_TIER_COST_LAMBDA", "2.5")
|
|
1438
|
+
result = mod._resolve_cost_lambda()
|
|
1439
|
+
assert result == pytest.approx(2.5)
|
|
1440
|
+
assert capsys.readouterr().err == ""
|
|
1441
|
+
|
|
1442
|
+
def test_value_1_5_valid_in_extended_range(
|
|
1443
|
+
self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture
|
|
1444
|
+
) -> None:
|
|
1445
|
+
""""1.5" → 1.5(旧上限超だが新範囲 [0, 5.0] 内)・警告なし。"""
|
|
1446
|
+
mod = _load_hook_module()
|
|
1447
|
+
monkeypatch.setenv("C3_TIER_COST_LAMBDA", "1.5")
|
|
1448
|
+
result = mod._resolve_cost_lambda()
|
|
1449
|
+
assert result == pytest.approx(1.5)
|
|
1450
|
+
assert capsys.readouterr().err == ""
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
# ---------------------------------------------------------------------------
|
|
1454
|
+
# T6 (v2.27.0): CR-Q-001 精緻化 — _cost_tiebreak 全 tier コスト同値時の挙動
|
|
1455
|
+
# ---------------------------------------------------------------------------
|
|
1456
|
+
|
|
1457
|
+
|
|
1458
|
+
class TestCostTiebreakAllSameCost:
|
|
1459
|
+
"""CR-Q-001: _cost_tiebreak で全 tier コスト同値時は did_tiebreak=False。
|
|
1460
|
+
|
|
1461
|
+
cost_map の全 tier が同値のとき、hi == lo になるため cost は選択に無関与。
|
|
1462
|
+
chosen は argmax(sample) で不変、did_tiebreak は False。
|
|
1463
|
+
"""
|
|
1464
|
+
|
|
1465
|
+
def test_all_same_cost_did_tiebreak_false(self) -> None:
|
|
1466
|
+
"""CR-Q-001: 全 tier コスト同値で did_tiebreak=False・chosen==argmax(sample)。"""
|
|
1467
|
+
mod = _load_hook_module()
|
|
1468
|
+
epsilon = mod.EPSILON # 0.05
|
|
1469
|
+
base = 0.85
|
|
1470
|
+
# haiku と sonnet が拮抗(contenders が複数)、全 tier コスト同値
|
|
1471
|
+
samples = {"haiku": base, "sonnet": base - epsilon + 0.01, "opus": base - 0.2}
|
|
1472
|
+
# 全 tier に同値コストを設定(hi == lo になる)
|
|
1473
|
+
cost_map = {"haiku": 10.0, "sonnet": 10.0, "opus": 10.0}
|
|
1474
|
+
chosen, did_tiebreak, contenders = mod._cost_tiebreak(samples, cost_map)
|
|
1475
|
+
# 全コスト同値 → did_tiebreak=False
|
|
1476
|
+
assert did_tiebreak is False
|
|
1477
|
+
# chosen は argmax(sample) = haiku
|
|
1478
|
+
assert chosen == "haiku"
|
|
1479
|
+
|
|
1480
|
+
def test_all_same_cost_argmax_wins(self) -> None:
|
|
1481
|
+
"""CR-Q-001: 全 tier コスト同値・拮抗群複数で argmax(samples) が選ばれる。"""
|
|
1482
|
+
mod = _load_hook_module()
|
|
1483
|
+
# sonnet が最大サンプル
|
|
1484
|
+
samples = {"haiku": 0.70, "sonnet": 0.80, "opus": 0.75}
|
|
1485
|
+
cost_map = {"haiku": 5.0, "sonnet": 5.0, "opus": 5.0}
|
|
1486
|
+
chosen, did_tiebreak, _ = mod._cost_tiebreak(samples, cost_map)
|
|
1487
|
+
assert did_tiebreak is False
|
|
1488
|
+
assert chosen == "sonnet" # argmax
|
|
1489
|
+
|
|
1490
|
+
def test_distinct_cost_did_tiebreak_true_unchanged(self) -> None:
|
|
1491
|
+
"""既存テスト不変確認: distinct コストで did_tiebreak=True のままであること。
|
|
1492
|
+
|
|
1493
|
+
test_tiebreak_picks_cheapest_among_contenders のシナリオを再現して
|
|
1494
|
+
CR-Q-001 修正後も既存挙動が変わらないことを確認する。
|
|
1495
|
+
"""
|
|
1496
|
+
mod = _load_hook_module()
|
|
1497
|
+
epsilon = mod.EPSILON # 0.05
|
|
1498
|
+
base = 0.85
|
|
1499
|
+
samples = {"haiku": base, "sonnet": base - epsilon + 0.01, "opus": base - 0.2}
|
|
1500
|
+
cost_map = {"haiku": 30.0, "sonnet": 6.0, "opus": 1.0} # distinct コスト
|
|
1501
|
+
chosen, did_tiebreak, contenders = mod._cost_tiebreak(samples, cost_map)
|
|
1502
|
+
assert did_tiebreak is True # distinct コストなので tiebreak 発動
|
|
1503
|
+
assert chosen == "sonnet" # sonnet が最安 contender
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
# ---------------------------------------------------------------------------
|
|
1507
|
+
# T7 (v2.27.0): parity テスト — hook _resolve_cost_lambda と db.resolve_cost_lambda の一致
|
|
1508
|
+
# ---------------------------------------------------------------------------
|
|
1509
|
+
|
|
1510
|
+
|
|
1511
|
+
class TestResolveCostLambdaParity:
|
|
1512
|
+
"""parity: hook の _resolve_cost_lambda() と db.resolve_cost_lambda() の戻り値が一致する。
|
|
1513
|
+
|
|
1514
|
+
値ドリフト防止。入力マトリクスで両関数の戻り値を比較する。
|
|
1515
|
+
"""
|
|
1516
|
+
|
|
1517
|
+
_CASES = [
|
|
1518
|
+
# (env_value_or_None, label)
|
|
1519
|
+
(None, "未設定"),
|
|
1520
|
+
("0", '"0"'),
|
|
1521
|
+
("2.5", '"2.5"'),
|
|
1522
|
+
("5.0", '"5.0"'),
|
|
1523
|
+
("5.1", '"5.1"'),
|
|
1524
|
+
("abc", '"abc"'),
|
|
1525
|
+
("nan", '"nan"'),
|
|
1526
|
+
("-1", '"-1"'),
|
|
1527
|
+
]
|
|
1528
|
+
|
|
1529
|
+
def _call_hook(self, env_val, monkeypatch: pytest.MonkeyPatch):
|
|
1530
|
+
mod = _load_hook_module()
|
|
1531
|
+
if env_val is None:
|
|
1532
|
+
monkeypatch.delenv("C3_TIER_COST_LAMBDA", raising=False)
|
|
1533
|
+
else:
|
|
1534
|
+
monkeypatch.setenv("C3_TIER_COST_LAMBDA", env_val)
|
|
1535
|
+
return mod._resolve_cost_lambda()
|
|
1536
|
+
|
|
1537
|
+
def _call_db(self, env_val, monkeypatch: pytest.MonkeyPatch):
|
|
1538
|
+
from c3 import db as c3_db # noqa: PLC0415
|
|
1539
|
+
if env_val is None:
|
|
1540
|
+
monkeypatch.delenv("C3_TIER_COST_LAMBDA", raising=False)
|
|
1541
|
+
else:
|
|
1542
|
+
monkeypatch.setenv("C3_TIER_COST_LAMBDA", env_val)
|
|
1543
|
+
return c3_db.resolve_cost_lambda()
|
|
1544
|
+
|
|
1545
|
+
@pytest.mark.parametrize("env_val,label", _CASES)
|
|
1546
|
+
def test_parity(
|
|
1547
|
+
self,
|
|
1548
|
+
env_val,
|
|
1549
|
+
label,
|
|
1550
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1551
|
+
capsys: pytest.CaptureFixture,
|
|
1552
|
+
) -> None:
|
|
1553
|
+
"""hook と db の _resolve_cost_lambda() 戻り値が一致する(入力: {label})。"""
|
|
1554
|
+
hook_result = self._call_hook(env_val, monkeypatch)
|
|
1555
|
+
capsys.readouterr() # hook の stderr をクリア
|
|
1556
|
+
db_result = self._call_db(env_val, monkeypatch)
|
|
1557
|
+
capsys.readouterr() # db の stderr をクリア
|
|
1558
|
+
|
|
1559
|
+
if hook_result is None:
|
|
1560
|
+
assert db_result is None, (
|
|
1561
|
+
f"label={label}: hook=None, db={db_result!r} — 不一致"
|
|
1562
|
+
)
|
|
1563
|
+
else:
|
|
1564
|
+
assert db_result is not None, (
|
|
1565
|
+
f"label={label}: hook={hook_result!r}, db=None — 不一致"
|
|
1566
|
+
)
|
|
1567
|
+
assert hook_result == pytest.approx(db_result), (
|
|
1568
|
+
f"label={label}: hook={hook_result!r}, db={db_result!r} — 値不一致"
|
|
1569
|
+
)
|
|
1570
|
+
|
|
1418
1571
|
|
|
1419
1572
|
# ---------------------------------------------------------------------------
|
|
1420
1573
|
# T4 (v2.26.0): cost-weighting 本体 — SelectionResult.cost_weighted /
|
|
@@ -1035,3 +1035,109 @@ class TestTierBanditCostDisplay:
|
|
|
1035
1035
|
assert target is not None
|
|
1036
1036
|
assert target["total_cost_usd"] == 0.0
|
|
1037
1037
|
assert target["cost_samples"] == 0
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
# ---------------------------------------------------------------------------
|
|
1041
|
+
# v2.27.0: routing パラメータ表示(routing_params)のテスト
|
|
1042
|
+
# ---------------------------------------------------------------------------
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
class TestRoutingParamsSection:
|
|
1046
|
+
"""T6: _collect_snapshot の routing_params キーと _render_human の routing パラメータセクション。
|
|
1047
|
+
|
|
1048
|
+
db.resolve_cost_lambda / resolve_epsilon / resolve_escalation_threshold は
|
|
1049
|
+
v2.27.0 で実装済み。このテスト群は全件 Green。
|
|
1050
|
+
"""
|
|
1051
|
+
|
|
1052
|
+
def test_collect_snapshot_contains_routing_params_key(
|
|
1053
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
|
|
1054
|
+
) -> None:
|
|
1055
|
+
"""_collect_snapshot の snapshot に routing_params キーが含まれる。"""
|
|
1056
|
+
db = tmp_path / "c3.db"
|
|
1057
|
+
_create_c3_db(db)
|
|
1058
|
+
monkeypatch.setattr(c3_db, "locate_c3_db", lambda start=None: db)
|
|
1059
|
+
|
|
1060
|
+
snapshot = cli_tier._collect_snapshot(db, recent_limit=10)
|
|
1061
|
+
|
|
1062
|
+
assert "routing_params" in snapshot
|
|
1063
|
+
|
|
1064
|
+
def test_collect_snapshot_routing_params_structure(
|
|
1065
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
|
|
1066
|
+
) -> None:
|
|
1067
|
+
"""routing_params に cost_lambda / epsilon / escalation_threshold キーが含まれる。"""
|
|
1068
|
+
db = tmp_path / "c3.db"
|
|
1069
|
+
_create_c3_db(db)
|
|
1070
|
+
monkeypatch.setattr(c3_db, "locate_c3_db", lambda start=None: db)
|
|
1071
|
+
monkeypatch.delenv("C3_TIER_COST_LAMBDA", raising=False)
|
|
1072
|
+
monkeypatch.delenv("C3_TIER_EPSILON", raising=False)
|
|
1073
|
+
monkeypatch.delenv("C3_ESCALATION_THRESHOLD", raising=False)
|
|
1074
|
+
|
|
1075
|
+
snapshot = cli_tier._collect_snapshot(db, recent_limit=10)
|
|
1076
|
+
|
|
1077
|
+
assert "routing_params" in snapshot
|
|
1078
|
+
rp = snapshot["routing_params"]
|
|
1079
|
+
assert "cost_lambda" in rp
|
|
1080
|
+
assert "epsilon" in rp
|
|
1081
|
+
assert "escalation_threshold" in rp
|
|
1082
|
+
|
|
1083
|
+
def test_render_human_shows_routing_params_section(
|
|
1084
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
|
|
1085
|
+
capsys: pytest.CaptureFixture,
|
|
1086
|
+
) -> None:
|
|
1087
|
+
"""human 出力に routing パラメータセクション見出しが含まれる。"""
|
|
1088
|
+
db = tmp_path / "c3.db"
|
|
1089
|
+
_create_c3_db(db)
|
|
1090
|
+
monkeypatch.delenv("C3_TIER_COST_LAMBDA", raising=False)
|
|
1091
|
+
|
|
1092
|
+
rc = _run(_make_args(), db, monkeypatch)
|
|
1093
|
+
|
|
1094
|
+
assert rc == 0
|
|
1095
|
+
out = capsys.readouterr().out
|
|
1096
|
+
assert "routing パラメータ" in out
|
|
1097
|
+
|
|
1098
|
+
def test_render_human_lambda_unset_shows_unset_message(
|
|
1099
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
|
|
1100
|
+
capsys: pytest.CaptureFixture,
|
|
1101
|
+
) -> None:
|
|
1102
|
+
"""λ 未設定時に「未設定」または v2.25.0 互換を示す文言が出る。"""
|
|
1103
|
+
db = tmp_path / "c3.db"
|
|
1104
|
+
_create_c3_db(db)
|
|
1105
|
+
monkeypatch.delenv("C3_TIER_COST_LAMBDA", raising=False)
|
|
1106
|
+
|
|
1107
|
+
rc = _run(_make_args(), db, monkeypatch)
|
|
1108
|
+
|
|
1109
|
+
assert rc == 0
|
|
1110
|
+
out = capsys.readouterr().out
|
|
1111
|
+
# 未設定 → 「未設定」または互換の文言
|
|
1112
|
+
assert "未設定" in out or "C3_TIER_COST_LAMBDA" in out
|
|
1113
|
+
|
|
1114
|
+
def test_render_human_lambda_2_5_shows_value(
|
|
1115
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
|
|
1116
|
+
capsys: pytest.CaptureFixture,
|
|
1117
|
+
) -> None:
|
|
1118
|
+
"""C3_TIER_COST_LAMBDA=2.5 設定時に 2.5 が出力に表示される。"""
|
|
1119
|
+
db = tmp_path / "c3.db"
|
|
1120
|
+
_create_c3_db(db)
|
|
1121
|
+
monkeypatch.setenv("C3_TIER_COST_LAMBDA", "2.5")
|
|
1122
|
+
|
|
1123
|
+
rc = _run(_make_args(), db, monkeypatch)
|
|
1124
|
+
|
|
1125
|
+
assert rc == 0
|
|
1126
|
+
out = capsys.readouterr().out
|
|
1127
|
+
assert "2.5" in out
|
|
1128
|
+
|
|
1129
|
+
def test_json_output_contains_routing_params_key(
|
|
1130
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
|
|
1131
|
+
capsys: pytest.CaptureFixture,
|
|
1132
|
+
) -> None:
|
|
1133
|
+
"""--json 出力の snapshot に routing_params キーが含まれる。"""
|
|
1134
|
+
db = tmp_path / "c3.db"
|
|
1135
|
+
_create_c3_db(db)
|
|
1136
|
+
monkeypatch.delenv("C3_TIER_COST_LAMBDA", raising=False)
|
|
1137
|
+
|
|
1138
|
+
rc = _run(_make_args(as_json=True), db, monkeypatch)
|
|
1139
|
+
|
|
1140
|
+
assert rc == 0
|
|
1141
|
+
out = capsys.readouterr().out
|
|
1142
|
+
data = json.loads(out)
|
|
1143
|
+
assert "routing_params" in data
|