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 @@
1
+ """Command modules for the C3 CLI."""
cli/commands/common.py ADDED
@@ -0,0 +1,312 @@
1
+ """Shared lightweight CLI command handlers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ import json
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+
10
+
11
+ @dataclass
12
+ class CommandDeps:
13
+ load_config: object
14
+ print_header: object
15
+ print_savings: object
16
+ count_tokens: object
17
+ format_token_count: object
18
+ CodeIndex: object
19
+ CodeCompressor: object
20
+ CompressionProtocol: object
21
+ SessionManager: object
22
+ HAS_RICH: bool
23
+ Table: object
24
+ console: object
25
+ __file__: str
26
+
27
+
28
+ def cmd_index(args, deps: CommandDeps):
29
+ """Rebuild the code index."""
30
+ config = deps.load_config()
31
+ project_path = config.get("project_path", ".")
32
+
33
+ deps.print_header("Rebuilding Code Index")
34
+ indexer = deps.CodeIndex(project_path)
35
+ result = indexer.build_index(max_files=args.max_files or 500)
36
+
37
+ if deps.HAS_RICH:
38
+ table = deps.Table(title="Index Results")
39
+ table.add_column("Metric", style="cyan")
40
+ table.add_column("Value", style="green")
41
+ table.add_row("Files Indexed", str(result["files_indexed"]))
42
+ table.add_row("Chunks Created", str(result["chunks_created"]))
43
+ table.add_row("Unique Symbols", str(result["unique_symbols"]))
44
+ deps.console.print(table)
45
+ else:
46
+ print(f" Files: {result['files_indexed']}, Chunks: {result['chunks_created']}, Symbols: {result['unique_symbols']}")
47
+
48
+
49
+ def cmd_compress(args, deps: CommandDeps):
50
+ """Compress a file and show results."""
51
+ config = deps.load_config()
52
+ mode = args.mode or "smart"
53
+
54
+ compressor = deps.CodeCompressor(str(Path(config.get("project_path", ".")) / ".c3/cache"))
55
+ result = compressor.compress_file(args.file, mode)
56
+
57
+ if "error" in result:
58
+ print(f"Error: {result['error']}")
59
+ return
60
+
61
+ deps.print_header(f"Compressed: {args.file} (mode: {result.get('mode', mode)})")
62
+ deps.print_savings(result)
63
+
64
+ if args.output:
65
+ print(f"\n--- Compressed Output ---\n{result['compressed']}")
66
+
67
+
68
+ def cmd_context(args, deps: CommandDeps):
69
+ """Get relevant context for a query."""
70
+ config = deps.load_config()
71
+ project_path = config.get("project_path", ".")
72
+
73
+ indexer = deps.CodeIndex(project_path)
74
+ context = indexer.get_context(
75
+ args.query,
76
+ top_k=args.top_k or 5,
77
+ max_tokens=args.max_tokens or 4000,
78
+ )
79
+
80
+ if args.pipe:
81
+ print(context)
82
+ else:
83
+ deps.print_header(f"Context for: {args.query}")
84
+ tokens = deps.count_tokens(context)
85
+ print(f" Context tokens: {deps.format_token_count(tokens)}")
86
+ print(f"\n{context}")
87
+
88
+
89
+ def cmd_encode(args, deps: CommandDeps):
90
+ """Encode text to compressed format."""
91
+ config = deps.load_config()
92
+ protocol = deps.CompressionProtocol(config.get("project_path", "."))
93
+
94
+ text = " ".join(args.text)
95
+ result = protocol.encode(text)
96
+
97
+ if args.pipe:
98
+ print(result["compressed"])
99
+ else:
100
+ deps.print_header("Compression Protocol - Encode")
101
+ print(f" Original: {result['original']}")
102
+ print(f" Compressed: {result['compressed']}")
103
+ deps.print_savings(result)
104
+
105
+
106
+ def cmd_decode(args, deps: CommandDeps):
107
+ """Decode compressed text back to readable format."""
108
+ config = deps.load_config()
109
+ protocol = deps.CompressionProtocol(config.get("project_path", "."))
110
+
111
+ text = " ".join(args.text)
112
+ decoded = protocol.decode(text)
113
+ print(decoded)
114
+
115
+
116
+ def cmd_session(args, deps: CommandDeps):
117
+ """Session management commands."""
118
+ config = deps.load_config()
119
+ sm = deps.SessionManager(config.get("project_path", "."))
120
+
121
+ if args.session_cmd == "start":
122
+ desc = " ".join(args.extra) if args.extra else ""
123
+ result = sm.start_session(desc)
124
+ print(f"Session started: {result['session_id']}")
125
+
126
+ elif args.session_cmd == "save":
127
+ summary = " ".join(args.extra) if args.extra else ""
128
+ result = sm.save_session(summary)
129
+ if "error" in result:
130
+ print(f"Error: {result['error']}")
131
+ else:
132
+ print(f"Session {result['session_id']} saved ({result['decisions']} decisions, {result['files']} files)")
133
+
134
+ elif args.session_cmd == "load":
135
+ session_id = args.extra[0] if args.extra else "latest"
136
+ session = sm.load_session(session_id)
137
+ if "error" in session:
138
+ print(f"Error: {session['error']}")
139
+ else:
140
+ print(json.dumps(session, indent=2))
141
+
142
+ elif args.session_cmd == "list":
143
+ sessions = sm.list_sessions()
144
+ if deps.HAS_RICH:
145
+ table = deps.Table(title="Session History")
146
+ table.add_column("ID", style="cyan")
147
+ table.add_column("Date", style="green")
148
+ table.add_column("Summary", style="white")
149
+ table.add_column("Decisions", style="yellow")
150
+ for session in sessions:
151
+ table.add_row(session["id"], session["started"], session["summary"][:60], str(session["decisions"]))
152
+ deps.console.print(table)
153
+ else:
154
+ for session in sessions:
155
+ print(f" {session['id']} | {session['started']} | {session['summary'][:50]} | {session['decisions']} decisions")
156
+
157
+ elif args.session_cmd == "context":
158
+ context = sm.get_session_context()
159
+ print(context)
160
+
161
+
162
+ def cmd_claudemd(args, deps: CommandDeps):
163
+ """Instructions file generation commands."""
164
+ config = deps.load_config()
165
+ project_path = config.get("project_path", ".")
166
+ sm = deps.SessionManager(project_path)
167
+
168
+ from core.ide import get_profile as _get_profile
169
+ from core.ide import load_ide_config
170
+ from services.claude_md import ClaudeMdManager
171
+ from services.memory import MemoryStore
172
+
173
+ ide_name = load_ide_config(project_path)
174
+ profile = _get_profile(ide_name)
175
+ instructions_file = profile.instructions_file or "CLAUDE.md"
176
+
177
+ if args.claudemd_cmd in ("generate", "save"):
178
+ indexer = deps.CodeIndex(project_path)
179
+ indexer._load_index()
180
+ memory = MemoryStore(project_path)
181
+ claude_md = ClaudeMdManager(
182
+ project_path,
183
+ sm,
184
+ indexer,
185
+ memory,
186
+ instructions_file=instructions_file,
187
+ line_limit=profile.instructions_line_limit or 200,
188
+ supports_hooks=profile.supports_hooks,
189
+ supports_clear=profile.supports_clear,
190
+ )
191
+
192
+ mode = "nano" if getattr(args, "nano", False) else "compact"
193
+ gen = claude_md.generate(mode=mode)
194
+ content = gen.get("content", "")
195
+ tokens = gen.get("tokens", 0)
196
+ mode_label = " [nano]" if mode == "nano" else ""
197
+
198
+ if args.claudemd_cmd == "generate":
199
+ deps.print_header(f"Generated {instructions_file}{mode_label} ({deps.format_token_count(tokens)} tokens)")
200
+ print(content)
201
+ else:
202
+ output_path = Path(project_path) / instructions_file
203
+ output_path.parent.mkdir(parents=True, exist_ok=True)
204
+ if output_path.exists():
205
+ existing = output_path.read_text(encoding="utf-8", errors="replace")
206
+ if "# User Notes" in existing:
207
+ user_section = existing[existing.index("# User Notes"):]
208
+ content += f"\n\n{user_section}"
209
+ output_path.write_text(content, encoding="utf-8")
210
+ print(f"{instructions_file} saved to {output_path} ({tokens} tokens)")
211
+
212
+ elif args.claudemd_cmd == "check":
213
+ indexer = deps.CodeIndex(project_path)
214
+ indexer._load_index()
215
+ memory = MemoryStore(project_path)
216
+ claude_md = ClaudeMdManager(
217
+ project_path,
218
+ sm,
219
+ indexer,
220
+ memory,
221
+ instructions_file=instructions_file,
222
+ line_limit=profile.instructions_line_limit or 200,
223
+ supports_hooks=profile.supports_hooks,
224
+ supports_clear=profile.supports_clear,
225
+ )
226
+ result = claude_md.check_staleness()
227
+ deps.print_header(f"{instructions_file} Health - {result['status'].upper()}")
228
+ if "lines" in result:
229
+ print(f" Size: {result['lines']} lines, {result['tokens']} tokens")
230
+ for issue in result.get("issues", []):
231
+ icon = {"error": "[ERROR]", "warning": "[WARN]", "info": "[INFO]"}.get(issue["severity"], "[?]")
232
+ print(f" {icon} {issue['message']}")
233
+
234
+
235
+ def cmd_stats(args, deps: CommandDeps):
236
+ """Show comprehensive stats."""
237
+ config = deps.load_config()
238
+ project_path = config.get("project_path", ".")
239
+
240
+ deps.print_header("C3 Statistics")
241
+ indexer = deps.CodeIndex(project_path)
242
+ idx_stats = indexer.get_stats()
243
+ protocol = deps.CompressionProtocol(project_path)
244
+ proto_stats = protocol.get_stats()
245
+
246
+ if deps.HAS_RICH:
247
+ table = deps.Table(title="System Overview")
248
+ table.add_column("Component", style="cyan")
249
+ table.add_column("Metric", style="white")
250
+ table.add_column("Value", style="green")
251
+
252
+ table.add_row("Index", "Files Indexed", str(idx_stats.get("files_indexed", 0)))
253
+ table.add_row("Index", "Total Chunks", str(idx_stats.get("total_chunks", 0)))
254
+ table.add_row("Index", "Codebase Tokens", deps.format_token_count(idx_stats.get("total_tokens_in_codebase", 0)))
255
+ table.add_row("Index", "Index Size", f"{idx_stats.get('index_size_kb', 0)} KB")
256
+ table.add_row("Protocol", "Built-in Codes", str(proto_stats.get("built_in_actions", 0) + proto_stats.get("built_in_terms", 0)))
257
+ table.add_row("Protocol", "Custom Terms", str(proto_stats.get("custom_terms", 0)))
258
+
259
+ deps.console.print(table)
260
+ else:
261
+ print(f" Index: {idx_stats.get('files_indexed', 0)} files, {idx_stats.get('total_chunks', 0)} chunks")
262
+ print(f" Codebase: {deps.format_token_count(idx_stats.get('total_tokens_in_codebase', 0))} tokens")
263
+ print(f" Protocol: {proto_stats.get('total_codes', 0)} compression codes")
264
+
265
+
266
+ def cmd_optimize(args, deps: CommandDeps):
267
+ """Show optimization suggestions."""
268
+ config = deps.load_config()
269
+ sm = deps.SessionManager(config.get("project_path", "."))
270
+
271
+ deps.print_header("Optimization Suggestions")
272
+ suggestions = sm.get_optimization_suggestions()
273
+ for i, suggestion in enumerate(suggestions, 1):
274
+ print(f" {i}. {suggestion}")
275
+
276
+
277
+ def cmd_pipe(args, deps: CommandDeps):
278
+ """All-in-one pipeline: get context + output for piping to Claude."""
279
+ config = deps.load_config()
280
+ project_path = config.get("project_path", ".")
281
+ query = " ".join(args.query)
282
+
283
+ indexer = deps.CodeIndex(project_path)
284
+ context = indexer.get_context(query, top_k=args.top_k or 5, max_tokens=args.max_tokens or 4000)
285
+
286
+ sm = deps.SessionManager(project_path)
287
+ session_context = sm.get_session_context(n_sessions=2)
288
+
289
+ output_parts = []
290
+ if session_context and "No previous" not in session_context:
291
+ output_parts.append(session_context)
292
+
293
+ output_parts.append(context)
294
+ output_parts.append(f"\n## Task\n{query}")
295
+ print("\n\n".join(output_parts))
296
+
297
+
298
+ def cmd_ui(args, deps: CommandDeps):
299
+ """Launch the web UI."""
300
+ server_path = Path(deps.__file__).parent / "server.py"
301
+ spec = importlib.util.spec_from_file_location("server", str(server_path))
302
+ server = importlib.util.module_from_spec(spec)
303
+ spec.loader.exec_module(server)
304
+
305
+ project_path = args.project_path or "."
306
+ server.run_server(
307
+ project_path,
308
+ port=args.port,
309
+ open_browser=not args.no_browser,
310
+ silent=args.silent,
311
+ nano=args.nano,
312
+ )
cli/commands/parser.py ADDED
@@ -0,0 +1,286 @@
1
+ """Argument parser construction for the C3 CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+
8
+ def build_parser(version: str, parse_cli_ide_arg):
9
+ parser = argparse.ArgumentParser(
10
+ prog="c3",
11
+ description="Claude Code Companion - Reduce token usage with local intelligence",
12
+ )
13
+ parser.add_argument("--version", "-v", action="version", version=f"c3 version {version}")
14
+ subparsers = parser.add_subparsers(dest="command")
15
+
16
+ p_init = subparsers.add_parser("init", help="Initialize C3 for a project")
17
+ p_init.add_argument("project_path", nargs="?", default=".")
18
+ p_init.add_argument("--force", action="store_true", help="Skip prompts and apply update non-interactively")
19
+ p_init.add_argument("--clear", action="store_true", help="Remove all C3 files and exit without rebuilding")
20
+ p_init.add_argument("--ide", default="auto", type=parse_cli_ide_arg, metavar="{auto,claude,vscode,cursor,codex,gemini,antigravity}", help="Target IDE for MCP config (default: auto-detect)")
21
+ p_init.add_argument("--mcp-mode", choices=["direct", "proxy"], default="direct", help="Default MCP mode if install is selected during init (default: direct)")
22
+ p_init.add_argument("--git", action="store_true", help="Initialize a local Git repository during init/update")
23
+ p_init.add_argument("--permissions", choices=["read-only", "c3-strict", "standard", "permissive"], default=None, help="Apply Claude Code permission tier (Claude Code only, used with --force)")
24
+ p_init.add_argument("--include-mcp-wildcard", action="store_true", help="Add mcp__* wildcard so non-C3 MCP servers don't prompt per-call")
25
+
26
+ p_index = subparsers.add_parser("index", help="Rebuild code index")
27
+ p_index.add_argument("--max-files", type=int, default=500)
28
+
29
+ p_compress = subparsers.add_parser("compress", help="Compress a file")
30
+ p_compress.add_argument("file", help="File to compress")
31
+ p_compress.add_argument("--mode", choices=["map", "dense_map", "smart", "diff"], default="smart")
32
+ p_compress.add_argument("--output", "-o", action="store_true", help="Show compressed output")
33
+
34
+ p_context = subparsers.add_parser("context", help="Get relevant context for a query")
35
+ p_context.add_argument("query", help="What you want to do")
36
+ p_context.add_argument("--top-k", type=int, default=5)
37
+ p_context.add_argument("--max-tokens", type=int, default=4000)
38
+ p_context.add_argument("--pipe", action="store_true", help="Raw output for piping")
39
+
40
+ p_encode = subparsers.add_parser("encode", help="Encode text to compressed format")
41
+ p_encode.add_argument("text", nargs="+")
42
+ p_encode.add_argument("--pipe", action="store_true")
43
+
44
+ p_decode = subparsers.add_parser("decode", help="Decode compressed format")
45
+ p_decode.add_argument("text", nargs="+")
46
+
47
+ p_session = subparsers.add_parser("session", help="Session management")
48
+ p_session.add_argument("session_cmd", choices=["start", "save", "load", "list", "context"])
49
+ p_session.add_argument("extra", nargs="*")
50
+
51
+ p_claudemd = subparsers.add_parser("claudemd", help="CLAUDE.md management")
52
+ p_claudemd.add_argument("claudemd_cmd", choices=["generate", "save", "check"])
53
+ p_claudemd.add_argument("--nano", action="store_true", help="Generate nano mode (~250 tokens) instead of full compact mode")
54
+
55
+ subparsers.add_parser("stats", help="Show statistics")
56
+
57
+ p_benchmark = subparsers.add_parser("benchmark", help="Run with/without-C3 workflow benchmark")
58
+ p_benchmark.add_argument("project_path", nargs="?", default=".")
59
+ p_benchmark.add_argument("--sample-size", type=int, default=25, help="Number of files for compression benchmark")
60
+ p_benchmark.add_argument("--min-tokens", type=int, default=200, help="Prefer files with at least this many tokens")
61
+ p_benchmark.add_argument("--top-k", type=int, default=5, help="Top-k files for retrieval benchmarks")
62
+ p_benchmark.add_argument("--max-tokens", type=int, default=4000, help="Max tokens in C3 retrieval context")
63
+ p_benchmark.add_argument("--json", action="store_true", help="Emit JSON report to stdout")
64
+ p_benchmark.add_argument("--output", help="Write JSON report to this path (relative to project)")
65
+ p_benchmark.add_argument("--html-output", help="Write HTML report to this path (relative to project)")
66
+ p_benchmark.add_argument("--no-html", action="store_true", help="Do not generate the HTML benchmark report")
67
+ p_benchmark.add_argument("--system-name", help="System/AI identifier for this benchmark run (e.g. codex, claude, cursor)")
68
+ p_benchmark.add_argument("--system-label", help="Display label for the benchmark system (e.g. OpenAI Codex)")
69
+ p_benchmark.add_argument("--system-version", help="Optional system version/build label for the benchmark output")
70
+
71
+ p_session_bench = subparsers.add_parser("session-benchmark", help="Run real-world session workflow benchmark")
72
+ p_session_bench.add_argument("project_path", nargs="?", default=".")
73
+ p_session_bench.add_argument("--sample-size", type=int, default=15, help="Number of files to sample")
74
+ p_session_bench.add_argument("--min-tokens", type=int, default=200, help="Prefer files with at least this many tokens")
75
+ p_session_bench.add_argument("--json", action="store_true", help="Emit JSON report to stdout")
76
+ p_session_bench.add_argument("--output", help="Write JSON report to this path")
77
+ p_session_bench.add_argument("--html-output", help="Write HTML report to this path")
78
+
79
+ subparsers.add_parser("optimize", help="Show optimization suggestions")
80
+
81
+ p_pipe = subparsers.add_parser("pipe", help="All-in-one pipeline for Claude")
82
+ p_pipe.add_argument("query", nargs="+")
83
+ p_pipe.add_argument("--top-k", type=int, default=5)
84
+ p_pipe.add_argument("--max-tokens", type=int, default=4000)
85
+
86
+ p_install_mcp = subparsers.add_parser("install-mcp", help="Generate MCP config for your IDE")
87
+ p_install_mcp.add_argument("targets", nargs="*", help="Optional project path and/or IDE shorthand (for example: `claude` or `. codex`)")
88
+ p_install_mcp.add_argument("--ide", default="auto", type=parse_cli_ide_arg, metavar="{auto,claude,vscode,cursor,codex,gemini,antigravity}", help="Target IDE (default: auto-detect)")
89
+ p_install_mcp.add_argument("--mcp-mode", choices=["direct", "proxy"], default="direct", help="MCP entrypoint mode (default: direct)")
90
+ p_install_mcp.add_argument("--permissions", choices=["read-only", "c3-strict", "standard", "permissive"], default=None, help="Apply Claude Code permission tier (Claude Code only)")
91
+ p_install_mcp.add_argument("--include-mcp-wildcard", action="store_true", help="Add mcp__* wildcard so non-C3 MCP servers don't prompt per-call")
92
+
93
+ p_mcp_install = subparsers.add_parser("mcp-install", help="Alias for install-mcp")
94
+ p_mcp_install.add_argument("targets", nargs="*", help="Optional project path and/or IDE shorthand")
95
+ p_mcp_install.add_argument("--ide", default="auto", type=parse_cli_ide_arg, metavar="{auto,claude,vscode,cursor,codex,gemini,antigravity}", help="Target IDE (default: auto-detect)")
96
+ p_mcp_install.add_argument("--mcp-mode", choices=["direct", "proxy"], default="direct", help="MCP entrypoint mode (default: direct)")
97
+ p_mcp_install.add_argument("--permissions", choices=["read-only", "c3-strict", "standard", "permissive"], default=None, help="Apply Claude Code permission tier (Claude Code only)")
98
+ p_mcp_install.add_argument("--include-mcp-wildcard", action="store_true", help="Add mcp__* wildcard so non-C3 MCP servers don't prompt per-call")
99
+
100
+ p_mcp_remove = subparsers.add_parser("mcp-remove", help="Remove an MCP server from your IDE config")
101
+ p_mcp_remove.add_argument("name", help="Name of the MCP server to remove (e.g. 'c3')")
102
+ p_mcp_remove.add_argument("project_path", nargs="?", default=".", help="Project path to resolve IDE and config")
103
+ p_mcp_remove.add_argument("--ide", default="auto", type=parse_cli_ide_arg, help="Target IDE (default: auto-detect)")
104
+
105
+ p_ui = subparsers.add_parser("ui", help="Launch the web dashboard")
106
+ p_ui.add_argument("project_path", nargs="?", default=".")
107
+ p_ui.add_argument("--port", type=int, default=3333)
108
+ p_ui.add_argument("--no-browser", action="store_true")
109
+ p_ui.add_argument("--silent", action="store_true", help="Hide API request logs in terminal")
110
+ p_ui.add_argument("--nano", action="store_true", help="Launch minimal mission-control UI")
111
+
112
+ p_hub = subparsers.add_parser("hub", help="Launch the Project Hub web dashboard")
113
+ p_hub.add_argument("--port", type=int, default=3330, help="Port to listen on (default: 3330)")
114
+ p_hub.add_argument("--no-browser", action="store_true", help="Don't open browser automatically")
115
+ p_hub.add_argument("--silent", action="store_true", help="Disable browser auto-open and suppress request logs")
116
+ p_hub.add_argument("--extra-silent", action="store_true", help="Also suppress hub startup banner output")
117
+ p_hub.add_argument("--install", action="store_true", help="Register as a login/startup service")
118
+ p_hub.add_argument("--uninstall", action="store_true", help="Remove startup service registration")
119
+ p_hub.add_argument("--status", action="store_true", help="Show startup service status")
120
+
121
+ p_projects = subparsers.add_parser("projects", help="Manage registered C3 projects (CLI)")
122
+ p_projects.add_argument(
123
+ "projects_cmd",
124
+ nargs="?",
125
+ choices=["list", "add", "remove", "start", "sessions"],
126
+ default="list",
127
+ help="Sub-command (default: list)",
128
+ )
129
+ p_projects.add_argument(
130
+ "project_path",
131
+ nargs="?",
132
+ default=None,
133
+ help="Project path (required for add, remove, start)",
134
+ )
135
+ p_projects.add_argument("--name", default=None, help="Display name (for add)")
136
+
137
+ p_perms = subparsers.add_parser("permissions",
138
+ help="Manage Claude Code permissions — show | preview <tier> | diff | clean | <tier>")
139
+ p_perms.add_argument("tier", nargs="?", default="show",
140
+ help="Action (show/preview/diff/clean) or tier (read-only, c3-strict, standard, permissive). Aliases: strict, unrestricted, readonly.")
141
+ p_perms.add_argument("target", nargs="?", default=None,
142
+ help="Target tier for 'preview' or 'diff' subcommands")
143
+ p_perms.add_argument("--include-mcp-wildcard", action="store_true",
144
+ help="Include mcp__* wildcard so non-C3 MCP servers don't prompt per-call")
145
+ p_perms.add_argument("--project-path", default=".",
146
+ help="Project path (default: current directory)")
147
+
148
+ p_e2e = subparsers.add_parser("benchmark-e2e", help="Run end-to-end AI session benchmark (C3 vs baseline)")
149
+ p_e2e.add_argument("project_path", nargs="?", default=".", help="Project path to benchmark")
150
+
151
+ p_e2e_common = p_e2e.add_argument_group("common options")
152
+ p_e2e_common.add_argument("--providers", default=None, help="Comma-separated: claude,gemini,codex (default: auto-detect)")
153
+ p_e2e_common.add_argument("--models", default=None, help="Model overrides: claude=sonnet,gemini=gemini-2.5-flash,codex=o3")
154
+ p_e2e_common.add_argument("--tasks", default="all", help="Task filter: all (default), or comma-separated categories")
155
+ p_e2e_common.add_argument("--max-tasks", type=int, default=1, help="Max tasks per category (default: 1)")
156
+ p_e2e_common.add_argument("--timeout", type=int, default=120, help="Per-task timeout in seconds (default: 120)")
157
+ p_e2e_common.add_argument("--dry-run", action="store_true", help="Show tasks and providers without running")
158
+ p_e2e_common.add_argument("--verbose", action="store_true", help="Print each result as it completes")
159
+ p_e2e_common.add_argument("--json", action="store_true", help="Emit JSON report to stdout")
160
+ p_e2e_common.add_argument("--output", help="Write JSON report to this path")
161
+ p_e2e_common.add_argument("--html-output", help="Write HTML report to this path")
162
+
163
+ p_e2e_adv = p_e2e.add_argument_group("advanced options")
164
+ p_e2e_adv.add_argument("--no-parallel", action="store_true", help="Run providers sequentially instead of in parallel")
165
+ p_e2e_adv.add_argument("--judge", default=None, help="Enable AI-as-judge scoring with this CLI (e.g. claude, gemini)")
166
+ p_e2e_adv.add_argument("--judge-model", default=None, help="Model override for the judge CLI")
167
+ p_e2e_adv.add_argument("--task-workers", type=int, default=1,
168
+ help="Run N tasks concurrently (default: 1). Higher values are faster but may hit rate limits.")
169
+ p_e2e_adv.add_argument("--no-cache", action="store_true",
170
+ help="Ignore cached results and re-run all tasks (cache is enabled by default, TTL=24h)")
171
+ p_e2e_adv.add_argument("--permission-mode", default="bypassPermissions",
172
+ help="Permission mode for AI CLI (default: bypassPermissions). Use 'plan' for read-only mode.")
173
+ p_e2e_adv.add_argument("--delegate-benchmark", action="store_true",
174
+ help="Run delegate backend comparison (Ollama vs Codex) instead of normal e2e benchmark")
175
+ p_e2e_adv.add_argument("--delegate-types", default=None,
176
+ help="Comma-separated delegate task types to benchmark (default: all). E.g. review,diagnose")
177
+
178
+ p_terse = subparsers.add_parser("terse", help="Manage the terse-advisor nudge state")
179
+ p_terse.add_argument(
180
+ "action",
181
+ nargs="?",
182
+ default="status",
183
+ choices=["dismiss", "later", "reset", "status"],
184
+ help="dismiss=silence forever, later=snooze 24h, reset=clear state, status=show state (default)",
185
+ )
186
+
187
+ # Unified `c3 bench <tier>` — wraps benchmark / session-benchmark / benchmark-e2e
188
+ # Legacy commands above kept for backward compatibility.
189
+ p_bench = subparsers.add_parser(
190
+ "bench",
191
+ help="Run C3 benchmarks (unified: quick | session | e2e | delegate | all | dashboard)",
192
+ )
193
+ bench_sub = p_bench.add_subparsers(dest="bench_tier")
194
+
195
+ p_bq = bench_sub.add_parser("quick", help="Local synthetic benchmark (no AI calls) [Synthetic]")
196
+ p_bq.add_argument("project_path", nargs="?", default=".")
197
+ p_bq.add_argument("--sample-size", type=int, default=25)
198
+ p_bq.add_argument("--min-tokens", type=int, default=200)
199
+ p_bq.add_argument("--top-k", type=int, default=5)
200
+ p_bq.add_argument("--max-tokens", type=int, default=4000)
201
+ p_bq.add_argument("--json", action="store_true")
202
+ p_bq.add_argument("--output")
203
+ p_bq.add_argument("--html-output")
204
+ p_bq.add_argument("--no-html", action="store_true")
205
+ p_bq.add_argument("--system-name")
206
+ p_bq.add_argument("--system-label")
207
+ p_bq.add_argument("--system-version")
208
+
209
+ p_bs = bench_sub.add_parser("session", help="Workflow scenario benchmark (6 scenarios) [Synthetic]")
210
+ p_bs.add_argument("project_path", nargs="?", default=".")
211
+ p_bs.add_argument("--sample-size", type=int, default=15)
212
+ p_bs.add_argument("--min-tokens", type=int, default=200)
213
+ p_bs.add_argument("--json", action="store_true")
214
+ p_bs.add_argument("--output")
215
+ p_bs.add_argument("--html-output")
216
+
217
+ p_be = bench_sub.add_parser("e2e", help="End-to-end AI benchmark (real claude/gemini/codex) [Live AI]")
218
+ p_be.add_argument("project_path", nargs="?", default=".")
219
+ p_be.add_argument("--providers", default=None, help="Comma-separated: claude,gemini,codex (default: auto-detect)")
220
+ p_be.add_argument("--models", default=None, help="Model overrides: claude=sonnet,gemini=gemini-2.5-flash,codex=o3")
221
+ p_be.add_argument("--tasks", default="all", help="Task filter: all (default), or comma-separated categories")
222
+ p_be.add_argument("--max-tasks", type=int, default=1)
223
+ p_be.add_argument("--timeout", type=int, default=120)
224
+ p_be.add_argument("--dry-run", action="store_true")
225
+ p_be.add_argument("--verbose", action="store_true")
226
+ p_be.add_argument("--json", action="store_true")
227
+ p_be.add_argument("--output")
228
+ p_be.add_argument("--html-output")
229
+ p_be.add_argument("--no-parallel", action="store_true")
230
+ p_be.add_argument("--judge", default=None)
231
+ p_be.add_argument("--judge-model", default=None)
232
+ p_be.add_argument("--task-workers", type=int, default=1)
233
+ p_be.add_argument("--no-cache", action="store_true")
234
+ p_be.add_argument("--permission-mode", default="bypassPermissions")
235
+
236
+ p_bd = bench_sub.add_parser("delegate", help="Delegate backend comparison (Ollama vs Codex) [Live AI]")
237
+ p_bd.add_argument("project_path", nargs="?", default=".")
238
+ p_bd.add_argument("--delegate-types", default=None, help="Comma-separated delegate task types (default: all)")
239
+ p_bd.add_argument("--verbose", action="store_true")
240
+ p_bd.add_argument("--json", action="store_true")
241
+ p_bd.add_argument("--output")
242
+
243
+ p_ba = bench_sub.add_parser("all", help="Run full benchmark suite (quick + session + e2e + dashboard)")
244
+ p_ba.add_argument("project_path", nargs="?", default=".")
245
+ p_ba.add_argument("--skip-e2e", action="store_true", help="Skip the e2e benchmark (slow; requires AI CLIs)")
246
+ p_ba.add_argument("--sample-size", type=int, default=15)
247
+ p_ba.add_argument("--min-tokens", type=int, default=200)
248
+ p_ba.add_argument("--max-tasks", type=int, default=1)
249
+ p_ba.add_argument("--timeout", type=int, default=120)
250
+ p_ba.add_argument("--providers", default=None)
251
+
252
+ p_bdash = bench_sub.add_parser("dashboard", help="Regenerate unified HTML dashboard")
253
+ p_bdash.add_argument("project_path", nargs="?", default=".")
254
+ p_bdash.add_argument("--open", action="store_true", help="Open in browser after generating")
255
+
256
+ p_bext = bench_sub.add_parser("external",
257
+ help="External benchmark suites (Aider Polyglot / SWE-bench) [External]")
258
+ p_bext.add_argument("project_path", nargs="?", default=".")
259
+ p_bext.add_argument("--suite", choices=["aider-polyglot", "swe-bench-lite"],
260
+ default="aider-polyglot",
261
+ help="External benchmark suite (default: aider-polyglot)")
262
+ p_bext.add_argument("--path", default=None,
263
+ help="Path to the benchmark corpus (aider-polyglot: repo dir; swe-bench-lite: unused)")
264
+ p_bext.add_argument("--dataset", default=None,
265
+ help="swe-bench-lite: path to swe_bench_lite.jsonl or HF dataset id "
266
+ "(default: princeton-nlp/SWE-bench_Lite)")
267
+ p_bext.add_argument("--agent", choices=["aider"], default="aider",
268
+ help="swe-bench-lite: agent to generate patches (default: aider)")
269
+ p_bext.add_argument("--languages", default="python",
270
+ help="aider-polyglot: python,javascript,go,rust,java,cpp (default: python)")
271
+ p_bext.add_argument("--max-exercises", type=int, default=5,
272
+ help="aider-polyglot: max exercises per language (default: 5)")
273
+ p_bext.add_argument("--max-tasks", type=int, default=5,
274
+ help="swe-bench-lite: max instances to run (default: 5)")
275
+ p_bext.add_argument("--model", default="gpt-4o-mini",
276
+ help="Model ID to pass to the agent (default: gpt-4o-mini)")
277
+ p_bext.add_argument("--timeout", type=int, default=300,
278
+ help="Per-task agent timeout in seconds (default: 300)")
279
+ p_bext.add_argument("--docker-eval", action="store_true",
280
+ help="swe-bench-lite: run the official Docker-based evaluation after patch generation")
281
+ p_bext.add_argument("--verbose", action="store_true",
282
+ help="Print each task result as it completes")
283
+ p_bext.add_argument("--dry-run", action="store_true",
284
+ help="Validate setup (CLIs, datasets) without running the agent")
285
+
286
+ return parser