claude-code-conductor 2.74.0__tar.gz → 2.75.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 (277) hide show
  1. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/statusline.py +6 -3
  2. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.gitignore +4 -0
  3. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/CHANGELOG.md +35 -0
  4. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/PKG-INFO +27 -1
  5. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/README.md +26 -0
  6. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/__init__.py +1 -1
  7. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_init.py +74 -2
  8. claude_code_conductor-2.75.0/tests/conftest.py +57 -0
  9. claude_code_conductor-2.75.0/tests/hooks/test_statusline_reconfigure_guard.py +197 -0
  10. claude_code_conductor-2.75.0/tests/test_cli_init_symlink_guard.py +704 -0
  11. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_statusline.py +10 -1
  12. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_stop_agent_memory_warn.py +5 -22
  13. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_three_file_sync.py +9 -11
  14. claude_code_conductor-2.74.0/tests/conftest.py +0 -15
  15. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/.gitignore +0 -0
  16. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/CLAUDE.md +0 -0
  17. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/architect.md +0 -0
  18. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/code-reviewer.md +0 -0
  19. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/design-critic.md +0 -0
  20. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/developer.md +0 -0
  21. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/doc-writer.md +0 -0
  22. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/interviewer.md +0 -0
  23. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/planner.md +0 -0
  24. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/project-setup.md +0 -0
  25. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/security-reviewer.md +0 -0
  26. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/systematic-debugger.md +0 -0
  27. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/tester.md +0 -0
  28. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/wt_developer.md +0 -0
  29. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/wt_systematic-debugger.md +0 -0
  30. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/wt_tester.md +0 -0
  31. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/breaking-changes.txt +0 -0
  32. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/deletions.txt +0 -0
  33. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/docs/autonomous-mode-onboarding.md +0 -0
  34. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/docs/config-policy.md +0 -0
  35. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/docs/nul-boundary.md +0 -0
  36. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/docs/parallel-agents-setup.md +0 -0
  37. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/docs/platform-adapters.md +0 -0
  38. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/docs/settings.json.md +0 -0
  39. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/_hook_utils.py +0 -0
  40. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/check_agent_invocation.py +0 -0
  41. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/consolidate_memory.py +0 -0
  42. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/patterns_guard.py +0 -0
  43. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/planner_check.py +0 -0
  44. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/post_tool.py +0 -0
  45. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/pre_compact.py +0 -0
  46. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/pre_tool.py +0 -0
  47. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/recall_autorebuild.py +0 -0
  48. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/recall_inject.py +0 -0
  49. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/report_contract_check.py +0 -0
  50. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/report_path_guard.py +0 -0
  51. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/restore_session.py +0 -0
  52. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/select_tier.py +0 -0
  53. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/session_mode_watch.py +0 -0
  54. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/session_start.py +0 -0
  55. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/session_stop.py +0 -0
  56. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/session_utils.py +0 -0
  57. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/stop.py +0 -0
  58. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/tier_autoapply.py +0 -0
  59. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/tier_gap_check.py +0 -0
  60. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/worktree_guard.py +0 -0
  61. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/memory/.gitkeep +0 -0
  62. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/memory/archive/.gitkeep +0 -0
  63. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/memory/sessions/.gitkeep +0 -0
  64. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/reports/.gitkeep +0 -0
  65. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/rules/judgment-principles.md +0 -0
  66. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/rules/promoted/index.md +0 -0
  67. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/settings.json +0 -0
  68. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/autonomous-mode/SKILL.md +0 -0
  69. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/autonomous-mode/scripts/mode_line.py +0 -0
  70. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/brainstorm/SKILL.md +0 -0
  71. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/codex-review/SKILL.md +0 -0
  72. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
  73. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/c3-omission-declaration.md +0 -0
  74. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
  75. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/design-critic-rubric.md +0 -0
  76. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/design-rubric.md +0 -0
  77. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/interview-rubric.md +0 -0
  78. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
  79. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/reachability-map.md +0 -0
  80. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/record-protocol.md +0 -0
  81. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
  82. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/scripts/detect_execution_verification.py +0 -0
  83. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/scripts/record_agent_outcome.py +0 -0
  84. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
  85. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
  86. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/develop/SKILL.md +0 -0
  87. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/doc/SKILL.md +0 -0
  88. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/extract-lib/SKILL.md +0 -0
  89. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/init-session/SKILL.md +0 -0
  90. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/init-session/scripts/session_guard.py +0 -0
  91. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/mcp-config/SKILL.md +0 -0
  92. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
  93. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/pattern-status/SKILL.md +0 -0
  94. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
  95. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/recall/SKILL.md +0 -0
  96. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
  97. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
  98. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/review-phase/SKILL.md +0 -0
  99. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/setup/SKILL.md +0 -0
  100. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/setup/reference.md +0 -0
  101. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
  102. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
  103. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/start/SKILL.md +0 -0
  104. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/start/scripts/archive_reports.py +0 -0
  105. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/state/.gitkeep +0 -0
  106. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/state/setup_done.flag +0 -0
  107. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/tmp/.gitkeep +0 -0
  108. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/ARCHITECTURE.md +0 -0
  109. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/LICENSE +0 -0
  110. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/LICENSES/fastembed-LICENSE +0 -0
  111. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/LICENSES/fastembed-NOTICE +0 -0
  112. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/LICENSES/numpy-LICENSE +0 -0
  113. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/LICENSES/onnxruntime-LICENSE +0 -0
  114. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
  115. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/hatch_build.py +0 -0
  116. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/pyproject.toml +0 -0
  117. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/__main__.py +0 -0
  118. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/_db_params.py +0 -0
  119. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/_excludes.py +0 -0
  120. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/_terminal.py +0 -0
  121. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/adapters.py +0 -0
  122. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli.py +0 -0
  123. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_ask.py +0 -0
  124. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_doctor.py +0 -0
  125. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_list.py +0 -0
  126. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_metrics.py +0 -0
  127. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_plan.py +0 -0
  128. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_recall.py +0 -0
  129. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_run.py +0 -0
  130. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_tier.py +0 -0
  131. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_update.py +0 -0
  132. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/db.py +0 -0
  133. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/embedding.py +0 -0
  134. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/gitutil.py +0 -0
  135. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/mcp_server.py +0 -0
  136. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrate.py +0 -0
  137. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/001_initial.sql +0 -0
  138. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
  139. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/003_tier_cost.sql +0 -0
  140. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/004_agent_outcomes.sql +0 -0
  141. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/005_drop_agent_tier_bandit.sql +0 -0
  142. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/006_review_decisions_severity.sql +0 -0
  143. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/007_review_decisions_resolution.sql +0 -0
  144. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/008_drop_agent_runs.sql +0 -0
  145. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/README.md +0 -0
  146. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/__init__.py +0 -0
  147. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/paths.py +0 -0
  148. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/plan_validator.py +0 -0
  149. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/platforms.py +0 -0
  150. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/pricing.py +0 -0
  151. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/question.py +0 -0
  152. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/recall_chunker.py +0 -0
  153. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/recall_index.py +0 -0
  154. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/refgraph.py +0 -0
  155. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/usage_ingester.py +0 -0
  156. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/__init__.py +0 -0
  157. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/_pre_compact_helpers.py +0 -0
  158. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/_sync_semantics.py +0 -0
  159. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/fixtures/usage/README.md +0 -0
  160. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/fixtures/usage/mainline.jsonl +0 -0
  161. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
  162. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
  163. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/__init__.py +0 -0
  164. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_check_agent_invocation.py +0 -0
  165. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_consolidate_memory.py +0 -0
  166. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_detect_execution_verification.py +0 -0
  167. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_hook_utils.py +0 -0
  168. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
  169. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_planner_check.py +0 -0
  170. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_planner_check_dev.py +0 -0
  171. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_post_tool.py +0 -0
  172. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_pre_tool.py +0 -0
  173. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_recall_autorebuild.py +0 -0
  174. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_recall_inject.py +0 -0
  175. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_record_agent_outcome.py +0 -0
  176. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_record_review_decision.py +0 -0
  177. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_report_path_guard.py +0 -0
  178. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_restore_session.py +0 -0
  179. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_review_hint_inject.py +0 -0
  180. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_select_tier.py +0 -0
  181. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_select_tier_escalation.py +0 -0
  182. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_session_start.py +0 -0
  183. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_session_stop.py +0 -0
  184. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_session_utils.py +0 -0
  185. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
  186. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_settings_tier_autoapply_registration.py +0 -0
  187. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_similarity_boost.py +0 -0
  188. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_statusline.py +0 -0
  189. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_statusline_template_sync.py +0 -0
  190. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_sync_check.py +0 -0
  191. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_template_guard.py +0 -0
  192. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_tier_autoapply.py +0 -0
  193. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_tier_gap_check.py +0 -0
  194. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_tier_security_fixes.py +0 -0
  195. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/__init__.py +0 -0
  196. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/_skill_helpers.py +0 -0
  197. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_c3_omission_declaration.py +0 -0
  198. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
  199. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_e0_verification_exit.py +0 -0
  200. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_init_session_no_task_type.py +0 -0
  201. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_marker_compliance.py +0 -0
  202. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_parallel_agents_skill_tier.py +0 -0
  203. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_planner_lightweight.py +0 -0
  204. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_reachability_map.py +0 -0
  205. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_recall_skill.py +0 -0
  206. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
  207. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_session_guard.py +0 -0
  208. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_setup_templates.py +0 -0
  209. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
  210. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_start_skill_new_flow.py +0 -0
  211. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
  212. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_adapters.py +0 -0
  213. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_archive_reports.py +0 -0
  214. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_audit_review_decisions.py +0 -0
  215. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_check_deletions.py +0 -0
  216. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_ci_workflows.py +0 -0
  217. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_claude_gitignore.py +0 -0
  218. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_ask.py +0 -0
  219. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_doctor.py +0 -0
  220. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_doctor_launcher.py +0 -0
  221. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_entry.py +0 -0
  222. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_init.py +0 -0
  223. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_list.py +0 -0
  224. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_metrics.py +0 -0
  225. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_plan.py +0 -0
  226. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_recall.py +0 -0
  227. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_run.py +0 -0
  228. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_tier.py +0 -0
  229. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_update_breaking_changes.py +0 -0
  230. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_update_deletions.py +0 -0
  231. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_db.py +0 -0
  232. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_db_connect_lint.py +0 -0
  233. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_docstring_consistency.py +0 -0
  234. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_embedding.py +0 -0
  235. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_excludes.py +0 -0
  236. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_extract_breaking_changes.py +0 -0
  237. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_gitutil.py +0 -0
  238. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_hooks_stdin_idiom.py +0 -0
  239. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_init_only_patterns.py +0 -0
  240. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_mcp_server_elicit.py +0 -0
  241. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_migrate.py +0 -0
  242. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_mode_line.py +0 -0
  243. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_no_bare_python_launcher.py +0 -0
  244. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_no_passonly_tests.py +0 -0
  245. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_nul_boundary_lint.py +0 -0
  246. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_paths.py +0 -0
  247. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_patterns_guard.py +0 -0
  248. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_permission_handler_removal.py +0 -0
  249. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_plan_validator.py +0 -0
  250. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_pre_compact.py +0 -0
  251. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_pre_tool_hook.py +0 -0
  252. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_precompact_additional.py +0 -0
  253. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_precompact_toctou_fixes.py +0 -0
  254. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_pricing.py +0 -0
  255. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_recall_chunker.py +0 -0
  256. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_recall_index.py +0 -0
  257. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_references_migration.py +0 -0
  258. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_refgraph.py +0 -0
  259. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_refgraph_query.py +0 -0
  260. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_refgraph_structural.py +0 -0
  261. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_report_contract_check.py +0 -0
  262. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_scripts_src_bootstrap.py +0 -0
  263. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_scripts_stdout_encoding.py +0 -0
  264. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_session_mode_watch.py +0 -0
  265. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_session_utils_additional.py +0 -0
  266. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_settings_hooks_registration.py +0 -0
  267. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_skill_no_builtin_conflict.py +0 -0
  268. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_start_skill_archive_contract.py +0 -0
  269. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_stop_additional.py +0 -0
  270. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_stop_hook.py +0 -0
  271. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_stop_precompact_fixes.py +0 -0
  272. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_sync_template_stop.py +0 -0
  273. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_template_pre_tool_hook.py +0 -0
  274. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_usage_ingester.py +0 -0
  275. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_verify_wheel.py +0 -0
  276. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_worktree_guard.py +0 -0
  277. {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_wt_agent_sync.py +0 -0
@@ -13,9 +13,12 @@ import threading
13
13
  from datetime import datetime, timezone
14
14
  from typing import Any
15
15
 
16
- sys.stdout.reconfigure(encoding='utf-8')
17
- sys.stderr.reconfigure(encoding='utf-8')
18
- sys.stdin.reconfigure(encoding='utf-8')
16
+ try:
17
+ sys.stdin.reconfigure(encoding='utf-8')
18
+ sys.stdout.reconfigure(encoding='utf-8')
19
+ sys.stderr.reconfigure(encoding='utf-8')
20
+ except AttributeError:
21
+ pass
19
22
 
20
23
  MAX_INPUT = 64 * 1024 # 64 KB
21
24
 
@@ -86,3 +86,7 @@ site/
86
86
  # Personal Claude Code overrides (per-user / per-worktree, never committed)
87
87
  # wheel からは _excludes.py / hatch_build.py で既に除外済み。git でも tracking しない方針。
88
88
  .claude/settings.local.json
89
+
90
+ # agent 作業残骸 pytest_temp.ini の再発防止ガード(2026-05-02 混入・v0.4.2〔44be00f〕で削除+
91
+ # EXCLUDE_PATTERNS ガード化。本行はその git 側の対)
92
+ .claude/pytest_temp.ini
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.75.0] - 2026-08-16
4
+
5
+ ### 追加
6
+
7
+ - **`c3 init` のテンプレート展開(`_copytree`)に symlink / NTFS ジャンクション防御を追加した**:
8
+ 各 entry の realpath が「コピー元ルートの realpath + 相対パス」と完全一致することを検査し、
9
+ 一致しない entry(symlink・ジャンクション・壊れたリンク)はコピーせず stderr へ 1 件 1 行の警告を
10
+ 出してスキップする。`is_symlink()` 単体判定はジャンクションに `False` を返すため採用せず、
11
+ realpath 完全一致方式に一本化した。比較の両辺とも realpath 済みのため、コピー元ルート自体が
12
+ リンク経由(macOS の `/tmp` 等)でも全件スキップに縮退しない。加えて:
13
+ - entry 側の realpath が `OSError`(`ELOOP` 含む)を送出した場合は「リンク疑い」として
14
+ 警告付きスキップに倒す(fail-soft・`c3 init` 全体は異常終了しない。ルート側の解決不能は
15
+ テンプレート根本の異常のため従来どおり例外で停止する)
16
+ - `shutil.copy2` 直前にリンク再検証を行い、検査とコピーの間の差し替え(TOCTOU)の窓を狭めた
17
+ (残余の窓は脅威モデル上許容・docstring に記録)
18
+ - 性質テスト `tests/test_cli_init_symlink_guard.py` を新設(symlink 系・ジャンクション実測・
19
+ ルートをリンク経由で渡す回帰ガード・例外系/再検証の検知テストを含む)。ジャンクション/リンク作成の
20
+ テストヘルパー `_make_link` は `tests/conftest.py` へ共有化した
21
+ - `.claude/hooks/statusline.py` の reconfigure 保護テスト
22
+ `tests/hooks/test_statusline_reconfigure_guard.py` を新設した(`reconfigure` 属性を持たない
23
+ ストリーム環境でも hook のロードがクラッシュしないこと)
24
+
25
+ ### 変更
26
+
27
+ - `.claude/hooks/statusline.py` のストリーム reconfigure を canonical 順序(stdin→stdout→stderr)+
28
+ `try/except AttributeError` の確立イディオムへ統一した(配布 hook 22 本で唯一の無保護だった)
29
+ - `README.md` / `docs/getting-started.md` の前提条件に仮想環境(venv)の作成手順を追加した。
30
+ PEP 668(externally-managed-environment)環境で素の `pip install` が失敗する旨と、
31
+ 隔離環境の但し書き 3 点(Claude Code を起動するシェルでも同じ venv を activate すること・
32
+ `pipx install claude-code-conductor` の代替・同一シェルでの `c3 doctor` による PATH 解決確認)を明記した
33
+ - root `.gitignore` に `.claude/pytest_temp.ini` のガード行を追加した(2026-05-02 に agent 作業残骸として
34
+ 混入し v0.4.2 で削除+`EXCLUDE_PATTERNS` ガード化された経緯の git 側の対)。これに伴い
35
+ `tests/test_three_file_sync.py` の許容リスト A から同エントリを削除し、実効検査件数の下限を
36
+ 16 / 16(遊びなし)へ締めた
37
+
3
38
  ## [2.74.0] - 2026-08-15
4
39
 
5
40
  ### 追加
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: claude-code-conductor
3
- Version: 2.74.0
3
+ Version: 2.75.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
@@ -268,6 +268,30 @@ C3 の `.claude/` はプロジェクトコードに一切触れません。既
268
268
  - [Claude Code](https://claude.ai/code) がインストール済みでログインしていること
269
269
  - Python 3.10 以上がインストール済みであること
270
270
 
271
+ **仮想環境(venv)の準備(推奨)**
272
+
273
+ Python の導入方法によっては、素の `pip install` が PEP 668(externally-managed-environment)により拒否されます(Debian/Ubuntu の apt 版 Python、Homebrew の Python など)。仮想環境(venv)を作ってその中へインストールすれば、この制約を受けずに済みます。
274
+
275
+ Windows(PowerShell):
276
+
277
+ ```powershell
278
+ python -m venv .venv
279
+ .\.venv\Scripts\Activate.ps1
280
+ ```
281
+
282
+ macOS / Linux:
283
+
284
+ ```bash
285
+ python3 -m venv .venv
286
+ source .venv/bin/activate
287
+ ```
288
+
289
+ venv に入れた場合の注意:
290
+
291
+ - C3 の hook は `c3 run` で起動されるため、**Claude Code を起動するシェルでも同じ venv を activate してください**。activate されていないシェルから Claude Code を起動すると `c3` が見つからず、hook が動作しません
292
+ - venv を使わずグローバルに使いたい場合は [pipx](https://pipx.pypa.io/) が代替になります: `pipx install claude-code-conductor`
293
+ - インストール後は、**Claude Code を起動するのと同じシェルで** `c3 doctor` を実行し、`c3` が PATH で解決されることを確認してください(`c3 doctor` が検査するのは実行中シェルの PATH です。別のシェルで実行しても、Claude Code の hook が起動される文脈は再現できません)
294
+
271
295
  ### 手順(推奨: PyPI から)
272
296
 
273
297
  **1. C3 をインストールする**
@@ -276,6 +300,8 @@ C3 の `.claude/` はプロジェクトコードに一切触れません。既
276
300
  pip install claude-code-conductor
277
301
  ```
278
302
 
303
+ > このコマンドが PEP 668(externally-managed-environment)で拒否される場合や、インストール後に hook が動作しない場合は、[前提条件](#前提条件)の venv 手順(activate するシェルの注意点を含む)を参照してください。
304
+
279
305
  **2. プロジェクトに `.claude/` を展開する**
280
306
 
281
307
  ```bash
@@ -223,6 +223,30 @@ C3 の `.claude/` はプロジェクトコードに一切触れません。既
223
223
  - [Claude Code](https://claude.ai/code) がインストール済みでログインしていること
224
224
  - Python 3.10 以上がインストール済みであること
225
225
 
226
+ **仮想環境(venv)の準備(推奨)**
227
+
228
+ Python の導入方法によっては、素の `pip install` が PEP 668(externally-managed-environment)により拒否されます(Debian/Ubuntu の apt 版 Python、Homebrew の Python など)。仮想環境(venv)を作ってその中へインストールすれば、この制約を受けずに済みます。
229
+
230
+ Windows(PowerShell):
231
+
232
+ ```powershell
233
+ python -m venv .venv
234
+ .\.venv\Scripts\Activate.ps1
235
+ ```
236
+
237
+ macOS / Linux:
238
+
239
+ ```bash
240
+ python3 -m venv .venv
241
+ source .venv/bin/activate
242
+ ```
243
+
244
+ venv に入れた場合の注意:
245
+
246
+ - C3 の hook は `c3 run` で起動されるため、**Claude Code を起動するシェルでも同じ venv を activate してください**。activate されていないシェルから Claude Code を起動すると `c3` が見つからず、hook が動作しません
247
+ - venv を使わずグローバルに使いたい場合は [pipx](https://pipx.pypa.io/) が代替になります: `pipx install claude-code-conductor`
248
+ - インストール後は、**Claude Code を起動するのと同じシェルで** `c3 doctor` を実行し、`c3` が PATH で解決されることを確認してください(`c3 doctor` が検査するのは実行中シェルの PATH です。別のシェルで実行しても、Claude Code の hook が起動される文脈は再現できません)
249
+
226
250
  ### 手順(推奨: PyPI から)
227
251
 
228
252
  **1. C3 をインストールする**
@@ -231,6 +255,8 @@ C3 の `.claude/` はプロジェクトコードに一切触れません。既
231
255
  pip install claude-code-conductor
232
256
  ```
233
257
 
258
+ > このコマンドが PEP 668(externally-managed-environment)で拒否される場合や、インストール後に hook が動作しない場合は、[前提条件](#前提条件)の venv 手順(activate するシェルの注意点を含む)を参照してください。
259
+
234
260
  **2. プロジェクトに `.claude/` を展開する**
235
261
 
236
262
  ```bash
@@ -1,3 +1,3 @@
1
1
  """Claude Code Conductor (C3) - multi-agent orchestration framework for Claude Code."""
2
2
 
3
- __version__ = "2.74.0"
3
+ __version__ = "2.75.0"
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
+ import os
6
7
  import shutil
7
8
  import sys
8
9
  from collections.abc import Callable
@@ -181,28 +182,99 @@ def _do_git_init(target_root: Path) -> None:
181
182
  )
182
183
 
183
184
 
184
- def _copytree(src: Path, dst: Path, *, root: Path | None = None) -> int:
185
+ def _is_link_entry(entry: Path, real_root: str, rel: str) -> bool:
186
+ """FR-4 choke point: realpath 完全一致でリンク経由の entry を検出する。
187
+
188
+ ``entry.is_symlink()`` は NTFS ディレクトリジャンクションに ``False`` を返し
189
+ 素通りする(ADR-3・既知パターンの再発 5 回)ため採用しない。判定は
190
+ 「entry の realpath」と「コピー元ルートの realpath + ルートからの相対パス」の
191
+ 完全一致(``.claude/skills/start/scripts/archive_reports.py`` の
192
+ ``_archive_dir_is_contained`` と同じ realpath 封じ込め方式)。
193
+
194
+ ``real_root`` は**呼び出し側で解決済みの** コピー元ルートの realpath(文字列)を
195
+ 受け取る契約(ADR-3 追補 2・``_copytree`` が再帰境界で 1 回だけ計算して伝播する)。
196
+ 本関数は root 側の realpath を行わない。比較の両辺とも realpath 済みであることが
197
+ 判定の前提であり、その担保は呼び出し側の責務になる。
198
+
199
+ entry 側の realpath は ``OSError``(ELOOP・EACCES 等)を送出しうる。解決できない
200
+ entry は安全側に倒して「リンク疑い(``True``)」として扱い、呼び出し側の
201
+ 警告付きスキップに委ねる(fail-soft・ADR-3 追補 1)。
202
+ """
203
+ # expected は「rel の各コンポーネントをリンクとして解決せず」文字列結合した
204
+ # 期待パス。os.path.realpath はここでは呼ばない(呼ぶと entry 側と同じ
205
+ # リンクを辿ってしまい、判定そのものが無効化される)。
206
+ expected = os.path.normpath(os.path.join(real_root, *rel.split("/")))
207
+ try:
208
+ actual = os.path.realpath(entry)
209
+ except OSError:
210
+ return True
211
+ return actual != expected
212
+
213
+
214
+ def _copytree(
215
+ src: Path, dst: Path, *, root: Path | None = None, real_root: str | None = None
216
+ ) -> int:
185
217
  """Copy ``src`` -> ``dst`` recursively, skipping personal/working files.
186
218
 
187
219
  ``root`` defaults to ``src`` and represents the ``.claude/`` directory; the
188
220
  relative path from ``root`` is what ``should_skip`` matches against.
189
221
  Returns the number of regular files written.
222
+
223
+ ``real_root`` は ``root`` の realpath(解決済み文字列)。``None`` の初回呼び出しで
224
+ 1 回だけ計算し、再帰には計算済みの値を渡す(ADR-3 追補 2 の hoist。entry 総数 N 回の
225
+ 重複計算を 1 回に落とす)。
226
+
227
+ root 側 realpath の失敗(``OSError``)は捕捉せず伝播させる。root が解決不能なのは
228
+ テンプレート根本の異常であり、全 entry を「リンク疑い」として警告付きスキップし
229
+ 「0 件コピーの exit 0」を返すより、例外で止める方が利用者に正しく伝わるため
230
+ (per-entry の fail-soft はコピー継続に意味がある場合の設計であり、根本が解決不能なら
231
+ 継続に意味がない・ADR-3 追補 1)。
232
+
233
+ リンク判定 ``_is_link_entry`` の適用位置はファイル経路の 2 箇所のみ:
234
+ (i) ``should_skip`` 前段の初回判定 (ii) ``shutil.copy2`` 直前の再検証。
235
+ ディレクトリ側に再検証は追加しない。ディレクトリ entry が判定後にリンクへ
236
+ 差し替わっても、再帰先の per-entry 検査で全子 entry の realpath が
237
+ ``real_root`` + rel と一致しなくなり内容は漏れないため(構造的再検証・ADR-3 追補 3)。
190
238
  """
191
239
  if root is None:
192
240
  root = src
241
+ if real_root is None:
242
+ # root 側の realpath はここで 1 回だけ。失敗は上記のとおり伝播させる。
243
+ real_root = os.path.realpath(root)
193
244
  dst.mkdir(parents=True, exist_ok=True)
194
245
  count = 0
195
246
  for entry in src.iterdir():
196
247
  rel = entry.relative_to(root).as_posix()
197
248
  target = dst / entry.name
249
+ # FR-4: リンク経由の entry は is_dir()/is_file() 分岐より前にスキップする
250
+ # (両者ともリンクを暗黙に辿るため)。EXCLUDE 対象かどうかに関わらず
251
+ # 検査するため should_skip より前段に置く(架空の EXCLUDE リンクも警告する・
252
+ # architecture DC-GP-003 裁定)。
253
+ if _is_link_entry(entry, real_root, rel):
254
+ print(
255
+ f"c3 init: リンク(symlink/junction)疑いの entry をスキップしました: {rel}",
256
+ file=sys.stderr,
257
+ )
258
+ continue
198
259
  if entry.is_dir():
199
- count += _copytree(entry, target, root=root)
260
+ count += _copytree(entry, target, root=root, real_root=real_root)
200
261
  # Drop directories that ended up empty (everything inside was skipped).
201
262
  if not any(target.iterdir()):
202
263
  target.rmdir()
203
264
  elif entry.is_file():
204
265
  if should_skip(rel):
205
266
  continue
267
+ # 初回判定から copy2 到達までの間にリンクへ差し替わる TOCTOU を狭めるため、
268
+ # copy2 の直前で同じ choke point を再検証する(ADR-3 追補 3)。
269
+ # 残余リスク: 再検証と copy2 の間の窓は閉じない。窓を閉じる ``O_NOFOLLOW`` は
270
+ # POSIX 限定で Windows を含む配布対象では採用できないため不採用とし、
271
+ # 脅威モデル(ローカル開発者ツール)上この残余窓は許容する(ADR-3 追補 5)。
272
+ if _is_link_entry(entry, real_root, rel):
273
+ print(
274
+ f"c3 init: リンク(symlink/junction)疑いの entry をスキップしました: {rel}",
275
+ file=sys.stderr,
276
+ )
277
+ continue
206
278
  shutil.copy2(entry, target)
207
279
  count += 1
208
280
  return count
@@ -0,0 +1,57 @@
1
+ """
2
+ tests/conftest.py
3
+
4
+ pytest 共通セットアップ。
5
+ worktree の src/ を sys.path の最初に追加し、テストがworktree のコードを使用するようにする。
6
+ .claude/hooks/ も追加し、importlib 経由で stop.py / pre_compact.py を
7
+ ロードするテストが session_utils をインポートできるようにする。
8
+
9
+ リンク(symlink / junction)を実ファイルシステム上に作る共有ヘルパー ``_make_link``
10
+ もここに置く。利用側は ``from tests.conftest import _make_link`` の**絶対 import**
11
+ で参照する(tests/__init__.py が実在するパッケージ構成のため
12
+ ``from conftest import ...`` は ModuleNotFoundError になる)。
13
+ """
14
+ import os
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ import pytest
19
+
20
+ # Ensure worktree src is imported, not system-installed c3 package
21
+ worktree_root = Path(__file__).parent.parent
22
+ sys.path.insert(0, str(worktree_root / "src"))
23
+ sys.path.insert(1, str(worktree_root / ".claude" / "hooks"))
24
+
25
+
26
+ def _make_link(target: Path, link: Path) -> None:
27
+ """``link`` を ``target`` を指すリンクとして作る(OS 差を吸収する共有ヘルパー).
28
+
29
+ Windows は junction(``_winapi.CreateJunction``・管理者権限不要)、
30
+ POSIX は ``os.symlink`` を使う。作成に失敗した場合は skip せず fail させる
31
+ (skip すると「リンクを辿らない」という契約が全 OS で無検証になりうる)。
32
+
33
+ 呼び出し側が知っておくべき OS 差(実測・2026-08-15 / Windows 11):
34
+
35
+ - junction は ``Path.is_symlink()`` が **False** を返し、``Path.is_dir()`` は
36
+ **True** を返す(=素朴な ``is_symlink()`` ガードでは弾けない)
37
+ - junction は**ディレクトリ専用**。ファイルへのリンクは
38
+ ``os.symlink`` が必要で、非特権 Windows では ``WinError 1314``
39
+ (privilege not held) で失敗する
40
+ - 存在しないターゲットへの junction は ``FileNotFoundError`` で作れない
41
+ (dangling リンクのテストは POSIX 限定になる)
42
+
43
+ 元は tests/test_stop_agent_memory_warn.py のローカルヘルパーだったものを、
44
+ tests/test_cli_init_symlink_guard.py と共有するため conftest へ移設した。
45
+ """
46
+ if sys.platform == "win32":
47
+ try:
48
+ import _winapi
49
+
50
+ _winapi.CreateJunction(str(target), str(link))
51
+ except Exception as exc: # pragma: no cover - 環境不備時のみ
52
+ pytest.fail(f"junction の作成に失敗した(skip せず fail させる契約): {exc!r}")
53
+ else:
54
+ try:
55
+ os.symlink(str(target), str(link), target_is_directory=True)
56
+ except OSError as exc: # pragma: no cover - 環境不備時のみ
57
+ pytest.fail(f"symlink の作成に失敗した(skip せず fail させる契約): {exc!r}")
@@ -0,0 +1,197 @@
1
+ """[FR-3] .claude/hooks/statusline.py の reconfigure 保護に関する性質テスト。
2
+
3
+ 測るべき性質:
4
+ statusline.py はモジュールレベルで sys.stdout / sys.stderr / sys.stdin の
5
+ ``reconfigure(encoding='utf-8')`` を呼ぶ。``reconfigure`` 属性を持たない
6
+ ストリームオブジェクトが差し込まれている環境(pytest の ``DontReadFromInput``、
7
+ パイプ・ラッパ経由で差し替えられた擬似ストリーム等)でも、
8
+ **モジュールのロードが AttributeError でクラッシュしてはならない**。
9
+ すなわち CLAUDE.md §9-3 の canonical idiom
10
+ (``try: ... except AttributeError: pass``)による保護が入っていること。
11
+
12
+ Red の正体:
13
+ 無保護の現行実装(statusline.py:16-18)では、reconfigure を持たない
14
+ ストリーム注入下で exec_module が ``AttributeError`` を送出して落ちる。
15
+ 保護が入ると緑に反転する。AttributeError は握り潰さず伝播させ、
16
+ pytest の traceback に実際の例外型・メッセージが出るようにしている
17
+ (Red が「正しい理由で赤い」ことを実出力で確認できるようにするため)。
18
+
19
+ 意図的な設計上の制約(変更しないこと):
20
+ * モジュールのロードは ``importlib.util.spec_from_file_location`` +
21
+ ``exec_module`` で **毎回再実行** する。``import statusline`` は
22
+ tests/conftest.py:15 の sys.path 挿入で成立してしまうが、sys.modules
23
+ キャッシュにより 2 回目以降モジュール本体が再実行されず「空の緑」になる。
24
+ 前例は tests/hooks/test_statusline.py:38-53 の ``load_statusline()``。
25
+ * 注入するストリームは reconfigure 属性を持たない **実オブジェクト**
26
+ (``io.StringIO``)とする。``MagicMock`` は未定義属性を自動生成するため
27
+ 「reconfigure を持つ」ことになり、無保護実装でも赤にならない。
28
+ * 3 ストリームの差し替えは ``monkeypatch.setattr(sys, ...)`` でテスト内に閉じ、
29
+ teardown で確実に復元する。ambient なストリームには依存しない
30
+ (tests/test_statusline.py:18-21 は sys.stdin をグローバル置換したまま
31
+ 復元しないため、ambient に依存すると Red が Red にならない)。
32
+ * **reconfigure 呼び出しの「粒度」は敢えて検証しない。** CLAUDE.md §9-3 の
33
+ canonical idiom は 3 ストリームを 1 つの try ブロックで囲む形であり、
34
+ 「1 本壊れても残り 2 本は reconfigure されること」を要求すると
35
+ canonical idiom を弾いてしまう。要求するのは
36
+ 「クラッシュしないこと」+「対応ストリームには実際に適用されること」の 2 点のみ。
37
+ """
38
+
39
+ import importlib.util
40
+ import io
41
+ import itertools
42
+ import sys
43
+ import types
44
+ from pathlib import Path
45
+
46
+ import pytest
47
+
48
+ STATUSLINE_PATH = (
49
+ Path(__file__).parents[2]
50
+ / ".claude"
51
+ / "hooks"
52
+ / "statusline.py"
53
+ )
54
+
55
+ #: statusline.py がモジュールレベルで reconfigure を呼ぶ 3 ストリーム
56
+ STREAM_NAMES = ("stdin", "stdout", "stderr")
57
+
58
+ #: exec_module ごとにユニークなモジュール名を振り、キャッシュ経由の再利用を避ける
59
+ _load_counter = itertools.count()
60
+
61
+
62
+ def _stream_without_reconfigure() -> io.StringIO:
63
+ """``reconfigure`` を持たない実ストリームを返す。
64
+
65
+ テスト自体の有効性(この注入が無保護実装を確実に赤にすること)を
66
+ その場で表明しておく。将来 io.StringIO に reconfigure が生えたら、
67
+ サイレントに空の緑になるのではなくここで落ちる。
68
+ """
69
+ stream = io.StringIO()
70
+ assert not hasattr(stream, "reconfigure"), (
71
+ "io.StringIO が reconfigure を持つ実装では、この注入は無保護実装を"
72
+ "赤にできない(テストの検出力が失われている)"
73
+ )
74
+ return stream
75
+
76
+
77
+ def _stream_with_reconfigure() -> io.TextIOWrapper:
78
+ """``reconfigure`` を持ち、適用されたかを ``encoding`` で観測できる実ストリーム。
79
+
80
+ 初期 encoding は utf-8 以外(latin-1)にしておく。こうすることで
81
+ 「utf-8 になっている」ことが reconfigure が実際に呼ばれた証拠になる。
82
+ """
83
+ stream = io.TextIOWrapper(io.BytesIO(), encoding="latin-1")
84
+ assert hasattr(stream, "reconfigure")
85
+ assert stream.encoding != "utf-8"
86
+ return stream
87
+
88
+
89
+ def _exec_statusline() -> types.ModuleType:
90
+ """statusline.py を毎回新規に exec してモジュールオブジェクトを返す。
91
+
92
+ ``__name__`` にユニーク名を与えるため ``if __name__ == '__main__'`` 配下の
93
+ ``main()`` は起動しない(ロード自体は副作用を持たない)。
94
+ """
95
+ name = f"_statusline_reconfigure_probe_{next(_load_counter)}"
96
+ spec = importlib.util.spec_from_file_location(name, STATUSLINE_PATH)
97
+ assert spec is not None and spec.loader is not None
98
+ mod = importlib.util.module_from_spec(spec)
99
+ # module_from_spec は sys.modules に登録しない = 毎回本体が再実行される
100
+ assert name not in sys.modules
101
+ spec.loader.exec_module(mod)
102
+ return mod
103
+
104
+
105
+ def _assert_module_fully_executed(mod: types.ModuleType) -> None:
106
+ """モジュール本体が最後まで実行されたことを確認する。
107
+
108
+ ``main`` は statusline.py:136 の最終 def。これが存在することで
109
+ 「reconfigure 行で中断せず本体を最後まで通った」ことを担保する
110
+ (途中で例外を握り潰して打ち切る実装を空の緑にしないため)。
111
+ """
112
+ assert mod.MAX_INPUT == 64 * 1024
113
+ assert callable(mod.main)
114
+ assert callable(mod.render_output)
115
+ assert callable(mod.pct_color)
116
+
117
+
118
+ # ---------------------------------------------------------------------------
119
+ # 本命の性質: reconfigure 非対応ストリーム下でもロードがクラッシュしない
120
+ # ---------------------------------------------------------------------------
121
+
122
+ def test_module_loads_when_all_three_streams_lack_reconfigure(monkeypatch):
123
+ """3 ストリーム全てが reconfigure 非対応でも statusline のロードは成功する。
124
+
125
+ 無保護実装では exec_module が AttributeError で落ちる(Red)。
126
+ try/except AttributeError 保護が入ると緑に反転する。
127
+ """
128
+ for name in STREAM_NAMES:
129
+ monkeypatch.setattr(sys, name, _stream_without_reconfigure())
130
+
131
+ mod = _exec_statusline()
132
+
133
+ _assert_module_fully_executed(mod)
134
+
135
+
136
+ @pytest.mark.parametrize("broken", STREAM_NAMES)
137
+ def test_module_loads_when_single_stream_lacks_reconfigure(monkeypatch, broken):
138
+ """3 ストリームのうち 1 本だけが reconfigure 非対応でもロードは成功する。
139
+
140
+ 「どのストリームが欠けても落ちない」ことを 1 本ずつ独立に固定する。
141
+ 残り 2 本は reconfigure 対応の実オブジェクトにしておくことで、
142
+ 落ちる原因が対象ストリーム 1 本に限定されることを保証する。
143
+ """
144
+ for name in STREAM_NAMES:
145
+ if name == broken:
146
+ monkeypatch.setattr(sys, name, _stream_without_reconfigure())
147
+ else:
148
+ monkeypatch.setattr(sys, name, _stream_with_reconfigure())
149
+
150
+ mod = _exec_statusline()
151
+
152
+ _assert_module_fully_executed(mod)
153
+
154
+
155
+ # ---------------------------------------------------------------------------
156
+ # 反・空実装ガード: 保護を「reconfigure 呼び出しの削除」で通してはならない
157
+ # ---------------------------------------------------------------------------
158
+
159
+ def test_reconfigure_is_still_applied_when_streams_support_it(monkeypatch):
160
+ """reconfigure 対応ストリームには utf-8 が実際に適用される。
161
+
162
+ 本テストは保護導入前から緑であり、Red の一部ではない。
163
+ 「reconfigure 行を丸ごと削除する」という空実装で FR-3 を通す退行を防ぐ
164
+ 回帰ガードとして置く(CLAUDE.md §9-3 が reconfigure 自体を必須としており、
165
+ 削除は cp1252 環境での UnicodeEncodeError クラッシュを復活させる)。
166
+ """
167
+ streams = {}
168
+ for name in STREAM_NAMES:
169
+ stream = _stream_with_reconfigure()
170
+ monkeypatch.setattr(sys, name, stream)
171
+ streams[name] = stream
172
+
173
+ mod = _exec_statusline()
174
+
175
+ _assert_module_fully_executed(mod)
176
+ for name, stream in streams.items():
177
+ assert stream.encoding == "utf-8", (
178
+ f"sys.{name} が utf-8 に reconfigure されていない "
179
+ f"(encoding={stream.encoding!r})。reconfigure 呼び出しが"
180
+ "削除された可能性がある"
181
+ )
182
+
183
+
184
+ def test_broken_stream_injection_is_actually_detectable(monkeypatch):
185
+ """テスト媒体の有効性: 注入したストリームが reconfigure を持たないこと。
186
+
187
+ 本テストが緑である限り、上の 2 テストの Red は
188
+ 「reconfigure 属性の不在」に由来すると言い切れる。
189
+ MagicMock 注入に差し替えられた場合はここが落ちる。
190
+ """
191
+ for name in STREAM_NAMES:
192
+ monkeypatch.setattr(sys, name, _stream_without_reconfigure())
193
+
194
+ for name in STREAM_NAMES:
195
+ stream = getattr(sys, name)
196
+ assert isinstance(stream, io.StringIO)
197
+ assert not hasattr(stream, "reconfigure")