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,190 @@
1
+ """Stop hook: detect verbose responses and nudge user to activate /terse.
2
+
3
+ Triggered by the Claude Code 'Stop' event (fires after each response turn).
4
+ Reads the transcript to measure the last assistant message's character count.
5
+ If verbose and /terse not already active, prints a one-time nudge per session.
6
+
7
+ State file: ~/.c3/terse_advisor.json
8
+ {
9
+ "dismissed": false, -- permanent silence
10
+ "remind_after": null, -- ISO timestamp; snooze until then
11
+ "last_nudge_session": null -- session_id of last nudged session
12
+ }
13
+
14
+ Silence forever : c3 terse dismiss
15
+ Snooze 24h : c3 terse later
16
+ Reset state : c3 terse reset
17
+ """
18
+ import json
19
+ import sys
20
+ from datetime import datetime, timezone
21
+ from pathlib import Path
22
+
23
+ sys.path.insert(0, str(Path(__file__).parent.parent))
24
+
25
+ from cli._hook_utils import log_hook_error # noqa: E402
26
+
27
+ _STATE_FILE = Path.home() / ".c3" / "terse_advisor.json"
28
+ _VERBOSE_THRESHOLD = 600 # chars of assistant text that counts as "verbose"
29
+ _SCAN_ENTRIES = 60 # how many recent transcript entries to scan
30
+
31
+
32
+ def _load_state() -> dict:
33
+ if _STATE_FILE.exists():
34
+ try:
35
+ return json.loads(_STATE_FILE.read_text("utf-8"))
36
+ except Exception:
37
+ pass
38
+ return {"dismissed": False, "remind_after": None, "last_nudge_session": None}
39
+
40
+
41
+ def _save_state(state: dict) -> None:
42
+ _STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
43
+ _STATE_FILE.write_text(json.dumps(state, indent=2), "utf-8")
44
+
45
+
46
+ def _extract_text(content) -> str:
47
+ """Extract plain text from a message content field (str or list of blocks)."""
48
+ if isinstance(content, str):
49
+ return content
50
+ if isinstance(content, list):
51
+ parts = []
52
+ for block in content:
53
+ if isinstance(block, dict) and block.get("type") == "text":
54
+ parts.append(block.get("text", ""))
55
+ elif isinstance(block, str):
56
+ parts.append(block)
57
+ return " ".join(parts)
58
+ return ""
59
+
60
+
61
+ def _read_tail(path: str, n: int) -> list:
62
+ """Return the last *n* non-empty JSONL entries from *path*."""
63
+ entries = []
64
+ try:
65
+ with open(path, encoding="utf-8", errors="replace") as f:
66
+ for line in f:
67
+ line = line.strip()
68
+ if not line:
69
+ continue
70
+ try:
71
+ entries.append(json.loads(line))
72
+ except Exception:
73
+ pass
74
+ except Exception:
75
+ pass
76
+ return entries[-n:]
77
+
78
+
79
+ def _last_assistant_char_count(entries: list) -> int:
80
+ """Return char count of the last assistant message text, or 0."""
81
+ for entry in reversed(entries):
82
+ entry_type = entry.get("type", "")
83
+ if entry_type in ("progress", "file-history-snapshot", "system"):
84
+ continue
85
+ role = entry.get("role", "")
86
+ msg = entry.get("message", {})
87
+ if isinstance(msg, dict):
88
+ role = role or msg.get("role", "")
89
+ content = msg.get("content", "")
90
+ else:
91
+ content = entry.get("content", "")
92
+
93
+ if role == "assistant":
94
+ return len(_extract_text(content))
95
+ return 0
96
+
97
+
98
+ def _terse_active(entries: list) -> bool:
99
+ """Return True if /terse was recently activated in this transcript."""
100
+ for entry in reversed(entries):
101
+ entry_type = entry.get("type", "")
102
+
103
+ # Slash-command invocation entries
104
+ if entry_type in ("command", "slash_command"):
105
+ cmd = entry.get("command", "") or entry.get("command-name", "") or ""
106
+ if "terse" in cmd.lower():
107
+ return True
108
+
109
+ role = entry.get("role", "")
110
+ msg = entry.get("message", {})
111
+ if isinstance(msg, dict):
112
+ role = role or msg.get("role", "")
113
+ content = msg.get("content", "")
114
+ else:
115
+ content = entry.get("content", "")
116
+
117
+ if role == "user":
118
+ text = _extract_text(content).lower()
119
+ # Detect /terse invocation or skill expansion markers
120
+ if "/terse" in text or "terse output mode" in text:
121
+ return True
122
+
123
+ return False
124
+
125
+
126
+ def main() -> None:
127
+ try:
128
+ data = json.load(sys.stdin)
129
+ except Exception as exc:
130
+ log_hook_error("hook_terse_advisor", exc)
131
+ sys.exit(0)
132
+
133
+ try:
134
+ state = _load_state()
135
+
136
+ # Permanent dismiss
137
+ if state.get("dismissed"):
138
+ sys.exit(0)
139
+
140
+ # Snooze check
141
+ remind_after = state.get("remind_after")
142
+ if remind_after:
143
+ try:
144
+ if datetime.now(timezone.utc) < datetime.fromisoformat(remind_after):
145
+ sys.exit(0)
146
+ except Exception:
147
+ pass
148
+
149
+ session_id = data.get("session_id", "")
150
+ transcript_path = data.get("transcript_path", "")
151
+ if not transcript_path:
152
+ sys.exit(0)
153
+
154
+ # Only nudge once per session
155
+ if session_id and state.get("last_nudge_session") == session_id:
156
+ sys.exit(0)
157
+
158
+ entries = _read_tail(transcript_path, _SCAN_ENTRIES)
159
+ if not entries:
160
+ sys.exit(0)
161
+
162
+ # Skip if terse already active
163
+ if _terse_active(entries):
164
+ sys.exit(0)
165
+
166
+ # Check verbosity
167
+ char_count = _last_assistant_char_count(entries)
168
+ if char_count < _VERBOSE_THRESHOLD:
169
+ sys.exit(0)
170
+
171
+ # Nudge
172
+ bar = "─" * 52
173
+ print(f"\n{bar}")
174
+ print(f"[C3] Verbose response (~{char_count} chars). /terse saves ~50% output tokens.")
175
+ print(" Type /terse to activate.")
176
+ print(" Silence: c3 terse dismiss | Snooze 24h: c3 terse later")
177
+ print(bar)
178
+
179
+ # Update state
180
+ state["last_nudge_session"] = session_id
181
+ _save_state(state)
182
+
183
+ except Exception as exc:
184
+ log_hook_error("hook_terse_advisor", exc)
185
+
186
+ sys.exit(0)
187
+
188
+
189
+ if __name__ == "__main__":
190
+ main()