claude-code-conductor 2.19.0__tar.gz → 2.20.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 (183) hide show
  1. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/breaking-changes.txt +1 -0
  2. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/deletions.txt +3 -0
  3. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/docs/config-policy.md +2 -1
  4. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/session_start.py +49 -39
  5. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/CHANGELOG.md +64 -0
  6. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/PKG-INFO +1 -1
  7. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/__init__.py +1 -1
  8. claude_code_conductor-2.20.0/src/c3/migrate.py +260 -0
  9. claude_code_conductor-2.19.0/.claude/hooks/schema.sql → claude_code_conductor-2.20.0/src/c3/migrations/001_initial.sql +30 -12
  10. claude_code_conductor-2.20.0/src/c3/migrations/README.md +90 -0
  11. claude_code_conductor-2.20.0/src/c3/migrations/__init__.py +1 -0
  12. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_record_tier_outcome.py +2 -6
  13. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_review_hint_inject.py +2 -7
  14. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_select_tier.py +2 -6
  15. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_select_tier_escalation.py +2 -6
  16. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_session_start.py +178 -46
  17. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_cli_tier.py +2 -6
  18. claude_code_conductor-2.20.0/tests/test_migrate.py +457 -0
  19. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/CLAUDE.md +0 -0
  20. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/architect.md +0 -0
  21. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/code-reviewer.md +0 -0
  22. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/developer.md +0 -0
  23. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/doc-writer.md +0 -0
  24. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/interviewer.md +0 -0
  25. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/planner.md +0 -0
  26. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/project-setup.md +0 -0
  27. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/security-reviewer.md +0 -0
  28. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/systematic-debugger.md +0 -0
  29. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/tester.md +0 -0
  30. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/wt_developer.md +0 -0
  31. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/wt_systematic-debugger.md +0 -0
  32. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/agents/wt_tester.md +0 -0
  33. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/docs/parallel-agents-setup.md +0 -0
  34. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/docs/platform-adapters.md +0 -0
  35. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/docs/settings.json.md +0 -0
  36. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/_hook_utils.py +0 -0
  37. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/check_agent_invocation.py +0 -0
  38. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/consolidate_memory.py +0 -0
  39. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/permission_handler.py +0 -0
  40. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/permission_handler_toast.py +0 -0
  41. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/planner_check.py +0 -0
  42. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/post_tool.py +0 -0
  43. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/pre_compact.py +0 -0
  44. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/pre_tool.py +0 -0
  45. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/recall_inject.py +0 -0
  46. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/restore_session.py +0 -0
  47. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/select_tier.py +0 -0
  48. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/session_stop.py +0 -0
  49. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/session_utils.py +0 -0
  50. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/statusline.py +0 -0
  51. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/stop.py +0 -0
  52. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/hooks/worktree_guard.py +0 -0
  53. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/memory/.gitkeep +0 -0
  54. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/permission_rules.json +0 -0
  55. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/rules/promoted/index.md +0 -0
  56. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/settings.json +0 -0
  57. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/codex-review/SKILL.md +0 -0
  58. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
  59. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
  60. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
  61. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
  62. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
  63. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/dev-workflow/scripts/record_tier_outcome.py +0 -0
  64. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
  65. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/develop/SKILL.md +0 -0
  66. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/doc/SKILL.md +0 -0
  67. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/extract-lib/SKILL.md +0 -0
  68. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/init-session/SKILL.md +0 -0
  69. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/mcp-config/SKILL.md +0 -0
  70. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
  71. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/pattern-status/SKILL.md +0 -0
  72. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
  73. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/recall/SKILL.md +0 -0
  74. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
  75. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
  76. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/review-phase/SKILL.md +0 -0
  77. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/setup/SKILL.md +0 -0
  78. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/setup/reference.md +0 -0
  79. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
  80. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
  81. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/skills/start/SKILL.md +0 -0
  82. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.claude/state/.gitkeep +0 -0
  83. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/.gitignore +0 -0
  84. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/LICENSE +0 -0
  85. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/LICENSES/chroma-hnswlib-LICENSE +0 -0
  86. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/LICENSES/chroma-hnswlib-NOTICE +0 -0
  87. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/LICENSES/fastembed-LICENSE +0 -0
  88. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/LICENSES/fastembed-NOTICE +0 -0
  89. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/LICENSES/onnxruntime-LICENSE +0 -0
  90. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
  91. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/README.md +0 -0
  92. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/hatch_build.py +0 -0
  93. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/pyproject.toml +0 -0
  94. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/__main__.py +0 -0
  95. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/_excludes.py +0 -0
  96. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/_terminal.py +0 -0
  97. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/adapters.py +0 -0
  98. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/cli.py +0 -0
  99. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/cli_ask.py +0 -0
  100. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/cli_doctor.py +0 -0
  101. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/cli_init.py +0 -0
  102. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/cli_list.py +0 -0
  103. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/cli_plan.py +0 -0
  104. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/cli_recall.py +0 -0
  105. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/cli_tier.py +0 -0
  106. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/cli_update.py +0 -0
  107. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/db.py +0 -0
  108. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/embedding.py +0 -0
  109. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/mcp_server.py +0 -0
  110. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/paths.py +0 -0
  111. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/plan_validator.py +0 -0
  112. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/platforms.py +0 -0
  113. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/question.py +0 -0
  114. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/recall_chunker.py +0 -0
  115. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/src/c3/recall_index.py +0 -0
  116. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/__init__.py +0 -0
  117. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/conftest.py +0 -0
  118. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/__init__.py +0 -0
  119. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_check_agent_invocation.py +0 -0
  120. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_consolidate_memory.py +0 -0
  121. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_hook_utils.py +0 -0
  122. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_permission_handler.py +0 -0
  123. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_permission_handler_toast.py +0 -0
  124. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
  125. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_planner_check.py +0 -0
  126. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_planner_check_dev.py +0 -0
  127. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_post_tool.py +0 -0
  128. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_pre_tool.py +0 -0
  129. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_recall_inject.py +0 -0
  130. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_record_review_decision.py +0 -0
  131. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_restore_session.py +0 -0
  132. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_session_stop.py +0 -0
  133. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_session_utils.py +0 -0
  134. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
  135. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_similarity_boost.py +0 -0
  136. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_statusline.py +0 -0
  137. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_statusline_template_sync.py +0 -0
  138. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_sync_check.py +0 -0
  139. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/hooks/test_template_guard.py +0 -0
  140. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/skills/__init__.py +0 -0
  141. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/skills/_skill_helpers.py +0 -0
  142. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
  143. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/skills/test_init_session_no_task_type.py +0 -0
  144. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/skills/test_planner_lightweight.py +0 -0
  145. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/skills/test_recall_skill.py +0 -0
  146. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
  147. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/skills/test_setup_templates.py +0 -0
  148. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
  149. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/skills/test_start_skill_new_flow.py +0 -0
  150. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
  151. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_adapters.py +0 -0
  152. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_cli_ask.py +0 -0
  153. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_cli_entry.py +0 -0
  154. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_cli_init.py +0 -0
  155. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_cli_list.py +0 -0
  156. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_cli_plan.py +0 -0
  157. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_cli_recall.py +0 -0
  158. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_cli_update_breaking_changes.py +0 -0
  159. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_cli_update_deletions.py +0 -0
  160. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_db.py +0 -0
  161. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_docstring_consistency.py +0 -0
  162. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_embedding.py +0 -0
  163. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_excludes.py +0 -0
  164. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_extract_breaking_changes.py +0 -0
  165. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_mcp_server_elicit.py +0 -0
  166. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_paths.py +0 -0
  167. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_plan_validator.py +0 -0
  168. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_pre_compact.py +0 -0
  169. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_pre_tool_hook.py +0 -0
  170. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_precompact_additional.py +0 -0
  171. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_precompact_toctou_fixes.py +0 -0
  172. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_recall_chunker.py +0 -0
  173. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_recall_index.py +0 -0
  174. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_references_migration.py +0 -0
  175. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_session_utils_additional.py +0 -0
  176. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_skill_no_builtin_conflict.py +0 -0
  177. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_statusline.py +0 -0
  178. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_stop_additional.py +0 -0
  179. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_stop_hook.py +0 -0
  180. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_stop_precompact_fixes.py +0 -0
  181. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_sync_template_stop.py +0 -0
  182. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_template_pre_tool_hook.py +0 -0
  183. {claude_code_conductor-2.19.0 → claude_code_conductor-2.20.0}/tests/test_worktree_guard.py +0 -0
@@ -5,3 +5,4 @@
5
5
  # Lines starting with '#' are comments. Blank lines ignored.
6
6
 
7
7
  v2.11.0|summarize-memory agent + skill removed; replaced by `c3 recall`|summarize-memory エージェント / skill を完全廃止。代替は `c3 recall` の意味検索。
8
+ v2.20.0|hooks/schema.sql removed; SQLite schema is now managed by c3/migrations/ (auto-applied at session_start)|hooks/schema.sql を削除しました。SQLite スキーマは c3/migrations/ (wheel 内) で管理され session_start で自動適用されます。
@@ -37,3 +37,6 @@ skills/phase-c-plan/SKILL.md
37
37
  skills/phase-d-implement/SKILL.md
38
38
  skills/phase-debug/SKILL.md
39
39
  skills/phase-e-review/SKILL.md
40
+
41
+ # ---- v2.20.0: schema.sql を c3/migrations/ へ移行 ----
42
+ hooks/schema.sql
@@ -24,6 +24,7 @@
24
24
  .dev/hooks/ 配布元専用 hook(配布されない)
25
25
  src/c3/_excludes.py wheel 除外パターン(配布元のビルド制御)
26
26
  hatch_build.py _excludes.py の重複定義(ビルド時専用)
27
+ src/c3/migrations/ SQLite schema migration SQL ファイル群(Python package・wheel 同梱・.claude/ 配下ではないため 3 ファイル同期対象外)
27
28
  ```
28
29
 
29
30
  ### 1-2. 利用先プロジェクト(C3 ユーザー向け)
@@ -111,7 +112,7 @@ hatch_build.py _excludes.py の重複定義(ビルド
111
112
 
112
113
  | # | カテゴリ | 配布 | c3 update が更新 | 理由 |
113
114
  |---|---|---|---|---|
114
- | 1 | `.claude/hooks/*.py` | ○ | ○ | Claude Code lifecycle hook の実体。配布先で動作する。例外: `subagent_log.py` のみ除外(個人デバッグ用) |
115
+ | 1 | `.claude/hooks/*.py` | ○ | ○ | Claude Code lifecycle hook の実体。配布先で動作する。例外: `subagent_log.py` のみ除外(個人デバッグ用)。v2.20.0 で `hooks/schema.sql` を削除し SQLite スキーマは `src/c3/migrations/` に移管 |
115
116
  | 2 | `.claude/agents/*.md` | ○ | ○ | ペルソナ定義。配布先で読まれる。例外: `tdd-develop.md` のみ除外(v2.1.0 廃止) |
116
117
  | 3 | `.claude/skills/*/` | ○ | ○ | オーケストレーション/ユーティリティ skill 定義(`scripts/` / `templates/` 等サブディレクトリ含む)。例外: `worktree-tdd-workflow/*` のみ除外(v2.1.0 廃止) |
117
118
  | 4 | `.claude/rules/*.md` | ○ | ○ | C3 配布デフォルトルール(常時注入対象) |
@@ -12,22 +12,23 @@
12
12
  設計判断:
13
13
  - 旧 3 ファイルを統合することで SessionStart hook は本ファイル 1 本のみで完結
14
14
  - init-session SKILL.md からの手動 2 回呼び出しが不要になる
15
- - 旧 init_c3_db.py `apply_schema()` / `SCHEMA_VERSION` は test 互換性のため
16
- module レベルでそのまま公開する
15
+ - v2.20.0 apply_schema() c3.migrate.apply_pending_migrations() に委譲。
16
+ SCHEMA_VERSION / SCHEMA_PATH 定数および schema.sql は廃止。
17
17
  """
18
18
 
19
19
  from __future__ import annotations
20
20
 
21
21
  import json
22
+ import logging
22
23
  import os
23
24
  import shutil
24
- import sqlite3
25
25
  import sys
26
26
  import tempfile
27
- from datetime import datetime, timezone
28
27
 
29
28
  from session_utils import is_worktree
30
29
 
30
+ logger = logging.getLogger(__name__)
31
+
31
32
  try:
32
33
  sys.stdin.reconfigure(encoding="utf-8")
33
34
  sys.stdout.reconfigure(encoding="utf-8")
@@ -71,7 +72,11 @@ def _run_clear_file_history() -> None:
71
72
  except FileNotFoundError:
72
73
  pass # already deleted by another process between listdir and unlink/rmtree
73
74
  except Exception as e:
74
- print(f'[clear-file-history] 削除に失敗: {name} ({e})', file=sys.stderr)
75
+ # [SR-R-001] shutil.rmtree / os.unlink のエラーメッセージには失敗したパス
76
+ # (FILE_HISTORY_DIR はホームディレクトリ配下のためユーザー名を含む)が
77
+ # 含まれる可能性がある。main() の SR M-1 修正と一貫して例外型名のみ出力する。
78
+ logger.debug('[clear-file-history] delete failed: %s', name, exc_info=True)
79
+ print(f'[clear-file-history] 削除に失敗: {name} ({type(e).__name__})', file=sys.stderr)
75
80
 
76
81
  print(f'[clear-file-history] {deleted} 件削除しました。')
77
82
 
@@ -113,7 +118,9 @@ def _run_enable_sandbox() -> None:
113
118
  try:
114
119
  settings = json.load(f)
115
120
  except json.JSONDecodeError as e:
116
- print(f'[enable-sandbox] settings.json の JSON 解析に失敗しました: {e}')
121
+ # [SR-R-001] 一貫性のため例外型名のみ出力(JSONDecodeError 自体はパスを含まない
122
+ # が、SR M-1 / _run_clear_file_history と方針を統一する)。
123
+ print(f'[enable-sandbox] settings.json の JSON 解析に失敗しました: {type(e).__name__}')
117
124
  return
118
125
 
119
126
  if settings.get('sandbox', {}).get('enabled') is True:
@@ -132,7 +139,14 @@ def _run_enable_sandbox() -> None:
132
139
  json.dump(settings, tmp_f, ensure_ascii=False, indent=2)
133
140
  tmp_f.write('\n')
134
141
  except Exception:
135
- os.close(fd)
142
+ # [CR-E-001] os.fdopen 成功後の書き込み例外では with __exit__ が既に
143
+ # fd を閉じているため、ここでの os.close(fd) は OSError(Bad file
144
+ # descriptor) を raise し元の例外を上書きしてしまう。os.fdopen 自体が
145
+ # 失敗して with に入らなかった場合のみ fd 解放が必要なので OSError を無視する。
146
+ try:
147
+ os.close(fd)
148
+ except OSError:
149
+ pass
136
150
  raise
137
151
  os.replace(tmp_path, settings_path)
138
152
  tmp_path = None # os.replace が成功したので finally でのクリーンアップ不要
@@ -147,19 +161,10 @@ def _run_enable_sandbox() -> None:
147
161
  # 3. C3 SQLite DB 初期化(旧 init_c3_db.py)
148
162
  # =============================================================================
149
163
 
150
- # 現行スキーマバージョン。schema.sql に破壊的変更を入れたら +1 して
151
- # マイグレーションロジックを apply_schema() に追加する。
152
- SCHEMA_VERSION = 3 # v2.0.0 で PO 廃止に伴い po_results / po_status を削除
153
-
154
164
  _HOOKS_DIR = os.path.dirname(os.path.abspath(__file__))
155
165
  _CLAUDE_DIR = os.path.dirname(_HOOKS_DIR)
156
166
  STATE_DIR = os.path.join(_CLAUDE_DIR, 'state')
157
167
  DB_PATH = os.path.join(STATE_DIR, 'c3.db')
158
- SCHEMA_PATH = os.path.join(_HOOKS_DIR, 'schema.sql')
159
-
160
-
161
- def _now_iso() -> str:
162
- return datetime.now(timezone.utc).astimezone().isoformat(timespec='seconds')
163
168
 
164
169
 
165
170
  def _ensure_state_dir() -> None:
@@ -167,38 +172,36 @@ def _ensure_state_dir() -> None:
167
172
  os.makedirs(STATE_DIR, exist_ok=True)
168
173
 
169
174
 
170
- def apply_schema(db_path: str = DB_PATH, schema_path: str = SCHEMA_PATH) -> None:
171
- """schema_path DDL db_path の SQLite に適用する。
175
+ def apply_schema(db_path: str = DB_PATH) -> list[str]:
176
+ """SQLite DB にスキーマ migration を適用する。
172
177
 
173
- - WAL モードに切り替える
174
- - schema.sql CREATE TABLE IF NOT EXISTS 等を実行
175
- - schema_version テーブルに現行バージョンを INSERT OR IGNORE
178
+ v2.20.0+: 実体は c3.migrate.apply_pending_migrations() に委譲。
179
+ schema.sql は廃止され、src/c3/migrations/ の連番 SQL ファイルで管理される。
176
180
 
177
- 冪等: 既存 DB に何度呼んでもエラーにならない。
181
+ 戻り値: 今回新たに適用した migration version のリスト(例: ['001'])
178
182
  """
179
- with open(schema_path, 'r', encoding='utf-8') as f:
180
- ddl = f.read()
181
-
182
- conn = sqlite3.connect(db_path, timeout=30)
183
+ from c3.migrate import apply_pending_migrations
183
184
  try:
184
- # WAL モードを有効化(reader が writer をブロックしない)
185
- conn.execute('PRAGMA journal_mode=WAL')
186
- # DDL を一括実行(CREATE TABLE IF NOT EXISTS なので冪等)
187
- conn.executescript(ddl)
188
- # スキーマバージョンを記録(既存なら無視)
189
- conn.execute(
190
- 'INSERT OR IGNORE INTO schema_version (version, applied_at) VALUES (?, ?)',
191
- (SCHEMA_VERSION, _now_iso()),
185
+ return apply_pending_migrations(db_path)
186
+ except FileNotFoundError:
187
+ # migrations ディレクトリ不在(wheel が壊れている等)でもセッションは続行。
188
+ # [SR-R-001] 例外 e にはインストールパス(ユーザー名含む)が含まれるため
189
+ # stderr には固定文言のみ出力する。詳細は内部 logger(DEBUG)に残す。
190
+ logger.debug('apply_schema: migrations directory not found', exc_info=True)
191
+ print(
192
+ 'warning: c3 migrations directory not found (wheel may be corrupted)',
193
+ file=sys.stderr,
192
194
  )
193
- conn.commit()
194
- finally:
195
- conn.close()
195
+ return []
196
196
 
197
197
 
198
198
  def _run_init_c3_db() -> None:
199
199
  """C3 SQLite DB を初期化する."""
200
200
  _ensure_state_dir()
201
- apply_schema()
201
+ # [CR-M-003] apply_schema() の戻り値(適用した migration version の list[str])は
202
+ # 現状ログ・利用しない。セッション開始を妨げないことを最優先し、適用結果の通知は
203
+ # 行わない設計(将来 welcome メッセージ等で利用する場合はここで受ける)。
204
+ _ = apply_schema()
202
205
 
203
206
 
204
207
  # =============================================================================
@@ -222,7 +225,14 @@ def main() -> int:
222
225
  handler()
223
226
  except Exception as e:
224
227
  # 各ハンドラ失敗時は警告のみ(次のハンドラを継続)
225
- print(f'[session_start:{label}] failed: {e}', file=sys.stderr)
228
+ # [SR-R-001] 例外メッセージには db_path 等のプロジェクトパスが含まれる
229
+ # 可能性がある(例: sqlite3.OperationalError)。stderr には例外型名のみ
230
+ # 出力し、詳細は内部 logger(DEBUG)に残す。
231
+ # NOTE(SR Info-2): exc_info=True は現状ハンドラ未設定で出力されないが、
232
+ # 将来 logging ハンドラを追加する場合はスタックトレースにインストールパスが
233
+ # 含まれる点に留意する。
234
+ logger.debug('[session_start:%s] handler failed', label, exc_info=True)
235
+ print(f'[session_start:{label}] failed: {type(e).__name__}', file=sys.stderr)
226
236
  return 0
227
237
 
228
238
 
@@ -1,5 +1,69 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.20.0] - 2026-05-25
4
+
5
+ **SQLite schema migration 枠組み導入**: `.claude/hooks/schema.sql` の「冪等 DDL 一発実行」から、`src/c3/migrations/` の「連番 NNN_xxx.sql migration runner」へ移行する基盤を整備する。
6
+ v2.18.0 の `deletions.txt` 機構・v2.19.0 の `breaking-changes.txt` 機構の初の実運用ドッグフーディングでもある。
7
+
8
+ ### 機能追加
9
+
10
+ - **`src/c3/migrate.py`(新規)**: SQLite migration runner。
11
+ 公開 API `apply_pending_migrations(db_path, migrations_dir=None) -> list[str]` および例外クラス `MigrationError(RuntimeError)` を提供。
12
+ `src/c3/migrations/` 配下の連番 SQL ファイルを昇順に適用し、適用済み migration を `schema_migrations` テーブルで管理する。
13
+ WAL モード / busy_timeout=5000ms を冒頭で設定(既存 `c3.db.BUSY_TIMEOUT_MS` を SSOT として参照)。
14
+
15
+ - **`src/c3/migrations/001_initial.sql`(新規)**: 既存 `hooks/schema.sql` の DDL を逐語移植 + bootstrap。
16
+ `schema_migrations` テーブル新設、旧 `schema_version` テーブル DROP、`BEGIN;` / `COMMIT;` 明示記述による transaction 境界保証を含む。
17
+
18
+ - **`src/c3/migrations/__init__.py`(新規)**: Python package marker(wheel 自動同梱のため)。
19
+
20
+ - **`src/c3/migrations/README.md`(新規)**: 命名規約(`NNN_xxx.sql`)・`BEGIN;`/`COMMIT;` 必須運用ルール・002 以降の追加手順を記載。
21
+
22
+ - **`schema_migrations` テーブル(新規)**: 適用済み migration の一覧を保持(`version TEXT PK`, `applied_at TIMESTAMP`)。
23
+ 旧 `schema_version (version INTEGER PK, applied_at TEXT)` を置換。
24
+
25
+ ### 変更
26
+
27
+ - **`.claude/hooks/session_start.py`**: `apply_schema(db_path)` の実体を `c3.migrate.apply_pending_migrations()` に委譲。
28
+ 戻り値が `None` → `list[str]`(適用した migration version のリスト)に変更。
29
+ migrations_dir 不在時は `FileNotFoundError` を warning として stderr に出力し、セッションは続行する(exit 0 維持の既存方針踏襲)。
30
+
31
+ - **`src/c3/__init__.py`**: `__version__` を `"2.19.0"` から `"2.20.0"` に更新。
32
+
33
+ ### 破壊的変更
34
+
35
+ - **`hooks/schema.sql` 削除**: `.claude/hooks/schema.sql` をリポジトリから削除。
36
+ SQLite スキーマは `src/c3/migrations/` (wheel 内) で管理され、`session_start` で自動適用される。
37
+ 利用先からの削除は `deletions.txt` 機構(v2.18.0)によって `c3 update` 実行時に処理される。
38
+
39
+ - **`apply_schema` の `schema_path` 引数削除**: `session_start.py::apply_schema()` から `schema_path` 引数を完全削除(deprecation 期間なし)。
40
+ 呼び出し側で `schema_path=` を渡している場合は削除が必要(`c3 update` 実行後のテストコード等)。
41
+
42
+ ### ドキュメント
43
+
44
+ - **`.claude/docs/config-policy.md`**:
45
+ - §1-1 配布元ディレクトリ表に `src/c3/migrations/` を追加(Python package・wheel 同梱・3 ファイル同期対象外)
46
+ - §3 カテゴリ #1 hooks 備考に「v2.20.0 で `hooks/schema.sql` を削除、SQLite スキーマは `src/c3/migrations/` に移管」を追記
47
+
48
+ - **`.claude/breaking-changes.txt`**: v2.20.0 エントリ追記(`hooks/schema.sql` 削除)
49
+
50
+ - **`.claude/deletions.txt`**: `hooks/schema.sql` 追記(利用先からの自動削除を `c3 update` 経由で実現)
51
+
52
+ ### ドッグフーディング
53
+
54
+ - **v2.18.0 `deletions.txt` 機構**: `hooks/` 配下の非テスト SQL ファイル削除の初の実運用事例(過去は `agents/` / `skills/` の `.md` のみ)。
55
+ - **v2.19.0 `breaking-changes.txt` 機構**: v2.20.0 エントリを新規追加して MINOR bump 表示路を初通過させる。
56
+ - **`scripts/extract_breaking_changes.py`**: `--dry-run` / 通常実行 / `--check` のフルライフサイクル運用。
57
+
58
+ ### 影響
59
+
60
+ - **既存利用先**: `c3 update` 実行時に v2.20.0 breaking change が表示される(v2.19.0 以降の利用先)。
61
+ `.claude/hooks/schema.sql` は `c3 update` 実行時に自動削除される。
62
+ 次回セッション開始時に `apply_pending_migrations` が `schema_migrations` テーブルを新設し 001 を適用する。
63
+ 既存データ(`review_decisions` 等)は保持される。`schema_version` テーブルは削除される。
64
+
65
+ ---
66
+
3
67
  ## [2.19.0] - 2026-05-24
4
68
 
5
69
  **基盤整備リリース第 3 弾**: `c3 update` 実行時に breaking changes 表示 + MAJOR 承認プロンプトを導入する。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-conductor
3
- Version: 2.19.0
3
+ Version: 2.20.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.19.0"
3
+ __version__ = "2.20.0"
@@ -0,0 +1,260 @@
1
+ """C3 SQLite migration runner (v2.20.0 新規)。
2
+
3
+ 公開 API:
4
+ - apply_pending_migrations(db_path, migrations_dir=None) -> list[str]
5
+ - MigrationError(RuntimeError)
6
+
7
+ 命名規約:
8
+ migrations/ 配下の NNN_xxx.sql ファイル(NNN は 3 桁ゼロパディング連番)を
9
+ 昇順で適用する。適用済みの version は schema_migrations テーブルで管理する。
10
+
11
+ 落とし穴 (architecture §10.4):
12
+ executescript() は内部で自動 COMMIT を発行するため、SQL ファイル内に BEGIN;
13
+ を書かないとステートメント単位の autocommit になり ROLLBACK が効かない。
14
+ 各 migration ファイルは必ず BEGIN; で始まり COMMIT; で終わること。
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import logging
20
+ import re
21
+ import sqlite3
22
+ import warnings
23
+ from pathlib import Path
24
+
25
+ from c3.db import BUSY_TIMEOUT_MS
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # migration ファイル名の命名規約パターン: NNN_説明.sql (ASCII 小文字・数字・アンダースコア)
30
+ _MIGRATION_FILENAME_RE = re.compile(r"^(\d{3})_[a-z0-9_]+\.sql$")
31
+
32
+ # デフォルト migrations ディレクトリ: このファイルと同階層の migrations/
33
+ _DEFAULT_MIGRATIONS_DIR = Path(__file__).parent / "migrations"
34
+
35
+
36
+ class MigrationError(RuntimeError):
37
+ """Migration の適用に失敗したことを示す例外。
38
+
39
+ raise されるのは _run_migration() 内のみ。発生時:
40
+ - 当該 migration の transaction は ROLLBACK 済み
41
+ - schema_migrations への INSERT も未実行(version 列に当該 version は無い)
42
+ - これより後ろの pending migration は適用されない
43
+ """
44
+
45
+
46
+ def apply_pending_migrations(
47
+ db_path: Path | str,
48
+ migrations_dir: Path | None = None,
49
+ ) -> list[str]:
50
+ """未適用の migration を順次適用し、適用した version のリストを返す。
51
+
52
+ Args:
53
+ db_path: c3.db の絶対パス。文字列でも Path でも受け付ける。
54
+ migrations_dir: migration SQL を置くディレクトリ。省略時は
55
+ Path(__file__).parent / "migrations"(= src/c3/migrations/)。
56
+
57
+ Returns:
58
+ 今回新たに適用した version のリスト(適用順)。
59
+ 全 migration が既に適用済みなら空リスト。
60
+
61
+ Raises:
62
+ MigrationError: いずれかの migration の実行に失敗した場合。
63
+ FileNotFoundError: migrations_dir が存在しない場合。
64
+ sqlite3.Error: db_path が SQLite として開けない等の場合。
65
+
66
+ Note:
67
+ 本関数を C3 runtime の session_start.py 経由(apply_schema())で呼んだ場合、
68
+ MigrationError は session_start.py::main() の except Exception で warning 出力に
69
+ 変換され exit 0 が維持される(architecture §3.2)。Python から直接呼んだ場合は
70
+ 上記 Raises のとおり例外が伝播する。
71
+ """
72
+ if migrations_dir is None:
73
+ migrations_dir = _DEFAULT_MIGRATIONS_DIR
74
+
75
+ # migrations_dir 不在は早期 raise(wheel が壊れている兆候)
76
+ if not migrations_dir.exists():
77
+ raise FileNotFoundError(
78
+ f"migrations_dir が存在しません: {migrations_dir}"
79
+ )
80
+
81
+ # migration ファイル一覧取得(FileNotFoundError は伝播させる)
82
+ migrations = _list_migrations(migrations_dir)
83
+
84
+ conn = sqlite3.connect(str(db_path))
85
+ try:
86
+ # PRAGMA はパラメータバインドできないため int() で型を強制する
87
+ # (PRAGMA インジェクション防御 [SR-INJ-001] の踏襲)
88
+ conn.execute("PRAGMA journal_mode=WAL")
89
+ conn.execute(f"PRAGMA busy_timeout={int(BUSY_TIMEOUT_MS)}")
90
+
91
+ # schema_migrations テーブルを事前に作成する
92
+ # (_get_applied_versions の SELECT が失敗しないよう先行して CREATE)
93
+ _ensure_schema_migrations_table(conn)
94
+
95
+ applied_versions = _get_applied_versions(conn)
96
+
97
+ newly_applied: list[str] = []
98
+ for version, path in migrations:
99
+ if version in applied_versions:
100
+ continue
101
+ _run_migration(conn, version, path)
102
+ newly_applied.append(version)
103
+
104
+ return newly_applied
105
+ finally:
106
+ conn.close()
107
+
108
+
109
+ def _list_migrations(migrations_dir: Path) -> list[tuple[str, Path]]:
110
+ """migrations_dir 内の NNN_xxx.sql ファイルを version 昇順で返す。
111
+
112
+ Args:
113
+ migrations_dir: migration SQL を置くディレクトリ。
114
+
115
+ Returns:
116
+ [(version, path), ...] を version の文字列昇順にソートしたリスト。
117
+ 命名規約に違反するファイルはスキップ(warning ログを出す)。
118
+ version 重複の場合は最初の 1 件のみ採用(warning ログを出す)。
119
+
120
+ Raises:
121
+ FileNotFoundError: migrations_dir が存在しない場合。
122
+ apply_pending_migrations() から呼ばれる場合は呼び出し元 (L70) で先に
123
+ exists チェックされるため到達しない。本チェックは _list_migrations を
124
+ 単独で利用するケースのための防衛的チェック。
125
+ """
126
+ if not migrations_dir.exists():
127
+ raise FileNotFoundError(
128
+ f"migrations_dir が存在しません: {migrations_dir}"
129
+ )
130
+
131
+ result: list[tuple[str, Path]] = []
132
+ seen_versions: dict[str, Path] = {}
133
+
134
+ for path in sorted(migrations_dir.glob("*.sql")):
135
+ if path.is_symlink(): # [SR-V-002] symlink は信頼境界外のため無視
136
+ logger.warning("シンボリックリンクをスキップします: %s", path.name)
137
+ continue
138
+ m = _MIGRATION_FILENAME_RE.match(path.name)
139
+ if not m:
140
+ logger.warning(
141
+ "命名規約違反のファイルをスキップします: %s "
142
+ "(期待パターン: NNN_description.sql, 例: 001_initial.sql)",
143
+ path.name,
144
+ )
145
+ continue
146
+
147
+ version = m.group(1)
148
+ if version in seen_versions:
149
+ logger.warning(
150
+ "version '%s' が重複しています: %s と %s。最初の 1 件のみ採用します。",
151
+ version,
152
+ seen_versions[version].name,
153
+ path.name,
154
+ )
155
+ continue
156
+
157
+ seen_versions[version] = path
158
+ result.append((version, path))
159
+
160
+ # version の文字列昇順でソート(= 連番昇順と一致)
161
+ result.sort(key=lambda t: t[0])
162
+ return result
163
+
164
+
165
+ def _ensure_schema_migrations_table(conn: sqlite3.Connection) -> None:
166
+ """schema_migrations テーブルが存在しなければ作成する。
167
+
168
+ _get_applied_versions() の SELECT が失敗しないよう、事前に呼び出す。
169
+ テーブルが既に存在する場合は何もしない(IF NOT EXISTS)。
170
+ """
171
+ conn.execute(
172
+ """
173
+ CREATE TABLE IF NOT EXISTS schema_migrations (
174
+ version TEXT PRIMARY KEY,
175
+ applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
176
+ )
177
+ """
178
+ )
179
+ conn.commit()
180
+
181
+
182
+ def _get_applied_versions(conn: sqlite3.Connection) -> set[str]:
183
+ """schema_migrations テーブルから適用済み version の集合を返す。
184
+
185
+ Args:
186
+ conn: SQLite 接続。
187
+
188
+ Returns:
189
+ 適用済み version の集合。テーブルが存在しない場合は空集合(防御的挙動)。
190
+ 通常は apply_pending_migrations() 内で _ensure_schema_migrations_table()
191
+ を先行呼び出しするため、テーブル不在には到達しない。
192
+ """
193
+ try:
194
+ rows = conn.execute(
195
+ "SELECT version FROM schema_migrations"
196
+ ).fetchall()
197
+ return {row[0] for row in rows}
198
+ except sqlite3.OperationalError:
199
+ # schema_migrations テーブルが存在しない場合(防御的挙動)
200
+ logger.debug(
201
+ "_get_applied_versions: schema_migrations テーブルが見つかりません。"
202
+ "空集合を返します。"
203
+ )
204
+ return set()
205
+
206
+
207
+ def _run_migration(
208
+ conn: sqlite3.Connection,
209
+ version: str,
210
+ path: Path,
211
+ ) -> None:
212
+ """1 件の migration SQL ファイルを実行し、schema_migrations に記録する。
213
+
214
+ Args:
215
+ conn: SQLite 接続(PRAGMA 設定済みであること)。
216
+ version: migration の version 文字列(例: '001')。
217
+ path: migration SQL ファイルのパス。
218
+
219
+ Raises:
220
+ MigrationError: SQL 実行失敗 / schema_migrations への INSERT 失敗。
221
+
222
+ Notes:
223
+ SQL ファイル内に BEGIN; / COMMIT; を明示記述すること(architecture §10.4)。
224
+ executescript() は autocommit モードで動作するため、ファイル内 BEGIN;
225
+ がないとステートメント単位の autocommit になり ROLLBACK が効かない。
226
+
227
+ schema_migrations への INSERT は SQL ファイルの executescript() 後に
228
+ Python 側の別 transaction として実行する。SQL ファイル内に
229
+ INSERT OR IGNORE を書いている場合(001_initial.sql の bootstrap)と
230
+ 二重になるが OR IGNORE で吸収される。
231
+
232
+ 注意: executescript() 内の COMMIT; が発行された後に schema_migrations への
233
+ INSERT が失敗した場合、続く except 節の conn.rollback() は SQL 本体には作用しない
234
+ (SQL 本体は既に commit 済み)。この場合 schema_migrations への記録のみ未実行となり、
235
+ 次回 session_start で同 version が再適用される。001_initial.sql の IF NOT EXISTS /
236
+ INSERT OR IGNORE による冪等設計で整合性を回復する(architecture §7.1 既知の落とし穴)。
237
+ """
238
+ sql = path.read_text(encoding="utf-8")
239
+
240
+ try:
241
+ # executescript() は内部で BEGIN を発行しないため、SQL ファイル内の
242
+ # BEGIN; ... COMMIT; の transaction 境界をそのまま尊重する。
243
+ conn.executescript(sql)
244
+
245
+ # schema_migrations への記録(SQL 本体とは別 transaction)
246
+ conn.execute(
247
+ "INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)",
248
+ (version,),
249
+ )
250
+ conn.commit()
251
+
252
+ except sqlite3.Error as e:
253
+ # SQL 本体または INSERT が失敗した場合は ROLLBACK して MigrationError を raise
254
+ try:
255
+ conn.rollback()
256
+ except sqlite3.Error:
257
+ pass # rollback 自体が失敗しても元の例外を優先する
258
+ raise MigrationError(
259
+ f"migration '{version}' ({path.name}) の適用に失敗しました: {e}"
260
+ ) from e
@@ -1,15 +1,13 @@
1
- -- C3 SQLite schema (duckdb-hybrid: DuckDB ハイブリッド構成の基盤)
1
+ -- C3 SQLite migration 001: 初期スキーマ定義
2
2
  --
3
- -- このファイルは session_start.py_run_init_c3_db ハンドラから読まれ、
4
- -- `.claude/state/c3.db` に対して冪等に CREATE TABLE IF NOT EXISTS で適用される。
5
- -- WAL モードへの切り替えは session_start.py 側で PRAGMA journal_mode=WAL を実行する。
3
+ -- .claude/hooks/schema.sql (v2.19.0 まで使用) DDL を逐語移植し、
4
+ -- schema_migrations テーブル (v2.20.0 新規) を追加する。
5
+ -- schema_version テーブルは末尾で DROP する。
6
6
  --
7
7
  -- 書き込みは Python の sqlite3 経由、読み・分析は DuckDB の sqlite_scanner で
8
8
  -- ATTACH してアクセスする想定(書き込みフローは sqlite3 に統一する)。
9
- --
10
- -- スキーマ変更時は schema_version を上げて、session_start.py の apply_schema()
11
- -- にマイグレーション処理を追加すること(CREATE TABLE IF NOT EXISTS だけで
12
- -- 表現できない変更が必要になった場合の備え)。
9
+
10
+ BEGIN;
13
11
 
14
12
  -- ---------------------------------------------------------------------------
15
13
  -- v2.0.0 マイグレーション: PO(Parallel Orchestra)廃止に伴うテーブル削除
@@ -20,12 +18,15 @@ DROP TABLE IF EXISTS po_results;
20
18
  DROP TABLE IF EXISTS po_status;
21
19
 
22
20
  -- ---------------------------------------------------------------------------
23
- -- スキーマバージョン管理
21
+ -- スキーマバージョン管理 (v2.20.0 新規)
24
22
  -- ---------------------------------------------------------------------------
23
+ -- 旧 schema_version (version INTEGER PK) の代わりに、適用済み migration の
24
+ -- 一覧を保持する schema_migrations テーブルを定義する。
25
+ -- version は NNN_xxx.sql のファイル名先頭 3 桁の文字列(例: '001')。
25
26
 
26
- CREATE TABLE IF NOT EXISTS schema_version (
27
- version INTEGER PRIMARY KEY,
28
- applied_at TEXT NOT NULL
27
+ CREATE TABLE IF NOT EXISTS schema_migrations (
28
+ version TEXT PRIMARY KEY,
29
+ applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
29
30
  );
30
31
 
31
32
  -- ---------------------------------------------------------------------------
@@ -101,3 +102,20 @@ CREATE INDEX IF NOT EXISTS idx_agent_runs_session
101
102
  ON agent_runs(session_id, ts DESC);
102
103
  CREATE INDEX IF NOT EXISTS idx_agent_runs_agent
103
104
  ON agent_runs(agent_id, ts DESC);
105
+
106
+ -- ---------------------------------------------------------------------------
107
+ -- bootstrap: この migration 自体を schema_migrations に記録する
108
+ -- ---------------------------------------------------------------------------
109
+ -- apply_pending_migrations() の Python 側でも INSERT OR IGNORE を実行するが、
110
+ -- 既存 DB(schema_version あり)からの初回 upgrade 時に SQL 内で先行記録しておく。
111
+ -- Python 側の INSERT と二重になるが OR IGNORE で吸収される。
112
+ INSERT OR IGNORE INTO schema_migrations (version) VALUES ('001');
113
+
114
+ -- ---------------------------------------------------------------------------
115
+ -- 旧 schema_version テーブルの削除 (v2.19.0 以前の DB からの upgrade)
116
+ -- ---------------------------------------------------------------------------
117
+ -- v2.19.0 以前の c3.db には schema_version (version INTEGER PK) が存在する。
118
+ -- v2.20.0 では schema_migrations に一本化するため DROP する。
119
+ DROP TABLE IF EXISTS schema_version;
120
+
121
+ COMMIT;