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.
Files changed (193) hide show
  1. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/CHANGELOG.md +19 -0
  2. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/PKG-INFO +1 -1
  3. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/__init__.py +1 -1
  4. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_recall.py +142 -19
  5. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/recall_index.py +28 -3
  6. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_session_start.py +16 -4
  7. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_init.py +10 -0
  8. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_recall.py +379 -0
  9. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_recall_index.py +119 -0
  10. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/CLAUDE.md +0 -0
  11. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/architect.md +0 -0
  12. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/code-reviewer.md +0 -0
  13. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/developer.md +0 -0
  14. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/doc-writer.md +0 -0
  15. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/interviewer.md +0 -0
  16. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/planner.md +0 -0
  17. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/project-setup.md +0 -0
  18. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/security-reviewer.md +0 -0
  19. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/systematic-debugger.md +0 -0
  20. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/tester.md +0 -0
  21. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/wt_developer.md +0 -0
  22. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/wt_systematic-debugger.md +0 -0
  23. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/agents/wt_tester.md +0 -0
  24. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/breaking-changes.txt +0 -0
  25. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/deletions.txt +0 -0
  26. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/docs/config-policy.md +0 -0
  27. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/docs/parallel-agents-setup.md +0 -0
  28. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/docs/platform-adapters.md +0 -0
  29. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/docs/settings.json.md +0 -0
  30. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/_hook_utils.py +0 -0
  31. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/check_agent_invocation.py +0 -0
  32. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/consolidate_memory.py +0 -0
  33. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/permission_handler.py +0 -0
  34. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/permission_handler_toast.py +0 -0
  35. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/planner_check.py +0 -0
  36. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/post_tool.py +0 -0
  37. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/pre_compact.py +0 -0
  38. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/pre_tool.py +0 -0
  39. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/recall_inject.py +0 -0
  40. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/restore_session.py +0 -0
  41. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/select_tier.py +0 -0
  42. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/session_start.py +0 -0
  43. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/session_stop.py +0 -0
  44. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/session_utils.py +0 -0
  45. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/statusline.py +0 -0
  46. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/stop.py +0 -0
  47. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/hooks/worktree_guard.py +0 -0
  48. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/memory/.gitkeep +0 -0
  49. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/permission_rules.json +0 -0
  50. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/rules/promoted/index.md +0 -0
  51. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/settings.json +0 -0
  52. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/codex-review/SKILL.md +0 -0
  53. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
  54. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
  55. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
  56. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
  57. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
  58. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/scripts/record_tier_outcome.py +0 -0
  59. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
  60. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/develop/SKILL.md +0 -0
  61. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/doc/SKILL.md +0 -0
  62. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/extract-lib/SKILL.md +0 -0
  63. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/init-session/SKILL.md +0 -0
  64. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/mcp-config/SKILL.md +0 -0
  65. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
  66. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/pattern-status/SKILL.md +0 -0
  67. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
  68. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/recall/SKILL.md +0 -0
  69. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
  70. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
  71. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/review-phase/SKILL.md +0 -0
  72. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/setup/SKILL.md +0 -0
  73. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/setup/reference.md +0 -0
  74. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
  75. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
  76. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/skills/start/SKILL.md +0 -0
  77. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.claude/state/.gitkeep +0 -0
  78. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/.gitignore +0 -0
  79. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSE +0 -0
  80. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSES/chroma-hnswlib-LICENSE +0 -0
  81. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSES/chroma-hnswlib-NOTICE +0 -0
  82. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSES/fastembed-LICENSE +0 -0
  83. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSES/fastembed-NOTICE +0 -0
  84. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSES/onnxruntime-LICENSE +0 -0
  85. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
  86. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/README.md +0 -0
  87. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/hatch_build.py +0 -0
  88. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/pyproject.toml +0 -0
  89. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/__main__.py +0 -0
  90. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/_excludes.py +0 -0
  91. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/_terminal.py +0 -0
  92. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/adapters.py +0 -0
  93. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli.py +0 -0
  94. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_ask.py +0 -0
  95. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_doctor.py +0 -0
  96. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_init.py +0 -0
  97. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_list.py +0 -0
  98. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_plan.py +0 -0
  99. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_tier.py +0 -0
  100. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/cli_update.py +0 -0
  101. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/db.py +0 -0
  102. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/embedding.py +0 -0
  103. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/mcp_server.py +0 -0
  104. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/migrate.py +0 -0
  105. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/migrations/001_initial.sql +0 -0
  106. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
  107. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/migrations/003_tier_cost.sql +0 -0
  108. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/migrations/README.md +0 -0
  109. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/migrations/__init__.py +0 -0
  110. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/paths.py +0 -0
  111. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/plan_validator.py +0 -0
  112. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/platforms.py +0 -0
  113. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/pricing.py +0 -0
  114. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/question.py +0 -0
  115. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/recall_chunker.py +0 -0
  116. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/src/c3/usage_ingester.py +0 -0
  117. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/__init__.py +0 -0
  118. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/conftest.py +0 -0
  119. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/fixtures/usage/README.md +0 -0
  120. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/fixtures/usage/mainline.jsonl +0 -0
  121. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
  122. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
  123. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/__init__.py +0 -0
  124. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_check_agent_invocation.py +0 -0
  125. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_consolidate_memory.py +0 -0
  126. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_hook_utils.py +0 -0
  127. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_permission_handler.py +0 -0
  128. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_permission_handler_toast.py +0 -0
  129. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
  130. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_planner_check.py +0 -0
  131. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_planner_check_dev.py +0 -0
  132. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_post_tool.py +0 -0
  133. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_pre_tool.py +0 -0
  134. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_recall_inject.py +0 -0
  135. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_record_review_decision.py +0 -0
  136. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_record_tier_outcome.py +0 -0
  137. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_restore_session.py +0 -0
  138. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_review_hint_inject.py +0 -0
  139. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_select_tier.py +0 -0
  140. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_select_tier_escalation.py +0 -0
  141. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_session_stop.py +0 -0
  142. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_session_utils.py +0 -0
  143. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
  144. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_similarity_boost.py +0 -0
  145. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_statusline.py +0 -0
  146. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_statusline_template_sync.py +0 -0
  147. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_sync_check.py +0 -0
  148. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/hooks/test_template_guard.py +0 -0
  149. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/__init__.py +0 -0
  150. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/_skill_helpers.py +0 -0
  151. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
  152. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_init_session_no_task_type.py +0 -0
  153. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_planner_lightweight.py +0 -0
  154. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_recall_skill.py +0 -0
  155. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
  156. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_setup_templates.py +0 -0
  157. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
  158. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_start_skill_new_flow.py +0 -0
  159. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
  160. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_adapters.py +0 -0
  161. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_ask.py +0 -0
  162. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_entry.py +0 -0
  163. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_list.py +0 -0
  164. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_plan.py +0 -0
  165. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_tier.py +0 -0
  166. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_update_breaking_changes.py +0 -0
  167. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_cli_update_deletions.py +0 -0
  168. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_db.py +0 -0
  169. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_docstring_consistency.py +0 -0
  170. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_embedding.py +0 -0
  171. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_excludes.py +0 -0
  172. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_extract_breaking_changes.py +0 -0
  173. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_mcp_server_elicit.py +0 -0
  174. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_migrate.py +0 -0
  175. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_paths.py +0 -0
  176. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_plan_validator.py +0 -0
  177. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_pre_compact.py +0 -0
  178. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_pre_tool_hook.py +0 -0
  179. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_precompact_additional.py +0 -0
  180. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_precompact_toctou_fixes.py +0 -0
  181. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_pricing.py +0 -0
  182. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_recall_chunker.py +0 -0
  183. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_references_migration.py +0 -0
  184. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_session_utils_additional.py +0 -0
  185. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_skill_no_builtin_conflict.py +0 -0
  186. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_statusline.py +0 -0
  187. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_stop_additional.py +0 -0
  188. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_stop_hook.py +0 -0
  189. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_stop_precompact_fixes.py +0 -0
  190. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_sync_template_stop.py +0 -0
  191. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_template_pre_tool_hook.py +0 -0
  192. {claude_code_conductor-2.27.0 → claude_code_conductor-2.28.0}/tests/test_usage_ingester.py +0 -0
  193. {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.27.0
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
@@ -1,3 +1,3 @@
1
1
  """Claude Code Conductor (C3) - multi-agent orchestration framework for Claude Code."""
2
2
 
3
- __version__ = "2.27.0"
3
+ __version__ = "2.28.0"
@@ -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
- print(f"[recall] embedding {len(chunks)} chunks...")
229
- vectors = embedder.embed_passages([c.content for c in chunks]) if chunks else []
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
- for src, vec in zip(chunks, vectors):
232
- # CR-M-04 / SR-L-5: compute source_hash from the full content so that
233
- # identical chunks in different files produce the same hash and changed
234
- # content is reliably detected across rebuilds.
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
- index_path, meta_path = default_index_paths(repo_root)
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 = hashlib.sha256(
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()
@@ -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 で削除(TOCTOU 対策)."""
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
- assert target_dir.exists()
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"