code-context-control 2.49.2__tar.gz → 2.51.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 (318) hide show
  1. {code_context_control-2.49.2/code_context_control.egg-info → code_context_control-2.51.0}/PKG-INFO +1 -1
  2. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/c3.py +109 -2
  3. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_dispatch.py +15 -4
  4. code_context_control-2.51.0/cli/hook_prompt_recall.py +97 -0
  5. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_server.py +8 -3
  6. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/config_editor.js +8 -2
  7. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/mcp_server.py +23 -0
  8. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/server.py +65 -0
  9. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/_helpers.py +51 -3
  10. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/edit.py +118 -6
  11. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/read.py +26 -7
  12. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/session.py +18 -0
  13. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/components/settings.js +120 -2
  14. {code_context_control-2.49.2 → code_context_control-2.51.0/code_context_control.egg-info}/PKG-INFO +1 -1
  15. {code_context_control-2.49.2 → code_context_control-2.51.0}/code_context_control.egg-info/SOURCES.txt +11 -1
  16. {code_context_control-2.49.2 → code_context_control-2.51.0}/core/config.py +41 -0
  17. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/oracle_server.py +1 -1
  18. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/chat_engine.py +1 -1
  19. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/insight_engine.py +1 -1
  20. {code_context_control-2.49.2 → code_context_control-2.51.0}/pyproject.toml +1 -1
  21. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/agents.py +70 -0
  22. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/memory.py +19 -7
  23. code_context_control-2.51.0/services/memory_distiller.py +591 -0
  24. code_context_control-2.51.0/services/memory_queue.py +107 -0
  25. {code_context_control-2.49.2/oracle → code_context_control-2.51.0}/services/ollama_bridge.py +20 -2
  26. code_context_control-2.51.0/services/ollama_credentials.py +78 -0
  27. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/runtime.py +14 -1
  28. code_context_control-2.51.0/tests/test_hook_prompt_recall.py +105 -0
  29. code_context_control-2.51.0/tests/test_memory_distiller.py +409 -0
  30. code_context_control-2.51.0/tests/test_memory_queue.py +87 -0
  31. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_memory_system.py +25 -0
  32. code_context_control-2.51.0/tests/test_ollama_credentials.py +102 -0
  33. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_oracle_ollama_bridge.py +2 -2
  34. code_context_control-2.51.0/tests/test_read_edit_parity.py +208 -0
  35. code_context_control-2.51.0/tests/test_related_facts.py +74 -0
  36. {code_context_control-2.49.2 → code_context_control-2.51.0}/LICENSE +0 -0
  37. {code_context_control-2.49.2 → code_context_control-2.51.0}/README.md +0 -0
  38. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/__init__.py +0 -0
  39. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/_hook_utils.py +0 -0
  40. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/commands/__init__.py +0 -0
  41. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/commands/common.py +0 -0
  42. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/commands/parser.py +0 -0
  43. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/docs.html +0 -0
  44. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/edits.html +0 -0
  45. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/guide/bitbucket.html +0 -0
  46. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/guide/getting-started.html +0 -0
  47. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/guide/index.html +0 -0
  48. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/guide/oracle.html +0 -0
  49. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/guide/shared.css +0 -0
  50. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/guide/tools.html +0 -0
  51. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/guide/workflow.html +0 -0
  52. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_artifact.py +0 -0
  53. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_auto_snapshot.py +0 -0
  54. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_c3_signal.py +0 -0
  55. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_c3read.py +0 -0
  56. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_edit_ledger.py +0 -0
  57. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_edit_unlock.py +0 -0
  58. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_filter.py +0 -0
  59. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_ghost_files.py +0 -0
  60. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_pretool_enforce.py +0 -0
  61. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_read.py +0 -0
  62. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_session_stats.py +0 -0
  63. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hook_terse_advisor.py +0 -0
  64. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub.html +0 -0
  65. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/app.js +0 -0
  66. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/add_project.js +0 -0
  67. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
  68. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/drill_health.js +0 -0
  69. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/drill_panel.js +0 -0
  70. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/drill_tasks.js +0 -0
  71. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/drill_views.js +0 -0
  72. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/global_search.js +0 -0
  73. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/mcp_manager.js +0 -0
  74. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/modals.js +0 -0
  75. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/project_card.js +0 -0
  76. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/project_tree.js +0 -0
  77. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/session_drawer.js +0 -0
  78. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/settings_modal.js +0 -0
  79. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/sidebar.js +0 -0
  80. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/summary_bar.js +0 -0
  81. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/task_board.js +0 -0
  82. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/toasts.js +0 -0
  83. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/components/topbar.js +0 -0
  84. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui/state.js +0 -0
  85. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/hub_ui.html +0 -0
  86. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/mcp_proxy.py +0 -0
  87. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/__init__.py +0 -0
  88. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/agent.py +0 -0
  89. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/artifacts.py +0 -0
  90. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/bitbucket.py +0 -0
  91. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/compress.py +0 -0
  92. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/delegate.py +0 -0
  93. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/edits.py +0 -0
  94. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/federate.py +0 -0
  95. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/filter.py +0 -0
  96. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/impact.py +0 -0
  97. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/memory.py +0 -0
  98. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/project.py +0 -0
  99. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/search.py +0 -0
  100. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/shell.py +0 -0
  101. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/status.py +0 -0
  102. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/tasks.py +0 -0
  103. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/tools/validate.py +0 -0
  104. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/api.js +0 -0
  105. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/app.js +0 -0
  106. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/components/bitbucket.js +0 -0
  107. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/components/chat.js +0 -0
  108. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/components/dashboard.js +0 -0
  109. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/components/edits.js +0 -0
  110. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/components/instructions.js +0 -0
  111. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/components/memory.js +0 -0
  112. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/components/sessions.js +0 -0
  113. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/components/sidebar.js +0 -0
  114. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/components/tasks.js +0 -0
  115. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/icons.js +0 -0
  116. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/pm_shared.js +0 -0
  117. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/shared.js +0 -0
  118. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui/theme.js +0 -0
  119. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui.html +0 -0
  120. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui_legacy.html +0 -0
  121. {code_context_control-2.49.2 → code_context_control-2.51.0}/cli/ui_nano.html +0 -0
  122. {code_context_control-2.49.2 → code_context_control-2.51.0}/code_context_control.egg-info/dependency_links.txt +0 -0
  123. {code_context_control-2.49.2 → code_context_control-2.51.0}/code_context_control.egg-info/entry_points.txt +0 -0
  124. {code_context_control-2.49.2 → code_context_control-2.51.0}/code_context_control.egg-info/requires.txt +0 -0
  125. {code_context_control-2.49.2 → code_context_control-2.51.0}/code_context_control.egg-info/top_level.txt +0 -0
  126. {code_context_control-2.49.2 → code_context_control-2.51.0}/core/__init__.py +0 -0
  127. {code_context_control-2.49.2 → code_context_control-2.51.0}/core/ide.py +0 -0
  128. {code_context_control-2.49.2 → code_context_control-2.51.0}/core/mcp_toml.py +0 -0
  129. {code_context_control-2.49.2 → code_context_control-2.51.0}/core/web_security.py +0 -0
  130. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/__init__.py +0 -0
  131. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/config.py +0 -0
  132. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/mcp_oracle.py +0 -0
  133. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/oracle_ui.html +0 -0
  134. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/__init__.py +0 -0
  135. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/activity_reporter.py +0 -0
  136. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/api_auth.py +0 -0
  137. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/c3_bridge.py +0 -0
  138. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/chat_store.py +0 -0
  139. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/cross_memory.py +0 -0
  140. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/federated_graph.py +0 -0
  141. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/health_checker.py +0 -0
  142. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/local_session.py +0 -0
  143. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/memory_reader.py +0 -0
  144. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/memory_writer.py +0 -0
  145. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/project_scanner.py +0 -0
  146. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/review_agent.py +0 -0
  147. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/tool_executor.py +0 -0
  148. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/services/tool_registry.py +0 -0
  149. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/activity.js +0 -0
  150. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/agents.js +0 -0
  151. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/app.js +0 -0
  152. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/busy.js +0 -0
  153. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/chat/conversations.js +0 -0
  154. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/chat/input.js +0 -0
  155. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/chat/markdown.js +0 -0
  156. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/chat/send.js +0 -0
  157. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/chat/stream_renderer.js +0 -0
  158. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/chat/toolbar.js +0 -0
  159. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/core.js +0 -0
  160. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/crossgraph.js +0 -0
  161. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/header.js +0 -0
  162. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/insights.js +0 -0
  163. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/projects.js +0 -0
  164. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/settings.js +0 -0
  165. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/suggestions.js +0 -0
  166. {code_context_control-2.49.2 → code_context_control-2.51.0}/oracle/ui/theme_tabs.js +0 -0
  167. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/__init__.py +0 -0
  168. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/activity_log.py +0 -0
  169. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/agent_base.py +0 -0
  170. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/artifact_defs.py +0 -0
  171. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/artifact_store.py +0 -0
  172. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/auto_memory.py +0 -0
  173. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/bench/__init__.py +0 -0
  174. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/bench/external/__init__.py +0 -0
  175. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/bench/external/aider_polyglot.py +0 -0
  176. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/bench/external/swe_bench.py +0 -0
  177. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/benchmark_dashboard.py +0 -0
  178. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/bitbucket_client.py +0 -0
  179. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/bitbucket_credentials.py +0 -0
  180. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/circuit_breaker.py +0 -0
  181. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/claude_md.py +0 -0
  182. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/compressor.py +0 -0
  183. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/context_snapshot.py +0 -0
  184. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/conversation_store.py +0 -0
  185. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/doc_index.py +0 -0
  186. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/e2e_benchmark.py +0 -0
  187. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/e2e_evaluator.py +0 -0
  188. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/e2e_tasks.py +0 -0
  189. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/edit_ledger.py +0 -0
  190. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/embedding_index.py +0 -0
  191. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/error_reporting.py +0 -0
  192. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/file_memory.py +0 -0
  193. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/git_context.py +0 -0
  194. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/hub_service.py +0 -0
  195. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/indexer.py +0 -0
  196. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/memory_consolidator.py +0 -0
  197. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/memory_graph.py +0 -0
  198. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/memory_grounder.py +0 -0
  199. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/memory_scorer.py +0 -0
  200. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/metrics.py +0 -0
  201. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/notifications.py +0 -0
  202. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/ollama_client.py +0 -0
  203. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/output_filter.py +0 -0
  204. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/parser.py +0 -0
  205. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/project_manager.py +0 -0
  206. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/project_runtime.py +0 -0
  207. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/protocol.py +0 -0
  208. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/proxy_state.py +0 -0
  209. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/retention.py +0 -0
  210. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/retrieval_broker.py +0 -0
  211. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/router.py +0 -0
  212. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/session_benchmark.py +0 -0
  213. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/session_manager.py +0 -0
  214. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/session_preloader.py +0 -0
  215. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/subprojects.py +0 -0
  216. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/task_store.py +0 -0
  217. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/telemetry.py +0 -0
  218. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/text_index.py +0 -0
  219. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/tool_classifier.py +0 -0
  220. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/transcript_index.py +0 -0
  221. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/validation_cache.py +0 -0
  222. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/vector_store.py +0 -0
  223. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/watcher.py +0 -0
  224. {code_context_control-2.49.2 → code_context_control-2.51.0}/services/win_subprocess.py +0 -0
  225. {code_context_control-2.49.2 → code_context_control-2.51.0}/setup.cfg +0 -0
  226. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_activity_reporter.py +0 -0
  227. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_aider_polyglot.py +0 -0
  228. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_artifact_store.py +0 -0
  229. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_artifact_tool.py +0 -0
  230. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_bitbucket_cli_smoke.py +0 -0
  231. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_bitbucket_client.py +0 -0
  232. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_bitbucket_config_fallback.py +0 -0
  233. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_bitbucket_credentials.py +0 -0
  234. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_bitbucket_tool.py +0 -0
  235. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
  236. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_c3_shell.py +0 -0
  237. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_circuit_breaker.py +0 -0
  238. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_claude_md_merge.py +0 -0
  239. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_cli_smoke.py +0 -0
  240. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_compressor_large_file.py +0 -0
  241. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_delegate_cascade.py +0 -0
  242. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_e2e_benchmark.py +0 -0
  243. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_edit_ledger_hook.py +0 -0
  244. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_edit_normalization.py +0 -0
  245. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_enforcement_flip.py +0 -0
  246. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_federated_graph.py +0 -0
  247. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_filter_backoff.py +0 -0
  248. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_ghost_files.py +0 -0
  249. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_ghost_generation.py +0 -0
  250. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_git_branch_awareness.py +0 -0
  251. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_hook_dispatch.py +0 -0
  252. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_hook_pretool_enforce.py +0 -0
  253. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_hook_smoke.py +0 -0
  254. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_hook_state.py +0 -0
  255. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_hub_inspect_api.py +0 -0
  256. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_hub_server_smoke.py +0 -0
  257. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_install_mcp_entrypoint.py +0 -0
  258. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_lazy_store_init.py +0 -0
  259. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_mcp_host_guard.py +0 -0
  260. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_mcp_server_smoke.py +0 -0
  261. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_mcp_toml.py +0 -0
  262. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_memory_graph_api.py +0 -0
  263. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_notification_dedup.py +0 -0
  264. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_notification_discipline.py +0 -0
  265. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_oracle_api_auth.py +0 -0
  266. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_oracle_apikey_api.py +0 -0
  267. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_oracle_c3_bridge.py +0 -0
  268. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_oracle_chat_engine.py +0 -0
  269. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_oracle_discovery_api.py +0 -0
  270. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_oracle_local_auth.py +0 -0
  271. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_oracle_scanner_cache.py +0 -0
  272. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_oracle_security_fixes.py +0 -0
  273. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_oracle_subproject_awareness.py +0 -0
  274. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_oracle_ui_bundle.py +0 -0
  275. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_output_filter.py +0 -0
  276. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_permissions.py +0 -0
  277. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_pm_api.py +0 -0
  278. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_project_manager.py +0 -0
  279. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_project_manager_merge.py +0 -0
  280. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_project_tool.py +0 -0
  281. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_read_coercion.py +0 -0
  282. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_response_boilerplate.py +0 -0
  283. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_retention.py +0 -0
  284. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_review_digest.py +0 -0
  285. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_service_durability.py +0 -0
  286. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_session_benchmark.py +0 -0
  287. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_session_budget.py +0 -0
  288. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_shell_robustness.py +0 -0
  289. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_subproject_exclusion.py +0 -0
  290. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_subproject_federation.py +0 -0
  291. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_subprojects.py +0 -0
  292. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_swe_bench.py +0 -0
  293. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_task_store.py +0 -0
  294. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_task_tool.py +0 -0
  295. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_token_telemetry.py +0 -0
  296. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_tool_registry.py +0 -0
  297. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_upgrade_and_version.py +0 -0
  298. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_validate.py +0 -0
  299. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_web_security.py +0 -0
  300. {code_context_control-2.49.2 → code_context_control-2.51.0}/tests/test_windows_reliability.py +0 -0
  301. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/__init__.py +0 -0
  302. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/backend.py +0 -0
  303. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/main.py +0 -0
  304. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/__init__.py +0 -0
  305. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/benchmark_view.py +0 -0
  306. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/claudemd_view.py +0 -0
  307. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/compress_view.py +0 -0
  308. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/index_view.py +0 -0
  309. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/init_view.py +0 -0
  310. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/mcp_view.py +0 -0
  311. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/optimize_view.py +0 -0
  312. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/pipe_view.py +0 -0
  313. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/projects_view.py +0 -0
  314. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/search_view.py +0 -0
  315. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/session_view.py +0 -0
  316. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/stats.py +0 -0
  317. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/screens/ui_view.py +0 -0
  318. {code_context_control-2.49.2 → code_context_control-2.51.0}/tui/theme.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.49.2
3
+ Version: 2.51.0
4
4
  Summary: Local code-intelligence layer for AI coding tools (Claude Code, Codex, Gemini, Copilot). Retrieve less, read less, edit safer — and version the configs that shape your agent (CLAUDE.md, skills, hooks, MCP).
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -60,7 +60,14 @@ from cli.commands.common import cmd_stats as common_cmd_stats
60
60
  from cli.commands.common import cmd_ui as common_cmd_ui
61
61
  from cli.commands.parser import build_parser
62
62
  from core import count_tokens, format_token_count
63
- from core.config import AGENT_DEFAULTS, BITBUCKET_DEFAULTS, DELEGATE_DEFAULTS, PROXY_DEFAULTS, load_delegate_config
63
+ from core.config import (
64
+ AGENT_DEFAULTS,
65
+ BITBUCKET_DEFAULTS,
66
+ DELEGATE_DEFAULTS,
67
+ MEMORY_LLM_DEFAULTS,
68
+ PROXY_DEFAULTS,
69
+ load_delegate_config,
70
+ )
64
71
  from core.config import DEFAULTS as HYBRID_DEFAULTS
65
72
  from core.ide import PROFILES, detect_ide, get_profile, load_ide_config, normalize_ide_name
66
73
  from services.compressor import CodeCompressor
@@ -85,7 +92,7 @@ console = Console() if HAS_RICH else None
85
92
  # Config
86
93
  CONFIG_DIR = ".c3"
87
94
  CONFIG_FILE = ".c3/config.json"
88
- __version__ = "2.49.1"
95
+ __version__ = "2.51.0"
89
96
 
90
97
 
91
98
  def _command_deps() -> CommandDeps:
@@ -165,6 +172,7 @@ def _build_init_config(project_path: str) -> dict:
165
172
  "delegate": deepcopy(DELEGATE_DEFAULTS),
166
173
  "agents": deepcopy(AGENT_DEFAULTS),
167
174
  "bitbucket": deepcopy(BITBUCKET_DEFAULTS),
175
+ "memory_llm": deepcopy(MEMORY_LLM_DEFAULTS),
168
176
  }
169
177
  merged = _deep_merge_dict(defaults, existing if isinstance(existing, dict) else {})
170
178
  # Always persist current path/version on init/update.
@@ -721,6 +729,80 @@ def _select_init_ide(default_ide: str) -> str:
721
729
  return chosen
722
730
 
723
731
 
732
+ def _prompt_memory_llm(project_path: str) -> None:
733
+ """Interactive memory_llm setup: privacy-first local model + optional cloud opt-in.
734
+
735
+ Skipped entirely under --force (that path never enters _prompt_init_steps),
736
+ which keeps the privacy defaults: distillation on, cloud OFF.
737
+ """
738
+ print()
739
+ choice = _prompt_choice(
740
+ "Memory — distill each session into durable project facts with an LLM?",
741
+ [
742
+ "Local only — a local Ollama model, nothing leaves this machine (recommended)",
743
+ "Cloud — a Sonnet-class Ollama Cloud model (API key or signed-in daemon)",
744
+ "Off — keep the basic pattern-based capture only",
745
+ ],
746
+ )
747
+ if not choice:
748
+ return # EOF/Ctrl+C — keep defaults (local-only, cloud off)
749
+
750
+ existing = load_config(project_path).get("memory_llm")
751
+ section = dict(existing) if isinstance(existing, dict) else {}
752
+
753
+ if choice.startswith("Off"):
754
+ section["enabled"] = False
755
+ section["cloud_enabled"] = False
756
+ save_config({"memory_llm": section}, project_path)
757
+ print("Memory distillation: off (regex capture only).")
758
+ return
759
+
760
+ section["enabled"] = True
761
+ section["cloud_enabled"] = choice.startswith("Cloud")
762
+
763
+ # Local model pick — it is the only tier in local mode and the privacy
764
+ # fallback tier in cloud mode.
765
+ default_local = section.get("local_model") or MEMORY_LLM_DEFAULTS["local_model"]
766
+ models = None
767
+ try:
768
+ client = OllamaClient()
769
+ if client.is_available(timeout=2):
770
+ models = client.list_models()
771
+ except Exception:
772
+ models = None
773
+ if models:
774
+ options = [f"Keep default — {default_local}"] + [m for m in models if m != default_local][:8]
775
+ pick = _prompt_choice("Pick the local model for distillation:", options)
776
+ if pick and not pick.startswith("Keep default"):
777
+ section["local_model"] = pick
778
+ else:
779
+ print(f" (Local Ollama not reachable — keeping '{default_local}'; "
780
+ "change it later in the Settings UI.)")
781
+
782
+ if section["cloud_enabled"]:
783
+ if os.environ.get("OLLAMA_API_KEY"):
784
+ print(" Cloud key: using OLLAMA_API_KEY from the environment.")
785
+ else:
786
+ try:
787
+ key = input(" Paste Ollama Cloud API key (Enter to skip — a signed-in "
788
+ "local daemon also works): ").strip()
789
+ except (EOFError, KeyboardInterrupt):
790
+ key = ""
791
+ if key:
792
+ try:
793
+ from services.ollama_credentials import save_api_key
794
+ save_api_key(key, section.get("cloud_base_url")
795
+ or MEMORY_LLM_DEFAULTS["cloud_base_url"])
796
+ print(" Key stored in the OS keyring (never in config.json).")
797
+ except Exception as exc:
798
+ print(f" Could not store key in keyring ({exc}); "
799
+ "set the OLLAMA_API_KEY environment variable instead.")
800
+
801
+ save_config({"memory_llm": section}, project_path)
802
+ print("Memory distillation: "
803
+ + ("cloud + local fallback." if section["cloud_enabled"] else "local only (private)."))
804
+
805
+
724
806
  def _prompt_init_steps(project_path: str, ide_name: str, default_mode: str = "direct") -> tuple[str, bool]:
725
807
  """Run guided post-init setup steps for Git and MCP."""
726
808
  chosen_ide = _select_init_ide(ide_name or "auto")
@@ -739,6 +821,8 @@ def _prompt_init_steps(project_path: str, ide_name: str, default_mode: str = "di
739
821
  else:
740
822
  print("Git: skipped.")
741
823
 
824
+ _prompt_memory_llm(project_path)
825
+
742
826
  print()
743
827
  install_choice = _prompt_choice(
744
828
  "Step 3/3 — Install MCP tooling for this project?",
@@ -5030,6 +5114,7 @@ def cmd_install_mcp(args):
5030
5114
  hook_pretool_cmd = f"{_dispatch_base} pretool"
5031
5115
  hook_posttool_cmd = f"{_dispatch_base} posttool"
5032
5116
  hook_stop_cmd = f"{_dispatch_base} stop"
5117
+ hook_prompt_cmd = f"{_dispatch_base} prompt"
5033
5118
 
5034
5119
  # Tool matcher names differ by IDE: Gemini uses snake_case built-in names.
5035
5120
  if profile.name == "gemini":
@@ -5153,6 +5238,28 @@ def cmd_install_mcp(args):
5153
5238
  existing_stop.extend(desired_stop_hooks)
5154
5239
  settings.setdefault("hooks", {})[stop_event] = existing_stop
5155
5240
 
5241
+ # ── UserPromptSubmit hook (per-prompt project-memory injection) ──
5242
+ # Claude Code only: Gemini CLI has no equivalent hook event. Same
5243
+ # merge discipline as Stop: replace only C3's own entries (identified
5244
+ # by the dispatcher script in the command), keep user-added ones.
5245
+ if profile.name != "gemini":
5246
+ prompt_event = "UserPromptSubmit"
5247
+
5248
+ def _is_c3_prompt_hook(entry: dict) -> bool:
5249
+ return any(
5250
+ "hook_dispatch.py" in (hk.get("command") or "")
5251
+ for hk in entry.get("hooks", [])
5252
+ )
5253
+
5254
+ existing_prompt = [
5255
+ h for h in settings.get("hooks", {}).get(prompt_event, [])
5256
+ if not _is_c3_prompt_hook(h)
5257
+ ]
5258
+ existing_prompt.append(
5259
+ {"matcher": "", "hooks": [{"type": "command", "command": hook_prompt_cmd}]}
5260
+ )
5261
+ settings.setdefault("hooks", {})[prompt_event] = existing_prompt
5262
+
5156
5263
  # Claude Code only: enable MCP server prompt settings
5157
5264
  if profile.name == "claude-code":
5158
5265
  settings["enableAllProjectMcpServers"] = True
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """Per-event hook dispatcher: ONE process per Claude Code / Gemini hook event.
3
3
 
4
- Usage: python hook_dispatch.py <pretool|posttool|stop>
4
+ Usage: python hook_dispatch.py <pretool|posttool|stop|prompt>
5
5
 
6
6
  Before v2.42 every hook was registered as its own "cmd /c python <hook>.py"
7
7
  subprocess, so a single native Read fired up to three interpreter spawns
@@ -46,7 +46,7 @@ sys.modules.setdefault("_hook_utils", _hook_utils)
46
46
 
47
47
  from cli._hook_utils import log_hook_error, normalize_tool_name # noqa: E402
48
48
 
49
- VALID_EVENTS = ("pretool", "posttool", "stop")
49
+ VALID_EVENTS = ("pretool", "posttool", "stop", "prompt")
50
50
 
51
51
  # ── Routing tables (parity with the pre-v2.42 per-matcher registration) ─────
52
52
 
@@ -103,6 +103,8 @@ def _routes(event: str, raw_tool: str, norm_tool: str):
103
103
  yield "hook_session_stats"
104
104
  yield "hook_auto_snapshot"
105
105
  yield "hook_terse_advisor"
106
+ elif event == "prompt":
107
+ yield "hook_prompt_recall"
106
108
 
107
109
 
108
110
  _RUN_CACHE: dict = {}
@@ -128,7 +130,8 @@ def _load_run(module_name: str):
128
130
  return result
129
131
 
130
132
 
131
- def merge_outputs(outputs: list, warnings: list, is_gemini: bool = False) -> dict | None:
133
+ def merge_outputs(outputs: list, warnings: list, is_gemini: bool = False,
134
+ event: str = "") -> dict | None:
132
135
  """Compose sub-hook outputs per Claude Code hook semantics.
133
136
 
134
137
  - deny beats allow: the first hookSpecificOutput carrying a
@@ -186,6 +189,14 @@ def merge_outputs(outputs: list, warnings: list, is_gemini: bool = False) -> dic
186
189
  if texts:
187
190
  result["_text"] = "\n".join(texts)
188
191
 
192
+ # UserPromptSubmit expects context under hookSpecificOutput (the
193
+ # top-level additionalContext key is a PostToolUse shape).
194
+ if event == "prompt" and result.get("additionalContext"):
195
+ result["hookSpecificOutput"] = {
196
+ "hookEventName": "UserPromptSubmit",
197
+ "additionalContext": result.pop("additionalContext"),
198
+ }
199
+
189
200
  return result or None
190
201
 
191
202
 
@@ -220,7 +231,7 @@ def dispatch(event: str, payload: dict, project_path: Path | None = None) -> dic
220
231
  # become visible instead of silently disabling enforcement.
221
232
  warnings.extend(_hook_utils.drain_state_warnings())
222
233
 
223
- return merge_outputs(outputs, warnings, is_gemini=is_gemini)
234
+ return merge_outputs(outputs, warnings, is_gemini=is_gemini, event=event)
224
235
 
225
236
 
226
237
  def main() -> None:
@@ -0,0 +1,97 @@
1
+ """UserPromptSubmit hook: inject relevant project memory into each prompt.
2
+
3
+ STRICTLY READ-ONLY. Loads .c3/facts/facts.json directly and ranks with the
4
+ pure-stdlib TextIndex. Never instantiates MemoryStore — its recall() writes
5
+ telemetry back to facts.json, and this hook runs in a separate process from
6
+ the live MCP server (double-writer clobber). Never imports the runtime/ML
7
+ stack — the whole hook must stay well under a second.
8
+
9
+ Output is capped to memory_llm.inject_max_tokens (~400 tokens default).
10
+ Any failure returns None: memory injection is never worth breaking a prompt.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ from pathlib import Path
17
+
18
+ _MIN_PROMPT_CHARS = 15
19
+ _LINE_CAP = 220
20
+ _WALK_UP_LEVELS = 4
21
+
22
+
23
+ def _find_project(payload: dict, project_path=None) -> Path | None:
24
+ candidates = []
25
+ if project_path:
26
+ candidates.append(Path(project_path))
27
+ if payload.get("cwd"):
28
+ candidates.append(Path(str(payload["cwd"])))
29
+ for base in candidates:
30
+ node = base
31
+ for _ in range(_WALK_UP_LEVELS):
32
+ if (node / ".c3").is_dir():
33
+ return node
34
+ if node.parent == node:
35
+ break
36
+ node = node.parent
37
+ return None
38
+
39
+
40
+ def run(payload: dict, project_path=None) -> dict | None:
41
+ try:
42
+ prompt = str(payload.get("prompt") or "").strip()
43
+ if len(prompt) < _MIN_PROMPT_CHARS:
44
+ return None
45
+ project = _find_project(payload, project_path)
46
+ if project is None:
47
+ return None
48
+ try:
49
+ cfg = json.loads((project / ".c3" / "config.json").read_text(encoding="utf-8"))
50
+ except Exception:
51
+ cfg = {}
52
+ mem_cfg = cfg.get("memory_llm", {}) if isinstance(cfg, dict) else {}
53
+ if not mem_cfg.get("prompt_inject_enabled", True):
54
+ return None
55
+ try:
56
+ facts = json.loads(
57
+ (project / ".c3" / "facts" / "facts.json").read_text(encoding="utf-8"))
58
+ except Exception:
59
+ return None
60
+ if not isinstance(facts, list) or not facts:
61
+ return None
62
+
63
+ from services.text_index import TextIndex # pure stdlib (math/re/collections)
64
+ index = TextIndex()
65
+ by_id: dict = {}
66
+ for fact in facts:
67
+ if not isinstance(fact, dict):
68
+ continue
69
+ if fact.get("lifecycle", "active") != "active":
70
+ continue
71
+ fid, text = fact.get("id"), fact.get("fact", "")
72
+ if not fid or not text:
73
+ continue
74
+ by_id[fid] = fact
75
+ index.add_or_update(fid, f"{text} {fact.get('category', '')}")
76
+ if not by_id:
77
+ return None
78
+
79
+ hits = index.search(prompt, top_k=max(1, int(mem_cfg.get("inject_top_k", 4))))
80
+ max_chars = int(mem_cfg.get("inject_max_tokens", 400)) * 4
81
+ lines = ["[c3:memory] Possibly relevant project facts:"]
82
+ used = len(lines[0])
83
+ for fid, score in hits:
84
+ if score <= 0:
85
+ continue
86
+ fact = by_id[fid]
87
+ body = " ".join(str(fact.get("fact", "")).split())[:_LINE_CAP]
88
+ line = f"- ({fact.get('category', 'general')}) {body}"
89
+ if used + len(line) > max_chars:
90
+ break
91
+ lines.append(line)
92
+ used += len(line)
93
+ if len(lines) < 2:
94
+ return None
95
+ return {"additionalContext": "\n".join(lines)}
96
+ except Exception:
97
+ return None
@@ -1574,9 +1574,13 @@ def api_search_global():
1574
1574
  "results": results, "skipped": skipped})
1575
1575
 
1576
1576
 
1577
- _CONFIG_READ_SECTIONS = ("hybrid", "agents", "delegate", "proxy", "mcp", "bitbucket", "meta")
1578
- _CONFIG_WRITE_SECTIONS = ("hybrid", "agents", "delegate", "proxy", "mcp", "meta")
1579
- _CONFIG_REFUSED_KEYS = ("version", "project_path", "permission_tier", "subprojects", "parent")
1577
+ _CONFIG_READ_SECTIONS = ("hybrid", "agents", "delegate", "proxy", "mcp", "bitbucket", "meta",
1578
+ "memory_llm")
1579
+ _CONFIG_WRITE_SECTIONS = ("hybrid", "agents", "delegate", "proxy", "mcp", "meta", "memory_llm")
1580
+ # api_key: secrets never transit the hub or land in config.json — the Ollama
1581
+ # cloud key lives in the OS keyring (project Settings UI / OLLAMA_API_KEY env).
1582
+ _CONFIG_REFUSED_KEYS = ("version", "project_path", "permission_tier", "subprojects", "parent",
1583
+ "api_key")
1580
1584
 
1581
1585
 
1582
1586
  def _config_defaults(section: str) -> dict:
@@ -1589,6 +1593,7 @@ def _config_defaults(section: str) -> dict:
1589
1593
  "bitbucket": core_config.BITBUCKET_DEFAULTS,
1590
1594
  "mcp": {"mode": "direct"},
1591
1595
  "meta": {},
1596
+ "memory_llm": core_config.MEMORY_LLM_DEFAULTS,
1592
1597
  }.get(section, {})
1593
1598
 
1594
1599
 
@@ -3,9 +3,9 @@
3
3
  // PUT {path, section, values} (writable sections only). The
4
4
  // bitbucket section is shown read-only; server-refused keys hidden.
5
5
 
6
- const CFG_ED_SECTIONS = ['hybrid', 'agents', 'delegate', 'proxy', 'mcp', 'meta', 'bitbucket'];
6
+ const CFG_ED_SECTIONS = ['hybrid', 'agents', 'delegate', 'proxy', 'mcp', 'meta', 'memory_llm', 'bitbucket'];
7
7
  const CFG_ED_READONLY = ['bitbucket'];
8
- const CFG_ED_REFUSED = ['version', 'project_path', 'permission_tier', 'subprojects', 'parent'];
8
+ const CFG_ED_REFUSED = ['version', 'project_path', 'permission_tier', 'subprojects', 'parent', 'api_key'];
9
9
 
10
10
  function ConfigEditor({ project }) {
11
11
  const [section, setSection] = useState('hybrid');
@@ -205,6 +205,12 @@ function ConfigEditor({ project }) {
205
205
  This section is read-only in the hub (credentials live in the OS keyring — use <span className="mono">c3 bitbucket login</span>).
206
206
  </div>
207
207
  )}
208
+ {section === 'memory_llm' && (
209
+ <div style={{ fontSize: 11, color: T.textMuted, marginBottom: 10 }}>
210
+ <span className="mono">cloud_enabled</span> sends session content to Ollama Cloud — leave off for local-only privacy (<span className="mono">local_model</span> is used instead).
211
+ The cloud API key is not editable here: it lives in the OS keyring — set it in the project's Settings tab or via <span className="mono">OLLAMA_API_KEY</span>.
212
+ </div>
213
+ )}
208
214
  {keys.length === 0 && <DrillMsg text="No editable keys in this section." />}
209
215
  <div style={{ display: 'flex', flexDirection: 'column' }}>
210
216
  {keys.map(renderField)}
@@ -283,6 +283,18 @@ async def lifespan(server):
283
283
  services.auto_memory.on_session_end()
284
284
  except Exception:
285
285
  pass
286
+ # Memory distillation: durably enqueue a digest job while the session
287
+ # is still current. Processing happens at the END of this finally
288
+ # block (after save_session + convo sync) so the inline pass sees the
289
+ # richest material; if the process dies first, the job file survives
290
+ # and the next session's MemoryDistillerAgent drains it.
291
+ _distill_job = None
292
+ if getattr(services, "memory_distiller", None):
293
+ try:
294
+ _distill_job = services.memory_distiller.enqueue_session(
295
+ services.session_mgr.current_session)
296
+ except Exception:
297
+ _distill_job = None
286
298
  # Memory consolidation: triage + prune at session end (lightweight).
287
299
  if services.memory_consolidator:
288
300
  try:
@@ -301,6 +313,17 @@ async def lifespan(server):
301
313
  services.retrieval.mark_sessions_dirty()
302
314
  except Exception:
303
315
  pass
316
+ # Best-effort inline distillation. A daemon thread dies with the
317
+ # interpreter without blocking shutdown — the queued job is the
318
+ # durable fallback either way.
319
+ if _distill_job and getattr(services, "memory_distiller", None):
320
+ try:
321
+ threading.Thread(
322
+ target=services.memory_distiller.process_job_safe,
323
+ args=(_distill_job,), daemon=True,
324
+ name="c3-memory-distill").start()
325
+ except Exception:
326
+ pass
304
327
 
305
328
 
306
329
  mcp = FastMCP(f"C3 v{C3_VERSION}", instructions=_build_instructions(_IDE_NAME), lifespan=lifespan)
@@ -2457,6 +2457,71 @@ def api_ollama_models():
2457
2457
  return jsonify({"models": models})
2458
2458
 
2459
2459
 
2460
+ @app.route('/api/memory-llm/config', methods=['GET'])
2461
+ def api_memory_llm_config_get():
2462
+ """Get memory_llm config (merged with defaults). Never returns the API key."""
2463
+ from core.config import load_memory_llm_config
2464
+ cfg = load_memory_llm_config(str(PROJECT_PATH))
2465
+ key_set = False
2466
+ try:
2467
+ from services.ollama_credentials import api_key_available
2468
+ key_set = api_key_available(cfg.get("cloud_base_url", ""),
2469
+ cfg.get("api_key_env", ""),
2470
+ cfg.get("api_key", ""))
2471
+ except Exception:
2472
+ pass
2473
+ cfg["api_key"] = "*****" if cfg.get("api_key") else ""
2474
+ cfg["api_key_set"] = bool(key_set)
2475
+ return jsonify(cfg)
2476
+
2477
+
2478
+ @app.route('/api/memory-llm/config', methods=['PUT'])
2479
+ def api_memory_llm_config_put():
2480
+ """Update memory_llm config. Persists to .c3/config.json (never the key)."""
2481
+ from core.config import MEMORY_LLM_DEFAULTS, load_memory_llm_config
2482
+ data = request.get_json() or {}
2483
+ # api_key is keyring-only (set via /api/memory-llm/key) — config.json is
2484
+ # not gitignored by default, so a plaintext key there could get committed.
2485
+ allowed_keys = set(MEMORY_LLM_DEFAULTS.keys()) - {"api_key"}
2486
+ updates = {k: v for k, v in data.items() if k in allowed_keys}
2487
+
2488
+ if not updates:
2489
+ return jsonify({"error": "No valid memory_llm config keys to update"}), 400
2490
+
2491
+ config_path = PROJECT_PATH / ".c3" / "config.json"
2492
+ config = {}
2493
+ if config_path.exists():
2494
+ try:
2495
+ with open(config_path, encoding="utf-8") as f:
2496
+ config = json.load(f)
2497
+ except Exception:
2498
+ pass
2499
+ config.setdefault("memory_llm", {}).update(updates)
2500
+ config_path.parent.mkdir(parents=True, exist_ok=True)
2501
+ with open(config_path, "w", encoding="utf-8") as f:
2502
+ json.dump(config, f, indent=2)
2503
+
2504
+ return jsonify(load_memory_llm_config(str(PROJECT_PATH)))
2505
+
2506
+
2507
+ @app.route('/api/memory-llm/key', methods=['POST'])
2508
+ def api_memory_llm_key_set():
2509
+ """Store (or clear, with an empty key) the Ollama Cloud API key in the OS keyring."""
2510
+ from core.config import load_memory_llm_config
2511
+ from services.ollama_credentials import delete_api_key, save_api_key
2512
+ data = request.get_json() or {}
2513
+ key = str(data.get("key") or "").strip()
2514
+ base = load_memory_llm_config(str(PROJECT_PATH)).get("cloud_base_url", "")
2515
+ if not key:
2516
+ removed = delete_api_key(base)
2517
+ return jsonify({"cleared": bool(removed), "api_key_set": False})
2518
+ try:
2519
+ save_api_key(key, base)
2520
+ except Exception as exc:
2521
+ return jsonify({"error": str(exc)}), 500
2522
+ return jsonify({"api_key_set": True})
2523
+
2524
+
2460
2525
  @app.route('/api/delegate', methods=['POST'])
2461
2526
  def api_delegate():
2462
2527
  """Delegate a task to local LLM with optional streaming."""
@@ -3,9 +3,57 @@
3
3
  from pathlib import Path
4
4
 
5
5
 
6
- def maybe_related_facts(svc, topic: str, top_k: int = 3, width: int = 100) -> str:
7
- """Append related facts if enabled. Currently disabled — adds noise."""
8
- return ""
6
+ def maybe_related_facts(svc, topic: str, top_k: int = 3, width: int = 100,
7
+ context: str = "") -> str:
8
+ """Format facts related to `topic` as compact advisory lines.
9
+
10
+ Enabled ONLY for the read path (context="read", gated by
11
+ memory_llm.read_related_facts_enabled): a gotcha about the file being
12
+ read is worth the tokens at that moment. Search/compress callers stay
13
+ disabled — facts surfaced next to ranked search output were noise.
14
+ """
15
+ if context != "read":
16
+ return ""
17
+ try:
18
+ cfg = getattr(svc, "_memory_llm_cfg", None)
19
+ if cfg is None:
20
+ from core.config import load_memory_llm_config
21
+ cfg = load_memory_llm_config(str(getattr(svc, "project_path", ".")))
22
+ try:
23
+ svc._memory_llm_cfg = cfg
24
+ except Exception:
25
+ pass
26
+ if not cfg.get("read_related_facts_enabled", True):
27
+ return ""
28
+ memory = getattr(svc, "memory", None)
29
+ if memory is None:
30
+ return ""
31
+ path = Path(topic)
32
+ query = " ".join(filter(None, [
33
+ path.name, path.stem.replace("_", " "), path.parent.name]))
34
+ hits = memory.recall(query, top_k=max(top_k, 3))
35
+ if not hits:
36
+ return ""
37
+ # Relative score floor: recall scores are scale-free (TF-IDF-only when
38
+ # the vector backend is off), so an absolute threshold misfires.
39
+ top_score = max(float(h.get("score", 0) or 0) for h in hits)
40
+ floor = max(0.05, 0.3 * top_score)
41
+ lines = []
42
+ for hit in hits:
43
+ if float(hit.get("score", 0) or 0) < floor:
44
+ continue
45
+ category = str(hit.get("category", "") or "general")
46
+ if category.startswith("auto:"):
47
+ continue
48
+ body = " ".join(str(hit.get("fact", "")).split())
49
+ if not body:
50
+ continue
51
+ lines.append(f"[c3:related] ({category}) {body[:width]}")
52
+ if len(lines) >= top_k:
53
+ break
54
+ return ("\n" + "\n".join(lines)) if lines else ""
55
+ except Exception:
56
+ return ""
9
57
 
10
58
 
11
59
  # ── Response boilerplate diet (P6) ───────────────────────────────────────────