code-context-control 2.28.0__py3-none-any.whl
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.
- cli/__init__.py +1 -0
- cli/_hook_utils.py +99 -0
- cli/c3.py +6152 -0
- cli/commands/__init__.py +1 -0
- cli/commands/common.py +312 -0
- cli/commands/parser.py +286 -0
- cli/docs.html +3178 -0
- cli/edits.html +878 -0
- cli/hook_auto_snapshot.py +142 -0
- cli/hook_c3_signal.py +61 -0
- cli/hook_c3read.py +116 -0
- cli/hook_edit_ledger.py +213 -0
- cli/hook_edit_unlock.py +170 -0
- cli/hook_filter.py +130 -0
- cli/hook_ghost_files.py +238 -0
- cli/hook_pretool_enforce.py +334 -0
- cli/hook_read.py +200 -0
- cli/hook_session_stats.py +62 -0
- cli/hook_terse_advisor.py +190 -0
- cli/hub.html +3764 -0
- cli/hub_server.py +1619 -0
- cli/mcp_proxy.py +428 -0
- cli/mcp_server.py +660 -0
- cli/server.py +2985 -0
- cli/tools/__init__.py +4 -0
- cli/tools/_helpers.py +65 -0
- cli/tools/agent.py +1165 -0
- cli/tools/compress.py +215 -0
- cli/tools/delegate.py +1184 -0
- cli/tools/edit.py +313 -0
- cli/tools/edits.py +118 -0
- cli/tools/filter.py +285 -0
- cli/tools/impact.py +163 -0
- cli/tools/memory.py +469 -0
- cli/tools/read.py +224 -0
- cli/tools/search.py +337 -0
- cli/tools/session.py +95 -0
- cli/tools/shell.py +193 -0
- cli/tools/status.py +306 -0
- cli/tools/validate.py +310 -0
- cli/ui/api.js +36 -0
- cli/ui/app.js +207 -0
- cli/ui/components/chat.js +758 -0
- cli/ui/components/dashboard.js +689 -0
- cli/ui/components/edits.js +220 -0
- cli/ui/components/instructions.js +481 -0
- cli/ui/components/memory.js +626 -0
- cli/ui/components/sessions.js +606 -0
- cli/ui/components/settings.js +1404 -0
- cli/ui/components/sidebar.js +156 -0
- cli/ui/icons.js +51 -0
- cli/ui/shared.js +119 -0
- cli/ui/theme.js +22 -0
- cli/ui.html +168 -0
- cli/ui_legacy.html +6797 -0
- cli/ui_nano.html +503 -0
- code_context_control-2.28.0.dist-info/METADATA +248 -0
- code_context_control-2.28.0.dist-info/RECORD +150 -0
- code_context_control-2.28.0.dist-info/WHEEL +5 -0
- code_context_control-2.28.0.dist-info/entry_points.txt +4 -0
- code_context_control-2.28.0.dist-info/licenses/LICENSE +201 -0
- code_context_control-2.28.0.dist-info/top_level.txt +5 -0
- core/__init__.py +75 -0
- core/config.py +269 -0
- core/ide.py +188 -0
- oracle/__init__.py +1 -0
- oracle/config.py +75 -0
- oracle/oracle.html +3900 -0
- oracle/oracle_server.py +663 -0
- oracle/services/__init__.py +1 -0
- oracle/services/c3_bridge.py +210 -0
- oracle/services/chat_engine.py +1103 -0
- oracle/services/chat_store.py +155 -0
- oracle/services/cross_memory.py +154 -0
- oracle/services/federated_graph.py +463 -0
- oracle/services/health_checker.py +117 -0
- oracle/services/insight_engine.py +307 -0
- oracle/services/memory_reader.py +106 -0
- oracle/services/memory_writer.py +182 -0
- oracle/services/ollama_bridge.py +332 -0
- oracle/services/project_scanner.py +87 -0
- oracle/services/review_agent.py +206 -0
- services/__init__.py +1 -0
- services/activity_log.py +93 -0
- services/agent_base.py +124 -0
- services/agents.py +1529 -0
- services/auto_memory.py +407 -0
- services/bench/__init__.py +6 -0
- services/bench/external/__init__.py +29 -0
- services/bench/external/aider_polyglot.py +405 -0
- services/bench/external/swe_bench.py +485 -0
- services/benchmark_dashboard.py +596 -0
- services/claude_md.py +785 -0
- services/compressor.py +592 -0
- services/context_snapshot.py +356 -0
- services/conversation_store.py +870 -0
- services/doc_index.py +537 -0
- services/e2e_benchmark.py +2884 -0
- services/e2e_evaluator.py +396 -0
- services/e2e_tasks.py +743 -0
- services/edit_ledger.py +459 -0
- services/embedding_index.py +341 -0
- services/error_reporting.py +123 -0
- services/file_memory.py +734 -0
- services/hub_service.py +585 -0
- services/indexer.py +712 -0
- services/memory.py +318 -0
- services/memory_consolidator.py +538 -0
- services/memory_graph.py +382 -0
- services/memory_grounder.py +304 -0
- services/memory_scorer.py +246 -0
- services/metrics.py +86 -0
- services/notifications.py +209 -0
- services/ollama_client.py +201 -0
- services/output_filter.py +488 -0
- services/parser.py +1238 -0
- services/project_manager.py +579 -0
- services/protocol.py +306 -0
- services/proxy_state.py +152 -0
- services/retrieval_broker.py +129 -0
- services/router.py +414 -0
- services/runtime.py +326 -0
- services/session_benchmark.py +1945 -0
- services/session_manager.py +1026 -0
- services/session_preloader.py +251 -0
- services/text_index.py +90 -0
- services/tool_classifier.py +176 -0
- services/transcript_index.py +340 -0
- services/validation_cache.py +155 -0
- services/vector_store.py +299 -0
- services/version_tracker.py +271 -0
- services/watcher.py +192 -0
- tui/__init__.py +0 -0
- tui/backend.py +59 -0
- tui/main.py +145 -0
- tui/screens/__init__.py +1 -0
- tui/screens/benchmark_view.py +109 -0
- tui/screens/claudemd_view.py +46 -0
- tui/screens/compress_view.py +52 -0
- tui/screens/index_view.py +74 -0
- tui/screens/init_view.py +82 -0
- tui/screens/mcp_view.py +73 -0
- tui/screens/optimize_view.py +41 -0
- tui/screens/pipe_view.py +46 -0
- tui/screens/projects_view.py +355 -0
- tui/screens/search_view.py +55 -0
- tui/screens/session_view.py +143 -0
- tui/screens/stats.py +158 -0
- tui/screens/ui_view.py +54 -0
- tui/theme.tcss +335 -0
services/activity_log.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""ActivityLog — Append-only JSONL activity log for C3 events."""
|
|
2
|
+
import json
|
|
3
|
+
from collections import Counter, deque
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ActivityLog:
|
|
9
|
+
"""Persistent activity log stored as .c3/activity_log.jsonl."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, project_path: str):
|
|
12
|
+
self.log_file = Path(project_path) / ".c3" / "activity_log.jsonl"
|
|
13
|
+
self.log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
|
|
15
|
+
def log(self, event_type: str, data: dict) -> dict:
|
|
16
|
+
"""Append an event. Returns the written entry.
|
|
17
|
+
|
|
18
|
+
event_type: tool_call, decision, file_change, fact_stored,
|
|
19
|
+
session_start, session_save
|
|
20
|
+
"""
|
|
21
|
+
entry = {
|
|
22
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
23
|
+
"type": event_type,
|
|
24
|
+
**data,
|
|
25
|
+
}
|
|
26
|
+
with open(self.log_file, "a", encoding="utf-8") as f:
|
|
27
|
+
f.write(json.dumps(entry) + "\n")
|
|
28
|
+
return entry
|
|
29
|
+
|
|
30
|
+
def get_recent(self, limit: int = 100, event_type: str = None,
|
|
31
|
+
since: str = None, until: str = None) -> list:
|
|
32
|
+
"""Read last N events, optionally filtered by type and time range.
|
|
33
|
+
|
|
34
|
+
since/until: ISO timestamp strings for inclusive time-range filtering.
|
|
35
|
+
"""
|
|
36
|
+
if not self.log_file.exists():
|
|
37
|
+
return []
|
|
38
|
+
events = []
|
|
39
|
+
# When filtering by event_type, rare events (e.g. session_start) may be
|
|
40
|
+
# far back in the log behind many tool_call entries. Use a larger scan
|
|
41
|
+
# window so they aren't missed.
|
|
42
|
+
scan_factor = 100 if event_type else 5
|
|
43
|
+
tail = deque(maxlen=max(1, limit * scan_factor))
|
|
44
|
+
with open(self.log_file, encoding="utf-8") as handle:
|
|
45
|
+
for line in handle:
|
|
46
|
+
if line.strip():
|
|
47
|
+
tail.append(line)
|
|
48
|
+
for line in reversed(tail):
|
|
49
|
+
try:
|
|
50
|
+
entry = json.loads(line)
|
|
51
|
+
except json.JSONDecodeError:
|
|
52
|
+
continue
|
|
53
|
+
if event_type and entry.get("type") != event_type:
|
|
54
|
+
continue
|
|
55
|
+
ts = entry.get("timestamp", "")
|
|
56
|
+
if since and ts < since:
|
|
57
|
+
continue
|
|
58
|
+
if until and ts > until:
|
|
59
|
+
continue
|
|
60
|
+
events.append(entry)
|
|
61
|
+
if len(events) >= limit:
|
|
62
|
+
break
|
|
63
|
+
return events
|
|
64
|
+
|
|
65
|
+
def get_stats(self) -> dict:
|
|
66
|
+
"""Counts by event type, total events, time range."""
|
|
67
|
+
if not self.log_file.exists():
|
|
68
|
+
return {"total": 0, "by_type": {}, "first": None, "last": None}
|
|
69
|
+
counts = Counter()
|
|
70
|
+
first_ts = None
|
|
71
|
+
last_ts = None
|
|
72
|
+
total = 0
|
|
73
|
+
with open(self.log_file, encoding="utf-8") as handle:
|
|
74
|
+
for line in handle:
|
|
75
|
+
if not line.strip():
|
|
76
|
+
continue
|
|
77
|
+
try:
|
|
78
|
+
entry = json.loads(line)
|
|
79
|
+
except json.JSONDecodeError:
|
|
80
|
+
continue
|
|
81
|
+
total += 1
|
|
82
|
+
counts[entry.get("type", "unknown")] += 1
|
|
83
|
+
ts = entry.get("timestamp")
|
|
84
|
+
if ts:
|
|
85
|
+
if first_ts is None:
|
|
86
|
+
first_ts = ts
|
|
87
|
+
last_ts = ts
|
|
88
|
+
return {
|
|
89
|
+
"total": total,
|
|
90
|
+
"by_type": dict(counts),
|
|
91
|
+
"first": first_ts,
|
|
92
|
+
"last": last_ts,
|
|
93
|
+
}
|
services/agent_base.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""BackgroundAgent — Base class for autonomous daemon threads.
|
|
2
|
+
|
|
3
|
+
Agents run periodic checks and surface findings via NotificationStore.
|
|
4
|
+
They cannot invoke Claude, but prepend warnings to the next tool response.
|
|
5
|
+
|
|
6
|
+
Each agent supports optional AI enhancement via OllamaClient. When AI is
|
|
7
|
+
unavailable or disabled, agents fall back to heuristic logic.
|
|
8
|
+
"""
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BackgroundAgent(ABC):
|
|
15
|
+
"""Base class for background analysis agents."""
|
|
16
|
+
|
|
17
|
+
_IDLE_BACKOFF_MAX = 4
|
|
18
|
+
|
|
19
|
+
def __init__(self, name: str, interval: int, notifications, enabled: bool = True,
|
|
20
|
+
use_ai: bool = False, ollama=None, ai_model: str = "gemma3n:latest"):
|
|
21
|
+
self.name = name
|
|
22
|
+
self.interval = interval
|
|
23
|
+
self.notifications = notifications
|
|
24
|
+
self.enabled = enabled
|
|
25
|
+
self.use_ai = use_ai
|
|
26
|
+
self.ollama = ollama
|
|
27
|
+
self.ai_model = ai_model
|
|
28
|
+
self._stop_event = threading.Event()
|
|
29
|
+
self._thread = None
|
|
30
|
+
self._last_check_time = 0.0
|
|
31
|
+
self._check_count = 0
|
|
32
|
+
self._error_count = 0
|
|
33
|
+
self._idle_multiplier = 1
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def ai_available(self) -> bool:
|
|
37
|
+
"""True only if AI is enabled, client exists, and Ollama is reachable."""
|
|
38
|
+
return bool(self.use_ai and self.ollama and self.ollama.is_available())
|
|
39
|
+
|
|
40
|
+
def _ai_generate(self, prompt: str, system: str = "", max_tokens: int = 256) -> str | None:
|
|
41
|
+
"""Safe wrapper around ollama.generate. Returns None on any failure."""
|
|
42
|
+
if not self.ollama:
|
|
43
|
+
return None
|
|
44
|
+
try:
|
|
45
|
+
return self.ollama.generate(prompt, model=self.ai_model,
|
|
46
|
+
system=system, max_tokens=max_tokens)
|
|
47
|
+
except Exception:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def start(self):
|
|
51
|
+
"""Launch daemon thread."""
|
|
52
|
+
if not self.enabled:
|
|
53
|
+
return
|
|
54
|
+
if self._thread and self._thread.is_alive():
|
|
55
|
+
return
|
|
56
|
+
self._stop_event.clear()
|
|
57
|
+
self._thread = threading.Thread(target=self._loop, name=f"c3-agent-{self.name}", daemon=True)
|
|
58
|
+
self._thread.start()
|
|
59
|
+
|
|
60
|
+
def stop(self):
|
|
61
|
+
"""Signal stop and join with timeout."""
|
|
62
|
+
self._stop_event.set()
|
|
63
|
+
if self._thread and self._thread.is_alive():
|
|
64
|
+
self._thread.join(timeout=3)
|
|
65
|
+
|
|
66
|
+
def _loop(self):
|
|
67
|
+
"""Main loop: initial delay, then check() every interval.
|
|
68
|
+
|
|
69
|
+
Idle backoff: subclasses may return ``False`` from ``check()`` to signal
|
|
70
|
+
"no work this cycle". The next sleep is multiplied up to
|
|
71
|
+
``_IDLE_BACKOFF_MAX``. Any other return value (including ``None``)
|
|
72
|
+
resets to the base interval, preserving legacy behavior.
|
|
73
|
+
"""
|
|
74
|
+
if self._stop_event.wait(timeout=5):
|
|
75
|
+
return
|
|
76
|
+
while not self._stop_event.is_set():
|
|
77
|
+
result = None
|
|
78
|
+
try:
|
|
79
|
+
self._last_check_time = time.time()
|
|
80
|
+
self._check_count += 1
|
|
81
|
+
result = self.check()
|
|
82
|
+
except Exception:
|
|
83
|
+
self._error_count += 1
|
|
84
|
+
if result is False:
|
|
85
|
+
self._idle_multiplier = min(self._idle_multiplier * 2, self._IDLE_BACKOFF_MAX)
|
|
86
|
+
else:
|
|
87
|
+
self._idle_multiplier = 1
|
|
88
|
+
if self._stop_event.wait(timeout=self.interval * self._idle_multiplier):
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
def check(self):
|
|
93
|
+
"""Override in subclasses to perform periodic analysis."""
|
|
94
|
+
|
|
95
|
+
def notify(self, severity: str, title: str, message: str, ai_enhanced: bool = False,
|
|
96
|
+
replace_if_unacked: bool = False):
|
|
97
|
+
"""Convenience wrapper for notifications.add()."""
|
|
98
|
+
self.notifications.add(self.name, severity, title, message,
|
|
99
|
+
ai_enhanced=ai_enhanced, replace_if_unacked=replace_if_unacked)
|
|
100
|
+
|
|
101
|
+
def get_status(self) -> dict:
|
|
102
|
+
"""Return agent runtime status for UI/API consumption."""
|
|
103
|
+
return {
|
|
104
|
+
"name": self.name,
|
|
105
|
+
"enabled": self.enabled,
|
|
106
|
+
"running": self._thread is not None and self._thread.is_alive() if self._thread else False,
|
|
107
|
+
"interval": self.interval,
|
|
108
|
+
"use_ai": self.use_ai,
|
|
109
|
+
"ai_available": self.ai_available,
|
|
110
|
+
"check_count": self._check_count,
|
|
111
|
+
"error_count": self._error_count,
|
|
112
|
+
"last_check": self._last_check_time,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
def run_once(self) -> dict:
|
|
116
|
+
"""Execute a single check immediately for manual UI/API triggers."""
|
|
117
|
+
self._last_check_time = time.time()
|
|
118
|
+
self._check_count += 1
|
|
119
|
+
try:
|
|
120
|
+
self.check()
|
|
121
|
+
return {"ok": True, "status": self.get_status()}
|
|
122
|
+
except Exception as e:
|
|
123
|
+
self._error_count += 1
|
|
124
|
+
return {"ok": False, "error": str(e), "status": self.get_status()}
|