claude-code-conductor 2.27.0__tar.gz → 2.28.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.27.0 → claude_code_conductor-2.28.0}/CHANGELOG.md +19 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/PKG-INFO +1 -1
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/__init__.py +1 -1
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_recall.py +142 -19
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/recall_index.py +28 -3
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_session_start.py +16 -4
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_init.py +10 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_recall.py +379 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_recall_index.py +119 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/CLAUDE.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/code-reviewer.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/developer.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/security-reviewer.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/systematic-debugger.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/tester.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/wt_developer.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/wt_systematic-debugger.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/wt_tester.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/breaking-changes.txt +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/deletions.txt +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/docs/config-policy.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/docs/parallel-agents-setup.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/docs/platform-adapters.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/docs/settings.json.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/_hook_utils.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/check_agent_invocation.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/consolidate_memory.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/permission_handler.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/permission_handler_toast.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/planner_check.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/post_tool.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/pre_compact.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/pre_tool.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/recall_inject.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/restore_session.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/select_tier.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/session_start.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/session_stop.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/session_utils.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/statusline.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/stop.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/worktree_guard.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/memory/.gitkeep +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/permission_rules.json +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/settings.json +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/codex-review/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/scripts/record_tier_outcome.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/develop/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/doc/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/extract-lib/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/init-session/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/mcp-config/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/pattern-status/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/recall/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/review-phase/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/setup/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/setup/reference.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/start/SKILL.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/state/.gitkeep +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.gitignore +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSE +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSES/chroma-hnswlib-LICENSE +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSES/chroma-hnswlib-NOTICE +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSES/fastembed-LICENSE +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSES/fastembed-NOTICE +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSES/onnxruntime-LICENSE +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/README.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/hatch_build.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/pyproject.toml +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/__main__.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/_excludes.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/_terminal.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/adapters.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_ask.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_init.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_list.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_plan.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_tier.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/db.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/embedding.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/mcp_server.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/migrate.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/migrations/001_initial.sql +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/migrations/003_tier_cost.sql +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/migrations/README.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/migrations/__init__.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/paths.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/plan_validator.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/platforms.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/pricing.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/question.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/recall_chunker.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/usage_ingester.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/__init__.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/conftest.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/fixtures/usage/README.md +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/fixtures/usage/mainline.jsonl +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/__init__.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_check_agent_invocation.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_consolidate_memory.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_hook_utils.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_permission_handler.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_permission_handler_toast.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_planner_check.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_planner_check_dev.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_post_tool.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_pre_tool.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_recall_inject.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_record_review_decision.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_record_tier_outcome.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_restore_session.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_review_hint_inject.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_select_tier.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_select_tier_escalation.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_session_stop.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_session_utils.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_similarity_boost.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_statusline.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_statusline_template_sync.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_sync_check.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_template_guard.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/__init__.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/_skill_helpers.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_init_session_no_task_type.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_planner_lightweight.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_recall_skill.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_setup_templates.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_start_skill_new_flow.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_adapters.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_ask.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_entry.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_list.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_plan.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_tier.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_update_breaking_changes.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_update_deletions.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_db.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_docstring_consistency.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_embedding.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_excludes.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_extract_breaking_changes.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_mcp_server_elicit.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_migrate.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_paths.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_plan_validator.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_pre_compact.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_precompact_additional.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_precompact_toctou_fixes.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_pricing.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_recall_chunker.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_references_migration.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_session_utils_additional.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_skill_no_builtin_conflict.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_statusline.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_stop_additional.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_stop_hook.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_stop_precompact_fixes.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_sync_template_stop.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_template_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_usage_ingester.py +0 -0
- {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_worktree_guard.py +0 -0
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.28.0] - 2026-05-27
|
|
4
|
+
|
|
5
|
+
**recall 増分 rebuild**: `c3 recall rebuild` を全再構築から増分に最適化。未変更チャンクは既存インデックスのベクトルを再利用し、変更/新規チャンクのみ再埋め込みする。律速の埋め込み(fastembed 推論)を削減して rebuild を高速化する。検索結果・インデックス形式は全再構築と一致。**破壊的変更なし**。
|
|
6
|
+
|
|
7
|
+
### 機能追加
|
|
8
|
+
|
|
9
|
+
- **`src/c3/cli_recall.py`: `c3 recall rebuild` の増分化**: `(source_type, path, chunk_id)` と `source_hash`(v2 で既に保存済み)が一致する未変更チャンクは旧ベクトルを再利用し、変更/新規チャンクのみ `embed_passages` に渡す。出力は `embedded M / reused K chunks` 形式。`--force` 指定時は従来どおり全再構築。
|
|
10
|
+
- **`src/c3/recall_index.py`: `RecallIndex.get_vector(chunk_id)` / 公開 `content_hash(text)` を追加**: `get_vector` は hnswlib 格納ベクトルを取得(増分時の再利用に使用)。`content_hash` は source_hash 計算を一元化した公開ヘルパー(`build` と `cli_recall` が共用)。
|
|
11
|
+
|
|
12
|
+
### 変更
|
|
13
|
+
|
|
14
|
+
- **増分不可時の安全フォールバック**: 既存インデックス不在・`--force`・`load()` 失敗(model/dim 不一致・破損)の場合は全再構築にフォールバックし、stderr に理由(例外型名のみ)を 1 行出力する。
|
|
15
|
+
|
|
16
|
+
### 後方互換
|
|
17
|
+
|
|
18
|
+
- 検索結果・インデックス形式は全再構築と完全一致(増分はベクトル再利用のみで意味論を変えない)。
|
|
19
|
+
- `--force` で従来の全再構築を維持。
|
|
20
|
+
- migration 不要。**破壊的変更なし**。
|
|
21
|
+
|
|
3
22
|
## [2.27.0] - 2026-05-26
|
|
4
23
|
|
|
5
24
|
**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 と一致。**破壊的変更なし**。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-conductor
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.28.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
|
|
@@ -14,7 +14,6 @@ fast even when the embedding model has not yet been downloaded.
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import argparse
|
|
17
|
-
import hashlib
|
|
18
17
|
import json
|
|
19
18
|
import sys
|
|
20
19
|
from pathlib import Path
|
|
@@ -30,6 +29,7 @@ from c3.recall_index import (
|
|
|
30
29
|
RecallIndex,
|
|
31
30
|
SourceChunk,
|
|
32
31
|
collect_sources,
|
|
32
|
+
content_hash,
|
|
33
33
|
default_index_paths,
|
|
34
34
|
snippet_of,
|
|
35
35
|
warn_if_stale,
|
|
@@ -200,6 +200,58 @@ def _handle_search(args: argparse.Namespace) -> int:
|
|
|
200
200
|
return 0
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
def _partition_chunks_for_reuse(
|
|
204
|
+
chunks: list[SourceChunk],
|
|
205
|
+
old_index: RecallIndex,
|
|
206
|
+
) -> tuple[list[int], dict[int, list[float]], int]:
|
|
207
|
+
"""Partition *chunks* into embed targets and reusable vectors from *old_index*.
|
|
208
|
+
|
|
209
|
+
Returns a triple ``(to_embed_indices, reuse_map, reused_count)`` where:
|
|
210
|
+
|
|
211
|
+
- ``to_embed_indices``: positions in *chunks* that need fresh embedding
|
|
212
|
+
(new or content-changed chunks).
|
|
213
|
+
- ``reuse_map``: ``{chunk_position: vector}`` for unchanged chunks whose
|
|
214
|
+
vector was successfully retrieved from *old_index*.
|
|
215
|
+
- ``reused_count``: number of chunks successfully reused.
|
|
216
|
+
|
|
217
|
+
The caller is responsible for embedding ``to_embed_indices`` and then
|
|
218
|
+
assembling the final ``items`` list in original *chunks* order to keep
|
|
219
|
+
``build()`` ID assignment consistent with a full rebuild.
|
|
220
|
+
"""
|
|
221
|
+
# Build a lookup: (source_type, path, chunk_id) -> (source_hash, int_id)
|
|
222
|
+
old_key_map: dict[tuple[str, str, str], tuple[str, int]] = {}
|
|
223
|
+
for id_str, rec in old_index.meta.chunks.items():
|
|
224
|
+
key = (rec.source_type, rec.path, rec.chunk_id)
|
|
225
|
+
old_key_map[key] = (rec.source_hash, int(id_str))
|
|
226
|
+
|
|
227
|
+
# Partition chunks into those that need embedding and those that can be reused.
|
|
228
|
+
# We fill reuse_map for unchanged slots so the final items list preserves the
|
|
229
|
+
# original order — this ensures build() assigns sequential IDs identically to
|
|
230
|
+
# a full rebuild (same order → same IDs → same search results).
|
|
231
|
+
to_embed_indices: list[int] = [] # indices into chunks that need embedding
|
|
232
|
+
reuse_map: dict[int, list[float]] = {} # chunk index → reused vector
|
|
233
|
+
reused_count = 0
|
|
234
|
+
|
|
235
|
+
for i, src in enumerate(chunks):
|
|
236
|
+
key = (src.source_type, src.path, src.chunk_id)
|
|
237
|
+
new_hash = content_hash(src.content)
|
|
238
|
+
if key in old_key_map:
|
|
239
|
+
old_hash, old_id = old_key_map[key]
|
|
240
|
+
if old_hash == new_hash:
|
|
241
|
+
# Unchanged: reuse the stored vector.
|
|
242
|
+
try:
|
|
243
|
+
vec = old_index.get_vector(old_id)
|
|
244
|
+
reuse_map[i] = vec
|
|
245
|
+
reused_count += 1
|
|
246
|
+
continue
|
|
247
|
+
except Exception:
|
|
248
|
+
pass # Vector retrieval failed; fall through to embed
|
|
249
|
+
# New or changed chunk: needs embedding.
|
|
250
|
+
to_embed_indices.append(i)
|
|
251
|
+
|
|
252
|
+
return to_embed_indices, reuse_map, reused_count
|
|
253
|
+
|
|
254
|
+
|
|
203
255
|
def _handle_rebuild(args: argparse.Namespace) -> int:
|
|
204
256
|
repo_root = _resolve_repo_root(getattr(args, "target", None))
|
|
205
257
|
if repo_root is None:
|
|
@@ -225,27 +277,98 @@ def _handle_rebuild(args: argparse.Namespace) -> int:
|
|
|
225
277
|
)
|
|
226
278
|
return 1
|
|
227
279
|
|
|
228
|
-
|
|
229
|
-
|
|
280
|
+
index_path, meta_path = default_index_paths(repo_root)
|
|
281
|
+
|
|
282
|
+
# ----- incremental path -----
|
|
283
|
+
# Attempt to load the existing index and reuse vectors for unchanged chunks.
|
|
284
|
+
# Falls back to full embed when:
|
|
285
|
+
# - --force is requested
|
|
286
|
+
# - no existing index files
|
|
287
|
+
# - load() raises RuntimeError (model/dim mismatch or corrupt meta)
|
|
288
|
+
# - any other unexpected error during load
|
|
289
|
+
|
|
290
|
+
old_index: RecallIndex | None = None
|
|
291
|
+
if not args.force and index_path.exists() and meta_path.exists():
|
|
292
|
+
candidate = RecallIndex(
|
|
293
|
+
index_path=index_path,
|
|
294
|
+
meta_path=meta_path,
|
|
295
|
+
model_name=embedder.model_name,
|
|
296
|
+
dim=embedder.dim,
|
|
297
|
+
)
|
|
298
|
+
try:
|
|
299
|
+
candidate.load()
|
|
300
|
+
old_index = candidate
|
|
301
|
+
except Exception as exc:
|
|
302
|
+
# CR-E-002/SR-R-004: log the failure reason so operators can diagnose
|
|
303
|
+
# corrupt or mismatched index files. Exception message is intentionally
|
|
304
|
+
# omitted (type name only) to avoid leaking internal state — consistent
|
|
305
|
+
# with the SR-R-001 policy used in _hnsw_save/_hnsw_load.
|
|
306
|
+
print(
|
|
307
|
+
f"[recall] 既存 index を読めず増分不可・全再構築にフォールバック: {type(exc).__name__}",
|
|
308
|
+
file=sys.stderr,
|
|
309
|
+
)
|
|
310
|
+
old_index = None
|
|
311
|
+
|
|
230
312
|
items: list[tuple[ChunkRecord, list[float]]] = []
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
content_hash = hashlib.sha256(
|
|
236
|
-
src.content.encode("utf-8", errors="replace")
|
|
237
|
-
).hexdigest()
|
|
238
|
-
record = ChunkRecord(
|
|
239
|
-
source_type=src.source_type,
|
|
240
|
-
path=src.path,
|
|
241
|
-
chunk_id=src.chunk_id,
|
|
242
|
-
snippet=snippet_of(src.content),
|
|
243
|
-
mtime=src.mtime,
|
|
244
|
-
source_hash=content_hash,
|
|
313
|
+
|
|
314
|
+
if old_index is not None:
|
|
315
|
+
to_embed_indices, reuse_map, reused_count = _partition_chunks_for_reuse(
|
|
316
|
+
chunks, old_index
|
|
245
317
|
)
|
|
246
|
-
items.append((record, vec))
|
|
247
318
|
|
|
248
|
-
|
|
319
|
+
# Embed only the changed/new chunks.
|
|
320
|
+
embed_count = len(to_embed_indices)
|
|
321
|
+
to_embed_contents = [chunks[i].content for i in to_embed_indices]
|
|
322
|
+
if to_embed_contents:
|
|
323
|
+
new_vectors = embedder.embed_passages(to_embed_contents)
|
|
324
|
+
else:
|
|
325
|
+
new_vectors = []
|
|
326
|
+
|
|
327
|
+
# i is always present in embed_vec_map because it was added to
|
|
328
|
+
# to_embed_indices during _partition_chunks_for_reuse — every chunk
|
|
329
|
+
# not in reuse_map is guaranteed to have an entry here.
|
|
330
|
+
embed_vec_map: dict[int, list[float]] = {
|
|
331
|
+
idx: vec for idx, vec in zip(to_embed_indices, new_vectors)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# Build items in original chunks order to preserve ID assignment consistency.
|
|
335
|
+
for i, src in enumerate(chunks):
|
|
336
|
+
if i in reuse_map:
|
|
337
|
+
vec = reuse_map[i]
|
|
338
|
+
else:
|
|
339
|
+
# Invariant: i is in to_embed_indices, so it is always present in
|
|
340
|
+
# embed_vec_map. Every chunk not placed in reuse_map during
|
|
341
|
+
# _partition_chunks_for_reuse was added to to_embed_indices and
|
|
342
|
+
# therefore has a corresponding entry in embed_vec_map.
|
|
343
|
+
vec = embed_vec_map[i]
|
|
344
|
+
record = ChunkRecord(
|
|
345
|
+
source_type=src.source_type,
|
|
346
|
+
path=src.path,
|
|
347
|
+
chunk_id=src.chunk_id,
|
|
348
|
+
snippet=snippet_of(src.content),
|
|
349
|
+
mtime=src.mtime,
|
|
350
|
+
source_hash=content_hash(src.content),
|
|
351
|
+
)
|
|
352
|
+
items.append((record, vec))
|
|
353
|
+
|
|
354
|
+
print(f"[recall] embedded {embed_count} / reused {reused_count} chunks")
|
|
355
|
+
else:
|
|
356
|
+
# Full embed path (--force, no prior index, or load failure).
|
|
357
|
+
print(f"[recall] embedding {len(chunks)} chunks...")
|
|
358
|
+
vectors = embedder.embed_passages([c.content for c in chunks]) if chunks else []
|
|
359
|
+
for src, vec in zip(chunks, vectors):
|
|
360
|
+
# CR-M-04 / SR-L-5: compute source_hash from the full content so that
|
|
361
|
+
# identical chunks in different files produce the same hash and changed
|
|
362
|
+
# content is reliably detected across rebuilds.
|
|
363
|
+
record = ChunkRecord(
|
|
364
|
+
source_type=src.source_type,
|
|
365
|
+
path=src.path,
|
|
366
|
+
chunk_id=src.chunk_id,
|
|
367
|
+
snippet=snippet_of(src.content),
|
|
368
|
+
mtime=src.mtime,
|
|
369
|
+
source_hash=content_hash(src.content),
|
|
370
|
+
)
|
|
371
|
+
items.append((record, vec))
|
|
249
372
|
|
|
250
373
|
if args.force:
|
|
251
374
|
for p in (index_path, meta_path):
|
|
@@ -159,9 +159,7 @@ class RecallIndex:
|
|
|
159
159
|
# If the caller already computed a hash (e.g. from full content),
|
|
160
160
|
# keep it; otherwise derive from the stored snippet as a fallback.
|
|
161
161
|
if not record.source_hash:
|
|
162
|
-
record.source_hash =
|
|
163
|
-
record.snippet.encode("utf-8", errors="replace")
|
|
164
|
-
).hexdigest()
|
|
162
|
+
record.source_hash = content_hash(record.snippet)
|
|
165
163
|
self._meta.chunks[str(new_id)] = record
|
|
166
164
|
self._index.add_items(vecs, ids)
|
|
167
165
|
self._meta.rebuilt_at = _utcnow_iso()
|
|
@@ -293,6 +291,22 @@ class RecallIndex:
|
|
|
293
291
|
def chunk_count(self) -> int:
|
|
294
292
|
return len(self._meta.chunks)
|
|
295
293
|
|
|
294
|
+
def get_vector(self, chunk_id: int) -> list[float]:
|
|
295
|
+
"""Return the stored vector for ``chunk_id`` as ``list[float]``.
|
|
296
|
+
|
|
297
|
+
Raises ``RuntimeError`` if the index has not been built or loaded yet.
|
|
298
|
+
Propagates hnswlib's exception (typically ``RuntimeError`` or
|
|
299
|
+
``IndexError``) when ``chunk_id`` is not present in the index.
|
|
300
|
+
"""
|
|
301
|
+
if self._index is None:
|
|
302
|
+
raise RuntimeError(
|
|
303
|
+
"index has not been built or loaded; call build() or load() first"
|
|
304
|
+
)
|
|
305
|
+
# hnswlib.get_items returns a 2-D array of shape (n, dim).
|
|
306
|
+
# We pass a single-element list and take the first row.
|
|
307
|
+
result = self._index.get_items([chunk_id])
|
|
308
|
+
return [float(x) for x in result[0]]
|
|
309
|
+
|
|
296
310
|
# ----- internals -----
|
|
297
311
|
|
|
298
312
|
def _new_index(self, *, max_elements: int):
|
|
@@ -475,6 +489,17 @@ def is_stale(repo_root: Path, index_path: Path) -> bool:
|
|
|
475
489
|
# ----- helpers -----
|
|
476
490
|
|
|
477
491
|
|
|
492
|
+
def content_hash(text: str) -> str:
|
|
493
|
+
"""Return the SHA-256 hex digest of *text* encoded as UTF-8.
|
|
494
|
+
|
|
495
|
+
``errors="replace"`` ensures arbitrary Unicode input never raises.
|
|
496
|
+
This is the canonical hash used for incremental-rebuild change detection
|
|
497
|
+
(``cli_recall._handle_rebuild``) and as the ``source_hash`` fallback in
|
|
498
|
+
:meth:`RecallIndex.build`.
|
|
499
|
+
"""
|
|
500
|
+
return hashlib.sha256(text.encode("utf-8", errors="replace")).hexdigest()
|
|
501
|
+
|
|
502
|
+
|
|
478
503
|
def snippet_of(text: str, *, max_chars: int = SNIPPET_CHARS) -> str:
|
|
479
504
|
"""Return a stable preview of ``text`` for storage / display."""
|
|
480
505
|
text = (text or "").strip()
|
{claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_session_start.py
RENAMED
|
@@ -161,7 +161,14 @@ class TestClearFileHistory:
|
|
|
161
161
|
assert not sub.exists()
|
|
162
162
|
|
|
163
163
|
def test_symlink_uses_unlink_not_rmtree(self, tmp_path: Path):
|
|
164
|
-
"""シンボリックリンクは os.unlink
|
|
164
|
+
"""シンボリックリンクは os.unlink で除去し shutil.rmtree には渡さない(TOCTOU 対策)。
|
|
165
|
+
|
|
166
|
+
OS 非依存にするため削除ルーティングを spy で検証する。リンク先 dir も
|
|
167
|
+
file-history 直下にあるため別エントリとして rmtree される(=消えるのが正しい)。
|
|
168
|
+
旧実装の ``target_dir.exists()`` assertion は「リンク先も直下エントリ」である点を
|
|
169
|
+
見落としており、symlink を作れない Windows では skip され露呈しなかった
|
|
170
|
+
(Linux CI で顕在化)。
|
|
171
|
+
"""
|
|
165
172
|
module = _load_hook_module()
|
|
166
173
|
fake_history = tmp_path / "file-history"
|
|
167
174
|
fake_history.mkdir()
|
|
@@ -173,12 +180,17 @@ class TestClearFileHistory:
|
|
|
173
180
|
except OSError:
|
|
174
181
|
pytest.skip("シンボリックリンクを作れない環境(権限不足等)")
|
|
175
182
|
|
|
176
|
-
with patch.object(module, "FILE_HISTORY_DIR", str(fake_history))
|
|
183
|
+
with patch.object(module, "FILE_HISTORY_DIR", str(fake_history)), \
|
|
184
|
+
patch.object(module.os, "unlink", wraps=module.os.unlink) as unlink_spy, \
|
|
185
|
+
patch.object(module.shutil, "rmtree", wraps=module.shutil.rmtree) as rmtree_spy:
|
|
177
186
|
module._run_clear_file_history()
|
|
178
187
|
|
|
179
|
-
# symlink
|
|
188
|
+
# symlink 自体は unlink で除去され、rmtree には渡らない
|
|
180
189
|
assert not symlink.exists()
|
|
181
|
-
|
|
190
|
+
unlinked = [call.args[0] for call in unlink_spy.call_args_list]
|
|
191
|
+
rmtree_targets = [call.args[0] for call in rmtree_spy.call_args_list]
|
|
192
|
+
assert str(symlink) in unlinked
|
|
193
|
+
assert str(symlink) not in rmtree_targets
|
|
182
194
|
|
|
183
195
|
def test_external_symlink_is_skipped(self, tmp_path: Path):
|
|
184
196
|
"""リンク先が FILE_HISTORY_DIR 外のシンボリックリンクはスキップする."""
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import os
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
8
11
|
from c3 import cli_init, cli_update
|
|
9
12
|
|
|
10
13
|
|
|
@@ -181,6 +184,13 @@ def test_init_codex_refuses_unmanaged_existing_c3_mcp_table(tmp_path: Path, caps
|
|
|
181
184
|
assert "already defines [mcp_servers.c3]" in capsys.readouterr().err
|
|
182
185
|
|
|
183
186
|
|
|
187
|
+
@pytest.mark.skipif(
|
|
188
|
+
os.sep != "\\",
|
|
189
|
+
reason=(
|
|
190
|
+
"config の PYTHONPATH バックスラッシュエスケープは Windows パスでのみ発生する"
|
|
191
|
+
"(escape ロジック自体は tests/test_adapters.py で OS 非依存に検証済み)"
|
|
192
|
+
),
|
|
193
|
+
)
|
|
184
194
|
def test_update_codex_preserves_escaped_backslashes_in_managed_config(tmp_path: Path):
|
|
185
195
|
_run_init(tmp_path, platform="codex")
|
|
186
196
|
config = tmp_path / ".codex" / "config.toml"
|