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.
- {code_context_control-2.36.0/code_context_control.egg-info → code_context_control-2.38.1}/PKG-INFO +9 -1
- {code_context_control-2.36.0 → code_context_control-2.38.1}/README.md +8 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/c3.py +64 -7
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/commands/common.py +3 -7
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/getting-started.html +17 -1
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/oracle.html +1 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hub_server.py +4 -2
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/mcp_server.py +23 -8
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/server.py +12 -10
- {code_context_control-2.36.0 → code_context_control-2.38.1/code_context_control.egg-info}/PKG-INFO +9 -1
- {code_context_control-2.36.0 → code_context_control-2.38.1}/code_context_control.egg-info/SOURCES.txt +4 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/oracle.html +108 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/oracle_server.py +36 -1
- code_context_control-2.38.1/oracle/services/activity_reporter.py +256 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/chat_engine.py +18 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/tool_registry.py +22 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/pyproject.toml +1 -1
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/claude_md.py +139 -20
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/embedding_index.py +26 -2
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/session_manager.py +7 -12
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/vector_store.py +28 -2
- code_context_control-2.38.1/tests/test_activity_reporter.py +130 -0
- code_context_control-2.38.1/tests/test_claude_md_merge.py +157 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_install_mcp_entrypoint.py +48 -0
- code_context_control-2.38.1/tests/test_lazy_store_init.py +77 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_oracle_discovery_api.py +11 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_permissions.py +35 -7
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_tool_registry.py +9 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/LICENSE +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/__init__.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/_hook_utils.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/commands/__init__.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/commands/parser.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/docs.html +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/edits.html +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/bitbucket.html +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/index.html +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/shared.css +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/tools.html +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/guide/workflow.html +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_auto_snapshot.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_c3_signal.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_c3read.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_edit_ledger.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_edit_unlock.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_filter.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_ghost_files.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_pretool_enforce.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_read.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_session_stats.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hook_terse_advisor.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/hub.html +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/mcp_proxy.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/__init__.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/_helpers.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/agent.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/bitbucket.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/compress.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/delegate.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/edit.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/edits.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/filter.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/impact.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/memory.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/project.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/read.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/search.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/session.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/shell.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/status.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/tools/validate.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/api.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/app.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/bitbucket.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/chat.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/dashboard.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/edits.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/instructions.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/memory.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/sessions.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/settings.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/components/sidebar.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/icons.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/shared.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui/theme.js +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui.html +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui_legacy.html +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/cli/ui_nano.html +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/code_context_control.egg-info/dependency_links.txt +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/code_context_control.egg-info/entry_points.txt +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/code_context_control.egg-info/requires.txt +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/code_context_control.egg-info/top_level.txt +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/core/__init__.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/core/config.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/core/ide.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/core/mcp_toml.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/core/web_security.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/__init__.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/config.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/mcp_oracle.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/__init__.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/api_auth.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/c3_bridge.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/chat_store.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/cross_memory.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/federated_graph.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/health_checker.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/insight_engine.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/memory_reader.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/memory_writer.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/ollama_bridge.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/project_scanner.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/review_agent.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/oracle/services/tool_executor.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/__init__.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/activity_log.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/agent_base.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/agents.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/auto_memory.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/bench/__init__.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/bench/external/__init__.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/bench/external/aider_polyglot.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/bench/external/swe_bench.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/benchmark_dashboard.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/bitbucket_client.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/bitbucket_credentials.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/compressor.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/context_snapshot.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/conversation_store.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/doc_index.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/e2e_benchmark.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/e2e_evaluator.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/e2e_tasks.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/edit_ledger.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/error_reporting.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/file_memory.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/git_context.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/hub_service.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/indexer.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/memory.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/memory_consolidator.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/memory_graph.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/memory_grounder.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/memory_scorer.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/metrics.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/notifications.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/ollama_client.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/output_filter.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/parser.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/project_manager.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/project_runtime.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/protocol.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/proxy_state.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/retrieval_broker.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/router.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/runtime.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/session_benchmark.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/session_preloader.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/text_index.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/tool_classifier.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/transcript_index.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/validation_cache.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/version_tracker.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/services/watcher.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/setup.cfg +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_aider_polyglot.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_bitbucket_cli_smoke.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_bitbucket_client.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_bitbucket_credentials.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_bitbucket_tool.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_c3_shell.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_cli_smoke.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_e2e_benchmark.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_edit_normalization.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_enforcement_flip.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_federated_graph.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_ghost_files.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_git_branch_awareness.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_hub_server_smoke.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_mcp_host_guard.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_mcp_server_smoke.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_mcp_toml.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_memory_graph_api.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_memory_system.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_notification_discipline.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_oracle_api_auth.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_oracle_apikey_api.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_output_filter.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_project_manager.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_project_manager_merge.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_project_tool.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_read_coercion.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_session_benchmark.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_session_budget.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_shell_robustness.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_swe_bench.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_upgrade_and_version.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_validate.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_web_security.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tests/test_windows_reliability.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/__init__.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/backend.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/main.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/__init__.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/benchmark_view.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/claudemd_view.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/compress_view.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/index_view.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/init_view.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/mcp_view.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/optimize_view.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/pipe_view.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/projects_view.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/search_view.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/session_view.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/stats.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/screens/ui_view.py +0 -0
- {code_context_control-2.36.0 → code_context_control-2.38.1}/tui/theme.tcss +0 -0
{code_context_control-2.36.0/code_context_control.egg-info → code_context_control-2.38.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-context-control
|
|
3
|
-
Version: 2.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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"] =
|
|
5164
|
-
|
|
5165
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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><!-- C3:BEGIN … --></code> / <code><!-- C3:END --></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
|
|
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"] =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
#
|
|
1127
|
-
|
|
1128
|
-
|
|
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
|
|
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"] =
|
|
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
|
|
{code_context_control-2.36.0 → code_context_control-2.38.1/code_context_control.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-context-control
|
|
3
|
-
Version: 2.
|
|
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()
|