code-context-control 2.30.0__tar.gz → 2.32.2__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 (196) hide show
  1. {code_context_control-2.30.0/code_context_control.egg-info → code_context_control-2.32.2}/PKG-INFO +33 -3
  2. {code_context_control-2.30.0 → code_context_control-2.32.2}/README.md +32 -2
  3. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/c3.py +58 -1
  4. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/commands/parser.py +21 -0
  5. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/docs.html +1 -1
  6. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/mcp_server.py +59 -0
  7. code_context_control-2.32.2/cli/tools/project.py +287 -0
  8. {code_context_control-2.30.0 → code_context_control-2.32.2/code_context_control.egg-info}/PKG-INFO +33 -3
  9. {code_context_control-2.30.0 → code_context_control-2.32.2}/code_context_control.egg-info/SOURCES.txt +11 -0
  10. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/config.py +7 -0
  11. code_context_control-2.32.2/oracle/mcp_oracle.py +146 -0
  12. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/oracle.html +130 -2
  13. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/oracle_server.py +210 -3
  14. code_context_control-2.32.2/oracle/services/api_auth.py +120 -0
  15. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/chat_engine.py +9 -0
  16. code_context_control-2.32.2/oracle/services/tool_executor.py +30 -0
  17. code_context_control-2.32.2/oracle/services/tool_registry.py +412 -0
  18. {code_context_control-2.30.0 → code_context_control-2.32.2}/pyproject.toml +1 -1
  19. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/claude_md.py +1 -0
  20. code_context_control-2.32.2/services/project_runtime.py +291 -0
  21. code_context_control-2.32.2/tests/test_oracle_api_auth.py +94 -0
  22. code_context_control-2.32.2/tests/test_oracle_apikey_api.py +101 -0
  23. code_context_control-2.32.2/tests/test_oracle_discovery_api.py +100 -0
  24. code_context_control-2.32.2/tests/test_project_tool.py +287 -0
  25. code_context_control-2.32.2/tests/test_tool_registry.py +91 -0
  26. {code_context_control-2.30.0 → code_context_control-2.32.2}/LICENSE +0 -0
  27. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/__init__.py +0 -0
  28. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/_hook_utils.py +0 -0
  29. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/commands/__init__.py +0 -0
  30. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/commands/common.py +0 -0
  31. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/edits.html +0 -0
  32. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hook_auto_snapshot.py +0 -0
  33. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hook_c3_signal.py +0 -0
  34. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hook_c3read.py +0 -0
  35. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hook_edit_ledger.py +0 -0
  36. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hook_edit_unlock.py +0 -0
  37. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hook_filter.py +0 -0
  38. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hook_ghost_files.py +0 -0
  39. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hook_pretool_enforce.py +0 -0
  40. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hook_read.py +0 -0
  41. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hook_session_stats.py +0 -0
  42. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hook_terse_advisor.py +0 -0
  43. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hub.html +0 -0
  44. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/hub_server.py +0 -0
  45. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/mcp_proxy.py +0 -0
  46. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/server.py +0 -0
  47. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/__init__.py +0 -0
  48. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/_helpers.py +0 -0
  49. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/agent.py +0 -0
  50. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/bitbucket.py +0 -0
  51. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/compress.py +0 -0
  52. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/delegate.py +0 -0
  53. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/edit.py +0 -0
  54. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/edits.py +0 -0
  55. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/filter.py +0 -0
  56. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/impact.py +0 -0
  57. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/memory.py +0 -0
  58. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/read.py +0 -0
  59. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/search.py +0 -0
  60. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/session.py +0 -0
  61. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/shell.py +0 -0
  62. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/status.py +0 -0
  63. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/tools/validate.py +0 -0
  64. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/api.js +0 -0
  65. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/app.js +0 -0
  66. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/components/bitbucket.js +0 -0
  67. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/components/chat.js +0 -0
  68. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/components/dashboard.js +0 -0
  69. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/components/edits.js +0 -0
  70. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/components/instructions.js +0 -0
  71. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/components/memory.js +0 -0
  72. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/components/sessions.js +0 -0
  73. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/components/settings.js +0 -0
  74. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/components/sidebar.js +0 -0
  75. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/icons.js +0 -0
  76. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/shared.js +0 -0
  77. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui/theme.js +0 -0
  78. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui.html +0 -0
  79. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui_legacy.html +0 -0
  80. {code_context_control-2.30.0 → code_context_control-2.32.2}/cli/ui_nano.html +0 -0
  81. {code_context_control-2.30.0 → code_context_control-2.32.2}/code_context_control.egg-info/dependency_links.txt +0 -0
  82. {code_context_control-2.30.0 → code_context_control-2.32.2}/code_context_control.egg-info/entry_points.txt +0 -0
  83. {code_context_control-2.30.0 → code_context_control-2.32.2}/code_context_control.egg-info/requires.txt +0 -0
  84. {code_context_control-2.30.0 → code_context_control-2.32.2}/code_context_control.egg-info/top_level.txt +0 -0
  85. {code_context_control-2.30.0 → code_context_control-2.32.2}/core/__init__.py +0 -0
  86. {code_context_control-2.30.0 → code_context_control-2.32.2}/core/config.py +0 -0
  87. {code_context_control-2.30.0 → code_context_control-2.32.2}/core/ide.py +0 -0
  88. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/__init__.py +0 -0
  89. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/__init__.py +0 -0
  90. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/c3_bridge.py +0 -0
  91. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/chat_store.py +0 -0
  92. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/cross_memory.py +0 -0
  93. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/federated_graph.py +0 -0
  94. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/health_checker.py +0 -0
  95. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/insight_engine.py +0 -0
  96. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/memory_reader.py +0 -0
  97. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/memory_writer.py +0 -0
  98. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/ollama_bridge.py +0 -0
  99. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/project_scanner.py +0 -0
  100. {code_context_control-2.30.0 → code_context_control-2.32.2}/oracle/services/review_agent.py +0 -0
  101. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/__init__.py +0 -0
  102. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/activity_log.py +0 -0
  103. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/agent_base.py +0 -0
  104. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/agents.py +0 -0
  105. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/auto_memory.py +0 -0
  106. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/bench/__init__.py +0 -0
  107. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/bench/external/__init__.py +0 -0
  108. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/bench/external/aider_polyglot.py +0 -0
  109. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/bench/external/swe_bench.py +0 -0
  110. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/benchmark_dashboard.py +0 -0
  111. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/bitbucket_client.py +0 -0
  112. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/bitbucket_credentials.py +0 -0
  113. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/compressor.py +0 -0
  114. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/context_snapshot.py +0 -0
  115. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/conversation_store.py +0 -0
  116. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/doc_index.py +0 -0
  117. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/e2e_benchmark.py +0 -0
  118. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/e2e_evaluator.py +0 -0
  119. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/e2e_tasks.py +0 -0
  120. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/edit_ledger.py +0 -0
  121. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/embedding_index.py +0 -0
  122. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/error_reporting.py +0 -0
  123. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/file_memory.py +0 -0
  124. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/hub_service.py +0 -0
  125. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/indexer.py +0 -0
  126. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/memory.py +0 -0
  127. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/memory_consolidator.py +0 -0
  128. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/memory_graph.py +0 -0
  129. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/memory_grounder.py +0 -0
  130. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/memory_scorer.py +0 -0
  131. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/metrics.py +0 -0
  132. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/notifications.py +0 -0
  133. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/ollama_client.py +0 -0
  134. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/output_filter.py +0 -0
  135. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/parser.py +0 -0
  136. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/project_manager.py +0 -0
  137. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/protocol.py +0 -0
  138. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/proxy_state.py +0 -0
  139. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/retrieval_broker.py +0 -0
  140. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/router.py +0 -0
  141. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/runtime.py +0 -0
  142. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/session_benchmark.py +0 -0
  143. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/session_manager.py +0 -0
  144. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/session_preloader.py +0 -0
  145. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/text_index.py +0 -0
  146. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/tool_classifier.py +0 -0
  147. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/transcript_index.py +0 -0
  148. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/validation_cache.py +0 -0
  149. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/vector_store.py +0 -0
  150. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/version_tracker.py +0 -0
  151. {code_context_control-2.30.0 → code_context_control-2.32.2}/services/watcher.py +0 -0
  152. {code_context_control-2.30.0 → code_context_control-2.32.2}/setup.cfg +0 -0
  153. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_aider_polyglot.py +0 -0
  154. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_bitbucket_cli_smoke.py +0 -0
  155. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_bitbucket_client.py +0 -0
  156. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_bitbucket_credentials.py +0 -0
  157. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_bitbucket_tool.py +0 -0
  158. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_c3_shell.py +0 -0
  159. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_cli_smoke.py +0 -0
  160. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_e2e_benchmark.py +0 -0
  161. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_edit_normalization.py +0 -0
  162. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_enforcement_flip.py +0 -0
  163. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_federated_graph.py +0 -0
  164. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_ghost_files.py +0 -0
  165. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_hub_server_smoke.py +0 -0
  166. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_mcp_server_smoke.py +0 -0
  167. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_memory_graph_api.py +0 -0
  168. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_memory_system.py +0 -0
  169. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_notification_discipline.py +0 -0
  170. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_output_filter.py +0 -0
  171. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_permissions.py +0 -0
  172. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_project_manager.py +0 -0
  173. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_project_manager_merge.py +0 -0
  174. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_session_benchmark.py +0 -0
  175. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_session_budget.py +0 -0
  176. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_swe_bench.py +0 -0
  177. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_validate.py +0 -0
  178. {code_context_control-2.30.0 → code_context_control-2.32.2}/tests/test_windows_reliability.py +0 -0
  179. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/__init__.py +0 -0
  180. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/backend.py +0 -0
  181. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/main.py +0 -0
  182. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/__init__.py +0 -0
  183. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/benchmark_view.py +0 -0
  184. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/claudemd_view.py +0 -0
  185. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/compress_view.py +0 -0
  186. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/index_view.py +0 -0
  187. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/init_view.py +0 -0
  188. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/mcp_view.py +0 -0
  189. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/optimize_view.py +0 -0
  190. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/pipe_view.py +0 -0
  191. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/projects_view.py +0 -0
  192. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/search_view.py +0 -0
  193. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/session_view.py +0 -0
  194. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/stats.py +0 -0
  195. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/screens/ui_view.py +0 -0
  196. {code_context_control-2.30.0 → code_context_control-2.32.2}/tui/theme.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.30.0
3
+ Version: 2.32.2
4
4
  Summary: Local code-intelligence layer for AI coding tools (Claude Code, Codex, Gemini, Copilot). Retrieve less, read less, edit safer.
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -228,7 +228,7 @@ Per-project knobs for everything: budget thresholds, feature flag mode, edit led
228
228
 
229
229
  ## The MCP tool suite
230
230
 
231
- C3 exposes 15 tools as a native MCP server. Your IDE calls them directly:
231
+ C3 exposes 16 tools as a native MCP server. Your IDE calls them directly:
232
232
 
233
233
  | Tool | What it does |
234
234
  |---|---|
@@ -247,8 +247,9 @@ C3 exposes 15 tools as a native MCP server. Your IDE calls them directly:
247
247
  | `c3_agent` | Multi-step agentic workflows (review, investigate, refactor) |
248
248
  | `c3_edits` | Edit-ledger queries + version diffs + restore points |
249
249
  | `c3_bitbucket` | Bitbucket Data Center integration — PRs, branches, builds, repo admin (v2.30.0) |
250
+ | `c3_project` | Cross-project — discover & operate on other c3-installed projects; guarded writes (v2.31.0) |
250
251
 
251
- Every tool is **read-only safe in plan mode** (except `c3_edit`, `c3_shell`, and write actions on `c3_bitbucket`).
252
+ Every tool is **read-only safe in plan mode** (except `c3_edit`, `c3_shell`, and write actions on `c3_bitbucket` / `c3_project`).
252
253
 
253
254
  ### Bitbucket Data Center / Server (v2.30.0)
254
255
 
@@ -280,6 +281,34 @@ to the C3 edit ledger so the audit trail covers platform-side changes too.
280
281
  The **Hub UI** (per-project) gains a "Bitbucket" tab with sub-views for
281
282
  Overview / Pull Requests / Branches / Activity / Admin.
282
283
 
284
+ ### Oracle Discovery API (v2.32.0)
285
+
286
+ The **Oracle** is C3's optional cross-project memory agent (a local web app). As of
287
+ v2.32.0 it can expose C3's cross-project code & memory intelligence as **tools for an
288
+ external LLM** — point Claude (or any function-calling model) at a running Oracle and
289
+ it can discover your projects and search code, memory, and the cross-project graph
290
+ across all of them.
291
+
292
+ Two transports share one tool core:
293
+
294
+ - **MCP** (streamable HTTP/SSE) at `http://127.0.0.1:3332/mcp` — native for Claude
295
+ Code / Claude Desktop / any MCP client.
296
+ - **OpenAPI REST** at `http://127.0.0.1:3331/api/discovery` — for any LLM with
297
+ function-calling (fetch `/openapi.json` to auto-register the tools).
298
+
299
+ ```bash
300
+ # Start the Oracle (serves the REST + MCP discovery endpoints)
301
+ python oracle/oracle_server.py --no-browser
302
+
303
+ # Print the Bearer token + a ready-to-paste .mcp.json snippet
304
+ c3 oracle api info
305
+ ```
306
+
307
+ Only **read** and **safe-action** tools are exposed (no code editing); requests need a
308
+ **Bearer token** (stored in the OS keyring) and both servers bind `127.0.0.1` by
309
+ default. Generate, rotate, and copy the token from the dashboard's **Settings →
310
+ Discovery API** tab. See the [Oracle Discovery API guide](oracle-guide/discovery-api.md).
311
+
283
312
  ---
284
313
 
285
314
  ## Tiered local AI (optional)
@@ -354,6 +383,7 @@ The author may introduce a paid offering or relicense future major versions; no
354
383
 
355
384
  - **PyPI:** https://pypi.org/project/code-context-control/
356
385
  - **Changelog:** [`CHANGELOG.md`](CHANGELOG.md)
386
+ - **Oracle Discovery API:** [`oracle-guide/discovery-api.md`](oracle-guide/discovery-api.md)
357
387
  - **Security policy:** [`SECURITY.md`](SECURITY.md)
358
388
  - **Licensing FAQ:** [`LICENSING.md`](LICENSING.md)
359
389
  - **Issues:** https://github.com/drknowhow/code-context-control/issues
@@ -166,7 +166,7 @@ Per-project knobs for everything: budget thresholds, feature flag mode, edit led
166
166
 
167
167
  ## The MCP tool suite
168
168
 
169
- C3 exposes 15 tools as a native MCP server. Your IDE calls them directly:
169
+ C3 exposes 16 tools as a native MCP server. Your IDE calls them directly:
170
170
 
171
171
  | Tool | What it does |
172
172
  |---|---|
@@ -185,8 +185,9 @@ C3 exposes 15 tools as a native MCP server. Your IDE calls them directly:
185
185
  | `c3_agent` | Multi-step agentic workflows (review, investigate, refactor) |
186
186
  | `c3_edits` | Edit-ledger queries + version diffs + restore points |
187
187
  | `c3_bitbucket` | Bitbucket Data Center integration — PRs, branches, builds, repo admin (v2.30.0) |
188
+ | `c3_project` | Cross-project — discover & operate on other c3-installed projects; guarded writes (v2.31.0) |
188
189
 
189
- Every tool is **read-only safe in plan mode** (except `c3_edit`, `c3_shell`, and write actions on `c3_bitbucket`).
190
+ Every tool is **read-only safe in plan mode** (except `c3_edit`, `c3_shell`, and write actions on `c3_bitbucket` / `c3_project`).
190
191
 
191
192
  ### Bitbucket Data Center / Server (v2.30.0)
192
193
 
@@ -218,6 +219,34 @@ to the C3 edit ledger so the audit trail covers platform-side changes too.
218
219
  The **Hub UI** (per-project) gains a "Bitbucket" tab with sub-views for
219
220
  Overview / Pull Requests / Branches / Activity / Admin.
220
221
 
222
+ ### Oracle Discovery API (v2.32.0)
223
+
224
+ The **Oracle** is C3's optional cross-project memory agent (a local web app). As of
225
+ v2.32.0 it can expose C3's cross-project code & memory intelligence as **tools for an
226
+ external LLM** — point Claude (or any function-calling model) at a running Oracle and
227
+ it can discover your projects and search code, memory, and the cross-project graph
228
+ across all of them.
229
+
230
+ Two transports share one tool core:
231
+
232
+ - **MCP** (streamable HTTP/SSE) at `http://127.0.0.1:3332/mcp` — native for Claude
233
+ Code / Claude Desktop / any MCP client.
234
+ - **OpenAPI REST** at `http://127.0.0.1:3331/api/discovery` — for any LLM with
235
+ function-calling (fetch `/openapi.json` to auto-register the tools).
236
+
237
+ ```bash
238
+ # Start the Oracle (serves the REST + MCP discovery endpoints)
239
+ python oracle/oracle_server.py --no-browser
240
+
241
+ # Print the Bearer token + a ready-to-paste .mcp.json snippet
242
+ c3 oracle api info
243
+ ```
244
+
245
+ Only **read** and **safe-action** tools are exposed (no code editing); requests need a
246
+ **Bearer token** (stored in the OS keyring) and both servers bind `127.0.0.1` by
247
+ default. Generate, rotate, and copy the token from the dashboard's **Settings →
248
+ Discovery API** tab. See the [Oracle Discovery API guide](oracle-guide/discovery-api.md).
249
+
221
250
  ---
222
251
 
223
252
  ## Tiered local AI (optional)
@@ -292,6 +321,7 @@ The author may introduce a paid offering or relicense future major versions; no
292
321
 
293
322
  - **PyPI:** https://pypi.org/project/code-context-control/
294
323
  - **Changelog:** [`CHANGELOG.md`](CHANGELOG.md)
324
+ - **Oracle Discovery API:** [`oracle-guide/discovery-api.md`](oracle-guide/discovery-api.md)
295
325
  - **Security policy:** [`SECURITY.md`](SECURITY.md)
296
326
  - **Licensing FAQ:** [`LICENSING.md`](LICENSING.md)
297
327
  - **Issues:** https://github.com/drknowhow/code-context-control/issues
@@ -85,7 +85,7 @@ console = Console() if HAS_RICH else None
85
85
  # Config
86
86
  CONFIG_DIR = ".c3"
87
87
  CONFIG_FILE = ".c3/config.json"
88
- __version__ = "2.30.0"
88
+ __version__ = "2.32.2"
89
89
 
90
90
 
91
91
  def _command_deps() -> CommandDeps:
@@ -239,6 +239,7 @@ _C3_MCP_ALLOW = [
239
239
  "mcp__c3__c3_memory", "mcp__c3__c3_validate", "mcp__c3__c3_edit",
240
240
  "mcp__c3__c3_agent", "mcp__c3__c3_delegate", "mcp__c3__c3_edits",
241
241
  "mcp__c3__c3_impact", "mcp__c3__c3_shell", "mcp__c3__c3_bitbucket",
242
+ "mcp__c3__c3_project",
242
243
  ]
243
244
 
244
245
  # Obsolete MCP tool names from earlier C3 versions. `c3 permissions clean`
@@ -4492,6 +4493,7 @@ back to native tools as the task progresses.
4492
4493
  - **Memory**: `c3_memory(action='recall')` — full recall. `index` + `fetch` for token-efficient two-step retrieval
4493
4494
  - **Delegate**: `c3_delegate(task, backend='ollama|codex|gemini|claude|auto')` — offload to other models
4494
4495
  - **Bitbucket** (v2.30.0+, when `c3 bitbucket login` has run): `c3_bitbucket(action='list_prs|get_pr|merge_pr|...')` — self-hosted Bitbucket Data Center / Server. Token in OS keyring; mutating actions auto-log to the edit ledger.
4496
+ - **Cross-project** (v2.31.0+): `c3_project(action='list|scan|search|read|edit|...', project='<name|path>')` — discover and operate on OTHER c3-installed projects. Reads run freely; writes (edit/shell/memory) need `allow_write=true`.
4495
4497
 
4496
4498
  ## Self-Check
4497
4499
  If you haven't called a c3_* tool in several turns during active development, re-engage
@@ -5556,6 +5558,60 @@ def _bb_cmd_set_default(args, project_path: str) -> None:
5556
5558
  print(f"[OK] Default repo: {args.project}/{args.repo}")
5557
5559
 
5558
5560
 
5561
+ def cmd_oracle(args):
5562
+ """Oracle Discovery API key + connection management."""
5563
+ sub = getattr(args, "oracle_cmd", None)
5564
+ if sub != "api":
5565
+ print("Usage: c3 oracle api {info,key,rotate,clear}")
5566
+ return
5567
+
5568
+ from oracle.config import load_config
5569
+ from oracle.mcp_oracle import mcp_url
5570
+ from oracle.services import api_auth
5571
+
5572
+ action = getattr(args, "action", "info") or "info"
5573
+
5574
+ if action == "rotate":
5575
+ print("Rotated. New Discovery API key:")
5576
+ print(f" {api_auth.rotate()}")
5577
+ return
5578
+ if action == "clear":
5579
+ removed = api_auth.clear()
5580
+ print("Discovery API key cleared." if removed else "No stored Discovery API key to clear.")
5581
+ return
5582
+
5583
+ key = api_auth.get_or_create_key()
5584
+ if action == "key":
5585
+ print(key)
5586
+ return
5587
+
5588
+ cfg = load_config()
5589
+ host = cfg.get("bind_host", "127.0.0.1")
5590
+ disp_host = "127.0.0.1" if host in ("0.0.0.0", "") else host
5591
+ rest_port = getattr(args, "port", None) or cfg.get("port", 3331)
5592
+ mcp_port = getattr(args, "mcp_port", None) or cfg.get("mcp_port", 3332)
5593
+ rest_base = f"http://{disp_host}:{rest_port}/api/discovery"
5594
+ url = mcp_url(host, mcp_port)
5595
+
5596
+ print("[oracle:api]")
5597
+ print(f" REST base : {rest_base}")
5598
+ print(f" OpenAPI : {rest_base}/openapi.json")
5599
+ print(f" MCP URL : {url}")
5600
+ print(f" Auth : Bearer {key}")
5601
+ print()
5602
+ print(" Claude .mcp.json entry:")
5603
+ snippet = {
5604
+ "mcpServers": {
5605
+ "c3-oracle": {
5606
+ "type": "http",
5607
+ "url": url,
5608
+ "headers": {"Authorization": f"Bearer {key}"},
5609
+ }
5610
+ }
5611
+ }
5612
+ print(json.dumps(snippet, indent=2))
5613
+
5614
+
5559
5615
  def cmd_projects(args):
5560
5616
  """Manage the global C3 project registry."""
5561
5617
  from services.project_manager import ProjectManager
@@ -6308,6 +6364,7 @@ def main():
6308
6364
  "projects": cmd_projects,
6309
6365
  "hub": cmd_hub,
6310
6366
  "bitbucket": cmd_bitbucket,
6367
+ "oracle": cmd_oracle,
6311
6368
  }
6312
6369
 
6313
6370
  cmd_func = commands.get(args.command)
@@ -322,4 +322,25 @@ def build_parser(version: str, parse_cli_ide_arg):
322
322
  bb_default.add_argument("--repo", required=True, help="Repository slug")
323
323
  bb_default.add_argument("project_path", nargs="?", default=".")
324
324
 
325
+ # ── Oracle Discovery API (v2.32.0) ──────────────────────────────────
326
+ p_oracle = subparsers.add_parser(
327
+ "oracle",
328
+ help="Oracle Discovery API key + connection management",
329
+ )
330
+ or_subs = p_oracle.add_subparsers(dest="oracle_cmd")
331
+ or_api = or_subs.add_parser(
332
+ "api",
333
+ help="Show connection info / manage the Discovery API key",
334
+ )
335
+ or_api.add_argument(
336
+ "action",
337
+ nargs="?",
338
+ default="info",
339
+ choices=["info", "key", "rotate", "clear"],
340
+ help="info (default): print REST+MCP URLs and a .mcp.json snippet; "
341
+ "key: print the token; rotate: replace it; clear: delete it",
342
+ )
343
+ or_api.add_argument("--port", type=int, default=None, help="Override REST port in printed info")
344
+ or_api.add_argument("--mcp-port", type=int, default=None, help="Override MCP port in printed info")
345
+
325
346
  return parser
@@ -1148,7 +1148,7 @@ python cli/c3.py install-mcp . gemini</code></pre>
1148
1148
 
1149
1149
  <!-- ─── MCP Tools ───────────────────── -->
1150
1150
  <h2 id="mcp-tools">MCP Tools Reference</h2>
1151
- <p>C3 exposes 15 MCP tools. All core tools work without Ollama; delegate requires it. The Bitbucket integration is optional and activated via <code>c3 bitbucket login</code>.</p>
1151
+ <p>C3 exposes 16 MCP tools. All core tools work without Ollama; delegate requires it. The Bitbucket integration is optional and activated via <code>c3 bitbucket login</code>.</p>
1152
1152
 
1153
1153
  <h3>Discovery &amp; Compression</h3>
1154
1154
  <table>
@@ -699,6 +699,65 @@ async def c3_bitbucket(
699
699
  )
700
700
 
701
701
 
702
+ @mcp.tool()
703
+ async def c3_project(
704
+ action: str,
705
+ project: str = "",
706
+ query: str = "",
707
+ file_path: str = "",
708
+ symbols: Any = None,
709
+ lines: Any = None,
710
+ mode: str = "map",
711
+ view: str = "health",
712
+ top_k: int = 5,
713
+ max_tokens: int = 1200,
714
+ search_action: str = "code",
715
+ mem_action: str = "recall",
716
+ fact: str = "",
717
+ category: str = "",
718
+ fact_id: str = "",
719
+ edits_action: str = "history",
720
+ file: str = "",
721
+ tag: str = "",
722
+ limit: int = 50,
723
+ target: str = "",
724
+ old_string: str = "",
725
+ new_string: str = "",
726
+ summary: str = "",
727
+ edits: str = "",
728
+ replace_all: bool = False,
729
+ tags: str = "",
730
+ cmd: str = "",
731
+ timeout: int = 60,
732
+ scan_roots: str = "",
733
+ allow_write: bool = False,
734
+ ctx: Context = None,
735
+ ) -> str:
736
+ """CROSS-PROJECT — run C3 against OTHER c3-installed projects (read-only safe in plan mode).
737
+ Discover: list (registry), scan (registry+filesystem), info, register, unregister.
738
+ Read : search, read, compress, status, memory, impact, edits, validate, filter.
739
+ Write : edit, shell, memory(add/update/delete) — require allow_write=true; logged to that project's ledger.
740
+ project = registered name OR absolute path (.c3 required). list/scan need no project.
741
+ search_action/mem_action/edits_action pick the sub-op for those verbs."""
742
+ svc = _svc(ctx)
743
+
744
+ def finalize(fname, fargs, fresp, fsumm, **kw):
745
+ return _finalize_response(ctx, fname, fargs, fresp, fsumm, **kw)
746
+
747
+ from cli.tools.project import handle_project
748
+ return await asyncio.to_thread(
749
+ handle_project, action, svc, finalize,
750
+ project=project, query=query, file_path=file_path, symbols=symbols,
751
+ lines=lines, mode=mode, view=view, top_k=top_k, max_tokens=max_tokens,
752
+ search_action=search_action, mem_action=mem_action, fact=fact,
753
+ category=category, fact_id=fact_id, edits_action=edits_action, file=file,
754
+ tag=tag, limit=limit, target=target, old_string=old_string,
755
+ new_string=new_string, summary=summary, edits=edits,
756
+ replace_all=replace_all, tags=tags, cmd=cmd, timeout=timeout,
757
+ scan_roots=scan_roots, allow_write=allow_write,
758
+ )
759
+
760
+
702
761
  def main() -> None:
703
762
  """Entry-point for the ``c3-mcp`` console script."""
704
763
  from services import error_reporting
@@ -0,0 +1,287 @@
1
+ """c3_project tool -- run C3 against OTHER c3-installed projects.
2
+
3
+ Discovery and read ops run freely against any registered/.c3 project. Write ops
4
+ (``edit``, ``shell``, and memory mutations) require ``allow_write=True`` and are
5
+ recorded on the *target* project (its edit ledger + activity log), so a foreign
6
+ mutation leaves an audit trail in the project it touched.
7
+
8
+ The heavy lifting reuses the existing per-tool handlers unchanged -- only the
9
+ ``svc`` (a ``C3Runtime``) differs, supplied by the shared foreign-runtime cache.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ from pathlib import Path
15
+
16
+ from services.project_runtime import (
17
+ discover_projects,
18
+ resolve_project,
19
+ shared_cache,
20
+ )
21
+
22
+ # Memory sub-actions that mutate the target project's fact store.
23
+ _MEMORY_WRITE = {"add", "update", "delete", "consolidate", "consolidate_deep", "ground"}
24
+ # Dispatch verbs that mutate the target project.
25
+ _WRITE_OPS = {"edit", "shell"}
26
+ _DISCOVERY_OPS = {"list", "scan", "info", "register", "unregister"}
27
+ _READ_OPS = {
28
+ "search", "read", "compress", "status", "memory",
29
+ "impact", "edits", "validate", "filter",
30
+ }
31
+
32
+
33
+ def _foreign_finalize(_name, _args, resp, _summ="", **_kw):
34
+ """No-op finalize for proxied calls.
35
+
36
+ The home session's finalize wraps the whole ``c3_project`` response, so the
37
+ inner handlers must not also charge budget / log against either session.
38
+ """
39
+ return resp
40
+
41
+
42
+ def _foreign_facts(*_a, **_kw):
43
+ return ""
44
+
45
+
46
+ def _runtime_for(path: str):
47
+ """Indirection point (monkeypatched in tests) -> foreign ``C3Runtime``."""
48
+ return shared_cache().get(path)
49
+
50
+
51
+ # ── Discovery renderers ────────────────────────────────────────────────────
52
+
53
+
54
+ def _render_discovery(scan_roots_csv: str, do_scan: bool) -> str:
55
+ roots = [r.strip() for r in (scan_roots_csv or "").split(",") if r.strip()] or None
56
+ data = discover_projects(scan_roots=roots, scan=do_scan)
57
+ reg = data["registered"]
58
+ unreg = data["unregistered"]
59
+
60
+ out = [f"Registered C3 projects ({len(reg)}):"]
61
+ if not reg:
62
+ out.append(" (none -- c3_project(action='register', project='<path>') to add one)")
63
+ for p in reg:
64
+ flag = "" if p["accessible"] else " [MISSING]"
65
+ out.append(f" - {p['name']:<28} {p['ide']:<12} {p['path']}{flag}")
66
+
67
+ if do_scan:
68
+ out.append("")
69
+ out.append(f"Unregistered .c3 projects found nearby ({len(unreg)}):")
70
+ if not unreg:
71
+ out.append(" (none found near registered projects)")
72
+ for p in unreg:
73
+ out.append(f" - {p['name']:<28} {'':<12} {p['path']}")
74
+ if unreg:
75
+ out.append("")
76
+ out.append("Register one: c3_project(action='register', project='<path>')")
77
+ return "\n".join(out)
78
+
79
+
80
+ def _render_info(project: str) -> str:
81
+ try:
82
+ resolved = resolve_project(project)
83
+ except ValueError as e:
84
+ return f"[c3_project:error] {e}"
85
+ p = Path(resolved["path"])
86
+ out = [
87
+ f"Project: {resolved['name']}",
88
+ f" path : {resolved['path']}",
89
+ f" .c3 present : {(p / '.c3').is_dir()}",
90
+ f" accessible : {p.is_dir()}",
91
+ ]
92
+ try:
93
+ from services.project_manager import ProjectManager
94
+
95
+ details = ProjectManager().get_project_details(resolved["path"]) or {}
96
+ for key in ("ide", "c3_version", "facts_count", "last_session", "active"):
97
+ if key in details and details[key] not in (None, ""):
98
+ out.append(f" {key:<11} : {details[key]}")
99
+ except Exception:
100
+ pass
101
+ return "\n".join(out)
102
+
103
+
104
+ def _do_register(project: str) -> str:
105
+ if not (project or "").strip():
106
+ return "[c3_project:error] register requires project='<path>'."
107
+ path = Path(project).expanduser()
108
+ if not path.exists():
109
+ return f"[c3_project:error] Path does not exist: {project}"
110
+ if not (path / ".c3").is_dir():
111
+ return (
112
+ f"[c3_project:error] No .c3 directory in {path}. "
113
+ "Run 'c3 init' there first."
114
+ )
115
+ from services.project_manager import ProjectManager
116
+
117
+ entry = ProjectManager().add_project(str(path.resolve()))
118
+ return f"Registered: {entry['name']} ({entry['path']})"
119
+
120
+
121
+ def _do_unregister(project: str) -> str:
122
+ try:
123
+ resolved = resolve_project(project)
124
+ except ValueError as e:
125
+ return f"[c3_project:error] {e}"
126
+ from services.project_manager import ProjectManager
127
+
128
+ removed = ProjectManager().remove_project(resolved["path"])
129
+ return (
130
+ f"Unregistered: {resolved['name']}"
131
+ if removed
132
+ else f"Not in registry: {resolved['name']}"
133
+ )
134
+
135
+
136
+ # ── Proxied op dispatch ────────────────────────────────────────────────────
137
+
138
+
139
+ def _proxy(action, fsvc, *, query, file_path, symbols, lines, mode, view, top_k,
140
+ max_tokens, search_action, mem_action, fact, category, fact_id,
141
+ edits_action, file, tag, limit, target, old_string, new_string,
142
+ summary, edits, replace_all, tags, cmd, timeout, project_path):
143
+ if action == "search":
144
+ from cli.tools.search import handle_search
145
+
146
+ return handle_search(query, search_action, top_k, max_tokens,
147
+ fsvc, _foreign_finalize, _foreign_facts)
148
+ if action == "read":
149
+ from cli.tools.read import handle_read
150
+
151
+ return handle_read(file_path, symbols=symbols, lines=lines,
152
+ svc=fsvc, finalize=_foreign_finalize)
153
+ if action == "compress":
154
+ from cli.tools.compress import handle_compress
155
+
156
+ return handle_compress(file_path, mode, fsvc, _foreign_finalize, _foreign_facts)
157
+ if action == "status":
158
+ from cli.tools.status import handle_status
159
+
160
+ return handle_status(view, False, fsvc, _foreign_finalize)
161
+ if action == "memory":
162
+ from cli.tools.memory import handle_memory
163
+
164
+ return handle_memory(mem_action, query, fact, category, top_k,
165
+ fsvc, _foreign_finalize, fact_id=fact_id)
166
+ if action == "impact":
167
+ from cli.tools.impact import handle_impact
168
+
169
+ imode = mode if mode in ("symbol", "unstaged") else "symbol"
170
+ return handle_impact(target, file_path, imode, fsvc, _foreign_finalize)
171
+ if action == "edits":
172
+ from cli.tools.edits import handle_edits
173
+
174
+ return handle_edits(edits_action, file, "", "", "", tags, limit, "", "",
175
+ tag, fsvc, _foreign_finalize)
176
+ if action == "validate":
177
+ from cli.tools.validate import handle_validate
178
+
179
+ return asyncio.run(handle_validate(file_path, fsvc, _foreign_finalize))
180
+ if action == "filter":
181
+ from cli.tools.filter import handle_filter
182
+
183
+ return handle_filter(file_path, "", query, 100, "smart", False,
184
+ fsvc, _foreign_finalize)
185
+ if action == "edit":
186
+ from cli.tools.edit import handle_edit
187
+
188
+ return handle_edit(file_path, old_string, new_string, summary, tags,
189
+ replace_all, fsvc, _foreign_finalize, edits)
190
+ if action == "shell":
191
+ from cli.tools.shell import handle_shell
192
+
193
+ return asyncio.run(handle_shell(cmd, project_path, timeout, True, True,
194
+ fsvc, _foreign_finalize))
195
+ return f"[c3_project:error] Unhandled op '{action}'."
196
+
197
+
198
+ # ── Entry point ────────────────────────────────────────────────────────────
199
+
200
+
201
+ def handle_project(action, svc, finalize, *, project="", query="", file_path="",
202
+ symbols=None, lines=None, mode="map", view="health", top_k=5,
203
+ max_tokens=1200, search_action="code", mem_action="recall",
204
+ fact="", category="", fact_id="", edits_action="history",
205
+ file="", tag="", limit=50, target="", old_string="",
206
+ new_string="", summary="", edits="", replace_all=False,
207
+ tags="", cmd="", timeout=60, scan_roots="", allow_write=False):
208
+ action = (action or "").strip().lower()
209
+
210
+ def done(resp, summ="ok"):
211
+ return finalize("c3_project", {"action": action, "project": project},
212
+ resp, summ)
213
+
214
+ if not action:
215
+ return done(
216
+ "[c3_project:error] action required. "
217
+ f"Discovery: {', '.join(sorted(_DISCOVERY_OPS))}. "
218
+ f"Read: {', '.join(sorted(_READ_OPS))}. "
219
+ f"Write (allow_write=true): {', '.join(sorted(_WRITE_OPS))}.",
220
+ "error")
221
+
222
+ # ── Discovery (no foreign runtime needed) ──────────────────────────
223
+ if action in ("list", "scan"):
224
+ return done(_render_discovery(scan_roots, action == "scan"), f"{action} projects")
225
+ if action == "info":
226
+ return done(_render_info(project), "project info")
227
+ if action == "register":
228
+ return done(_do_register(project), "register project")
229
+ if action == "unregister":
230
+ return done(_do_unregister(project), "unregister project")
231
+
232
+ if action not in _READ_OPS and action not in _WRITE_OPS:
233
+ return done(
234
+ f"[c3_project:error] Unknown action '{action}'. "
235
+ f"Discovery: {', '.join(sorted(_DISCOVERY_OPS))}. "
236
+ f"Read: {', '.join(sorted(_READ_OPS))}. "
237
+ f"Write (allow_write=true): {', '.join(sorted(_WRITE_OPS))}.",
238
+ "error")
239
+
240
+ # ── Write guard ────────────────────────────────────────────────────
241
+ is_write = action in _WRITE_OPS or (
242
+ action == "memory" and (mem_action or "").lower() in _MEMORY_WRITE
243
+ )
244
+ if is_write and not allow_write:
245
+ label = action + (f"/{mem_action}" if action == "memory" else "")
246
+ return done(
247
+ f"[c3_project:blocked] '{label}' would modify project '{project}'. "
248
+ "Re-run with allow_write=true to proceed.",
249
+ "blocked")
250
+
251
+ # ── Resolve + borrow the foreign runtime ───────────────────────────
252
+ try:
253
+ resolved = resolve_project(project)
254
+ except ValueError as e:
255
+ return done(f"[c3_project:error] {e}", "error")
256
+ try:
257
+ fsvc = _runtime_for(resolved["path"])
258
+ except Exception as e:
259
+ return done(
260
+ f"[c3_project:error] Could not load '{resolved['name']}': {e}", "error")
261
+
262
+ banner = f"[c3_project:{resolved['name']}] {action}\n"
263
+ try:
264
+ body = _proxy(
265
+ action, fsvc, query=query, file_path=file_path, symbols=symbols,
266
+ lines=lines, mode=mode, view=view, top_k=top_k, max_tokens=max_tokens,
267
+ search_action=search_action, mem_action=mem_action, fact=fact,
268
+ category=category, fact_id=fact_id, edits_action=edits_action,
269
+ file=file, tag=tag, limit=limit, target=target, old_string=old_string,
270
+ new_string=new_string, summary=summary, edits=edits,
271
+ replace_all=replace_all, tags=tags, cmd=cmd, timeout=timeout,
272
+ project_path=resolved["path"],
273
+ )
274
+ except Exception as e:
275
+ return done(f"{banner}[error] {type(e).__name__}: {e}", "error")
276
+
277
+ # Audit foreign mutations on the target project itself.
278
+ if is_write and getattr(fsvc, "activity_log", None):
279
+ try:
280
+ fsvc.activity_log.log("cross_project_write", {
281
+ "action": action,
282
+ "from_project": getattr(svc, "project_path", ""),
283
+ })
284
+ except Exception:
285
+ pass
286
+
287
+ return done(banner + (body or ""), f"{action} on {resolved['name']}")