code-context-control 2.36.0__tar.gz → 2.38.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. {code_context_control-2.36.0/code_context_control.egg-info → code_context_control-2.38.1}/PKG-INFO +9 -1
  2. {code_context_control-2.36.0 → code_context_control-2.38.1}/README.md +8 -0
  3. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/c3.py +64 -7
  4. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/commands/common.py +3 -7
  5. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/getting-started.html +17 -1
  6. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/oracle.html +1 -0
  7. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hub_server.py +4 -2
  8. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/mcp_server.py +23 -8
  9. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/server.py +12 -10
  10. {code_context_control-2.36.0 → code_context_control-2.38.1/code_context_control.egg-info}/PKG-INFO +9 -1
  11. {code_context_control-2.36.0 → code_context_control-2.38.1}/code_context_control.egg-info/SOURCES.txt +4 -0
  12. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/oracle.html +108 -0
  13. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/oracle_server.py +36 -1
  14. code_context_control-2.38.1/oracle/services/activity_reporter.py +256 -0
  15. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/chat_engine.py +18 -0
  16. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/tool_registry.py +22 -0
  17. {code_context_control-2.36.0 → code_context_control-2.38.1}/pyproject.toml +1 -1
  18. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/claude_md.py +139 -20
  19. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/embedding_index.py +26 -2
  20. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/session_manager.py +7 -12
  21. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/vector_store.py +28 -2
  22. code_context_control-2.38.1/tests/test_activity_reporter.py +130 -0
  23. code_context_control-2.38.1/tests/test_claude_md_merge.py +157 -0
  24. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_install_mcp_entrypoint.py +48 -0
  25. code_context_control-2.38.1/tests/test_lazy_store_init.py +77 -0
  26. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_oracle_discovery_api.py +11 -0
  27. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_permissions.py +35 -7
  28. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_tool_registry.py +9 -0
  29. {code_context_control-2.36.0 → code_context_control-2.38.1}/LICENSE +0 -0
  30. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/__init__.py +0 -0
  31. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/_hook_utils.py +0 -0
  32. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/commands/__init__.py +0 -0
  33. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/commands/parser.py +0 -0
  34. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/docs.html +0 -0
  35. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/edits.html +0 -0
  36. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/bitbucket.html +0 -0
  37. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/index.html +0 -0
  38. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/shared.css +0 -0
  39. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/tools.html +0 -0
  40. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/workflow.html +0 -0
  41. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_auto_snapshot.py +0 -0
  42. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_c3_signal.py +0 -0
  43. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_c3read.py +0 -0
  44. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_edit_ledger.py +0 -0
  45. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_edit_unlock.py +0 -0
  46. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_filter.py +0 -0
  47. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_ghost_files.py +0 -0
  48. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_pretool_enforce.py +0 -0
  49. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_read.py +0 -0
  50. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_session_stats.py +0 -0
  51. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_terse_advisor.py +0 -0
  52. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hub.html +0 -0
  53. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/mcp_proxy.py +0 -0
  54. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/__init__.py +0 -0
  55. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/_helpers.py +0 -0
  56. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/agent.py +0 -0
  57. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/bitbucket.py +0 -0
  58. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/compress.py +0 -0
  59. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/delegate.py +0 -0
  60. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/edit.py +0 -0
  61. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/edits.py +0 -0
  62. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/filter.py +0 -0
  63. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/impact.py +0 -0
  64. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/memory.py +0 -0
  65. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/project.py +0 -0
  66. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/read.py +0 -0
  67. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/search.py +0 -0
  68. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/session.py +0 -0
  69. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/shell.py +0 -0
  70. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/status.py +0 -0
  71. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/validate.py +0 -0
  72. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/api.js +0 -0
  73. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/app.js +0 -0
  74. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/bitbucket.js +0 -0
  75. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/chat.js +0 -0
  76. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/dashboard.js +0 -0
  77. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/edits.js +0 -0
  78. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/instructions.js +0 -0
  79. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/memory.js +0 -0
  80. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/sessions.js +0 -0
  81. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/settings.js +0 -0
  82. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/sidebar.js +0 -0
  83. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/icons.js +0 -0
  84. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/shared.js +0 -0
  85. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/theme.js +0 -0
  86. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui.html +0 -0
  87. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui_legacy.html +0 -0
  88. {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui_nano.html +0 -0
  89. {code_context_control-2.36.0 → code_context_control-2.38.1}/code_context_control.egg-info/dependency_links.txt +0 -0
  90. {code_context_control-2.36.0 → code_context_control-2.38.1}/code_context_control.egg-info/entry_points.txt +0 -0
  91. {code_context_control-2.36.0 → code_context_control-2.38.1}/code_context_control.egg-info/requires.txt +0 -0
  92. {code_context_control-2.36.0 → code_context_control-2.38.1}/code_context_control.egg-info/top_level.txt +0 -0
  93. {code_context_control-2.36.0 → code_context_control-2.38.1}/core/__init__.py +0 -0
  94. {code_context_control-2.36.0 → code_context_control-2.38.1}/core/config.py +0 -0
  95. {code_context_control-2.36.0 → code_context_control-2.38.1}/core/ide.py +0 -0
  96. {code_context_control-2.36.0 → code_context_control-2.38.1}/core/mcp_toml.py +0 -0
  97. {code_context_control-2.36.0 → code_context_control-2.38.1}/core/web_security.py +0 -0
  98. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/__init__.py +0 -0
  99. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/config.py +0 -0
  100. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/mcp_oracle.py +0 -0
  101. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/__init__.py +0 -0
  102. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/api_auth.py +0 -0
  103. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/c3_bridge.py +0 -0
  104. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/chat_store.py +0 -0
  105. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/cross_memory.py +0 -0
  106. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/federated_graph.py +0 -0
  107. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/health_checker.py +0 -0
  108. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/insight_engine.py +0 -0
  109. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/memory_reader.py +0 -0
  110. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/memory_writer.py +0 -0
  111. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/ollama_bridge.py +0 -0
  112. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/project_scanner.py +0 -0
  113. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/review_agent.py +0 -0
  114. {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/tool_executor.py +0 -0
  115. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/__init__.py +0 -0
  116. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/activity_log.py +0 -0
  117. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/agent_base.py +0 -0
  118. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/agents.py +0 -0
  119. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/auto_memory.py +0 -0
  120. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/bench/__init__.py +0 -0
  121. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/bench/external/__init__.py +0 -0
  122. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/bench/external/aider_polyglot.py +0 -0
  123. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/bench/external/swe_bench.py +0 -0
  124. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/benchmark_dashboard.py +0 -0
  125. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/bitbucket_client.py +0 -0
  126. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/bitbucket_credentials.py +0 -0
  127. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/compressor.py +0 -0
  128. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/context_snapshot.py +0 -0
  129. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/conversation_store.py +0 -0
  130. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/doc_index.py +0 -0
  131. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/e2e_benchmark.py +0 -0
  132. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/e2e_evaluator.py +0 -0
  133. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/e2e_tasks.py +0 -0
  134. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/edit_ledger.py +0 -0
  135. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/error_reporting.py +0 -0
  136. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/file_memory.py +0 -0
  137. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/git_context.py +0 -0
  138. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/hub_service.py +0 -0
  139. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/indexer.py +0 -0
  140. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/memory.py +0 -0
  141. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/memory_consolidator.py +0 -0
  142. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/memory_graph.py +0 -0
  143. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/memory_grounder.py +0 -0
  144. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/memory_scorer.py +0 -0
  145. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/metrics.py +0 -0
  146. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/notifications.py +0 -0
  147. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/ollama_client.py +0 -0
  148. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/output_filter.py +0 -0
  149. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/parser.py +0 -0
  150. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/project_manager.py +0 -0
  151. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/project_runtime.py +0 -0
  152. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/protocol.py +0 -0
  153. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/proxy_state.py +0 -0
  154. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/retrieval_broker.py +0 -0
  155. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/router.py +0 -0
  156. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/runtime.py +0 -0
  157. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/session_benchmark.py +0 -0
  158. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/session_preloader.py +0 -0
  159. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/text_index.py +0 -0
  160. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/tool_classifier.py +0 -0
  161. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/transcript_index.py +0 -0
  162. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/validation_cache.py +0 -0
  163. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/version_tracker.py +0 -0
  164. {code_context_control-2.36.0 → code_context_control-2.38.1}/services/watcher.py +0 -0
  165. {code_context_control-2.36.0 → code_context_control-2.38.1}/setup.cfg +0 -0
  166. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_aider_polyglot.py +0 -0
  167. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_bitbucket_cli_smoke.py +0 -0
  168. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_bitbucket_client.py +0 -0
  169. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_bitbucket_credentials.py +0 -0
  170. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_bitbucket_tool.py +0 -0
  171. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_c3_shell.py +0 -0
  172. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_cli_smoke.py +0 -0
  173. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_e2e_benchmark.py +0 -0
  174. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_edit_normalization.py +0 -0
  175. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_enforcement_flip.py +0 -0
  176. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_federated_graph.py +0 -0
  177. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_ghost_files.py +0 -0
  178. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_git_branch_awareness.py +0 -0
  179. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_hub_server_smoke.py +0 -0
  180. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_mcp_host_guard.py +0 -0
  181. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_mcp_server_smoke.py +0 -0
  182. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_mcp_toml.py +0 -0
  183. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_memory_graph_api.py +0 -0
  184. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_memory_system.py +0 -0
  185. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_notification_discipline.py +0 -0
  186. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_oracle_api_auth.py +0 -0
  187. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_oracle_apikey_api.py +0 -0
  188. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_output_filter.py +0 -0
  189. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_project_manager.py +0 -0
  190. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_project_manager_merge.py +0 -0
  191. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_project_tool.py +0 -0
  192. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_read_coercion.py +0 -0
  193. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_session_benchmark.py +0 -0
  194. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_session_budget.py +0 -0
  195. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_shell_robustness.py +0 -0
  196. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_swe_bench.py +0 -0
  197. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_upgrade_and_version.py +0 -0
  198. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_validate.py +0 -0
  199. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_web_security.py +0 -0
  200. {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_windows_reliability.py +0 -0
  201. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/__init__.py +0 -0
  202. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/backend.py +0 -0
  203. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/main.py +0 -0
  204. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/__init__.py +0 -0
  205. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/benchmark_view.py +0 -0
  206. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/claudemd_view.py +0 -0
  207. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/compress_view.py +0 -0
  208. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/index_view.py +0 -0
  209. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/init_view.py +0 -0
  210. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/mcp_view.py +0 -0
  211. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/optimize_view.py +0 -0
  212. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/pipe_view.py +0 -0
  213. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/projects_view.py +0 -0
  214. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/search_view.py +0 -0
  215. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/session_view.py +0 -0
  216. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/stats.py +0 -0
  217. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/ui_view.py +0 -0
  218. {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/theme.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.36.0
3
+ Version: 2.38.1
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
@@ -231,6 +231,8 @@ Every session you've ever run, with duration, decision count, file count, tool c
231
231
 
232
232
  Manage `CLAUDE.md`, `AGENTS.md` (Codex), `GEMINI.md`, and `.github/copilot-instructions.md` from one editor. Generate from project state, run a Health Check (drift detection vs the actual codebase), Compact stale sections, or Promote insights captured during sessions. **One source of truth** instead of four out-of-sync files.
233
233
 
234
+ C3-generated content is wrapped in a `<!-- C3:BEGIN … -->` / `<!-- C3:END -->` block. Regenerating (or `Compact`) only rewrites that block — **anything you write outside it is preserved**, so it's safe to keep your own notes in the same file.
235
+
234
236
  ### 7. Chat — browse prior AI conversations
235
237
 
236
238
  <p align="center">
@@ -332,6 +334,10 @@ Only **read** and **safe-action** tools are exposed (no code editing); requests
332
334
  default. Generate, rotate, and copy the token from the dashboard's **Settings →
333
335
  Discovery API** tab. See the [Oracle Discovery API guide](oracle-guide/discovery-api.md).
334
336
 
337
+ As of v2.38.0 the Oracle also reports a **cross-project activity digest** — sessions,
338
+ tool calls, edits, git mutations, and token/cost for a day — via the `activity_report`
339
+ discovery tool, the `GET /api/activity/digest` endpoint, and the dashboard's **Activity** tab.
340
+
335
341
  ---
336
342
 
337
343
  ## Tiered local AI (optional)
@@ -370,6 +376,8 @@ c3 permissions show
370
376
  c3 permissions standard
371
377
  ```
372
378
 
379
+ Applying or switching a tier **preserves your own `allow`/`deny` rules** (and keys like `ask`/`defaultMode`) — only C3-managed entries are replaced. Likewise, C3 never clobbers your other entries in `.mcp.json` (only its own `c3` server) or the hooks you've added to `settings.local.json` (only its own hooks).
380
+
373
381
  ---
374
382
 
375
383
  ## Benchmarks
@@ -169,6 +169,8 @@ Every session you've ever run, with duration, decision count, file count, tool c
169
169
 
170
170
  Manage `CLAUDE.md`, `AGENTS.md` (Codex), `GEMINI.md`, and `.github/copilot-instructions.md` from one editor. Generate from project state, run a Health Check (drift detection vs the actual codebase), Compact stale sections, or Promote insights captured during sessions. **One source of truth** instead of four out-of-sync files.
171
171
 
172
+ C3-generated content is wrapped in a `<!-- C3:BEGIN … -->` / `<!-- C3:END -->` block. Regenerating (or `Compact`) only rewrites that block — **anything you write outside it is preserved**, so it's safe to keep your own notes in the same file.
173
+
172
174
  ### 7. Chat — browse prior AI conversations
173
175
 
174
176
  <p align="center">
@@ -270,6 +272,10 @@ Only **read** and **safe-action** tools are exposed (no code editing); requests
270
272
  default. Generate, rotate, and copy the token from the dashboard's **Settings →
271
273
  Discovery API** tab. See the [Oracle Discovery API guide](oracle-guide/discovery-api.md).
272
274
 
275
+ As of v2.38.0 the Oracle also reports a **cross-project activity digest** — sessions,
276
+ tool calls, edits, git mutations, and token/cost for a day — via the `activity_report`
277
+ discovery tool, the `GET /api/activity/digest` endpoint, and the dashboard's **Activity** tab.
278
+
273
279
  ---
274
280
 
275
281
  ## Tiered local AI (optional)
@@ -308,6 +314,8 @@ c3 permissions show
308
314
  c3 permissions standard
309
315
  ```
310
316
 
317
+ Applying or switching a tier **preserves your own `allow`/`deny` rules** (and keys like `ask`/`defaultMode`) — only C3-managed entries are replaced. Likewise, C3 never clobbers your other entries in `.mcp.json` (only its own `c3` server) or the hooks you've added to `settings.local.json` (only its own hooks).
318
+
311
319
  ---
312
320
 
313
321
  ## Benchmarks
@@ -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.36.0"
88
+ __version__ = "2.38.1"
89
89
 
90
90
 
91
91
  def _command_deps() -> CommandDeps:
@@ -379,6 +379,44 @@ def _build_permission_tier(tier: str, include_mcp_wildcard: bool = False) -> dic
379
379
  }}
380
380
 
381
381
 
382
+ def _c3_managed_permission_entries() -> tuple[set, set]:
383
+ """Return (allow, deny) sets of every entry any C3 tier can emit.
384
+
385
+ Used to tell C3-managed permission rules apart from user-added ones so a
386
+ tier change replaces only the former and preserves the latter.
387
+ """
388
+ managed_allow: set = set()
389
+ managed_deny: set = set()
390
+ for _tier in PERMISSION_TIERS:
391
+ perms = _build_permission_tier(_tier, include_mcp_wildcard=True)["permissions"]
392
+ managed_allow.update(perms.get("allow", []))
393
+ managed_deny.update(perms.get("deny", []))
394
+ return managed_allow, managed_deny
395
+
396
+
397
+ def _merge_permission_tier(existing: dict, tier_perms: dict) -> dict:
398
+ """Merge a tier's permissions into existing ones, preserving user rules.
399
+
400
+ C3 owns every entry a tier can emit: those are replaced by the chosen tier.
401
+ Any other allow/deny entry the user added is kept, and non-list permission
402
+ keys (e.g. ``ask``, ``defaultMode``, ``additionalDirectories``) are left
403
+ untouched. Mirrors how hooks and .mcp.json preserve non-C3 content.
404
+ """
405
+ existing = existing if isinstance(existing, dict) else {}
406
+ managed = dict(zip(("allow", "deny"), _c3_managed_permission_entries()))
407
+ merged = dict(existing) # preserve unknown sub-keys (ask, defaultMode, ...)
408
+ for key in ("allow", "deny"):
409
+ user_custom = [e for e in (existing.get(key) or []) if e not in managed[key]]
410
+ out: list = []
411
+ seen: set = set()
412
+ for entry in user_custom + list(tier_perms.get(key) or []):
413
+ if entry not in seen:
414
+ seen.add(entry)
415
+ out.append(entry)
416
+ merged[key] = out
417
+ return merged
418
+
419
+
382
420
  def _detect_current_tier(settings_path) -> str | None:
383
421
  """Detect which permission tier is active in settings_path, or None.
384
422
 
@@ -456,9 +494,12 @@ def _apply_permission_tier(project_path: str, tier: str,
456
494
  settings_path = Path(project_path) / ".claude" / "settings.local.json"
457
495
  settings_path.parent.mkdir(parents=True, exist_ok=True)
458
496
  settings = _safe_read_json(settings_path, str(settings_path))
459
- settings["permissions"] = _build_permission_tier(
497
+ tier_perms = _build_permission_tier(
460
498
  tier, include_mcp_wildcard=include_mcp_wildcard
461
499
  )["permissions"]
500
+ settings["permissions"] = _merge_permission_tier(
501
+ settings.get("permissions") or {}, tier_perms
502
+ )
462
503
  # Persist chosen tier in .c3/config.json
463
504
  c3_config_path = Path(project_path) / ".c3" / "config.json"
464
505
  c3_config = _safe_read_json(c3_config_path, str(c3_config_path))
@@ -5139,10 +5180,23 @@ def cmd_install_mcp(args):
5139
5180
  },
5140
5181
  ]
5141
5182
  stop_event = "Stop"
5142
- # Replace any existing C3 stop hooks (matcher=""), keep user-added ones
5183
+ # Replace only C3's own stop hooks (identified by our hook scripts) and
5184
+ # keep every user-added stop hook — including matcher-less ones, which
5185
+ # are the normal shape for Stop hooks.
5186
+ _c3_stop_scripts = (
5187
+ "hook_session_stats.py", "hook_auto_snapshot.py", "hook_terse_advisor.py",
5188
+ )
5189
+
5190
+ def _is_c3_stop_hook(entry: dict) -> bool:
5191
+ return any(
5192
+ script in (hk.get("command") or "")
5193
+ for hk in entry.get("hooks", [])
5194
+ for script in _c3_stop_scripts
5195
+ )
5196
+
5143
5197
  existing_stop = [
5144
5198
  h for h in settings.get("hooks", {}).get(stop_event, [])
5145
- if h.get("matcher") # keep entries with a non-empty matcher
5199
+ if not _is_c3_stop_hook(h)
5146
5200
  ]
5147
5201
  existing_stop.extend(desired_stop_hooks)
5148
5202
  settings.setdefault("hooks", {})[stop_event] = existing_stop
@@ -5160,9 +5214,12 @@ def cmd_install_mcp(args):
5160
5214
  include_wildcard = bool(getattr(args, "include_mcp_wildcard", False))
5161
5215
  if perm_tier and profile.name == "claude-code":
5162
5216
  if perm_tier in PERMISSION_TIERS:
5163
- settings["permissions"] = _build_permission_tier(
5164
- perm_tier, include_mcp_wildcard=include_wildcard
5165
- )["permissions"]
5217
+ settings["permissions"] = _merge_permission_tier(
5218
+ settings.get("permissions") or {},
5219
+ _build_permission_tier(
5220
+ perm_tier, include_mcp_wildcard=include_wildcard
5221
+ )["permissions"],
5222
+ )
5166
5223
  # Persist tier choice in .c3/config.json
5167
5224
  _c3cfg = _safe_read_json(c3_config_path, str(c3_config_path))
5168
5225
  _c3cfg["permission_tier"] = perm_tier
@@ -200,13 +200,9 @@ def cmd_claudemd(args, deps: CommandDeps):
200
200
  print(content)
201
201
  else:
202
202
  output_path = Path(project_path) / instructions_file
203
- output_path.parent.mkdir(parents=True, exist_ok=True)
204
- if output_path.exists():
205
- existing = output_path.read_text(encoding="utf-8", errors="replace")
206
- if "# User Notes" in existing:
207
- user_section = existing[existing.index("# User Notes"):]
208
- content += f"\n\n{user_section}"
209
- output_path.write_text(content, encoding="utf-8")
203
+ # Wrap in the C3 managed block; preserve user content outside it.
204
+ from services.claude_md import write_c3_instruction_doc
205
+ write_c3_instruction_doc(output_path, content)
210
206
  print(f"{instructions_file} saved to {output_path} ({tokens} tokens)")
211
207
 
212
208
  elif args.claudemd_cmd == "check":
@@ -188,6 +188,14 @@ c3 init</code></pre>
188
188
 
189
189
  <h3>Re-init / upgrade</h3>
190
190
  <p>Running <code>c3 init</code> on an existing project is safe — it merges new config without overwriting your customizations.</p>
191
+
192
+ <div class="callout callout-info">
193
+ <span class="callout-icon">🛡️</span>
194
+ <div class="callout-body">
195
+ <strong>Your hand-written content is preserved</strong>
196
+ Generated instruction files (<code>CLAUDE.md</code>, <code>AGENTS.md</code>, <code>GEMINI.md</code>) wrap C3 content in a <code>&lt;!-- C3:BEGIN … --&gt;</code> / <code>&lt;!-- C3:END --&gt;</code> block. Re-running init (or <code>c3 claudemd save</code> / the <strong>Compact</strong> action) rewrites only that block — anything you add outside it stays put. An existing hand-written file with no block is kept and the C3 block is appended below it.
197
+ </div>
198
+ </div>
191
199
  </section>
192
200
 
193
201
  <hr class="divider">
@@ -200,7 +208,7 @@ c3 init</code></pre>
200
208
 
201
209
  <h3>Claude Code (primary)</h3>
202
210
  <pre><code>c3 install-mcp claude</code></pre>
203
- <p>This writes to <code>.mcp.json</code> (project scope) and optionally configures PreToolUse / PostToolUse hooks in <code>.claude/settings.local.json</code>.</p>
211
+ <p>This writes to <code>.mcp.json</code> (project scope) and optionally configures PreToolUse / PostToolUse hooks in <code>.claude/settings.local.json</code>. Both are merged, not overwritten: C3 only touches its own <code>c3</code> server entry and its own hooks, leaving any other MCP servers, hooks, and top-level keys you've added in place.</p>
204
212
 
205
213
  <h3>VS Code Copilot</h3>
206
214
  <pre><code>c3 install-mcp vscode</code></pre>
@@ -358,6 +366,14 @@ c3_session(action='snapshot') <span class="com"># before /clear</span></code><
358
366
  The <code>standard</code> tier blocks destructive shell commands (rm -rf, git reset --hard, etc.) while allowing safe operations. Upgrade to <code>unrestricted</code> only when needed.
359
367
  </div>
360
368
  </div>
369
+
370
+ <div class="callout callout-info">
371
+ <span class="callout-icon">🛡️</span>
372
+ <div class="callout-body">
373
+ <strong>Your custom rules survive a tier change</strong>
374
+ Applying or switching a tier preserves <code>allow</code>/<code>deny</code> entries you added yourself, plus keys like <code>ask</code> and <code>defaultMode</code>. C3 only replaces the entries it manages, so you can mix a tier with project-specific permissions.
375
+ </div>
376
+ </div>
361
377
  </section>
362
378
 
363
379
  <hr class="divider">
@@ -269,6 +269,7 @@ curl -X POST \
269
269
  <tr><td class="grp-read"><code>c3_status</code></td> <td>Project health / budget / sessions overview</td></tr>
270
270
  <tr><td class="grp-read"><code>c3_memory_query</code></td> <td>Read-only memory query (recall/list/score/graph/trends)</td></tr>
271
271
  <tr><td class="grp-read"><code>c3_edits</code> / <code>c3_edits_cross</code></td><td>Query the edit ledger (one / all projects)</td></tr>
272
+ <tr><td class="grp-read"><code>activity_report</code></td><td>Cross-project daily digest: sessions, tool calls, edits, git mutations, token/cost (optional LLM narration)</td></tr>
272
273
  </tbody>
273
274
  </table>
274
275
 
@@ -1206,7 +1206,7 @@ def api_projects_permissions_get():
1206
1206
  @app.route("/api/projects/permissions/apply", methods=["POST"])
1207
1207
  def api_projects_permissions_put():
1208
1208
  """Apply permission tier to a project. Body: {path, tier}"""
1209
- from cli.c3 import PERMISSION_TIERS, _build_permission_tier
1209
+ from cli.c3 import PERMISSION_TIERS, _build_permission_tier, _merge_permission_tier
1210
1210
  data = request.get_json(force=True) or {}
1211
1211
  path = (data.get("path") or "").strip()
1212
1212
  tier = (data.get("tier") or "").strip()
@@ -1228,7 +1228,9 @@ def api_projects_permissions_put():
1228
1228
  settings = json.load(f)
1229
1229
  except Exception:
1230
1230
  pass
1231
- settings["permissions"] = tier_perms["permissions"]
1231
+ settings["permissions"] = _merge_permission_tier(
1232
+ settings.get("permissions") or {}, tier_perms["permissions"]
1233
+ )
1232
1234
  with open(settings_path, "w", encoding="utf-8") as f:
1233
1235
  json.dump(settings, f, indent=2)
1234
1236
 
@@ -120,12 +120,19 @@ async def lifespan(server):
120
120
  services.indexer.build_index()
121
121
  except Exception:
122
122
  pass
123
- # After code index is built, build embedding index
124
- if services.embedding_index and services.embedding_index.ready:
123
+ # After code index is built, build embedding index. build() lazily
124
+ # inits its chromadb/ollama backends, kept off the handshake path.
125
+ if services.embedding_index:
125
126
  try:
126
127
  services.embedding_index.build(services.indexer)
127
128
  except Exception:
128
129
  pass
130
+ # Warm SLTM vector store so the first memory call isn't slow.
131
+ if services.vector_store:
132
+ try:
133
+ services.vector_store.warm()
134
+ except Exception:
135
+ pass
129
136
  # Build doc index for Local RAG Pipeline
130
137
  if services.doc_index:
131
138
  try:
@@ -136,15 +143,23 @@ async def lifespan(server):
136
143
  threading.Thread(target=_bg_build, daemon=True, name="c3-initial-index").start()
137
144
  else:
138
145
  services.indexer._load_index()
139
- # Build/update embedding index in background
140
- if services.embedding_index and services.embedding_index.ready:
146
+ # Build/update embedding index + warm SLTM in background. Deferred off
147
+ # the handshake path: build()/warm() lazily init the heavy backends, so
148
+ # this must NOT gate on .ready synchronously here.
149
+ if services.embedding_index or services.vector_store:
141
150
  import threading
142
151
 
143
152
  def _bg_embed():
144
- try:
145
- services.embedding_index.build(services.indexer)
146
- except Exception:
147
- pass
153
+ if services.embedding_index:
154
+ try:
155
+ services.embedding_index.build(services.indexer)
156
+ except Exception:
157
+ pass
158
+ if services.vector_store:
159
+ try:
160
+ services.vector_store.warm()
161
+ except Exception:
162
+ pass
148
163
 
149
164
  threading.Thread(target=_bg_embed, daemon=True, name="c3-embed-index").start()
150
165
 
@@ -1121,16 +1121,11 @@ def api_claudemd_save():
1121
1121
  return jsonify({"error": "Generation produced empty content"}), 500
1122
1122
 
1123
1123
  output_path = PROJECT_PATH / claude_md_mgr.instructions_file
1124
- output_path.parent.mkdir(parents=True, exist_ok=True)
1125
1124
 
1126
- # Preserve user-written sections
1127
- if output_path.exists():
1128
- existing = output_path.read_text(encoding="utf-8", errors="replace")
1129
- if "# User Notes" in existing:
1130
- user_section = existing[existing.index("# User Notes"):]
1131
- content += f"\n\n{user_section}"
1125
+ # Wrap in the C3 managed block; never clobber user content outside it.
1126
+ from services.claude_md import write_c3_instruction_doc
1127
+ write_c3_instruction_doc(output_path, content)
1132
1128
 
1133
- output_path.write_text(content, encoding="utf-8")
1134
1129
  return jsonify({
1135
1130
  "path": str(output_path),
1136
1131
  "tokens": gen.get("tokens", 0),
@@ -2021,7 +2016,12 @@ def api_permissions_get():
2021
2016
  @app.route('/api/permissions', methods=['PUT'])
2022
2017
  def api_permissions_put():
2023
2018
  """Apply a permission tier. Body: {tier: "read-only"|"standard"|"permissive"}"""
2024
- from cli.c3 import PERMISSION_TIERS, _build_permission_tier, _safe_read_json
2019
+ from cli.c3 import (
2020
+ PERMISSION_TIERS,
2021
+ _build_permission_tier,
2022
+ _merge_permission_tier,
2023
+ _safe_read_json,
2024
+ )
2025
2025
  data = request.get_json() or {}
2026
2026
  tier = data.get("tier", "").strip()
2027
2027
  if tier not in PERMISSION_TIERS:
@@ -2031,7 +2031,9 @@ def api_permissions_put():
2031
2031
  settings_path = PROJECT_PATH / ".claude" / "settings.local.json"
2032
2032
  settings_path.parent.mkdir(parents=True, exist_ok=True)
2033
2033
  settings = _safe_read_json(settings_path, str(settings_path))
2034
- settings["permissions"] = tier_perms["permissions"]
2034
+ settings["permissions"] = _merge_permission_tier(
2035
+ settings.get("permissions") or {}, tier_perms["permissions"]
2036
+ )
2035
2037
  with open(settings_path, "w", encoding="utf-8") as f:
2036
2038
  json.dump(settings, f, indent=2)
2037
2039
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.36.0
3
+ Version: 2.38.1
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
@@ -231,6 +231,8 @@ Every session you've ever run, with duration, decision count, file count, tool c
231
231
 
232
232
  Manage `CLAUDE.md`, `AGENTS.md` (Codex), `GEMINI.md`, and `.github/copilot-instructions.md` from one editor. Generate from project state, run a Health Check (drift detection vs the actual codebase), Compact stale sections, or Promote insights captured during sessions. **One source of truth** instead of four out-of-sync files.
233
233
 
234
+ C3-generated content is wrapped in a `<!-- C3:BEGIN … -->` / `<!-- C3:END -->` block. Regenerating (or `Compact`) only rewrites that block — **anything you write outside it is preserved**, so it's safe to keep your own notes in the same file.
235
+
234
236
  ### 7. Chat — browse prior AI conversations
235
237
 
236
238
  <p align="center">
@@ -332,6 +334,10 @@ Only **read** and **safe-action** tools are exposed (no code editing); requests
332
334
  default. Generate, rotate, and copy the token from the dashboard's **Settings →
333
335
  Discovery API** tab. See the [Oracle Discovery API guide](oracle-guide/discovery-api.md).
334
336
 
337
+ As of v2.38.0 the Oracle also reports a **cross-project activity digest** — sessions,
338
+ tool calls, edits, git mutations, and token/cost for a day — via the `activity_report`
339
+ discovery tool, the `GET /api/activity/digest` endpoint, and the dashboard's **Activity** tab.
340
+
335
341
  ---
336
342
 
337
343
  ## Tiered local AI (optional)
@@ -370,6 +376,8 @@ c3 permissions show
370
376
  c3 permissions standard
371
377
  ```
372
378
 
379
+ Applying or switching a tier **preserves your own `allow`/`deny` rules** (and keys like `ask`/`defaultMode`) — only C3-managed entries are replaced. Likewise, C3 never clobbers your other entries in `.mcp.json` (only its own `c3` server) or the hooks you've added to `settings.local.json` (only its own hooks).
380
+
373
381
  ---
374
382
 
375
383
  ## Benchmarks
@@ -84,6 +84,7 @@ oracle/mcp_oracle.py
84
84
  oracle/oracle.html
85
85
  oracle/oracle_server.py
86
86
  oracle/services/__init__.py
87
+ oracle/services/activity_reporter.py
87
88
  oracle/services/api_auth.py
88
89
  oracle/services/c3_bridge.py
89
90
  oracle/services/chat_engine.py
@@ -153,12 +154,14 @@ services/bench/__init__.py
153
154
  services/bench/external/__init__.py
154
155
  services/bench/external/aider_polyglot.py
155
156
  services/bench/external/swe_bench.py
157
+ tests/test_activity_reporter.py
156
158
  tests/test_aider_polyglot.py
157
159
  tests/test_bitbucket_cli_smoke.py
158
160
  tests/test_bitbucket_client.py
159
161
  tests/test_bitbucket_credentials.py
160
162
  tests/test_bitbucket_tool.py
161
163
  tests/test_c3_shell.py
164
+ tests/test_claude_md_merge.py
162
165
  tests/test_cli_smoke.py
163
166
  tests/test_e2e_benchmark.py
164
167
  tests/test_edit_normalization.py
@@ -168,6 +171,7 @@ tests/test_ghost_files.py
168
171
  tests/test_git_branch_awareness.py
169
172
  tests/test_hub_server_smoke.py
170
173
  tests/test_install_mcp_entrypoint.py
174
+ tests/test_lazy_store_init.py
171
175
  tests/test_mcp_host_guard.py
172
176
  tests/test_mcp_server_smoke.py
173
177
  tests/test_mcp_toml.py
@@ -841,6 +841,7 @@
841
841
  <div class="tab active" data-tab="chat">Chat</div>
842
842
  <div class="tab" data-tab="projects">Projects</div>
843
843
  <div class="tab" data-tab="insights">Insights</div>
844
+ <div class="tab" data-tab="activity">Activity</div>
844
845
  <div class="tab" data-tab="crossgraph">Cross-Graph</div>
845
846
  <div class="tab" data-tab="suggestions">Suggestions</div>
846
847
  <div class="tab" data-tab="agents">Team / Agents</div>
@@ -975,6 +976,27 @@
975
976
  <div id="insightsEmpty" class="empty" style="display:none">No insights yet. Click "Generate" with 2+ projects.</div>
976
977
  </div>
977
978
 
979
+ <!-- ── Activity ── -->
980
+ <div class="panel" id="panel-activity">
981
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;flex-wrap:wrap;gap:10px">
982
+ <div>
983
+ <h2 style="font-size:16px;font-weight:600">Activity Digest</h2>
984
+ <p style="font-size:12px;color:var(--text2);margin-top:4px">Cross-project sessions, tool calls, edits, git mutations and token/cost for one day (UTC).</p>
985
+ </div>
986
+ <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
987
+ <input type="date" id="actDate" style="padding:4px 6px" onchange="loadActivity()">
988
+ <label style="font-size:11px;color:var(--text2);display:flex;align-items:center;gap:6px">
989
+ <input type="checkbox" id="actNarrate"> Narrate
990
+ </label>
991
+ <button class="btn btn-primary btn-sm" id="btnLoadActivity" onclick="loadActivity()">Refresh</button>
992
+ </div>
993
+ </div>
994
+ <div id="actSummary" style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:14px"></div>
995
+ <div id="actNarrative" class="card" style="display:none;margin-bottom:14px"></div>
996
+ <div id="actProjects"></div>
997
+ <div id="actEmpty" class="empty" style="display:none">No activity recorded for this day.</div>
998
+ </div>
999
+
978
1000
  <!-- ── Cross-Graph ── -->
979
1001
  <div class="panel" id="panel-crossgraph">
980
1002
  <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;flex-wrap:wrap;gap:10px">
@@ -1279,6 +1301,9 @@ document.querySelectorAll('.tab').forEach(tab => {
1279
1301
  } else {
1280
1302
  mainEl.classList.remove('wide');
1281
1303
  }
1304
+ if (tab.dataset.tab === 'activity' && !window._actLoaded) {
1305
+ loadActivity(); window._actLoaded = true;
1306
+ }
1282
1307
  });
1283
1308
  });
1284
1309
 
@@ -1899,6 +1924,89 @@ async function dismissInsight(id) {
1899
1924
  }, { successMsg: 'Insight dismissed', silent: false });
1900
1925
  }
1901
1926
 
1927
+ // ═══════════════════════════════════════════════════════════
1928
+ // ── Activity Digest ──
1929
+ // ═══════════════════════════════════════════════════════════
1930
+ async function loadActivity() {
1931
+ const dateEl = document.getElementById('actDate');
1932
+ if (dateEl && !dateEl.value) dateEl.value = new Date().toISOString().slice(0, 10);
1933
+ const date = dateEl ? dateEl.value : '';
1934
+ const narrate = document.getElementById('actNarrate')?.checked ? 'true' : 'false';
1935
+ const btn = document.getElementById('btnLoadActivity');
1936
+ if (btn) btn.disabled = true;
1937
+ try {
1938
+ const qs = new URLSearchParams({ date, narrate }).toString();
1939
+ renderActivity(await api('/api/activity/digest?' + qs));
1940
+ } catch (e) {
1941
+ renderActivity({ error: (e && e.message) || 'Failed to load activity.' });
1942
+ } finally {
1943
+ if (btn) btn.disabled = false;
1944
+ }
1945
+ }
1946
+
1947
+ function _actCard(label, value) {
1948
+ return `<div class="card" style="flex:1;min-width:110px;text-align:center">
1949
+ <div style="font-size:22px;font-weight:700">${value}</div>
1950
+ <div style="font-size:11px;color:var(--text2);text-transform:uppercase;letter-spacing:.04em">${label}</div>
1951
+ </div>`;
1952
+ }
1953
+
1954
+ function renderActivity(data) {
1955
+ const summary = document.getElementById('actSummary');
1956
+ const projects = document.getElementById('actProjects');
1957
+ const empty = document.getElementById('actEmpty');
1958
+ const narrEl = document.getElementById('actNarrative');
1959
+ if (!data || data.error) {
1960
+ summary.innerHTML = ''; projects.innerHTML = ''; narrEl.style.display = 'none';
1961
+ empty.style.display = ''; empty.textContent = (data && data.error) || 'Failed to load activity.';
1962
+ return;
1963
+ }
1964
+ const t = data.totals || {};
1965
+ summary.innerHTML = [
1966
+ _actCard('Projects', t.projects_active || 0),
1967
+ _actCard('Sessions', t.sessions || 0),
1968
+ _actCard('Tool Calls', t.tool_calls || 0),
1969
+ _actCard('Edits', t.edits || 0),
1970
+ _actCard('Git', t.git_mutations || 0),
1971
+ _actCard('Cost $', (t.cost_usd || 0).toFixed(2)),
1972
+ ].join('');
1973
+
1974
+ if (data.narrative) {
1975
+ narrEl.style.display = '';
1976
+ narrEl.innerHTML = `<div style="font-size:11px;color:var(--text2);text-transform:uppercase;margin-bottom:6px">${esc(data.window && data.window.label || '')}</div>
1977
+ <p style="font-size:13px;line-height:1.6;white-space:pre-wrap">${esc(data.narrative)}</p>`;
1978
+ } else {
1979
+ narrEl.style.display = 'none';
1980
+ }
1981
+
1982
+ const rows = data.projects || [];
1983
+ if (!rows.length) {
1984
+ projects.innerHTML = ''; empty.style.display = '';
1985
+ empty.textContent = 'No activity recorded for ' + (data.window && data.window.label || 'this day') + '.';
1986
+ return;
1987
+ }
1988
+ empty.style.display = 'none';
1989
+ projects.innerHTML = `
1990
+ <table style="width:100%;border-collapse:collapse;font-size:13px">
1991
+ <thead><tr style="text-align:left;color:var(--text2);font-size:11px;text-transform:uppercase">
1992
+ <th style="padding:6px">Project</th><th style="padding:6px">Sessions</th>
1993
+ <th style="padding:6px">Tools</th><th style="padding:6px">Edits</th>
1994
+ <th style="padding:6px">Git</th><th style="padding:6px">Tokens</th>
1995
+ <th style="padding:6px">Cost $</th><th style="padding:6px">Last activity</th>
1996
+ </tr></thead>
1997
+ <tbody>${rows.map(p => `<tr style="border-top:1px solid var(--border)">
1998
+ <td style="padding:6px;font-weight:600">${esc(p.name)}</td>
1999
+ <td style="padding:6px">${(p.sessions || []).length}</td>
2000
+ <td style="padding:6px">${p.tool_calls || 0}</td>
2001
+ <td style="padding:6px">${p.edits || 0}</td>
2002
+ <td style="padding:6px">${p.git_mutations || 0}</td>
2003
+ <td style="padding:6px">${(((p.tokens || {}).input || 0) + ((p.tokens || {}).output || 0)).toLocaleString()}</td>
2004
+ <td style="padding:6px">${(p.cost_usd || 0).toFixed(2)}</td>
2005
+ <td style="padding:6px;color:var(--text2);font-size:11px">${esc((p.last_activity || '').replace('T', ' ').slice(0, 19))}</td>
2006
+ </tr>`).join('')}</tbody>
2007
+ </table>`;
2008
+ }
2009
+
1902
2010
  // ═══════════════════════════════════════════════════════════
1903
2011
  // ── Suggestions ──
1904
2012
  // ═══════════════════════════════════════════════════════════
@@ -21,6 +21,7 @@ from flask import Flask, Response, jsonify, request, send_from_directory # noqa
21
21
  from oracle.config import ORACLE_DIR, load_config, save_config # noqa: E402
22
22
  from oracle.mcp_oracle import mcp_url, start_mcp_thread # noqa: E402
23
23
  from oracle.services import api_auth # noqa: E402
24
+ from oracle.services.activity_reporter import ActivityReporter # noqa: E402
24
25
  from oracle.services.api_auth import extract_bearer # noqa: E402
25
26
  from oracle.services.api_auth import verify as verify_api_key # noqa: E402
26
27
  from oracle.services.c3_bridge import C3Bridge # noqa: E402
@@ -57,10 +58,11 @@ _chat_engine: ChatEngine | None = None
57
58
  _c3_bridge: C3Bridge | None = None
58
59
  _federated: FederatedGraph | None = None
59
60
  _tool_registry: ToolRegistry | None = None
61
+ _activity_reporter: ActivityReporter | None = None
60
62
 
61
63
 
62
64
  def _init_services():
63
- global _cfg, _bridge, _scanner, _reader, _checker, _writer, _cross_memory, _engine, _agent, _model_verified, _chat_store, _chat_engine, _c3_bridge, _federated, _tool_registry
65
+ global _cfg, _bridge, _scanner, _reader, _checker, _writer, _cross_memory, _engine, _agent, _model_verified, _chat_store, _chat_engine, _c3_bridge, _federated, _tool_registry, _activity_reporter
64
66
  _cfg = load_config()
65
67
  _bridge = OllamaBridge(
66
68
  base_url=_cfg.get("ollama_base_url", "https://ollama.com"),
@@ -97,6 +99,7 @@ def _init_services():
97
99
  interval=int(_cfg.get("review_interval_seconds", 1800)),
98
100
  federated_graph=_federated,
99
101
  )
102
+ _activity_reporter = ActivityReporter(scanner=_scanner, ollama_bridge=_bridge)
100
103
  _chat_engine = ChatEngine(
101
104
  bridge=_bridge,
102
105
  reader=_reader,
@@ -107,6 +110,7 @@ def _init_services():
107
110
  scanner=_scanner,
108
111
  store=_chat_store,
109
112
  c3_bridge=_c3_bridge,
113
+ activity_reporter=_activity_reporter,
110
114
  )
111
115
  _tool_registry = ToolRegistry(
112
116
  ToolExecutor(_chat_engine),
@@ -608,6 +612,26 @@ def api_chat_conversation_state(conv_id):
608
612
  return jsonify({"state": _chat_store.get_state(conv_id)})
609
613
 
610
614
 
615
+ # ── Activity digest (Oracle UI) ───────────────────────────
616
+ @app.route("/api/activity/digest", methods=["GET"])
617
+ def api_activity_digest():
618
+ """Cross-project activity digest for the Oracle UI.
619
+
620
+ Query params: date=YYYY-MM-DD, since, until, project (single-project path),
621
+ narrate=true|false. Defaults to today (UTC) across all registered projects.
622
+ """
623
+ if not _activity_reporter:
624
+ return jsonify({"error": "not initialized"}), 500
625
+ narrate = str(request.args.get("narrate", "")).lower() in ("1", "true", "yes", "on")
626
+ return jsonify(_activity_reporter.report(
627
+ date=request.args.get("date", ""),
628
+ since=request.args.get("since", ""),
629
+ until=request.args.get("until", ""),
630
+ project_path=request.args.get("project", ""),
631
+ narrate=narrate,
632
+ ))
633
+
634
+
611
635
  # ── Discovery API (external LLM tool surface) ─────────────
612
636
  @app.route("/api/discovery/tools", methods=["GET"])
613
637
  def api_discovery_tools():
@@ -801,8 +825,19 @@ def _is_oracle_running(port: int) -> bool:
801
825
  return False
802
826
 
803
827
 
828
+ def _force_utf8_console() -> None:
829
+ """Make stdout/stderr UTF-8 so banner/log output can't crash on legacy
830
+ Windows code pages (cp1252 raises UnicodeEncodeError on chars like '→')."""
831
+ for stream in (sys.stdout, sys.stderr):
832
+ try:
833
+ stream.reconfigure(encoding="utf-8", errors="replace")
834
+ except (AttributeError, ValueError, OSError):
835
+ pass
836
+
837
+
804
838
  def run_oracle(port: int = None, open_browser: bool = None):
805
839
  """Main entry point for Oracle server."""
840
+ _force_utf8_console()
806
841
  _init_services()
807
842
 
808
843
  cfg = load_config()