claude-code-conductor 2.55.0__tar.gz → 2.55.1__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 (239) hide show
  1. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/check_agent_invocation.py +4 -3
  2. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/patterns_guard.py +1 -0
  3. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/planner_check.py +4 -3
  4. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/post_tool.py +1 -0
  5. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/pre_compact.py +17 -4
  6. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/pre_tool.py +6 -2
  7. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/recall_autorebuild.py +3 -1
  8. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/report_contract_check.py +1 -0
  9. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/session_mode_watch.py +1 -0
  10. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/tier_autoapply.py +5 -3
  11. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/worktree_guard.py +6 -2
  12. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/CHANGELOG.md +20 -0
  13. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/PKG-INFO +1 -1
  14. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/__init__.py +1 -1
  15. claude_code_conductor-2.55.1/tests/test_hooks_stdin_idiom.py +357 -0
  16. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_session_mode_watch.py +142 -0
  17. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/CLAUDE.md +0 -0
  18. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/architect.md +0 -0
  19. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/code-reviewer.md +0 -0
  20. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/design-critic.md +0 -0
  21. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/developer.md +0 -0
  22. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/doc-writer.md +0 -0
  23. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/interviewer.md +0 -0
  24. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/planner.md +0 -0
  25. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/project-setup.md +0 -0
  26. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/security-reviewer.md +0 -0
  27. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/systematic-debugger.md +0 -0
  28. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/tester.md +0 -0
  29. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/wt_developer.md +0 -0
  30. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/wt_systematic-debugger.md +0 -0
  31. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/wt_tester.md +0 -0
  32. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/breaking-changes.txt +0 -0
  33. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/deletions.txt +0 -0
  34. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/docs/autonomous-mode-onboarding.md +0 -0
  35. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/docs/config-policy.md +0 -0
  36. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/docs/nul-boundary.md +0 -0
  37. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/docs/parallel-agents-setup.md +0 -0
  38. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/docs/platform-adapters.md +0 -0
  39. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/docs/settings.json.md +0 -0
  40. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/_hook_utils.py +0 -0
  41. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/consolidate_memory.py +0 -0
  42. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/permission_handler.py +0 -0
  43. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/permission_handler_toast.py +0 -0
  44. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/recall_inject.py +0 -0
  45. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/restore_session.py +0 -0
  46. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/select_tier.py +0 -0
  47. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/session_start.py +0 -0
  48. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/session_stop.py +0 -0
  49. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/session_utils.py +0 -0
  50. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/statusline.py +0 -0
  51. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/stop.py +0 -0
  52. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/tier_gap_check.py +0 -0
  53. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/memory/.gitkeep +0 -0
  54. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/permission_rules.json +0 -0
  55. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/rules/promoted/index.md +0 -0
  56. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/settings.json +0 -0
  57. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/autonomous-mode/SKILL.md +0 -0
  58. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/autonomous-mode/scripts/mode_line.py +0 -0
  59. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/brainstorm/SKILL.md +0 -0
  60. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/codex-review/SKILL.md +0 -0
  61. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/SKILL.md +0 -0
  62. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
  63. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/references/design-critic-rubric.md +0 -0
  64. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/references/design-rubric.md +0 -0
  65. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/references/interview-rubric.md +0 -0
  66. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
  67. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
  68. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/scripts/record_agent_outcome.py +0 -0
  69. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
  70. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
  71. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/develop/SKILL.md +0 -0
  72. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/doc/SKILL.md +0 -0
  73. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/extract-lib/SKILL.md +0 -0
  74. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/init-session/SKILL.md +0 -0
  75. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/init-session/scripts/session_guard.py +0 -0
  76. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/mcp-config/SKILL.md +0 -0
  77. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/parallel-agents/SKILL.md +0 -0
  78. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/pattern-status/SKILL.md +0 -0
  79. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/promote-pattern/SKILL.md +0 -0
  80. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/recall/SKILL.md +0 -0
  81. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/report-timestamp/SKILL.md +0 -0
  82. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
  83. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/review-phase/SKILL.md +0 -0
  84. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/setup/SKILL.md +0 -0
  85. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/setup/reference.md +0 -0
  86. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
  87. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
  88. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/start/SKILL.md +0 -0
  89. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/state/.gitkeep +0 -0
  90. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.gitignore +0 -0
  91. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/ARCHITECTURE.md +0 -0
  92. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/LICENSE +0 -0
  93. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/LICENSES/fastembed-LICENSE +0 -0
  94. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/LICENSES/fastembed-NOTICE +0 -0
  95. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/LICENSES/numpy-LICENSE +0 -0
  96. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/LICENSES/onnxruntime-LICENSE +0 -0
  97. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
  98. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/README.md +0 -0
  99. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/hatch_build.py +0 -0
  100. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/pyproject.toml +0 -0
  101. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/__main__.py +0 -0
  102. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/_db_params.py +0 -0
  103. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/_excludes.py +0 -0
  104. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/_terminal.py +0 -0
  105. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/adapters.py +0 -0
  106. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli.py +0 -0
  107. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_ask.py +0 -0
  108. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_doctor.py +0 -0
  109. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_init.py +0 -0
  110. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_list.py +0 -0
  111. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_metrics.py +0 -0
  112. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_plan.py +0 -0
  113. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_recall.py +0 -0
  114. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_run.py +0 -0
  115. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_tier.py +0 -0
  116. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_update.py +0 -0
  117. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/db.py +0 -0
  118. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/embedding.py +0 -0
  119. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/gitutil.py +0 -0
  120. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/mcp_server.py +0 -0
  121. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrate.py +0 -0
  122. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/001_initial.sql +0 -0
  123. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
  124. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/003_tier_cost.sql +0 -0
  125. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/004_agent_outcomes.sql +0 -0
  126. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/005_drop_agent_tier_bandit.sql +0 -0
  127. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/006_review_decisions_severity.sql +0 -0
  128. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/README.md +0 -0
  129. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/__init__.py +0 -0
  130. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/paths.py +0 -0
  131. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/plan_validator.py +0 -0
  132. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/platforms.py +0 -0
  133. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/pricing.py +0 -0
  134. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/question.py +0 -0
  135. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/recall_chunker.py +0 -0
  136. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/recall_index.py +0 -0
  137. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/usage_ingester.py +0 -0
  138. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/__init__.py +0 -0
  139. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/_pre_compact_helpers.py +0 -0
  140. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/conftest.py +0 -0
  141. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/fixtures/usage/README.md +0 -0
  142. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/fixtures/usage/mainline.jsonl +0 -0
  143. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
  144. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
  145. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/__init__.py +0 -0
  146. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_check_agent_invocation.py +0 -0
  147. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_consolidate_memory.py +0 -0
  148. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_hook_utils.py +0 -0
  149. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_permission_handler.py +0 -0
  150. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_permission_handler_toast.py +0 -0
  151. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
  152. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_planner_check.py +0 -0
  153. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_planner_check_dev.py +0 -0
  154. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_post_tool.py +0 -0
  155. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_pre_tool.py +0 -0
  156. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_recall_autorebuild.py +0 -0
  157. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_recall_inject.py +0 -0
  158. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_record_agent_outcome.py +0 -0
  159. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_record_review_decision.py +0 -0
  160. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_restore_session.py +0 -0
  161. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_review_hint_inject.py +0 -0
  162. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_select_tier.py +0 -0
  163. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_select_tier_escalation.py +0 -0
  164. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_session_start.py +0 -0
  165. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_session_stop.py +0 -0
  166. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_session_utils.py +0 -0
  167. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
  168. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_settings_tier_autoapply_registration.py +0 -0
  169. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_similarity_boost.py +0 -0
  170. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_statusline.py +0 -0
  171. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_statusline_template_sync.py +0 -0
  172. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_sync_check.py +0 -0
  173. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_template_guard.py +0 -0
  174. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_tier_autoapply.py +0 -0
  175. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_tier_gap_check.py +0 -0
  176. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_tier_security_fixes.py +0 -0
  177. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/__init__.py +0 -0
  178. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/_skill_helpers.py +0 -0
  179. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
  180. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_init_session_no_task_type.py +0 -0
  181. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_marker_compliance.py +0 -0
  182. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_parallel_agents_skill_tier.py +0 -0
  183. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_planner_lightweight.py +0 -0
  184. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_recall_skill.py +0 -0
  185. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_session_backlog_reconciliation.py +0 -0
  186. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_session_guard.py +0 -0
  187. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_setup_templates.py +0 -0
  188. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
  189. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_start_skill_new_flow.py +0 -0
  190. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
  191. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_adapters.py +0 -0
  192. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_check_deletions.py +0 -0
  193. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_ask.py +0 -0
  194. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_doctor.py +0 -0
  195. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_doctor_launcher.py +0 -0
  196. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_entry.py +0 -0
  197. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_init.py +0 -0
  198. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_list.py +0 -0
  199. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_metrics.py +0 -0
  200. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_plan.py +0 -0
  201. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_recall.py +0 -0
  202. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_run.py +0 -0
  203. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_tier.py +0 -0
  204. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_update_breaking_changes.py +0 -0
  205. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_update_deletions.py +0 -0
  206. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_db.py +0 -0
  207. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_docstring_consistency.py +0 -0
  208. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_embedding.py +0 -0
  209. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_excludes.py +0 -0
  210. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_extract_breaking_changes.py +0 -0
  211. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_gitutil.py +0 -0
  212. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_mcp_server_elicit.py +0 -0
  213. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_migrate.py +0 -0
  214. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_mode_line.py +0 -0
  215. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_no_bare_python_launcher.py +0 -0
  216. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_nul_boundary_lint.py +0 -0
  217. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_paths.py +0 -0
  218. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_patterns_guard.py +0 -0
  219. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_plan_validator.py +0 -0
  220. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_pre_compact.py +0 -0
  221. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_pre_tool_hook.py +0 -0
  222. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_precompact_additional.py +0 -0
  223. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_precompact_toctou_fixes.py +0 -0
  224. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_pricing.py +0 -0
  225. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_recall_chunker.py +0 -0
  226. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_recall_index.py +0 -0
  227. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_references_migration.py +0 -0
  228. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_report_contract_check.py +0 -0
  229. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_session_utils_additional.py +0 -0
  230. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_settings_hooks_registration.py +0 -0
  231. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_skill_no_builtin_conflict.py +0 -0
  232. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_statusline.py +0 -0
  233. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_stop_additional.py +0 -0
  234. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_stop_hook.py +0 -0
  235. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_stop_precompact_fixes.py +0 -0
  236. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_sync_template_stop.py +0 -0
  237. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_template_pre_tool_hook.py +0 -0
  238. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_usage_ingester.py +0 -0
  239. {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_worktree_guard.py +0 -0
@@ -41,6 +41,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
41
41
  from _hook_utils import write_debug_log # noqa: E402
42
42
 
43
43
  try:
44
+ sys.stdin.reconfigure(encoding="utf-8")
44
45
  sys.stdout.reconfigure(encoding="utf-8")
45
46
  sys.stderr.reconfigure(encoding="utf-8")
46
47
  except AttributeError:
@@ -53,8 +54,8 @@ REVIEWER_TYPES = frozenset({"code-reviewer", "security-reviewer"})
53
54
  PROJECT_ROOT = Path(__file__).resolve().parents[2]
54
55
  DEBUG_LOG_PATH = PROJECT_ROOT / ".claude" / "tmp" / "agent_hook_debug.log"
55
56
 
56
- # DoS 防御の読み取り上限(hook はローカル前提だが多層防御として明示する)。
57
- _STDIN_READ_LIMIT_BYTES = 1 * 1024 * 1024 # 1 MiB
57
+ # DoS 防御の読み取り上限(文字数ベース)。hook はローカル前提だが多層防御として明示する。
58
+ _STDIN_READ_LIMIT_CHARS = 1 * 1024 * 1024 # 1M chars
58
59
 
59
60
 
60
61
  def _escape_for_log(value: str) -> str:
@@ -64,7 +65,7 @@ def _escape_for_log(value: str) -> str:
64
65
 
65
66
  def main() -> None:
66
67
  try:
67
- payload = json.loads(sys.stdin.read(_STDIN_READ_LIMIT_BYTES))
68
+ payload = json.loads(sys.stdin.read(_STDIN_READ_LIMIT_CHARS))
68
69
  except (json.JSONDecodeError, ValueError):
69
70
  sys.exit(0)
70
71
 
@@ -30,6 +30,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
30
30
  from _hook_utils import sanitize_for_terminal # noqa: E402
31
31
 
32
32
  try:
33
+ sys.stdin.reconfigure(encoding='utf-8')
33
34
  sys.stdout.reconfigure(encoding='utf-8')
34
35
  sys.stderr.reconfigure(encoding='utf-8')
35
36
  except AttributeError:
@@ -41,6 +41,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
41
41
  from _hook_utils import write_debug_log # noqa: E402
42
42
 
43
43
  try:
44
+ sys.stdin.reconfigure(encoding="utf-8")
44
45
  sys.stdout.reconfigure(encoding="utf-8")
45
46
  sys.stderr.reconfigure(encoding="utf-8")
46
47
  except AttributeError:
@@ -58,8 +59,8 @@ except ImportError:
58
59
  PROJECT_ROOT = Path(__file__).resolve().parents[2]
59
60
  DEBUG_LOG_PATH = PROJECT_ROOT / ".claude" / "tmp" / "planner_check_debug.log"
60
61
 
61
- # DoS 防御の読み取り上限(hook はローカル前提だが多層防御として明示する)。
62
- _STDIN_READ_LIMIT_BYTES = 1 * 1024 * 1024 # 1 MiB
62
+ # DoS 防御の読み取り上限(stdin は文字数ベース)。hook はローカル前提だが多層防御として明示する。
63
+ _STDIN_READ_LIMIT_CHARS = 1 * 1024 * 1024 # 1M chars
63
64
  _FILE_READ_LIMIT_BYTES = 512 * 1024 # 512 KiB
64
65
 
65
66
 
@@ -230,7 +231,7 @@ def _check_r6_reviewer_absence(tasks: list) -> list[str]:
230
231
 
231
232
  def main() -> None:
232
233
  try:
233
- payload = json.loads(sys.stdin.read(_STDIN_READ_LIMIT_BYTES))
234
+ payload = json.loads(sys.stdin.read(_STDIN_READ_LIMIT_CHARS))
234
235
  except (json.JSONDecodeError, ValueError):
235
236
  sys.exit(0)
236
237
 
@@ -21,6 +21,7 @@ import re
21
21
  import sys
22
22
 
23
23
  try:
24
+ sys.stdin.reconfigure(encoding='utf-8')
24
25
  sys.stdout.reconfigure(encoding='utf-8')
25
26
  sys.stderr.reconfigure(encoding='utf-8')
26
27
  except AttributeError:
@@ -12,10 +12,23 @@ import re
12
12
  import sys
13
13
  from datetime import datetime, timedelta, timezone
14
14
 
15
- sys.stdout.reconfigure(encoding='utf-8')
16
- sys.stderr.reconfigure(encoding='utf-8')
17
-
18
- from session_utils import SESSION_JSON_MARKER, append_checkpoint, is_worktree, SESSIONS_DIR
15
+ try:
16
+ sys.stdin.reconfigure(encoding='utf-8')
17
+ sys.stdout.reconfigure(encoding='utf-8')
18
+ sys.stderr.reconfigure(encoding='utf-8')
19
+ except AttributeError:
20
+ pass
21
+
22
+ from session_utils import append_checkpoint, is_worktree, SESSIONS_DIR
23
+ # SESSION_JSON_MARKER は本ファイルのロジックでは未参照だが、過去レビュー(Code Medium-3a)の
24
+ # 契約として module-level 定数の存在を tests/test_stop_precompact_fixes.py が要求する再エクスポート。
25
+ # 未使用に見えても削除しないこと。
26
+ # `# noqa: F401` は flake8/ruff 固有の規約で生の pyflakes は解釈しないため、
27
+ # 代わりに __all__ へ列挙して re-export であることを明示する(pyflakes は
28
+ # __all__ 掲載名を使用済みとみなすため、警告が実際に抑制される)。
29
+ from session_utils import SESSION_JSON_MARKER
30
+
31
+ __all__ = ["SESSION_JSON_MARKER"]
19
32
 
20
33
 
21
34
  # デバウンス窓幅(秒)。直近の PreCompact checkpoint からこの秒数以内の再実行は
@@ -7,8 +7,12 @@ import re
7
7
  import shlex
8
8
  import sys
9
9
 
10
- sys.stdout.reconfigure(encoding='utf-8')
11
- sys.stderr.reconfigure(encoding='utf-8')
10
+ try:
11
+ sys.stdin.reconfigure(encoding='utf-8')
12
+ sys.stdout.reconfigure(encoding='utf-8')
13
+ sys.stderr.reconfigure(encoding='utf-8')
14
+ except AttributeError:
15
+ pass
12
16
 
13
17
 
14
18
  def _is_rm_rf(tokens: list[str], rm_idx: int) -> bool:
@@ -23,8 +23,10 @@ import sys
23
23
  import time
24
24
  from pathlib import Path
25
25
 
26
- # CLAUDE.md §9-3: reconfigure stdout/stderr to UTF-8 on Windows.
26
+ # CLAUDE.md §9-3: reconfigure stdin/stdout/stderr to UTF-8 on Windows.
27
27
  try:
28
+ if sys.stdin and hasattr(sys.stdin, "reconfigure"):
29
+ sys.stdin.reconfigure(encoding="utf-8")
28
30
  if sys.stdout and hasattr(sys.stdout, "reconfigure"):
29
31
  sys.stdout.reconfigure(encoding="utf-8")
30
32
  if sys.stderr and hasattr(sys.stderr, "reconfigure"):
@@ -38,6 +38,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
38
38
  from _hook_utils import norm_component, sanitize_for_terminal # noqa: E402
39
39
 
40
40
  try:
41
+ sys.stdin.reconfigure(encoding='utf-8')
41
42
  sys.stdout.reconfigure(encoding='utf-8')
42
43
  sys.stderr.reconfigure(encoding='utf-8')
43
44
  except AttributeError:
@@ -52,6 +52,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
52
52
  from _hook_utils import norm_component, sanitize_for_terminal # noqa: E402
53
53
 
54
54
  try:
55
+ sys.stdin.reconfigure(encoding='utf-8')
55
56
  sys.stdout.reconfigure(encoding='utf-8')
56
57
  sys.stderr.reconfigure(encoding='utf-8')
57
58
  except AttributeError:
@@ -106,8 +106,10 @@ _VALID_TIERS = ("haiku", "sonnet", "opus")
106
106
 
107
107
  KILL_SWITCH_ENV = "C3_TIER_AUTOAPPLY_DISABLE"
108
108
 
109
- # DoS 防御の stdin 読み取り上限(check_agent_invocation.py と同一)。
110
- _STDIN_READ_LIMIT_BYTES = 1 * 1024 * 1024 # 1 MiB
109
+ # DoS 防御の stdin 読み取り上限(文字数ベース・check_agent_invocation.py と同一)。
110
+ # sys.stdin はテキストストリームのため read(n) の n は文字数であってバイト数ではない
111
+ # (UTF-8 マルチバイト文字では実バイト量が最大 4 倍になり得る)。
112
+ _STDIN_READ_LIMIT_CHARS = 1 * 1024 * 1024 # 1M chars
111
113
 
112
114
  # applied-state のローテーション閾値(§3-4)。
113
115
  _MAX_JSONL_BYTES = 1 * 1024 * 1024 # 1 MB
@@ -371,7 +373,7 @@ def main() -> None:
371
373
  sys.exit(0)
372
374
 
373
375
  try:
374
- payload = json.loads(sys.stdin.read(_STDIN_READ_LIMIT_BYTES))
376
+ payload = json.loads(sys.stdin.read(_STDIN_READ_LIMIT_CHARS))
375
377
  except (json.JSONDecodeError, ValueError):
376
378
  sys.exit(0)
377
379
  if not isinstance(payload, dict):
@@ -16,8 +16,12 @@ import os
16
16
  import re
17
17
  import sys
18
18
 
19
- sys.stdout.reconfigure(encoding='utf-8')
20
- sys.stderr.reconfigure(encoding='utf-8')
19
+ try:
20
+ sys.stdin.reconfigure(encoding='utf-8')
21
+ sys.stdout.reconfigure(encoding='utf-8')
22
+ sys.stderr.reconfigure(encoding='utf-8')
23
+ except AttributeError:
24
+ pass
21
25
 
22
26
  # worktree パスの識別に使うコンポーネント名。
23
27
  # `.claude/worktrees/agent-<id>/` という構造を前提とし、
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.55.1] - 2026-07-26
4
+
5
+ ### 修正
6
+
7
+ - **hook stdin cp932 defect の修正(stdin UTF-8 reconfigure 横断適用)**: v2.55.0 の `session_mode_watch.py` が実機の Edit 発火で沈黙する defect を修正。Windows ネイティブ Python では hook の stdin が cp932 既定でデコードされるため、ハーネスが送る UTF-8 JSON payload 中の日本語(`モード:` 行)が mojibake となり `^モード: 自律` 正規表現が不一致→モード行変更を一切検知できていなかった(実機発火スモークで確定・同一 payload の素パイプ再現と `PYTHONIOENCODING=utf-8` 対照実験で原因を実測特定)。stdin reconfigure が欠落していた 10 hook(新規 3 本 + 既存 7 本: session_mode_watch / patterns_guard / report_contract_check / check_agent_invocation / planner_check / post_tool / pre_tool / pre_compact / worktree_guard / recall_autorebuild)へ `sys.stdin.reconfigure(encoding='utf-8')` を追加し、既存 12 hook のイディオムへ統一(canonical 順序 stdin → stdout → stderr・素の呼び出し 3 ファイルは try/except AttributeError ラップ化=crash-to-fail-open リスクの軽減方向)
8
+
9
+ ### 追加
10
+
11
+ - **cp932 実バイトリグレッションテスト**: `PYTHONIOENCODING=cp932` を強制した subprocess に `ensure_ascii=False` の生 UTF-8 payload をパイプし、Windows ネイティブ既定の欠陥環境を CI の Linux 上でも決定論的に再現するテストを `tests/test_session_mode_watch.py` に追加(ASCII エスケープ payload では defect を再現できないことが既存テスト素通しの真因だった)
12
+ - **静的イディオム検査 `tests/test_hooks_stdin_idiom.py` 新設**: `.claude/hooks/*.py` を走査し「`sys.stdin` を読む hook は stdin reconfigure 必須」を機械強制する。検出は純粋 AST 判定(reconfigure receiver 除外の 2 周走査)でコメント/docstring 内の文字列による偽陽性・誤検出なし。アンダースコア始まりファイルも検査対象
13
+
14
+ ### 変更
15
+
16
+ - **`_STDIN_READ_LIMIT_BYTES` → `_STDIN_READ_LIMIT_CHARS` 改名**(check_agent_invocation / planner_check / tier_autoapply): `sys.stdin.read(n)` の n は文字数であり命名のバイト数示唆と実際の意味論の乖離を解消(読み取り方式は不変・挙動変更なし)
17
+ - **`pre_compact.py` の `SESSION_JSON_MARKER` 再エクスポートを `__all__` 明示化**: 過去レビュー(Code Medium-3a)の契約再エクスポートである旨のコメントを付し、生 pyflakes でも未使用 import 警告が出ない形へ整理
18
+
19
+ ### 後方互換
20
+
21
+ - **破壊的変更なし**。修正は「stdin を UTF-8 として読む」の 1 点で、fail-open 挙動・payload 処理ロジック・ブロック/警告の判定条件は不変。修正前に誤って沈黙していた検知(session_mode_watch のモード行監視)が正しく発火するようになる方向のみの変更
22
+
3
23
  ## [2.55.0] - 2026-07-26
4
24
 
5
25
  ### 追加
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-conductor
3
- Version: 2.55.0
3
+ Version: 2.55.1
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.55.0"
3
+ __version__ = "2.55.1"
@@ -0,0 +1,357 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ test_hooks_stdin_idiom.py: Static analysis test for stdin reconfigure idiom in hooks.
4
+
5
+ Tests that all .claude/hooks/*.py files that use sys.stdin also contain a
6
+ sys.stdin.reconfigure(encoding='utf-8') call (or equivalent reconfigure with
7
+ encoding parameter), following the idiom established in CLAUDE.md §9-3.
8
+
9
+ ## Detection Strategy (AST-based)
10
+
11
+ 1. **sys.stdin Usage Detection (AST)**:
12
+ - Walks the AST for ast.Attribute nodes matching sys -> stdin
13
+ (Name "sys" / Attribute "stdin").
14
+ - Detects patterns: sys.stdin.read(), sys.stdin.readline(), json.load(sys.stdin), etc.
15
+ - Excludes the single sys.stdin node that is the receiver of a
16
+ sys.stdin.reconfigure(...) call (i.e. the func chain of an ast.Call):
17
+ configuring the stream is setup, not "usage". Every other sys.stdin node
18
+ counts as usage.
19
+ - No raw-source regex is involved at any stage, so comments and docstrings
20
+ cannot influence the result (see section 3).
21
+
22
+ 2. **Reconfigure Detection (AST)**:
23
+ - Walks the AST for an ast.Call whose func is the attribute chain
24
+ sys -> stdin -> reconfigure (Name "sys" / Attribute "stdin" /
25
+ Attribute "reconfigure") and which carries an `encoding=` keyword argument.
26
+ - Does NOT require try/except context (though production idiom uses it):
27
+ the visitor walks the whole tree regardless of control flow, so both the
28
+ `try/except AttributeError` idiom and the `if sys.stdin and hasattr(...)`
29
+ idiom are detected.
30
+ - Quote style of the encoding value is irrelevant, since the check inspects
31
+ the parsed keyword argument rather than raw source text.
32
+
33
+ 3. **Comments and Docstrings**:
34
+ - Both detections are AST-based, so comments and docstrings are excluded from
35
+ the detection surface automatically: comments never enter the AST at all,
36
+ and a docstring is a plain string constant, not an ast.Call. A file that
37
+ merely *mentions* sys.stdin.reconfigure(encoding='utf-8') in a comment or
38
+ docstring is therefore correctly reported as "reconfigure missing".
39
+
40
+ ## Known Limitations
41
+
42
+ - Does not validate that reconfigure is actually reached at runtime
43
+ (e.g., if it's in an if-block that never runs). Static detection is
44
+ best-effort; behavioral verification is in ADR-3 subprocess tests.
45
+ - A guard expression such as `if sys.stdin and hasattr(sys.stdin, 'reconfigure')`
46
+ counts as usage, because only the reconfigure call's own receiver node is
47
+ excluded. This is conservative-safe: a file carrying that guard also calls
48
+ reconfigure, so the idiom check still passes.
49
+ - Does not check if stdin.buffer or other alternatives are used
50
+ (acceptable per ADR-2: stdin.reconfigure is the standard idiom).
51
+ """
52
+
53
+ import ast
54
+ from pathlib import Path
55
+
56
+ import pytest
57
+
58
+
59
+ def get_hooks_dir() -> Path:
60
+ """Return the .claude/hooks directory path."""
61
+ project_root = Path(__file__).parent.parent
62
+ return project_root / ".claude" / "hooks"
63
+
64
+
65
+ def _is_sys_stdin(node: ast.AST) -> bool:
66
+ """Return True if node is the attribute expression ``sys.stdin``."""
67
+ return (
68
+ isinstance(node, ast.Attribute)
69
+ and node.attr == "stdin"
70
+ and isinstance(node.value, ast.Name)
71
+ and node.value.id == "sys"
72
+ )
73
+
74
+
75
+ def extract_stdin_refs(source: str, filename: str) -> bool:
76
+ """
77
+ Check if source code uses sys.stdin.
78
+
79
+ Returns True if sys.stdin is referenced in a way that requires reading
80
+ (not just in reconfigure calls).
81
+
82
+ Detection is purely AST-based: comments and docstrings never contribute to
83
+ the result, because comments do not enter the AST at all and a docstring is
84
+ a plain string constant rather than an attribute expression.
85
+
86
+ Args:
87
+ source: Python source code as string
88
+ filename: Name of file (for error reporting)
89
+
90
+ Returns:
91
+ bool: True if sys.stdin usage is detected
92
+ """
93
+ try:
94
+ tree = ast.parse(source)
95
+ except SyntaxError as e:
96
+ print(f" [SYNTAX ERROR] {filename}: {e}")
97
+ return False
98
+
99
+ # sys.stdin nodes acting as the receiver of a sys.stdin.reconfigure(...)
100
+ # call. Configuring the stream is setup, not usage, so they are excluded.
101
+ # `tree` stays alive for the whole function, so node identities are stable.
102
+ reconfigure_receivers = set()
103
+ for node in ast.walk(tree):
104
+ if (
105
+ isinstance(node, ast.Call)
106
+ and isinstance(node.func, ast.Attribute)
107
+ and node.func.attr == "reconfigure"
108
+ and _is_sys_stdin(node.func.value)
109
+ ):
110
+ reconfigure_receivers.add(id(node.func.value))
111
+
112
+ for node in ast.walk(tree):
113
+ if _is_sys_stdin(node) and id(node) not in reconfigure_receivers:
114
+ return True
115
+
116
+ return False
117
+
118
+
119
+ def has_stdin_reconfigure(source: str) -> bool:
120
+ """
121
+ Check if source code contains sys.stdin.reconfigure(encoding=...).
122
+
123
+ Uses AST-based detection to avoid false positives from comments/docstrings.
124
+
125
+ Args:
126
+ source: Python source code as string
127
+
128
+ Returns:
129
+ bool: True if reconfigure call with encoding parameter found
130
+ """
131
+ try:
132
+ tree = ast.parse(source)
133
+ except SyntaxError:
134
+ return False
135
+
136
+ class ReconfigureVisitor(ast.NodeVisitor):
137
+ def __init__(self):
138
+ self.found_reconfigure = False
139
+
140
+ def visit_Call(self, node: ast.Call) -> None:
141
+ # Check for sys.stdin.reconfigure(...) pattern
142
+ if (
143
+ isinstance(node.func, ast.Attribute)
144
+ and node.func.attr == "reconfigure"
145
+ and isinstance(node.func.value, ast.Attribute)
146
+ and node.func.value.attr == "stdin"
147
+ and isinstance(node.func.value.value, ast.Name)
148
+ and node.func.value.value.id == "sys"
149
+ ):
150
+ # Found sys.stdin.reconfigure call. Check for encoding keyword argument.
151
+ for keyword in node.keywords:
152
+ if keyword.arg == "encoding":
153
+ self.found_reconfigure = True
154
+ return
155
+ self.generic_visit(node)
156
+
157
+ visitor = ReconfigureVisitor()
158
+ visitor.visit(tree)
159
+ return visitor.found_reconfigure
160
+
161
+
162
+ def check_hook_file(hook_path: Path) -> tuple[bool, str]:
163
+ """
164
+ Check a single hook file for stdin reconfigure idiom.
165
+
166
+ Args:
167
+ hook_path: Path to hook .py file
168
+
169
+ Returns:
170
+ (passed, message): bool indicating pass/fail and explanatory message
171
+ """
172
+ try:
173
+ source = hook_path.read_text(encoding="utf-8")
174
+ except Exception as e:
175
+ return False, f"Failed to read: {e}"
176
+
177
+ uses_stdin = extract_stdin_refs(source, hook_path.name)
178
+ has_reconfigure = has_stdin_reconfigure(source)
179
+
180
+ if uses_stdin and not has_reconfigure:
181
+ return False, "Uses sys.stdin but missing reconfigure(encoding=...)"
182
+ elif uses_stdin and has_reconfigure:
183
+ return True, "OK: uses sys.stdin and has reconfigure"
184
+ else:
185
+ return True, "OK: does not use sys.stdin"
186
+
187
+
188
+ def test_hooks_stdin_idiom():
189
+ """
190
+ Test that all hooks using sys.stdin have stdin.reconfigure(encoding='utf-8').
191
+ """
192
+ hooks_dir = get_hooks_dir()
193
+
194
+ if not hooks_dir.exists():
195
+ raise RuntimeError(f"Hooks directory not found: {hooks_dir}")
196
+
197
+ hook_files = sorted(hooks_dir.glob("*.py"))
198
+
199
+ if not hook_files:
200
+ raise RuntimeError(f"No .py files found in {hooks_dir}")
201
+
202
+ print(f"\n=== Scanning {len(hook_files)} hook files ===")
203
+ print(f"Directory: {hooks_dir}\n")
204
+
205
+ failures = []
206
+ passes = []
207
+
208
+ for hook_path in hook_files:
209
+ # Check all hook files including private modules (_hook_utils.py, etc.)
210
+ # using the same reconfigure detection logic.
211
+ passed, message = check_hook_file(hook_path)
212
+
213
+ status = "PASS" if passed else "FAIL"
214
+ print(f"[{status}] {hook_path.name}: {message}")
215
+
216
+ if not passed:
217
+ failures.append((hook_path.name, message))
218
+ else:
219
+ passes.append(hook_path.name)
220
+
221
+ print("\n=== Summary ===")
222
+ print(f"Passed: {len(passes)}")
223
+ print(f"Failed: {len(failures)}")
224
+
225
+ if failures:
226
+ print("\n=== Failed hooks ===")
227
+ for name, msg in failures:
228
+ print(f" {name}: {msg}")
229
+
230
+ # Construct assertion message with failing file names
231
+ failing_files = ", ".join(name for name, _ in failures)
232
+ raise AssertionError(
233
+ f"stdin idiom check failed for {len(failures)} hook(s): {failing_files}"
234
+ )
235
+
236
+
237
+ # 以下は has_stdin_reconfigure() / extract_stdin_refs() の AST ベース判定そのものを
238
+ # 対象にした単体テスト。上の test_hooks_stdin_idiom() は実 hook ファイルを走査するため、
239
+ # 「コメント/docstring 内の文字列を検出しない」という偽陽性排除の効果は(そのような hook が
240
+ # 存在しないため)検証されない。正規表現ベースへの回帰を検知する回帰防止テストとして分離する。
241
+
242
+ # コメント内にのみ reconfigure が現れる負例(実コードでは stdin を読むだけ)
243
+ _SRC_RECONFIGURE_IN_COMMENT_ONLY = """\
244
+ import sys
245
+
246
+ # sys.stdin.reconfigure(encoding='utf-8')
247
+ data = sys.stdin.read()
248
+ """
249
+
250
+ # docstring 内にのみ reconfigure が現れる負例
251
+ _SRC_RECONFIGURE_IN_DOCSTRING_ONLY = '''\
252
+ """Hook that should call sys.stdin.reconfigure(encoding='utf-8') but does not."""
253
+
254
+ import sys
255
+
256
+ data = sys.stdin.read()
257
+ '''
258
+
259
+ # 本番 idiom その 1: try/except AttributeError
260
+ _SRC_RECONFIGURE_TRY_EXCEPT = """\
261
+ import sys
262
+
263
+ try:
264
+ sys.stdin.reconfigure(encoding='utf-8')
265
+ except AttributeError:
266
+ pass
267
+
268
+ data = sys.stdin.read()
269
+ """
270
+
271
+ # 本番 idiom その 2: if hasattr(recall_autorebuild.py 系)
272
+ _SRC_RECONFIGURE_IF_HASATTR = """\
273
+ import sys
274
+
275
+ if sys.stdin and hasattr(sys.stdin, 'reconfigure'):
276
+ sys.stdin.reconfigure(encoding='utf-8')
277
+
278
+ data = sys.stdin.read()
279
+ """
280
+
281
+ # stdin を一切使わないソース
282
+ _SRC_NO_STDIN = """\
283
+ import sys
284
+
285
+ print(sys.argv)
286
+ """
287
+
288
+ # extract_stdin_refs の「実使用検出」用の正例。実コードで sys.stdin.read() を呼ぶ。
289
+ _SRC_REAL_STDIN_READ = """\
290
+ import sys
291
+
292
+ data = sys.stdin.read()
293
+ print(data)
294
+ """
295
+
296
+ # CR-r3 finding #1 の反証シナリオ: 実コードは reconfigure 呼び出しのみで stdin を読まず、
297
+ # コメントに「.reconfigure で終わらない」sys.stdin 言及がある負例。
298
+ # 旧実装(AST ゲート後に生ソース全体へ regex フォールバック)では usage あり(True)と
299
+ # 誤検出していた。純粋 AST 判定への回帰防止テスト。
300
+ _SRC_RECONFIGURE_ONLY_WITH_STDIN_COMMENT = """\
301
+ import sys
302
+
303
+ try:
304
+ sys.stdin.reconfigure(encoding='utf-8')
305
+ except AttributeError:
306
+ pass
307
+
308
+ # NOTE: some tools call sys.stdin.read() directly instead
309
+ print("done")
310
+ """
311
+
312
+
313
+ @pytest.mark.parametrize(
314
+ "source, expected",
315
+ [
316
+ pytest.param(
317
+ _SRC_RECONFIGURE_IN_COMMENT_ONLY, False, id="comment-only-is-not-detected"
318
+ ),
319
+ pytest.param(
320
+ _SRC_RECONFIGURE_IN_DOCSTRING_ONLY,
321
+ False,
322
+ id="docstring-only-is-not-detected",
323
+ ),
324
+ pytest.param(_SRC_RECONFIGURE_TRY_EXCEPT, True, id="try-except-idiom"),
325
+ pytest.param(_SRC_RECONFIGURE_IF_HASATTR, True, id="if-hasattr-idiom"),
326
+ ],
327
+ )
328
+ def test_has_stdin_reconfigure_ignores_comments_and_docstrings(source, expected):
329
+ """AST ベース判定がコメント/docstring 内の文字列を検出しないことを検証する。"""
330
+ assert has_stdin_reconfigure(source) is expected
331
+
332
+
333
+ @pytest.mark.parametrize(
334
+ "source, expected",
335
+ [
336
+ pytest.param(_SRC_NO_STDIN, False, id="no-stdin-usage"),
337
+ pytest.param(_SRC_REAL_STDIN_READ, True, id="real-stdin-read-usage"),
338
+ pytest.param(
339
+ _SRC_RECONFIGURE_ONLY_WITH_STDIN_COMMENT,
340
+ False,
341
+ id="reconfigure-only-with-stdin-comment-is-not-usage",
342
+ ),
343
+ ],
344
+ )
345
+ def test_extract_stdin_refs_detects_only_real_usage(source, expected):
346
+ """extract_stdin_refs() が実コード上の stdin 使用のみを検出することを検証する。
347
+
348
+ - stdin を使わないソースでは False
349
+ - 実際に sys.stdin.read() を呼ぶソースでは True
350
+ - reconfigure 呼び出しのみ+コメント内の sys.stdin 言及では False
351
+ (純粋 AST 判定であり、コメントは検出面に入らない)
352
+ """
353
+ assert extract_stdin_refs(source, "synthetic.py") is expected
354
+
355
+
356
+ if __name__ == "__main__":
357
+ test_hooks_stdin_idiom()