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
|
@@ -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()
|