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