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.
Files changed (150) hide show
  1. cli/__init__.py +1 -0
  2. cli/_hook_utils.py +99 -0
  3. cli/c3.py +6152 -0
  4. cli/commands/__init__.py +1 -0
  5. cli/commands/common.py +312 -0
  6. cli/commands/parser.py +286 -0
  7. cli/docs.html +3178 -0
  8. cli/edits.html +878 -0
  9. cli/hook_auto_snapshot.py +142 -0
  10. cli/hook_c3_signal.py +61 -0
  11. cli/hook_c3read.py +116 -0
  12. cli/hook_edit_ledger.py +213 -0
  13. cli/hook_edit_unlock.py +170 -0
  14. cli/hook_filter.py +130 -0
  15. cli/hook_ghost_files.py +238 -0
  16. cli/hook_pretool_enforce.py +334 -0
  17. cli/hook_read.py +200 -0
  18. cli/hook_session_stats.py +62 -0
  19. cli/hook_terse_advisor.py +190 -0
  20. cli/hub.html +3764 -0
  21. cli/hub_server.py +1619 -0
  22. cli/mcp_proxy.py +428 -0
  23. cli/mcp_server.py +660 -0
  24. cli/server.py +2985 -0
  25. cli/tools/__init__.py +4 -0
  26. cli/tools/_helpers.py +65 -0
  27. cli/tools/agent.py +1165 -0
  28. cli/tools/compress.py +215 -0
  29. cli/tools/delegate.py +1184 -0
  30. cli/tools/edit.py +313 -0
  31. cli/tools/edits.py +118 -0
  32. cli/tools/filter.py +285 -0
  33. cli/tools/impact.py +163 -0
  34. cli/tools/memory.py +469 -0
  35. cli/tools/read.py +224 -0
  36. cli/tools/search.py +337 -0
  37. cli/tools/session.py +95 -0
  38. cli/tools/shell.py +193 -0
  39. cli/tools/status.py +306 -0
  40. cli/tools/validate.py +310 -0
  41. cli/ui/api.js +36 -0
  42. cli/ui/app.js +207 -0
  43. cli/ui/components/chat.js +758 -0
  44. cli/ui/components/dashboard.js +689 -0
  45. cli/ui/components/edits.js +220 -0
  46. cli/ui/components/instructions.js +481 -0
  47. cli/ui/components/memory.js +626 -0
  48. cli/ui/components/sessions.js +606 -0
  49. cli/ui/components/settings.js +1404 -0
  50. cli/ui/components/sidebar.js +156 -0
  51. cli/ui/icons.js +51 -0
  52. cli/ui/shared.js +119 -0
  53. cli/ui/theme.js +22 -0
  54. cli/ui.html +168 -0
  55. cli/ui_legacy.html +6797 -0
  56. cli/ui_nano.html +503 -0
  57. code_context_control-2.28.0.dist-info/METADATA +248 -0
  58. code_context_control-2.28.0.dist-info/RECORD +150 -0
  59. code_context_control-2.28.0.dist-info/WHEEL +5 -0
  60. code_context_control-2.28.0.dist-info/entry_points.txt +4 -0
  61. code_context_control-2.28.0.dist-info/licenses/LICENSE +201 -0
  62. code_context_control-2.28.0.dist-info/top_level.txt +5 -0
  63. core/__init__.py +75 -0
  64. core/config.py +269 -0
  65. core/ide.py +188 -0
  66. oracle/__init__.py +1 -0
  67. oracle/config.py +75 -0
  68. oracle/oracle.html +3900 -0
  69. oracle/oracle_server.py +663 -0
  70. oracle/services/__init__.py +1 -0
  71. oracle/services/c3_bridge.py +210 -0
  72. oracle/services/chat_engine.py +1103 -0
  73. oracle/services/chat_store.py +155 -0
  74. oracle/services/cross_memory.py +154 -0
  75. oracle/services/federated_graph.py +463 -0
  76. oracle/services/health_checker.py +117 -0
  77. oracle/services/insight_engine.py +307 -0
  78. oracle/services/memory_reader.py +106 -0
  79. oracle/services/memory_writer.py +182 -0
  80. oracle/services/ollama_bridge.py +332 -0
  81. oracle/services/project_scanner.py +87 -0
  82. oracle/services/review_agent.py +206 -0
  83. services/__init__.py +1 -0
  84. services/activity_log.py +93 -0
  85. services/agent_base.py +124 -0
  86. services/agents.py +1529 -0
  87. services/auto_memory.py +407 -0
  88. services/bench/__init__.py +6 -0
  89. services/bench/external/__init__.py +29 -0
  90. services/bench/external/aider_polyglot.py +405 -0
  91. services/bench/external/swe_bench.py +485 -0
  92. services/benchmark_dashboard.py +596 -0
  93. services/claude_md.py +785 -0
  94. services/compressor.py +592 -0
  95. services/context_snapshot.py +356 -0
  96. services/conversation_store.py +870 -0
  97. services/doc_index.py +537 -0
  98. services/e2e_benchmark.py +2884 -0
  99. services/e2e_evaluator.py +396 -0
  100. services/e2e_tasks.py +743 -0
  101. services/edit_ledger.py +459 -0
  102. services/embedding_index.py +341 -0
  103. services/error_reporting.py +123 -0
  104. services/file_memory.py +734 -0
  105. services/hub_service.py +585 -0
  106. services/indexer.py +712 -0
  107. services/memory.py +318 -0
  108. services/memory_consolidator.py +538 -0
  109. services/memory_graph.py +382 -0
  110. services/memory_grounder.py +304 -0
  111. services/memory_scorer.py +246 -0
  112. services/metrics.py +86 -0
  113. services/notifications.py +209 -0
  114. services/ollama_client.py +201 -0
  115. services/output_filter.py +488 -0
  116. services/parser.py +1238 -0
  117. services/project_manager.py +579 -0
  118. services/protocol.py +306 -0
  119. services/proxy_state.py +152 -0
  120. services/retrieval_broker.py +129 -0
  121. services/router.py +414 -0
  122. services/runtime.py +326 -0
  123. services/session_benchmark.py +1945 -0
  124. services/session_manager.py +1026 -0
  125. services/session_preloader.py +251 -0
  126. services/text_index.py +90 -0
  127. services/tool_classifier.py +176 -0
  128. services/transcript_index.py +340 -0
  129. services/validation_cache.py +155 -0
  130. services/vector_store.py +299 -0
  131. services/version_tracker.py +271 -0
  132. services/watcher.py +192 -0
  133. tui/__init__.py +0 -0
  134. tui/backend.py +59 -0
  135. tui/main.py +145 -0
  136. tui/screens/__init__.py +1 -0
  137. tui/screens/benchmark_view.py +109 -0
  138. tui/screens/claudemd_view.py +46 -0
  139. tui/screens/compress_view.py +52 -0
  140. tui/screens/index_view.py +74 -0
  141. tui/screens/init_view.py +82 -0
  142. tui/screens/mcp_view.py +73 -0
  143. tui/screens/optimize_view.py +41 -0
  144. tui/screens/pipe_view.py +46 -0
  145. tui/screens/projects_view.py +355 -0
  146. tui/screens/search_view.py +55 -0
  147. tui/screens/session_view.py +143 -0
  148. tui/screens/stats.py +158 -0
  149. tui/screens/ui_view.py +54 -0
  150. tui/theme.tcss +335 -0
@@ -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()}