claude-code-conductor 2.49.0__tar.gz → 2.50.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 (220) hide show
  1. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/docs/platform-adapters.md +12 -1
  2. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/CHANGELOG.md +18 -0
  3. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/PKG-INFO +1 -1
  4. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/__init__.py +1 -1
  5. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/adapters.py +2 -2
  6. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/cli_doctor.py +77 -0
  7. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_adapters.py +44 -1
  8. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_cli_doctor.py +168 -0
  9. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/CLAUDE.md +0 -0
  10. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/architect.md +0 -0
  11. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/code-reviewer.md +0 -0
  12. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/design-critic.md +0 -0
  13. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/developer.md +0 -0
  14. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/doc-writer.md +0 -0
  15. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/interviewer.md +0 -0
  16. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/planner.md +0 -0
  17. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/project-setup.md +0 -0
  18. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/security-reviewer.md +0 -0
  19. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/systematic-debugger.md +0 -0
  20. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/tester.md +0 -0
  21. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/wt_developer.md +0 -0
  22. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/wt_systematic-debugger.md +0 -0
  23. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/agents/wt_tester.md +0 -0
  24. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/breaking-changes.txt +0 -0
  25. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/deletions.txt +0 -0
  26. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/docs/config-policy.md +0 -0
  27. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/docs/parallel-agents-setup.md +0 -0
  28. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/docs/settings.json.md +0 -0
  29. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/_hook_utils.py +0 -0
  30. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/check_agent_invocation.py +0 -0
  31. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/consolidate_memory.py +0 -0
  32. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/permission_handler.py +0 -0
  33. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/permission_handler_toast.py +0 -0
  34. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/planner_check.py +0 -0
  35. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/post_tool.py +0 -0
  36. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/pre_compact.py +0 -0
  37. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/pre_tool.py +0 -0
  38. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/recall_autorebuild.py +0 -0
  39. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/recall_inject.py +0 -0
  40. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/restore_session.py +0 -0
  41. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/select_tier.py +0 -0
  42. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/session_start.py +0 -0
  43. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/session_stop.py +0 -0
  44. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/session_utils.py +0 -0
  45. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/statusline.py +0 -0
  46. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/stop.py +0 -0
  47. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/tier_autoapply.py +0 -0
  48. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/tier_gap_check.py +0 -0
  49. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/hooks/worktree_guard.py +0 -0
  50. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/memory/.gitkeep +0 -0
  51. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/permission_rules.json +0 -0
  52. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/rules/promoted/index.md +0 -0
  53. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/settings.json +0 -0
  54. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/brainstorm/SKILL.md +0 -0
  55. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/codex-review/SKILL.md +0 -0
  56. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
  57. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
  58. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/dev-workflow/references/design-critic-rubric.md +0 -0
  59. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/dev-workflow/references/design-rubric.md +0 -0
  60. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/dev-workflow/references/interview-rubric.md +0 -0
  61. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
  62. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
  63. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/dev-workflow/scripts/record_agent_outcome.py +0 -0
  64. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
  65. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
  66. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/develop/SKILL.md +0 -0
  67. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/doc/SKILL.md +0 -0
  68. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/extract-lib/SKILL.md +0 -0
  69. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/init-session/SKILL.md +0 -0
  70. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/init-session/scripts/session_guard.py +0 -0
  71. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/mcp-config/SKILL.md +0 -0
  72. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
  73. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/pattern-status/SKILL.md +0 -0
  74. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
  75. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/recall/SKILL.md +0 -0
  76. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
  77. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
  78. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/review-phase/SKILL.md +0 -0
  79. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/setup/SKILL.md +0 -0
  80. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/setup/reference.md +0 -0
  81. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
  82. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
  83. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/skills/start/SKILL.md +0 -0
  84. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.claude/state/.gitkeep +0 -0
  85. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/.gitignore +0 -0
  86. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/ARCHITECTURE.md +0 -0
  87. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/LICENSE +0 -0
  88. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/LICENSES/fastembed-LICENSE +0 -0
  89. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/LICENSES/fastembed-NOTICE +0 -0
  90. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/LICENSES/numpy-LICENSE +0 -0
  91. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/LICENSES/onnxruntime-LICENSE +0 -0
  92. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
  93. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/README.md +0 -0
  94. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/hatch_build.py +0 -0
  95. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/pyproject.toml +0 -0
  96. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/__main__.py +0 -0
  97. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/_db_params.py +0 -0
  98. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/_excludes.py +0 -0
  99. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/_terminal.py +0 -0
  100. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/cli.py +0 -0
  101. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/cli_ask.py +0 -0
  102. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/cli_init.py +0 -0
  103. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/cli_list.py +0 -0
  104. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/cli_metrics.py +0 -0
  105. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/cli_plan.py +0 -0
  106. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/cli_recall.py +0 -0
  107. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/cli_tier.py +0 -0
  108. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/cli_update.py +0 -0
  109. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/db.py +0 -0
  110. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/embedding.py +0 -0
  111. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/gitutil.py +0 -0
  112. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/mcp_server.py +0 -0
  113. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/migrate.py +0 -0
  114. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/migrations/001_initial.sql +0 -0
  115. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
  116. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/migrations/003_tier_cost.sql +0 -0
  117. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/migrations/004_agent_outcomes.sql +0 -0
  118. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/migrations/005_drop_agent_tier_bandit.sql +0 -0
  119. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/migrations/006_review_decisions_severity.sql +0 -0
  120. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/migrations/README.md +0 -0
  121. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/migrations/__init__.py +0 -0
  122. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/paths.py +0 -0
  123. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/plan_validator.py +0 -0
  124. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/platforms.py +0 -0
  125. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/pricing.py +0 -0
  126. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/question.py +0 -0
  127. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/recall_chunker.py +0 -0
  128. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/recall_index.py +0 -0
  129. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/src/c3/usage_ingester.py +0 -0
  130. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/__init__.py +0 -0
  131. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/_pre_compact_helpers.py +0 -0
  132. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/conftest.py +0 -0
  133. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/fixtures/usage/README.md +0 -0
  134. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/fixtures/usage/mainline.jsonl +0 -0
  135. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
  136. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
  137. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/__init__.py +0 -0
  138. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_check_agent_invocation.py +0 -0
  139. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_consolidate_memory.py +0 -0
  140. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_hook_utils.py +0 -0
  141. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_permission_handler.py +0 -0
  142. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_permission_handler_toast.py +0 -0
  143. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
  144. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_planner_check.py +0 -0
  145. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_planner_check_dev.py +0 -0
  146. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_post_tool.py +0 -0
  147. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_pre_tool.py +0 -0
  148. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_recall_autorebuild.py +0 -0
  149. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_recall_inject.py +0 -0
  150. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_record_agent_outcome.py +0 -0
  151. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_record_review_decision.py +0 -0
  152. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_restore_session.py +0 -0
  153. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_review_hint_inject.py +0 -0
  154. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_select_tier.py +0 -0
  155. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_select_tier_escalation.py +0 -0
  156. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_session_start.py +0 -0
  157. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_session_stop.py +0 -0
  158. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_session_utils.py +0 -0
  159. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
  160. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_settings_tier_autoapply_registration.py +0 -0
  161. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_similarity_boost.py +0 -0
  162. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_statusline.py +0 -0
  163. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_statusline_template_sync.py +0 -0
  164. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_sync_check.py +0 -0
  165. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_template_guard.py +0 -0
  166. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_tier_autoapply.py +0 -0
  167. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_tier_gap_check.py +0 -0
  168. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/hooks/test_tier_security_fixes.py +0 -0
  169. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/__init__.py +0 -0
  170. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/_skill_helpers.py +0 -0
  171. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
  172. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/test_init_session_no_task_type.py +0 -0
  173. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/test_parallel_agents_skill_tier.py +0 -0
  174. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/test_planner_lightweight.py +0 -0
  175. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/test_recall_skill.py +0 -0
  176. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
  177. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/test_session_guard.py +0 -0
  178. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/test_setup_templates.py +0 -0
  179. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
  180. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/test_start_skill_new_flow.py +0 -0
  181. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
  182. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_check_deletions.py +0 -0
  183. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_cli_ask.py +0 -0
  184. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_cli_entry.py +0 -0
  185. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_cli_init.py +0 -0
  186. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_cli_list.py +0 -0
  187. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_cli_metrics.py +0 -0
  188. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_cli_plan.py +0 -0
  189. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_cli_recall.py +0 -0
  190. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_cli_tier.py +0 -0
  191. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_cli_update_breaking_changes.py +0 -0
  192. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_cli_update_deletions.py +0 -0
  193. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_db.py +0 -0
  194. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_docstring_consistency.py +0 -0
  195. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_embedding.py +0 -0
  196. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_excludes.py +0 -0
  197. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_extract_breaking_changes.py +0 -0
  198. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_gitutil.py +0 -0
  199. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_mcp_server_elicit.py +0 -0
  200. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_migrate.py +0 -0
  201. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_paths.py +0 -0
  202. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_plan_validator.py +0 -0
  203. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_pre_compact.py +0 -0
  204. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_pre_tool_hook.py +0 -0
  205. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_precompact_additional.py +0 -0
  206. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_precompact_toctou_fixes.py +0 -0
  207. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_pricing.py +0 -0
  208. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_recall_chunker.py +0 -0
  209. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_recall_index.py +0 -0
  210. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_references_migration.py +0 -0
  211. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_session_utils_additional.py +0 -0
  212. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_skill_no_builtin_conflict.py +0 -0
  213. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_statusline.py +0 -0
  214. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_stop_additional.py +0 -0
  215. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_stop_hook.py +0 -0
  216. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_stop_precompact_fixes.py +0 -0
  217. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_sync_template_stop.py +0 -0
  218. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_template_pre_tool_hook.py +0 -0
  219. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_usage_ingester.py +0 -0
  220. {claude_code_conductor-2.49.0 → claude_code_conductor-2.50.0}/tests/test_worktree_guard.py +0 -0
@@ -11,7 +11,7 @@ C3 は **Claude Code** を canonical platform として設計されているが
11
11
  - 動作差分のまとめ
12
12
 
13
13
  を一箇所にまとめたリファレンス。実装は `src/c3/adapters.py` / `src/c3/mcp_server.py` /
14
- `src/c3/cli_ask.py` / `src/c3/platforms.py` を参照。
14
+ `src/c3/cli_ask.py` / `src/c3/platforms.py` / `src/c3/cli_doctor.py` を参照。
15
15
 
16
16
  ---
17
17
 
@@ -92,6 +92,9 @@ python -m c3.mcp_server
92
92
  `C3_PROJECT_ROOT` 環境変数でプロジェクトルートを指定可能(adapter 生成時に自動設定)。
93
93
  ソースインストール時は `PYTHONPATH` も adapter が `<repo>/src` に設定する。
94
94
 
95
+ 生成される `.codex/config.toml` / `.cursor/mcp.json` の `command` は `python` ではなく、
96
+ adapter 生成時点の `sys.executable`(絶対パス)を書き込む。理由は「7. 既知の制限」を参照。
97
+
95
98
  ---
96
99
 
97
100
  ## 4. `c3 ask` CLI fallback
@@ -178,3 +181,11 @@ Claude Code / Codex / Cursor / OpenCode が同じプロジェクトを行き来
178
181
  - **OpenCode の `AskUserQuestion`**: OpenCode adapter は MCP 設定を生成しないため、`c3_ask_user_question` ツールは使わず `AGENTS.md` の指示に従ってユーザーへ直接確認する。`multiSelect: true` は「複数選択の質問」として維持するよう指示済み
179
182
  - **OpenCode の `tools` 制限**: 生成される agent には一律で `bash/read/edit/write/websearch` を付与する。Claude 側 frontmatter の細かな `tools` 制限は反映されない(C3 の `code-reviewer` / `security-reviewer` は Claude 側でもレポート出力のため `write` を持つ)
180
183
  - **改行コード**: adapter 生成ファイルは LF 改行で書き出す(`newline="\n"`)。`.gitattributes` で `eol=lf` を固定済み
184
+ - **Codex の trust 要件**: プロジェクトが Codex に trust されていない場合、`.codex/config.toml` は**エラーなしで無視**される(Codex 公式仕様)。MCP サーバーが読み込まれず `AskUserQuestion` が動かない場合は、まず対象プロジェクトを Codex 側で trust 済みか確認する(Codex CLI: 初回起動時のプロンプトで trust するか、設定で許可済みディレクトリに追加する)
185
+ - **MCP `command` の絶対パス化**: adapter が書き込む `command` は `python` ではなく `sys.executable`(adapter 生成時の絶対パス)を使う。理由:
186
+ - macOS 12.3 以降、無印 `python` は標準に存在しない(`python3` のみ。Homebrew も同様)。`command: "python"` のままだと Codex が MCP spawn 時に program not found となり、「起動時にエラーで読み込まれない」症状になる
187
+ - `python` が PATH 上にあっても、pipx / venv などの隔離環境では `No module named c3` で起動失敗することがある
188
+ - GUI 起動の Codex(Desktop / IDE 拡張)はシェルの PATH を継承しないことがある(最小 PATH)
189
+ - 代案の「POSIX では `python3` にする」は不採用: 無印 python 不在の問題しか救えず、pipx 隔離や GUI PATH 最小の問題には無力なため
190
+ - `c3 doctor --platform codex|cursor` は生成済み config から `command` を読み取り、実行可能か・`<command> -c "import c3"` が成功するかを自己診断する
191
+ - **adapter 生成物はマシン固有**: `command` に埋め込む `sys.executable` は生成したマシン・環境(venv / pipx / システム Python)に依存する絶対パスであるため、adapter 生成物(`.codex/config.toml` / `.cursor/mcp.json` など)はチームで共有せず、各マシンで `c3 init --platform <p>` または `c3 update --platform <p>` を実行して再生成する運用とする
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.50.0] - 2026-07-17
4
+
5
+ ### 修正
6
+
7
+ - **MCP 設定の `command` を `sys.executable` 絶対パス化(Codex / Cursor adapter)**: 生成される `.codex/config.toml` / `.cursor/mcp.json` の `command` が `"python"` ハードコードだったため、無印 `python` が存在しない環境(macOS 12.3 以降の標準 / Homebrew)・pipx / venv 隔離(`No module named c3`)・GUI 起動の Codex(シェル PATH 非継承)で MCP サーバーが起動時エラーになり `c3_ask_user_question` が使えなかった問題を修正。`c3 init` / `c3 update --platform` を実行した Python の絶対パスを埋め込む(TOML は既存 `_toml_escape` でエスケープ)。X 上の実利用報告「Claude Code だと動くが Codex だと起動時にエラーで読み込まれない」が起点
8
+
9
+ ### 追加
10
+
11
+ - **`c3 doctor` の MCP 起動自己診断**: 生成済み config(`.codex/config.toml` / `.cursor/mcp.json`)から `command` を読み取り、(a) 実行可能性 (b) `<command> -c "import c3"` の成否を検証する。失敗時は WARN と `c3 update --platform <p>` での再生成を案内。この種の環境起因の起動失敗を利用者が一発で自己診断できるようにした
12
+
13
+ ### 変更
14
+
15
+ - `.claude/docs/platform-adapters.md` の「既知の制限」に、Codex の trust 要件(未 trust プロジェクトの `.codex/config.toml` は**エラーなしで無視**される・trust 手順)・`command` 絶対パス化の理由・adapter 生成物のマシン固有性(チームで共有せず各マシンで `c3 init/update --platform` 再生成する運用)を追記
16
+
17
+ ### 後方互換
18
+
19
+ - 既存の生成済み config は次回 `c3 update --platform` 実行まで従来のまま。再生成すると `command` が実行環境の絶対パスに変わる(マシン固有・意図された挙動。チーム共有リポジトリでは各メンバーが自マシンで再生成する)。**破壊的変更なし**
20
+
3
21
  ## [2.49.0] - 2026-07-17
4
22
 
5
23
  ### 追加
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-conductor
3
- Version: 2.49.0
3
+ Version: 2.50.0
4
4
  Summary: Multi-agent orchestration framework for Claude Code with Codex/Cursor adapters (C3)
5
5
  Project-URL: Homepage, https://github.com/satoh-y-0323/claude-code-conductor
6
6
  Project-URL: Repository, https://github.com/satoh-y-0323/claude-code-conductor
@@ -1,3 +1,3 @@
1
1
  """Claude Code Conductor (C3) - multi-agent orchestration framework for Claude Code."""
2
2
 
3
- __version__ = "2.49.0"
3
+ __version__ = "2.50.0"
@@ -223,7 +223,7 @@ def _write_cursor_mcp(target_root: Path, *, dry_run: bool) -> list[AdapterAction
223
223
  if pythonpath is not None:
224
224
  env["PYTHONPATH"] = str(pythonpath)
225
225
  servers["c3"] = {
226
- "command": "python",
226
+ "command": sys.executable,
227
227
  "args": ["-m", "c3.mcp_server"],
228
228
  "env": env,
229
229
  }
@@ -331,7 +331,7 @@ tool, read the matching generated Codex skill from `.agents/skills/<name>/SKILL.
331
331
  def _codex_config_section() -> str:
332
332
  lines = [
333
333
  "[mcp_servers.c3]",
334
- 'command = "python"',
334
+ f'command = "{_toml_escape(sys.executable)}"',
335
335
  'args = ["-m", "c3.mcp_server"]',
336
336
  "startup_timeout_sec = 10",
337
337
  "tool_timeout_sec = 600",
@@ -4,7 +4,9 @@ from __future__ import annotations
4
4
 
5
5
  import argparse
6
6
  import json
7
+ import re
7
8
  import shutil
9
+ import subprocess
8
10
  from pathlib import Path
9
11
 
10
12
  from c3._terminal import supports_color as _supports_color
@@ -15,6 +17,8 @@ _OK = "OK"
15
17
  _WARN = "WARN"
16
18
  _ERR = "ERR"
17
19
 
20
+ _MCP_STARTUP_TIMEOUT_SEC = 10
21
+
18
22
 
19
23
  def register(subparsers: argparse._SubParsersAction) -> None:
20
24
  parser = subparsers.add_parser(
@@ -111,6 +115,9 @@ def _check_codex_adapter() -> list[tuple[str, str, str]]:
111
115
  findings.append(_dir_or_warn(".agents/skills", skills, "run `c3 init --platform codex`"))
112
116
  findings.append(_file_or_warn(".codex/config.toml", config, "run `c3 init --platform codex`"))
113
117
  findings.append(_dir_or_warn(".codex/agents", custom_agents, "run `c3 init --platform codex`"))
118
+ if config.is_file():
119
+ command = _extract_codex_mcp_command(config.read_text(encoding="utf-8"))
120
+ findings.append(_check_mcp_command_startup("codex MCP startup", command, "codex"))
114
121
  codex = shutil.which("codex")
115
122
  if codex is None:
116
123
  findings.append((_WARN, "codex binary", "not on PATH; install Codex CLI to use the adapter"))
@@ -147,6 +154,9 @@ def _check_cursor_adapter() -> list[tuple[str, str, str]]:
147
154
  mcp = root / ".cursor" / "mcp.json"
148
155
  findings.append(_file_or_warn(".cursor/rules/c3-core.mdc", rule, "run `c3 init --platform cursor`"))
149
156
  findings.append(_file_or_warn(".cursor/mcp.json", mcp, "run `c3 init --platform cursor`"))
157
+ if mcp.is_file():
158
+ command = _extract_cursor_mcp_command(mcp.read_text(encoding="utf-8"))
159
+ findings.append(_check_mcp_command_startup("cursor MCP startup", command, "cursor"))
150
160
  cursor = shutil.which("cursor-agent") or shutil.which("cursor")
151
161
  if cursor is None:
152
162
  findings.append((_WARN, "cursor binary", "not on PATH; install Cursor CLI/editor to use the adapter"))
@@ -155,6 +165,73 @@ def _check_cursor_adapter() -> list[tuple[str, str, str]]:
155
165
  return findings
156
166
 
157
167
 
168
+ def _extract_codex_mcp_command(config_text: str) -> str | None:
169
+ """Read the ``command`` value from the ``[mcp_servers.c3]`` table.
170
+
171
+ Uses a targeted regex rather than a full TOML parser: ``tomllib`` is only
172
+ available from Python 3.11 while C3 supports 3.10+, and the generated
173
+ config is always a single-line ``key = "value"`` pair under the section.
174
+ """
175
+ match = re.search(
176
+ r'(?ms)^\[mcp_servers\.c3\]\s*$.*?^command\s*=\s*"((?:[^"\\]|\\.)*)"',
177
+ config_text,
178
+ )
179
+ if match is None:
180
+ return None
181
+ return match.group(1).replace('\\"', '"').replace("\\\\", "\\")
182
+
183
+
184
+ def _extract_cursor_mcp_command(mcp_json_text: str) -> str | None:
185
+ try:
186
+ payload = json.loads(mcp_json_text)
187
+ except json.JSONDecodeError:
188
+ return None
189
+ if not isinstance(payload, dict):
190
+ return None
191
+ servers = payload.get("mcpServers")
192
+ if not isinstance(servers, dict):
193
+ return None
194
+ c3 = servers.get("c3")
195
+ if not isinstance(c3, dict):
196
+ return None
197
+ command = c3.get("command")
198
+ return command if isinstance(command, str) else None
199
+
200
+
201
+ def _check_mcp_command_startup(
202
+ label: str, command: str | None, platform: str
203
+ ) -> tuple[str, str, str]:
204
+ """Verify that the generated MCP ``command`` can actually launch and import c3."""
205
+ hint = f"run `c3 update --platform {platform}` to regenerate"
206
+ if not command:
207
+ return _WARN, label, f"command not found in generated config; {hint}"
208
+
209
+ resolvable = (Path(command).is_absolute() and Path(command).is_file()) or (
210
+ shutil.which(command) is not None
211
+ )
212
+ if not resolvable:
213
+ return _WARN, label, f"command not executable: {command}; {hint}"
214
+
215
+ try:
216
+ result = subprocess.run(
217
+ [command, "-c", "import c3"],
218
+ capture_output=True,
219
+ text=True,
220
+ timeout=_MCP_STARTUP_TIMEOUT_SEC,
221
+ )
222
+ except (OSError, subprocess.TimeoutExpired) as exc:
223
+ return _WARN, label, f"failed to launch {command}: {exc}; {hint}"
224
+
225
+ if result.returncode != 0:
226
+ detail = next(
227
+ (line for line in reversed(result.stderr.strip().splitlines()) if line.strip()),
228
+ "import c3 failed",
229
+ )
230
+ return _WARN, label, f'`{command} -c "import c3"` failed: {detail}; {hint}'
231
+
232
+ return _OK, label, f"{command} -c \"import c3\" succeeded"
233
+
234
+
158
235
  def _file_or_warn(label: str, path: Path, hint: str) -> tuple[str, str, str]:
159
236
  if path.is_file():
160
237
  return _OK, label, str(path)
@@ -58,6 +58,48 @@ def test_toml_multiline_escape_keeps_newlines():
58
58
  assert _toml_multiline_escape(text) == text
59
59
 
60
60
 
61
+ # ----------------------------------------------------------------------
62
+ # MCP `command` resolution — absolute sys.executable, not bare "python"
63
+ # ----------------------------------------------------------------------
64
+
65
+
66
+ def test_codex_config_section_uses_absolute_sys_executable(monkeypatch):
67
+ monkeypatch.setattr(adapters.sys, "executable", r"C:\Python312\python.exe")
68
+
69
+ section = adapters._codex_config_section()
70
+
71
+ assert 'command = "C:\\\\Python312\\\\python.exe"' in section
72
+ assert 'command = "python"' not in section
73
+
74
+
75
+ def test_codex_config_section_escapes_plain_posix_executable(monkeypatch):
76
+ monkeypatch.setattr(adapters.sys, "executable", "/usr/bin/python3")
77
+
78
+ section = adapters._codex_config_section()
79
+
80
+ assert 'command = "/usr/bin/python3"' in section
81
+
82
+
83
+ def test_write_codex_config_writes_absolute_windows_command(tmp_path: Path, monkeypatch):
84
+ monkeypatch.setattr(adapters.sys, "executable", r"C:\Users\dev\venv\Scripts\python.exe")
85
+
86
+ adapters._write_codex_config(tmp_path, dry_run=False)
87
+
88
+ config_text = (tmp_path / ".codex" / "config.toml").read_text(encoding="utf-8")
89
+ assert 'command = "C:\\\\Users\\\\dev\\\\venv\\\\Scripts\\\\python.exe"' in config_text
90
+
91
+
92
+ def test_write_cursor_mcp_uses_absolute_sys_executable(tmp_path: Path, monkeypatch):
93
+ monkeypatch.setattr(adapters.sys, "executable", r"C:\Python312\python.exe")
94
+
95
+ adapters._write_cursor_mcp(tmp_path, dry_run=False)
96
+
97
+ payload = json.loads((tmp_path / ".cursor" / "mcp.json").read_text(encoding="utf-8"))
98
+ command = payload["mcpServers"]["c3"]["command"]
99
+ assert command == r"C:\Python312\python.exe"
100
+ assert Path(command).is_absolute()
101
+
102
+
61
103
  # ----------------------------------------------------------------------
62
104
  # _convert_skill
63
105
  # ----------------------------------------------------------------------
@@ -178,7 +220,8 @@ def test_write_cursor_mcp_creates_file_when_missing(tmp_path: Path):
178
220
  actions = _write_cursor_mcp(target, dry_run=False)
179
221
  assert any(a.action == "add" for a in actions)
180
222
  payload = json.loads((target / ".cursor" / "mcp.json").read_text(encoding="utf-8"))
181
- assert payload["mcpServers"]["c3"]["command"] == "python"
223
+ assert payload["mcpServers"]["c3"]["command"] == sys.executable
224
+ assert Path(payload["mcpServers"]["c3"]["command"]).is_absolute()
182
225
 
183
226
 
184
227
  def test_write_cursor_mcp_rejects_invalid_json(tmp_path: Path):
@@ -7,6 +7,8 @@ in handle() for opencode, codex, and cursor platforms.
7
7
  from __future__ import annotations
8
8
 
9
9
  import argparse
10
+ import subprocess
11
+ import sys
10
12
  from pathlib import Path
11
13
 
12
14
  import c3.cli_doctor as cli_doctor
@@ -181,3 +183,169 @@ def test_check_opencode_adapter_reports_binary_present(tmp_path: Path, monkeypat
181
183
  status == _OK and "opencode binary" in label and fake_path in detail
182
184
  for status, label, detail in findings
183
185
  ), f"Expected OK for 'opencode binary' with path, got: {findings}"
186
+
187
+
188
+ # ---------------------------------------------------------------------------
189
+ # MCP startup self-diagnostic — _check_mcp_command_startup
190
+ # ---------------------------------------------------------------------------
191
+
192
+
193
+ def test_check_mcp_command_startup_ok_when_import_succeeds(monkeypatch):
194
+ """When the resolved command can `import c3`, the check must return OK."""
195
+ monkeypatch.setattr(
196
+ cli_doctor.subprocess,
197
+ "run",
198
+ lambda *a, **k: subprocess.CompletedProcess(a[0], 0, stdout="", stderr=""),
199
+ )
200
+
201
+ status, label, detail = cli_doctor._check_mcp_command_startup(
202
+ "codex MCP startup", sys.executable, "codex"
203
+ )
204
+
205
+ assert status == _OK
206
+ assert "codex MCP startup" == label
207
+ assert "import c3" in detail
208
+
209
+
210
+ def test_check_mcp_command_startup_warns_when_command_missing():
211
+ """When the config has no `command` value at all, the check must WARN."""
212
+ status, label, detail = cli_doctor._check_mcp_command_startup(
213
+ "codex MCP startup", None, "codex"
214
+ )
215
+
216
+ assert status == _WARN
217
+ assert "not found" in detail
218
+ assert "c3 update --platform codex" in detail
219
+
220
+
221
+ def test_check_mcp_command_startup_warns_when_command_not_resolvable(monkeypatch):
222
+ """When the command path/binary cannot be located at all, the check must WARN
223
+ without attempting to spawn a subprocess."""
224
+ monkeypatch.setattr(cli_doctor.shutil, "which", lambda name: None)
225
+ called = []
226
+ monkeypatch.setattr(
227
+ cli_doctor.subprocess,
228
+ "run",
229
+ lambda *a, **k: called.append(a) or subprocess.CompletedProcess(a[0], 0),
230
+ )
231
+
232
+ status, label, detail = cli_doctor._check_mcp_command_startup(
233
+ "cursor MCP startup", "/nonexistent/path/to/python", "cursor"
234
+ )
235
+
236
+ assert status == _WARN
237
+ assert "not executable" in detail
238
+ assert "c3 update --platform cursor" in detail
239
+ assert called == []
240
+
241
+
242
+ def test_check_mcp_command_startup_warns_when_import_c3_fails(monkeypatch):
243
+ """When the resolved command runs but `import c3` fails (e.g. an isolated
244
+ venv/pipx environment without c3 installed), the check must WARN."""
245
+ monkeypatch.setattr(
246
+ cli_doctor.subprocess,
247
+ "run",
248
+ lambda *a, **k: subprocess.CompletedProcess(
249
+ a[0], 1, stdout="", stderr="ModuleNotFoundError: No module named 'c3'"
250
+ ),
251
+ )
252
+
253
+ status, label, detail = cli_doctor._check_mcp_command_startup(
254
+ "codex MCP startup", sys.executable, "codex"
255
+ )
256
+
257
+ assert status == _WARN
258
+ assert "No module named" in detail
259
+ assert "c3 update --platform codex" in detail
260
+
261
+
262
+ def test_check_mcp_command_startup_warns_when_launch_raises(monkeypatch):
263
+ """When subprocess.run itself raises (e.g. the binary vanished between the
264
+ resolvability check and the launch), the check must WARN, not raise."""
265
+ def _boom(*a, **k):
266
+ raise FileNotFoundError("no such file")
267
+
268
+ monkeypatch.setattr(cli_doctor.subprocess, "run", _boom)
269
+
270
+ status, label, detail = cli_doctor._check_mcp_command_startup(
271
+ "codex MCP startup", sys.executable, "codex"
272
+ )
273
+
274
+ assert status == _WARN
275
+ assert "failed to launch" in detail
276
+
277
+
278
+ # ---------------------------------------------------------------------------
279
+ # command extraction from generated adapter configs
280
+ # ---------------------------------------------------------------------------
281
+
282
+
283
+ def test_extract_codex_mcp_command_reads_absolute_windows_path():
284
+ config_text = (
285
+ "[mcp_servers.c3]\n"
286
+ 'command = "C:\\\\Python312\\\\python.exe"\n'
287
+ 'args = ["-m", "c3.mcp_server"]\n'
288
+ )
289
+
290
+ command = cli_doctor._extract_codex_mcp_command(config_text)
291
+
292
+ assert command == r"C:\Python312\python.exe"
293
+
294
+
295
+ def test_extract_codex_mcp_command_none_when_section_missing():
296
+ assert cli_doctor._extract_codex_mcp_command("[some_other_table]\nfoo = 1\n") is None
297
+
298
+
299
+ def test_extract_cursor_mcp_command_reads_absolute_path():
300
+ mcp_json = '{"mcpServers": {"c3": {"command": "/usr/bin/python3", "args": []}}}'
301
+
302
+ command = cli_doctor._extract_cursor_mcp_command(mcp_json)
303
+
304
+ assert command == "/usr/bin/python3"
305
+
306
+
307
+ def test_extract_cursor_mcp_command_none_when_invalid_json():
308
+ assert cli_doctor._extract_cursor_mcp_command("{not valid") is None
309
+
310
+
311
+ # ---------------------------------------------------------------------------
312
+ # integration — _check_codex_adapter / _check_cursor_adapter wire the
313
+ # self-diagnostic in using the real, currently-running interpreter
314
+ # ---------------------------------------------------------------------------
315
+
316
+
317
+ def test_check_codex_adapter_includes_ok_mcp_startup_with_real_interpreter(
318
+ tmp_path: Path, monkeypatch
319
+ ):
320
+ """End-to-end: generate a real adapter config pointed at sys.executable
321
+ (which does have c3 importable under the sandbox PYTHONPATH) and confirm
322
+ the doctor wires the self-diagnostic in as OK."""
323
+ (tmp_path / ".claude").mkdir()
324
+ monkeypatch.chdir(tmp_path)
325
+ from c3.adapters import _write_codex_config
326
+
327
+ _write_codex_config(tmp_path, dry_run=False)
328
+
329
+ findings = cli_doctor._check_codex_adapter()
330
+
331
+ assert any(
332
+ status == _OK and label == "codex MCP startup"
333
+ for status, label, _ in findings
334
+ ), f"Expected OK codex MCP startup finding, got: {findings}"
335
+
336
+
337
+ def test_check_cursor_adapter_includes_ok_mcp_startup_with_real_interpreter(
338
+ tmp_path: Path, monkeypatch
339
+ ):
340
+ (tmp_path / ".claude").mkdir()
341
+ monkeypatch.chdir(tmp_path)
342
+ from c3.adapters import _write_cursor_mcp
343
+
344
+ _write_cursor_mcp(tmp_path, dry_run=False)
345
+
346
+ findings = cli_doctor._check_cursor_adapter()
347
+
348
+ assert any(
349
+ status == _OK and label == "cursor MCP startup"
350
+ for status, label, _ in findings
351
+ ), f"Expected OK cursor MCP startup finding, got: {findings}"