code-context-control 2.37.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.37.0/code_context_control.egg-info → code_context_control-2.38.1}/PKG-INFO +5 -1
- {code_context_control-2.37.0 → code_context_control-2.38.1}/README.md +4 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/c3.py +1 -1
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/guide/oracle.html +1 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/mcp_server.py +23 -8
- {code_context_control-2.37.0 → code_context_control-2.38.1/code_context_control.egg-info}/PKG-INFO +5 -1
- {code_context_control-2.37.0 → code_context_control-2.38.1}/code_context_control.egg-info/SOURCES.txt +3 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/oracle.html +108 -0
- {code_context_control-2.37.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.37.0 → code_context_control-2.38.1}/oracle/services/chat_engine.py +18 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/tool_registry.py +22 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/pyproject.toml +1 -1
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/embedding_index.py +26 -2
- {code_context_control-2.37.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_lazy_store_init.py +77 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_oracle_discovery_api.py +11 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_tool_registry.py +9 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/LICENSE +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/__init__.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/_hook_utils.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/commands/__init__.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/commands/common.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/commands/parser.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/docs.html +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/edits.html +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/guide/bitbucket.html +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/guide/getting-started.html +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/guide/index.html +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/guide/shared.css +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/guide/tools.html +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/guide/workflow.html +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hook_auto_snapshot.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hook_c3_signal.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hook_c3read.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hook_edit_ledger.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hook_edit_unlock.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hook_filter.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hook_ghost_files.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hook_pretool_enforce.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hook_read.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hook_session_stats.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hook_terse_advisor.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hub.html +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/hub_server.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/mcp_proxy.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/server.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/__init__.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/_helpers.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/agent.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/bitbucket.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/compress.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/delegate.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/edit.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/edits.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/filter.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/impact.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/memory.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/project.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/read.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/search.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/session.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/shell.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/status.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/tools/validate.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/api.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/app.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/components/bitbucket.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/components/chat.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/components/dashboard.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/components/edits.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/components/instructions.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/components/memory.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/components/sessions.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/components/settings.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/components/sidebar.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/icons.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/shared.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui/theme.js +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui.html +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui_legacy.html +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/cli/ui_nano.html +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/code_context_control.egg-info/dependency_links.txt +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/code_context_control.egg-info/entry_points.txt +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/code_context_control.egg-info/requires.txt +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/code_context_control.egg-info/top_level.txt +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/core/__init__.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/core/config.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/core/ide.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/core/mcp_toml.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/core/web_security.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/__init__.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/config.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/mcp_oracle.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/__init__.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/api_auth.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/c3_bridge.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/chat_store.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/cross_memory.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/federated_graph.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/health_checker.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/insight_engine.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/memory_reader.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/memory_writer.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/ollama_bridge.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/project_scanner.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/review_agent.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/oracle/services/tool_executor.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/__init__.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/activity_log.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/agent_base.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/agents.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/auto_memory.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/bench/__init__.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/bench/external/__init__.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/bench/external/aider_polyglot.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/bench/external/swe_bench.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/benchmark_dashboard.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/bitbucket_client.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/bitbucket_credentials.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/claude_md.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/compressor.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/context_snapshot.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/conversation_store.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/doc_index.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/e2e_benchmark.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/e2e_evaluator.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/e2e_tasks.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/edit_ledger.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/error_reporting.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/file_memory.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/git_context.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/hub_service.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/indexer.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/memory.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/memory_consolidator.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/memory_graph.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/memory_grounder.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/memory_scorer.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/metrics.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/notifications.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/ollama_client.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/output_filter.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/parser.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/project_manager.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/project_runtime.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/protocol.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/proxy_state.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/retrieval_broker.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/router.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/runtime.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/session_benchmark.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/session_manager.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/session_preloader.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/text_index.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/tool_classifier.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/transcript_index.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/validation_cache.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/version_tracker.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/services/watcher.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/setup.cfg +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_aider_polyglot.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_bitbucket_cli_smoke.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_bitbucket_client.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_bitbucket_credentials.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_bitbucket_tool.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_c3_shell.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_claude_md_merge.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_cli_smoke.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_e2e_benchmark.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_edit_normalization.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_enforcement_flip.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_federated_graph.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_ghost_files.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_git_branch_awareness.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_hub_server_smoke.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_install_mcp_entrypoint.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_mcp_host_guard.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_mcp_server_smoke.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_mcp_toml.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_memory_graph_api.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_memory_system.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_notification_discipline.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_oracle_api_auth.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_oracle_apikey_api.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_output_filter.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_permissions.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_project_manager.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_project_manager_merge.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_project_tool.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_read_coercion.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_session_benchmark.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_session_budget.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_shell_robustness.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_swe_bench.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_upgrade_and_version.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_validate.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_web_security.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tests/test_windows_reliability.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/__init__.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/backend.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/main.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/__init__.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/benchmark_view.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/claudemd_view.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/compress_view.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/index_view.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/init_view.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/mcp_view.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/optimize_view.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/pipe_view.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/projects_view.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/search_view.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/session_view.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/stats.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/screens/ui_view.py +0 -0
- {code_context_control-2.37.0 → code_context_control-2.38.1}/tui/theme.tcss +0 -0
{code_context_control-2.37.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
|
|
@@ -334,6 +334,10 @@ Only **read** and **safe-action** tools are exposed (no code editing); requests
|
|
|
334
334
|
default. Generate, rotate, and copy the token from the dashboard's **Settings →
|
|
335
335
|
Discovery API** tab. See the [Oracle Discovery API guide](oracle-guide/discovery-api.md).
|
|
336
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
|
+
|
|
337
341
|
---
|
|
338
342
|
|
|
339
343
|
## Tiered local AI (optional)
|
|
@@ -272,6 +272,10 @@ Only **read** and **safe-action** tools are exposed (no code editing); requests
|
|
|
272
272
|
default. Generate, rotate, and copy the token from the dashboard's **Settings →
|
|
273
273
|
Discovery API** tab. See the [Oracle Discovery API guide](oracle-guide/discovery-api.md).
|
|
274
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
|
+
|
|
275
279
|
---
|
|
276
280
|
|
|
277
281
|
## Tiered local AI (optional)
|
|
@@ -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
|
|
|
@@ -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
|
|
{code_context_control-2.37.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
|
|
@@ -334,6 +334,10 @@ Only **read** and **safe-action** tools are exposed (no code editing); requests
|
|
|
334
334
|
default. Generate, rotate, and copy the token from the dashboard's **Settings →
|
|
335
335
|
Discovery API** tab. See the [Oracle Discovery API guide](oracle-guide/discovery-api.md).
|
|
336
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
|
+
|
|
337
341
|
---
|
|
338
342
|
|
|
339
343
|
## Tiered local AI (optional)
|
|
@@ -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,6 +154,7 @@ 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
|
|
@@ -169,6 +171,7 @@ tests/test_ghost_files.py
|
|
|
169
171
|
tests/test_git_branch_awareness.py
|
|
170
172
|
tests/test_hub_server_smoke.py
|
|
171
173
|
tests/test_install_mcp_entrypoint.py
|
|
174
|
+
tests/test_lazy_store_init.py
|
|
172
175
|
tests/test_mcp_host_guard.py
|
|
173
176
|
tests/test_mcp_server_smoke.py
|
|
174
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()
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Cross-project activity reporting for the Oracle.
|
|
2
|
+
|
|
3
|
+
Aggregates per-project C3 activity (sessions, tool calls, edits, git mutations,
|
|
4
|
+
token/cost) into a single daily digest. Reads the same ``.c3`` JSONL artifacts
|
|
5
|
+
the local UI uses, directly per project — no C3Runtime build required (mirrors
|
|
6
|
+
how ``MemoryReader`` / ``ProjectManager`` read project data straight off disk).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from services.activity_log import ActivityLog
|
|
17
|
+
from services.edit_ledger import EditLedger
|
|
18
|
+
from services.session_manager import SessionManager
|
|
19
|
+
|
|
20
|
+
log = logging.getLogger("oracle")
|
|
21
|
+
|
|
22
|
+
# Lexicographic sentinels for an open-ended window (ISO-8601 keeps string order).
|
|
23
|
+
_MIN_TS = "0000-01-01T00:00:00"
|
|
24
|
+
_MAX_TS = "9999-12-31T23:59:59"
|
|
25
|
+
|
|
26
|
+
_NARRATE_SYSTEM = (
|
|
27
|
+
"You are C3's activity reporter. Given a JSON digest of a developer's work "
|
|
28
|
+
"across their projects for a time window, write a concise, friendly summary "
|
|
29
|
+
"(3-6 sentences). Lead with the headline (busiest project and the totals), "
|
|
30
|
+
"then call out notable specifics. Use ONLY the numbers provided — never "
|
|
31
|
+
"invent data. If everything is zero, say it was a quiet period."
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ActivityReporter:
|
|
36
|
+
"""Builds cross-project (or single-project) activity digests.
|
|
37
|
+
|
|
38
|
+
Construct with an Oracle ``ProjectScanner`` and, optionally, an
|
|
39
|
+
``OllamaBridge`` for prose narration (``narrate=True``).
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, scanner, ollama_bridge=None):
|
|
43
|
+
self.scanner = scanner
|
|
44
|
+
self.ollama_bridge = ollama_bridge
|
|
45
|
+
|
|
46
|
+
# ── Public API ───────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
def report(self, date: str = "", since: str = "", until: str = "",
|
|
49
|
+
project_path: str = "", narrate: bool = False) -> dict:
|
|
50
|
+
"""Aggregate activity into a digest dict.
|
|
51
|
+
|
|
52
|
+
date: UTC day ``YYYY-MM-DD`` (default today). since/until: ISO bounds
|
|
53
|
+
that override ``date``. project_path: limit to one project (else all
|
|
54
|
+
registered projects with a ``.c3`` dir). narrate: add an LLM prose
|
|
55
|
+
summary (best-effort; never fails the structured result).
|
|
56
|
+
"""
|
|
57
|
+
lo, hi, window = self._resolve_window(date, since, until)
|
|
58
|
+
|
|
59
|
+
proj_reports = []
|
|
60
|
+
for proj in self._target_projects(project_path):
|
|
61
|
+
pr = self._report_project(proj, lo, hi)
|
|
62
|
+
if (pr["tool_calls"] or pr["edits"] or pr["git_mutations"]
|
|
63
|
+
or pr["sessions"] or pr["decisions"]):
|
|
64
|
+
proj_reports.append(pr)
|
|
65
|
+
|
|
66
|
+
digest = {
|
|
67
|
+
"window": window,
|
|
68
|
+
"totals": self._aggregate_totals(proj_reports),
|
|
69
|
+
"projects": proj_reports,
|
|
70
|
+
"narrative": None,
|
|
71
|
+
}
|
|
72
|
+
if narrate:
|
|
73
|
+
digest["narrative"] = self._narrate(digest)
|
|
74
|
+
return digest
|
|
75
|
+
|
|
76
|
+
# ── Window resolution ────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def _resolve_window(date: str, since: str, until: str):
|
|
80
|
+
"""Return (lo, hi, window_dict).
|
|
81
|
+
|
|
82
|
+
Bounds are naive (no tz suffix) on purpose: the date/time prefix
|
|
83
|
+
dominates lexicographically, so the same bounds correctly window both
|
|
84
|
+
naive ledger timestamps and tz-aware ``+00:00`` activity timestamps.
|
|
85
|
+
"""
|
|
86
|
+
if since or until:
|
|
87
|
+
lo = since or _MIN_TS
|
|
88
|
+
hi = until or _MAX_TS
|
|
89
|
+
window = {
|
|
90
|
+
"since": since or None,
|
|
91
|
+
"until": until or None,
|
|
92
|
+
"label": f"{since or 'beginning'} → {until or 'now'}",
|
|
93
|
+
"tz": "as-given",
|
|
94
|
+
}
|
|
95
|
+
return lo, hi, window
|
|
96
|
+
|
|
97
|
+
day = date.strip() if date else datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
98
|
+
lo = f"{day}T00:00:00"
|
|
99
|
+
hi = f"{day}T23:59:59.999999"
|
|
100
|
+
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
101
|
+
window = {
|
|
102
|
+
"since": lo,
|
|
103
|
+
"until": hi,
|
|
104
|
+
"label": f"{day}{' (today)' if day == today else ''}, UTC",
|
|
105
|
+
"tz": "UTC",
|
|
106
|
+
}
|
|
107
|
+
return lo, hi, window
|
|
108
|
+
|
|
109
|
+
# ── Project selection ────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
def _target_projects(self, project_path: str) -> list[dict]:
|
|
112
|
+
if project_path:
|
|
113
|
+
p = Path(project_path)
|
|
114
|
+
return [{"path": str(p), "name": p.name, "has_c3": (p / ".c3").is_dir()}]
|
|
115
|
+
return [p for p in self.scanner.discover() if p.get("has_c3")]
|
|
116
|
+
|
|
117
|
+
# ── Per-project aggregation ──────────────────────────────────
|
|
118
|
+
|
|
119
|
+
def _report_project(self, proj: dict, lo: str, hi: str) -> dict:
|
|
120
|
+
path = proj.get("path", "")
|
|
121
|
+
base = {
|
|
122
|
+
"name": proj.get("name") or Path(path).name,
|
|
123
|
+
"path": path,
|
|
124
|
+
"sessions": [],
|
|
125
|
+
"tool_calls": 0,
|
|
126
|
+
"edits": 0,
|
|
127
|
+
"git_mutations": 0,
|
|
128
|
+
"decisions": 0,
|
|
129
|
+
"events": {},
|
|
130
|
+
"tokens": {"input": 0, "output": 0},
|
|
131
|
+
"cost_usd": 0.0,
|
|
132
|
+
"first_activity": None,
|
|
133
|
+
"last_activity": None,
|
|
134
|
+
}
|
|
135
|
+
# Guard: only read projects that already have a .c3 dir — never create
|
|
136
|
+
# one as a side effect (ActivityLog/SessionManager mkdir on init).
|
|
137
|
+
if not path or not (Path(path) / ".c3").is_dir():
|
|
138
|
+
return base
|
|
139
|
+
|
|
140
|
+
timestamps: list[str] = []
|
|
141
|
+
|
|
142
|
+
# Activity log → per-type event counts.
|
|
143
|
+
try:
|
|
144
|
+
counts: dict[str, int] = {}
|
|
145
|
+
for e in ActivityLog(path).get_recent(limit=10000, since=lo, until=hi):
|
|
146
|
+
etype = e.get("type", "unknown")
|
|
147
|
+
counts[etype] = counts.get(etype, 0) + 1
|
|
148
|
+
if e.get("timestamp"):
|
|
149
|
+
timestamps.append(e["timestamp"])
|
|
150
|
+
base["events"] = counts
|
|
151
|
+
base["tool_calls"] = counts.get("tool_call", 0)
|
|
152
|
+
base["decisions"] = counts.get("decision", 0)
|
|
153
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
154
|
+
log.debug("activity_log read failed for %s: %s", path, exc)
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
sm = SessionManager(path)
|
|
158
|
+
# Sessions started within the window.
|
|
159
|
+
for s in sm.list_sessions(100):
|
|
160
|
+
started = s.get("started", "")
|
|
161
|
+
if started and lo <= started <= hi:
|
|
162
|
+
base["sessions"].append({
|
|
163
|
+
"id": s.get("id"),
|
|
164
|
+
"started": started,
|
|
165
|
+
"ended": s.get("ended", ""),
|
|
166
|
+
"description": s.get("description", ""),
|
|
167
|
+
"tool_calls": s.get("tool_calls", 0),
|
|
168
|
+
"duration": s.get("duration", ""),
|
|
169
|
+
})
|
|
170
|
+
timestamps.append(started)
|
|
171
|
+
# Token / cost from hook-captured session stats.
|
|
172
|
+
for st in sm.get_session_stats(500):
|
|
173
|
+
ts = st.get("ts", "")
|
|
174
|
+
if ts and lo <= ts <= hi:
|
|
175
|
+
base["tokens"]["input"] += int(st.get("input_tokens", 0) or 0)
|
|
176
|
+
base["tokens"]["output"] += int(st.get("output_tokens", 0) or 0)
|
|
177
|
+
base["cost_usd"] += float(st.get("cost_usd", 0) or 0)
|
|
178
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
179
|
+
log.debug("session read failed for %s: %s", path, exc)
|
|
180
|
+
|
|
181
|
+
# Edit ledger → edits vs git mutations (get_history filters >= lo only).
|
|
182
|
+
try:
|
|
183
|
+
for en in EditLedger(path).get_history(since=lo, limit=10000):
|
|
184
|
+
ts = en.get("timestamp", "")
|
|
185
|
+
if ts and ts > hi:
|
|
186
|
+
continue
|
|
187
|
+
if en.get("change_type") == "shell_git":
|
|
188
|
+
base["git_mutations"] += 1
|
|
189
|
+
else:
|
|
190
|
+
base["edits"] += 1
|
|
191
|
+
if ts:
|
|
192
|
+
timestamps.append(ts)
|
|
193
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
194
|
+
log.debug("edit ledger read failed for %s: %s", path, exc)
|
|
195
|
+
|
|
196
|
+
base["cost_usd"] = round(base["cost_usd"], 4)
|
|
197
|
+
if timestamps:
|
|
198
|
+
timestamps.sort()
|
|
199
|
+
base["first_activity"] = timestamps[0]
|
|
200
|
+
base["last_activity"] = timestamps[-1]
|
|
201
|
+
return base
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def _aggregate_totals(reports: list[dict]) -> dict:
|
|
205
|
+
totals = {
|
|
206
|
+
"projects_active": len(reports),
|
|
207
|
+
"sessions": 0,
|
|
208
|
+
"tool_calls": 0,
|
|
209
|
+
"edits": 0,
|
|
210
|
+
"git_mutations": 0,
|
|
211
|
+
"decisions": 0,
|
|
212
|
+
"input_tokens": 0,
|
|
213
|
+
"output_tokens": 0,
|
|
214
|
+
"cost_usd": 0.0,
|
|
215
|
+
}
|
|
216
|
+
for r in reports:
|
|
217
|
+
totals["sessions"] += len(r["sessions"])
|
|
218
|
+
totals["tool_calls"] += r["tool_calls"]
|
|
219
|
+
totals["edits"] += r["edits"]
|
|
220
|
+
totals["git_mutations"] += r["git_mutations"]
|
|
221
|
+
totals["decisions"] += r["decisions"]
|
|
222
|
+
totals["input_tokens"] += r["tokens"]["input"]
|
|
223
|
+
totals["output_tokens"] += r["tokens"]["output"]
|
|
224
|
+
totals["cost_usd"] += r["cost_usd"]
|
|
225
|
+
totals["cost_usd"] = round(totals["cost_usd"], 4)
|
|
226
|
+
return totals
|
|
227
|
+
|
|
228
|
+
# ── Narration (best-effort) ──────────────────────────────────
|
|
229
|
+
|
|
230
|
+
def _narrate(self, digest: dict) -> str | None:
|
|
231
|
+
if self.ollama_bridge is None:
|
|
232
|
+
digest["narrative_error"] = "No Ollama bridge configured."
|
|
233
|
+
return None
|
|
234
|
+
payload = {
|
|
235
|
+
"window": digest["window"]["label"],
|
|
236
|
+
"totals": digest["totals"],
|
|
237
|
+
"projects": [
|
|
238
|
+
{
|
|
239
|
+
"name": p["name"],
|
|
240
|
+
"tool_calls": p["tool_calls"],
|
|
241
|
+
"edits": p["edits"],
|
|
242
|
+
"git_mutations": p["git_mutations"],
|
|
243
|
+
"sessions": len(p["sessions"]),
|
|
244
|
+
"cost_usd": p["cost_usd"],
|
|
245
|
+
}
|
|
246
|
+
for p in digest["projects"]
|
|
247
|
+
],
|
|
248
|
+
}
|
|
249
|
+
prompt = "Summarize this developer activity digest:\n\n" + json.dumps(payload, indent=2)
|
|
250
|
+
try:
|
|
251
|
+
text = self.ollama_bridge.generate(prompt, system=_NARRATE_SYSTEM)
|
|
252
|
+
return (text or "").strip() or None
|
|
253
|
+
except Exception as exc:
|
|
254
|
+
log.warning("activity narration failed: %s", exc)
|
|
255
|
+
digest["narrative_error"] = str(exc)
|
|
256
|
+
return None
|