claude-code-conductor 2.71.0__tar.gz → 2.71.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 (272) hide show
  1. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/CHANGELOG.md +24 -0
  2. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/PKG-INFO +1 -1
  3. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/__init__.py +1 -1
  4. claude_code_conductor-2.71.1/src/c3/_terminal.py +118 -0
  5. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/cli_ask.py +11 -3
  6. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/question.py +22 -2
  7. claude_code_conductor-2.71.1/tests/test_cli_ask.py +793 -0
  8. claude_code_conductor-2.71.0/src/c3/_terminal.py +0 -64
  9. claude_code_conductor-2.71.0/tests/test_cli_ask.py +0 -349
  10. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/.gitignore +0 -0
  11. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/CLAUDE.md +0 -0
  12. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/architect.md +0 -0
  13. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/code-reviewer.md +0 -0
  14. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/design-critic.md +0 -0
  15. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/developer.md +0 -0
  16. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/doc-writer.md +0 -0
  17. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/interviewer.md +0 -0
  18. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/planner.md +0 -0
  19. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/project-setup.md +0 -0
  20. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/security-reviewer.md +0 -0
  21. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/systematic-debugger.md +0 -0
  22. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/tester.md +0 -0
  23. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/wt_developer.md +0 -0
  24. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/wt_systematic-debugger.md +0 -0
  25. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/agents/wt_tester.md +0 -0
  26. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/breaking-changes.txt +0 -0
  27. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/deletions.txt +0 -0
  28. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/docs/autonomous-mode-onboarding.md +0 -0
  29. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/docs/config-policy.md +0 -0
  30. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/docs/nul-boundary.md +0 -0
  31. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/docs/parallel-agents-setup.md +0 -0
  32. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/docs/platform-adapters.md +0 -0
  33. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/docs/settings.json.md +0 -0
  34. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/_hook_utils.py +0 -0
  35. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/check_agent_invocation.py +0 -0
  36. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/consolidate_memory.py +0 -0
  37. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/patterns_guard.py +0 -0
  38. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/permission_handler.py +0 -0
  39. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/permission_handler_toast.py +0 -0
  40. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/planner_check.py +0 -0
  41. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/post_tool.py +0 -0
  42. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/pre_compact.py +0 -0
  43. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/pre_tool.py +0 -0
  44. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/recall_autorebuild.py +0 -0
  45. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/recall_inject.py +0 -0
  46. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/report_contract_check.py +0 -0
  47. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/report_path_guard.py +0 -0
  48. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/restore_session.py +0 -0
  49. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/select_tier.py +0 -0
  50. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/session_mode_watch.py +0 -0
  51. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/session_start.py +0 -0
  52. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/session_stop.py +0 -0
  53. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/session_utils.py +0 -0
  54. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/statusline.py +0 -0
  55. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/stop.py +0 -0
  56. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/tier_autoapply.py +0 -0
  57. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/tier_gap_check.py +0 -0
  58. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/hooks/worktree_guard.py +0 -0
  59. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/memory/.gitkeep +0 -0
  60. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/permission_rules.json +0 -0
  61. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/rules/judgment-principles.md +0 -0
  62. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/rules/promoted/index.md +0 -0
  63. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/settings.json +0 -0
  64. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/autonomous-mode/SKILL.md +0 -0
  65. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/autonomous-mode/scripts/mode_line.py +0 -0
  66. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/brainstorm/SKILL.md +0 -0
  67. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/codex-review/SKILL.md +0 -0
  68. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/SKILL.md +0 -0
  69. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/references/c3-omission-declaration.md +0 -0
  70. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
  71. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/references/design-critic-rubric.md +0 -0
  72. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/references/design-rubric.md +0 -0
  73. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/references/interview-rubric.md +0 -0
  74. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
  75. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/references/reachability-map.md +0 -0
  76. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/references/record-protocol.md +0 -0
  77. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
  78. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/scripts/detect_execution_verification.py +0 -0
  79. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/scripts/record_agent_outcome.py +0 -0
  80. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
  81. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
  82. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/develop/SKILL.md +0 -0
  83. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/doc/SKILL.md +0 -0
  84. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/extract-lib/SKILL.md +0 -0
  85. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/init-session/SKILL.md +0 -0
  86. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/init-session/scripts/session_guard.py +0 -0
  87. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/mcp-config/SKILL.md +0 -0
  88. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/parallel-agents/SKILL.md +0 -0
  89. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/pattern-status/SKILL.md +0 -0
  90. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/promote-pattern/SKILL.md +0 -0
  91. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/recall/SKILL.md +0 -0
  92. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/report-timestamp/SKILL.md +0 -0
  93. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
  94. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/review-phase/SKILL.md +0 -0
  95. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/setup/SKILL.md +0 -0
  96. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/setup/reference.md +0 -0
  97. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
  98. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
  99. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/start/SKILL.md +0 -0
  100. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/skills/start/scripts/archive_reports.py +0 -0
  101. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.claude/state/.gitkeep +0 -0
  102. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/.gitignore +0 -0
  103. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/ARCHITECTURE.md +0 -0
  104. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/LICENSE +0 -0
  105. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/LICENSES/fastembed-LICENSE +0 -0
  106. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/LICENSES/fastembed-NOTICE +0 -0
  107. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/LICENSES/numpy-LICENSE +0 -0
  108. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/LICENSES/onnxruntime-LICENSE +0 -0
  109. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
  110. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/README.md +0 -0
  111. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/hatch_build.py +0 -0
  112. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/pyproject.toml +0 -0
  113. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/__main__.py +0 -0
  114. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/_db_params.py +0 -0
  115. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/_excludes.py +0 -0
  116. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/adapters.py +0 -0
  117. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/cli.py +0 -0
  118. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/cli_doctor.py +0 -0
  119. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/cli_init.py +0 -0
  120. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/cli_list.py +0 -0
  121. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/cli_metrics.py +0 -0
  122. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/cli_plan.py +0 -0
  123. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/cli_recall.py +0 -0
  124. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/cli_run.py +0 -0
  125. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/cli_tier.py +0 -0
  126. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/cli_update.py +0 -0
  127. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/db.py +0 -0
  128. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/embedding.py +0 -0
  129. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/gitutil.py +0 -0
  130. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/mcp_server.py +0 -0
  131. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/migrate.py +0 -0
  132. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/migrations/001_initial.sql +0 -0
  133. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
  134. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/migrations/003_tier_cost.sql +0 -0
  135. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/migrations/004_agent_outcomes.sql +0 -0
  136. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/migrations/005_drop_agent_tier_bandit.sql +0 -0
  137. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/migrations/006_review_decisions_severity.sql +0 -0
  138. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/migrations/007_review_decisions_resolution.sql +0 -0
  139. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/migrations/008_drop_agent_runs.sql +0 -0
  140. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/migrations/README.md +0 -0
  141. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/migrations/__init__.py +0 -0
  142. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/paths.py +0 -0
  143. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/plan_validator.py +0 -0
  144. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/platforms.py +0 -0
  145. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/pricing.py +0 -0
  146. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/recall_chunker.py +0 -0
  147. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/recall_index.py +0 -0
  148. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/refgraph.py +0 -0
  149. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/src/c3/usage_ingester.py +0 -0
  150. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/__init__.py +0 -0
  151. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/_pre_compact_helpers.py +0 -0
  152. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/conftest.py +0 -0
  153. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/fixtures/usage/README.md +0 -0
  154. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/fixtures/usage/mainline.jsonl +0 -0
  155. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
  156. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
  157. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/__init__.py +0 -0
  158. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_check_agent_invocation.py +0 -0
  159. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_consolidate_memory.py +0 -0
  160. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_detect_execution_verification.py +0 -0
  161. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_hook_utils.py +0 -0
  162. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_permission_handler.py +0 -0
  163. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_permission_handler_toast.py +0 -0
  164. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
  165. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_planner_check.py +0 -0
  166. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_planner_check_dev.py +0 -0
  167. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_post_tool.py +0 -0
  168. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_pre_tool.py +0 -0
  169. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_recall_autorebuild.py +0 -0
  170. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_recall_inject.py +0 -0
  171. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_record_agent_outcome.py +0 -0
  172. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_record_review_decision.py +0 -0
  173. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_report_path_guard.py +0 -0
  174. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_restore_session.py +0 -0
  175. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_review_hint_inject.py +0 -0
  176. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_select_tier.py +0 -0
  177. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_select_tier_escalation.py +0 -0
  178. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_session_start.py +0 -0
  179. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_session_stop.py +0 -0
  180. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_session_utils.py +0 -0
  181. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
  182. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_settings_tier_autoapply_registration.py +0 -0
  183. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_similarity_boost.py +0 -0
  184. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_statusline.py +0 -0
  185. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_statusline_template_sync.py +0 -0
  186. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_sync_check.py +0 -0
  187. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_template_guard.py +0 -0
  188. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_tier_autoapply.py +0 -0
  189. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_tier_gap_check.py +0 -0
  190. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/hooks/test_tier_security_fixes.py +0 -0
  191. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/__init__.py +0 -0
  192. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/_skill_helpers.py +0 -0
  193. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_c3_omission_declaration.py +0 -0
  194. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
  195. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_e0_verification_exit.py +0 -0
  196. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_init_session_no_task_type.py +0 -0
  197. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_marker_compliance.py +0 -0
  198. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_parallel_agents_skill_tier.py +0 -0
  199. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_planner_lightweight.py +0 -0
  200. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_reachability_map.py +0 -0
  201. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_recall_skill.py +0 -0
  202. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_session_backlog_reconciliation.py +0 -0
  203. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_session_guard.py +0 -0
  204. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_setup_templates.py +0 -0
  205. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
  206. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_start_skill_new_flow.py +0 -0
  207. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
  208. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_adapters.py +0 -0
  209. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_archive_reports.py +0 -0
  210. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_audit_review_decisions.py +0 -0
  211. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_check_deletions.py +0 -0
  212. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_ci_workflows.py +0 -0
  213. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_claude_gitignore.py +0 -0
  214. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_cli_doctor.py +0 -0
  215. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_cli_doctor_launcher.py +0 -0
  216. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_cli_entry.py +0 -0
  217. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_cli_init.py +0 -0
  218. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_cli_list.py +0 -0
  219. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_cli_metrics.py +0 -0
  220. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_cli_plan.py +0 -0
  221. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_cli_recall.py +0 -0
  222. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_cli_run.py +0 -0
  223. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_cli_tier.py +0 -0
  224. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_cli_update_breaking_changes.py +0 -0
  225. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_cli_update_deletions.py +0 -0
  226. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_db.py +0 -0
  227. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_db_connect_lint.py +0 -0
  228. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_docstring_consistency.py +0 -0
  229. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_embedding.py +0 -0
  230. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_excludes.py +0 -0
  231. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_extract_breaking_changes.py +0 -0
  232. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_gitutil.py +0 -0
  233. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_hooks_stdin_idiom.py +0 -0
  234. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_init_only_patterns.py +0 -0
  235. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_mcp_server_elicit.py +0 -0
  236. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_migrate.py +0 -0
  237. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_mode_line.py +0 -0
  238. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_no_bare_python_launcher.py +0 -0
  239. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_no_passonly_tests.py +0 -0
  240. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_nul_boundary_lint.py +0 -0
  241. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_paths.py +0 -0
  242. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_patterns_guard.py +0 -0
  243. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_plan_validator.py +0 -0
  244. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_pre_compact.py +0 -0
  245. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_pre_tool_hook.py +0 -0
  246. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_precompact_additional.py +0 -0
  247. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_precompact_toctou_fixes.py +0 -0
  248. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_pricing.py +0 -0
  249. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_recall_chunker.py +0 -0
  250. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_recall_index.py +0 -0
  251. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_references_migration.py +0 -0
  252. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_refgraph.py +0 -0
  253. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_refgraph_query.py +0 -0
  254. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_refgraph_structural.py +0 -0
  255. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_report_contract_check.py +0 -0
  256. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_scripts_src_bootstrap.py +0 -0
  257. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_scripts_stdout_encoding.py +0 -0
  258. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_session_mode_watch.py +0 -0
  259. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_session_utils_additional.py +0 -0
  260. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_settings_hooks_registration.py +0 -0
  261. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_skill_no_builtin_conflict.py +0 -0
  262. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_start_skill_archive_contract.py +0 -0
  263. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_statusline.py +0 -0
  264. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_stop_additional.py +0 -0
  265. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_stop_agent_memory_warn.py +0 -0
  266. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_stop_hook.py +0 -0
  267. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_stop_precompact_fixes.py +0 -0
  268. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_sync_template_stop.py +0 -0
  269. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_template_pre_tool_hook.py +0 -0
  270. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_usage_ingester.py +0 -0
  271. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_worktree_guard.py +0 -0
  272. {claude_code_conductor-2.71.0 → claude_code_conductor-2.71.1}/tests/test_wt_agent_sync.py +0 -0
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.71.1] - 2026-08-14
4
+
5
+ ### 修正
6
+
7
+ - **`c3 ask` の Windows 非対話判定が機能せず無限ハングする欠陥を修正した**(v2.71.0
8
+ 「既知の問題」の解消)。`sys.stdin.isatty()` が Windows で stdin を NUL / `/dev/null` へ
9
+ リダイレクトした場合にも True を誤返却し、`--response` 未指定で exit 1 にならず
10
+ `msvcrt.getwch()`(実コンソール直読み)で無限ハングしていた。判定を
11
+ `stdin_is_interactive_console()`(Windows は GetConsoleMode で実コンソール接続を判定・
12
+ POSIX は従来どおり `isatty()`)へ置換し、入口ゲートと対話パス直前の二重ゲートを
13
+ 同一判定へ統一した
14
+ - 判定ヘルパーを共有モジュール `src/c3/_terminal.py` へ移設し、関数全体を一箇所集約で
15
+ fail-closed 化した(想定外例外でも非対話扱い=exit 1 契約を維持・KeyboardInterrupt /
16
+ SystemExit は伝播。`question.py` 側は re-export でシーム維持)
17
+ - `_read_key` の POSIX 分岐に空読み EOF ガードを追加した(EOF 時のビジーループを解消し
18
+ 既存の exit 1 経路へ接続)
19
+ - `docs/cli-reference.md` の v2.71 既知問題注記を修正後の実挙動へ更新した
20
+
21
+ ### 内部
22
+
23
+ - `tests/test_cli_ask.py` に回帰・凍結テスト計 17 件を追加した(修正前状態での赤化実証・
24
+ 内部依存注入による fail-closed 凍結・Windows 限定 subprocess+timeout 統合テストを含む。
25
+ フルスイート 3322 緑・CI 全緑 run 31755508514)
26
+
3
27
  ## [2.71.0] - 2026-08-14
4
28
 
5
29
  ### 追加
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: claude-code-conductor
3
- Version: 2.71.0
3
+ Version: 2.71.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.71.0"
3
+ __version__ = "2.71.1"
@@ -0,0 +1,118 @@
1
+ """Shared terminal helpers for c3 CLI subcommands.
2
+
3
+ Used by cli_doctor.py, cli_tier.py, cli_ask.py, question.py, and future
4
+ cli_*.py to keep terminal-facing logic in one place:
5
+
6
+ - ``supports_color`` / ``strip_ansi`` / ``sanitize_terminal_text``: ANSI
7
+ color support detection, escape-sequence stripping, and sanitization of
8
+ untrusted text before printing.
9
+ - ``stdin_is_interactive_console``: console-attachment detection for stdin
10
+ (whether an interactive console is really attached, as opposed to a NUL /
11
+ pipe / file redirection).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import re
18
+ import sys
19
+
20
+
21
+ # CSI sequences with final byte 'm' (color / SGR). Other escape sequences
22
+ # (e.g. \033c reset, OSC \033]0;...\007 title) are not handled by _strip_ansi
23
+ # and would skew column-width calculations if they appeared in cell text.
24
+ _CSI_M_RE = re.compile(r"\033\[[0-9;]*m")
25
+
26
+ # Control characters disallowed when printing untrusted text to the terminal.
27
+ # Allow newline (\n), tab (\t), carriage return (\r) but strip any other
28
+ # C0 control or escape character (\x1b) so that ANSI/title-injection cannot
29
+ # happen via DB-stored values such as ``current_step`` / ``error_message``.
30
+ # Also strip the Unicode line/paragraph separators U+2028 / U+2029 so they
31
+ # match the init-session SR L-2 sanitization spec (removes U+2028 / U+2029).
32
+ # NEL (U+0085) is intentionally NOT included per that spec.
33
+ _DISALLOWED_CONTROL_RE = re.compile("[\x00-\x08\x0b\x0c\x0e-\x1f\x7f" + chr(0x2028) + chr(0x2029) + "]")
34
+
35
+
36
+ def supports_color() -> bool:
37
+ """Return True if stdout supports ANSI color sequences.
38
+
39
+ Honors the NO_COLOR environment variable (https://no-color.org/) and
40
+ requires stdout to be a TTY.
41
+ """
42
+ if os.environ.get("NO_COLOR"):
43
+ return False
44
+ if not sys.stdout.isatty():
45
+ return False
46
+ return True
47
+
48
+
49
+ def strip_ansi(s: str) -> str:
50
+ """Remove ANSI CSI 'm' (SGR / color) sequences for visible-width calc.
51
+
52
+ Note: handles only ``\\033[...m`` style sequences. Other escape sequences
53
+ such as cursor movement (``\\033[H``), screen clear (``\\033[2J``), or
54
+ OSC title-set are intentionally not stripped here because the cells we
55
+ measure should never contain them.
56
+ """
57
+ return _CSI_M_RE.sub("", s)
58
+
59
+
60
+ def sanitize_terminal_text(s: str) -> str:
61
+ """Strip control / escape characters from untrusted text before printing.
62
+
63
+ Used for DB-sourced strings (``current_step`` / ``error_message``) so
64
+ they cannot inject ANSI escape sequences (title, cursor, screen clear)
65
+ into the terminal. Newlines / tabs / carriage returns are preserved.
66
+ The Unicode line/paragraph separators U+2028 / U+2029 are also stripped
67
+ (init-session SR L-2 sanitization spec); NEL (U+0085) is out of scope.
68
+ """
69
+ if not s:
70
+ return s
71
+ return _DISALLOWED_CONTROL_RE.sub("", s)
72
+
73
+
74
+ def stdin_is_interactive_console() -> bool:
75
+ """Return True only when stdin is attached to a real interactive console.
76
+
77
+ Windows の CRT ``_isatty()``(``sys.stdin.isatty()`` の内部実装)は対象ハンドルが
78
+ ``FILE_TYPE_CHAR`` かどうかしか見ておらず、NUL デバイス(``subprocess.DEVNULL`` /
79
+ ``NUL`` リダイレクト)もコンソールと同じ ``FILE_TYPE_CHAR`` に分類されるため
80
+ 誤って True を返す(実測: debug-analysis-20260814-011032.md)。
81
+ Windows では ``GetConsoleMode``(実コンソールハンドルに対してのみ成功する
82
+ Win32 API)で実際のコンソール接続を確認し、NUL・パイプ・ファイルいずれの
83
+ リダイレクトも False として扱う。POSIX は ``isatty()`` が ``/dev/null`` に
84
+ 対して標準どおり正しく False を返すためそのまま使う。
85
+
86
+ 関数全体を ``except Exception`` で包み、内部でどの分岐がどの例外型を投げても
87
+ 送出せず False を返す (fail-closed)。POSIX 分岐の ``sys.stdin.isatty()`` は
88
+ ``sys.stdin`` が None のケース (pythonw.exe / PyInstaller --windowed など) で
89
+ ``AttributeError`` を投げうるが、これを呼び出し元へ伝播させると
90
+ ``c3 ask`` の「exit 1」契約が破れる (SR-R-001 / CR-E-001)。呼び出し位置ごとに
91
+ try/except を足すのではなく、ここ一箇所へ集約して将来の呼び出し箇所追加にも
92
+ 自動的に効かせる。``KeyboardInterrupt`` / ``SystemExit`` は ``BaseException``
93
+ 直属で ``Exception`` を継承しないため握り潰さない (Ctrl-C は従来どおり伝播)。
94
+ """
95
+ try:
96
+ if os.name == "nt":
97
+ return _windows_console_attached()
98
+ return sys.stdin.isatty()
99
+ except Exception:
100
+ return False
101
+
102
+
103
+ def _windows_console_attached() -> bool:
104
+ try:
105
+ import ctypes
106
+ import msvcrt
107
+ from ctypes import wintypes
108
+
109
+ handle = msvcrt.get_osfhandle(0)
110
+ kernel32 = ctypes.windll.kernel32
111
+ kernel32.GetConsoleMode.argtypes = [wintypes.HANDLE, ctypes.POINTER(wintypes.DWORD)]
112
+ kernel32.GetConsoleMode.restype = wintypes.BOOL
113
+ mode = wintypes.DWORD()
114
+ return bool(kernel32.GetConsoleMode(wintypes.HANDLE(handle), ctypes.byref(mode)))
115
+ except Exception:
116
+ # 判定不能ならすべて非対話扱いに倒す (fail-closed)。answer_questions() 経由の
117
+ # 呼び出し位置も含め、想定外の例外型で対話ゲートを突破させない。
118
+ return False
@@ -7,6 +7,7 @@ import json
7
7
  import sys
8
8
  from pathlib import Path
9
9
 
10
+ from c3._terminal import stdin_is_interactive_console
10
11
  from c3.question import answer_questions, load_questions
11
12
 
12
13
 
@@ -47,9 +48,16 @@ def register(subparsers: argparse._SubParsersAction) -> None:
47
48
 
48
49
 
49
50
  def handle(args: argparse.Namespace) -> int:
50
- if args.response is None and not sys.stdin.isatty():
51
- print("c3 ask: --response is required in non-interactive mode", file=sys.stderr)
52
- return 1
51
+ if args.response is None:
52
+ # 判定関数が想定外の例外を出しても traceback で落とさず「非対話」へ倒す
53
+ # (fail-closed)。--response 未指定時の exit 1 契約を例外型に依存させない。
54
+ try:
55
+ interactive = stdin_is_interactive_console()
56
+ except Exception:
57
+ interactive = False
58
+ if not interactive:
59
+ print("c3 ask: --response is required in non-interactive mode", file=sys.stderr)
60
+ return 1
53
61
 
54
62
  try:
55
63
  if args.file is not None:
@@ -13,6 +13,12 @@ from dataclasses import dataclass
13
13
  from pathlib import Path
14
14
  from typing import Any
15
15
 
16
+ # stdin_is_interactive_console() は共有ターミナルヘルパー _terminal.py が正本
17
+ # (CR-R-003)。ここでの import は question.py 内部の呼び出し元 (answer_questions /
18
+ # _raw_keyboard_supported) が使うと同時に、``question.stdin_is_interactive_console``
19
+ # という従来の参照名を後方互換で維持する re-export を兼ねる。
20
+ from c3._terminal import stdin_is_interactive_console
21
+
16
22
 
17
23
  @dataclass(frozen=True)
18
24
  class Option:
@@ -63,7 +69,7 @@ def answer_questions(
63
69
  if current_response is not None:
64
70
  selected = _select_from_response(question, current_response)
65
71
  else:
66
- use_interactive = sys.stdin.isatty() if interactive is None else interactive
72
+ use_interactive = stdin_is_interactive_console() if interactive is None else interactive
67
73
  selected = _select_interactively(question) if use_interactive else _select_default(question)
68
74
  answers.append(_answer_payload(question, selected))
69
75
  return {"answers": answers}
@@ -383,7 +389,16 @@ def _clear_screen() -> None:
383
389
 
384
390
 
385
391
  def _raw_keyboard_supported() -> bool:
386
- return os.name in ("nt", "posix")
392
+ # OS が raw keyboard API を持つかではなく、今この stdin に実コンソールが
393
+ # 接続されているかを見る。旧実装(os.name in ("nt", "posix"))は事実上
394
+ # 常に True を返し、NUL/パイプ/ファイルへのリダイレクト時でも
395
+ # _select_with_line_input() の安全網(input() → EOFError)を恒久的に
396
+ # 迂回させ msvcrt.getwch() の無限ブロックを招いていた
397
+ # (debug-analysis-20260814-011032.md 欠陥B)。
398
+ # answer_questions() の外側ゲートと同じ判定をここでも呼ぶのは、interactive=True を
399
+ # 明示指定して外側ゲートを迂回する呼び出し元にも安全網を効かせるための意図的な
400
+ # 再検証であり、単純な重複ではない(削除・簡略化しないこと)。
401
+ return stdin_is_interactive_console()
387
402
 
388
403
 
389
404
  def _read_key() -> str:
@@ -410,6 +425,11 @@ def _read_key() -> str:
410
425
  try:
411
426
  tty.setraw(fd)
412
427
  ch = sys.stdin.read(1)
428
+ if ch == "":
429
+ # EOF(read が空文字列)。空文字列を返すと _select_interactively() の
430
+ # ループが再描画しながら回り続け CPU ビジーループになるため、
431
+ # EOFError を送出して呼び出し元(cli_ask.handle の except EOFError)へ委ねる。
432
+ raise EOFError("stdin reached EOF while reading a key")
413
433
  if ch == "\x1b":
414
434
  nxt = sys.stdin.read(2)
415
435
  return {"[A": "up", "[B": "down", "[C": "right", "[D": "left"}.get(nxt, "escape")