code-context-control 2.51.0__py3-none-any.whl → 2.52.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/_hook_utils.py CHANGED
@@ -33,6 +33,28 @@ LEGACY_UNLOCK_FILE = ".c3/unlocked_files.json"
33
33
  STATE_WARNINGS: list = []
34
34
 
35
35
 
36
+ def ensure_utf8_stdio() -> None:
37
+ """Force UTF-8 on the hook process's stdio pipes.
38
+
39
+ On Windows a hook subprocess gets cp1252 stdout/stdin (piped, non-TTY),
40
+ so printing user-visible text containing box-drawing chars ("─") or emoji
41
+ raises UnicodeEncodeError and surfaces as a "Stop hook error" in the IDE.
42
+ Claude Code reads hook stdout as UTF-8. Call this at the top of every
43
+ entrypoint that prints raw (non-json.dumps) text. Never raises.
44
+ """
45
+ for stream in (sys.stdout, sys.stderr):
46
+ try:
47
+ if hasattr(stream, "reconfigure"):
48
+ stream.reconfigure(encoding="utf-8", errors="replace")
49
+ except Exception:
50
+ pass
51
+ try:
52
+ if hasattr(sys.stdin, "reconfigure"):
53
+ sys.stdin.reconfigure(encoding="utf-8", errors="strict")
54
+ except Exception:
55
+ pass
56
+
57
+
36
58
  def log_hook_error(hook_name: str, exc: BaseException) -> None:
37
59
  """Append a timestamped error entry to .c3/hook_errors.log.
38
60
 
cli/c3.py CHANGED
@@ -92,7 +92,7 @@ console = Console() if HAS_RICH else None
92
92
  # Config
93
93
  CONFIG_DIR = ".c3"
94
94
  CONFIG_FILE = ".c3/config.json"
95
- __version__ = "2.51.0"
95
+ __version__ = "2.52.0"
96
96
 
97
97
 
98
98
  def _command_deps() -> CommandDeps:
@@ -711,8 +711,7 @@ def _select_init_ide(default_ide: str) -> str:
711
711
  "VS Code — .vscode/mcp.json + Copilot instructions",
712
712
  "Cursor — .cursor/mcp.json",
713
713
  "Codex — .codex/config.toml + AGENTS.md",
714
- "Gemini .gemini/settings.json + GEMINI.md",
715
- "Antigravity — ~/.gemini/antigravity/mcp_config.json",
714
+ "Antigravity ~/.gemini/antigravity/mcp_config.json + AGENTS.md",
716
715
  ]
717
716
  selected = _prompt_choice("Step 1/3 — Choose IDE profile", choices)
718
717
  mapping = {
@@ -721,8 +720,7 @@ def _select_init_ide(default_ide: str) -> str:
721
720
  choices[2]: "vscode",
722
721
  choices[3]: "cursor",
723
722
  choices[4]: "codex",
724
- choices[5]: "gemini",
725
- choices[6]: "antigravity",
723
+ choices[5]: "antigravity",
726
724
  }
727
725
  chosen = mapping.get(selected or "", normalize_ide_name(default_ide) if default_ide != "auto" else "auto")
728
726
  print(f" IDE profile: {chosen}")
@@ -887,7 +885,7 @@ def _parse_cli_ide_arg(value: str) -> str:
887
885
  normalized = normalize_ide_name(raw)
888
886
  if normalized not in PROFILES:
889
887
  raise argparse.ArgumentTypeError(
890
- "Unsupported IDE. Use one of: auto, claude, vscode, cursor, codex, gemini, antigravity."
888
+ "Unsupported IDE. Use one of: auto, claude, vscode, cursor, codex, antigravity."
891
889
  )
892
890
  return normalized
893
891
 
@@ -4013,29 +4011,6 @@ enabled = true
4013
4011
  ```
4014
4012
  """
4015
4013
 
4016
- _GEMINI_MD_CONTENT = _C3_COMPACT_WORKFLOW + """
4017
-
4018
- ## IDE Configuration (Gemini CLI)
4019
- This project uses project-scoped MCP servers. Ensure your `.gemini/settings.json` includes:
4020
- ```json
4021
- {
4022
- "mcpServers": {
4023
- "c3": {
4024
- "command": "c3-mcp",
4025
- "args": ["--project", "."]
4026
- }
4027
- }
4028
- }
4029
- ```
4030
-
4031
- ## Gemini Enforcement
4032
- - `c3 init` and `c3 install-mcp` install this file as a required workflow, not a suggestion.
4033
- - After install, use the `c3` MCP server for recall, search, structural mapping, surgical reads, filtering, and session logging before native Gemini repo exploration.
4034
- - Do not bypass C3 with broad native search/read steps unless a matching `c3_*` tool failed or was too narrow for a final follow-up.
4035
- - If fallback is necessary, say which `c3_*` tool was attempted or skipped and why.
4036
- """
4037
-
4038
-
4039
4014
  _TERSE_SKILL_CONTENT = """\
4040
4015
  # /terse — Terse Output Mode
4041
4016
 
@@ -4113,28 +4088,16 @@ Say "normal mode", start a new session, or let the turn counter expire.
4113
4088
  _TERSE_SKILL_MARKER = "# /terse — Terse Output Mode"
4114
4089
 
4115
4090
 
4116
- _TERSE_GEMINI_TOML = (
4117
- 'description = "Terse output mode. Usage: /terse [lite|full|ultra] (default: full)."\n'
4118
- "prompt = '''\n"
4119
- + _TERSE_SKILL_CONTENT.replace("'''", "''\\''")
4120
- + "\n'''\n"
4121
- )
4122
-
4123
-
4124
4091
  def _ensure_terse_skill(ide: str = "claude-code") -> None:
4125
4092
  """Install the /terse slash command for the given IDE profile.
4126
4093
 
4127
4094
  claude-code -> ~/.claude/commands/terse.md
4128
4095
  codex -> ~/.codex/prompts/terse.md
4129
- gemini -> ~/.gemini/commands/terse.toml
4130
4096
  """
4131
4097
  home = Path.home()
4132
4098
  if ide == "codex":
4133
4099
  skill_path = home / ".codex" / "prompts" / "terse.md"
4134
4100
  content = _TERSE_SKILL_CONTENT
4135
- elif ide == "gemini":
4136
- skill_path = home / ".gemini" / "commands" / "terse.toml"
4137
- content = _TERSE_GEMINI_TOML
4138
4101
  else:
4139
4102
  skill_path = home / ".claude" / "commands" / "terse.md"
4140
4103
  content = _TERSE_SKILL_CONTENT
@@ -4357,7 +4320,7 @@ def _upsert_json_mcp_server(config_path: Path, config_key: str, server_name: str
4357
4320
 
4358
4321
  def _ensure_project_session_configs(target: Path, server_script: str, primary_profile: str | None = None,
4359
4322
  c3_mcp_exe: str | None = None) -> None:
4360
- """Keep project-local Codex and Gemini MCP configs in sync for new sessions."""
4323
+ """Keep the project-local Codex MCP config in sync for new sessions."""
4361
4324
  # Ensure forward slashes for config portability and avoid Windows path-splitting issues
4362
4325
  server_script_posix = Path(server_script).as_posix()
4363
4326
  if c3_mcp_exe:
@@ -4381,26 +4344,16 @@ def _ensure_project_session_configs(target: Path, server_script: str, primary_pr
4381
4344
  )
4382
4345
  print(f"{codex_state.capitalize()} {codex_path}")
4383
4346
 
4384
- if primary_profile != "gemini":
4385
- gemini_path = target / ".gemini" / "settings.json"
4386
- gemini_state = _upsert_json_mcp_server(
4387
- gemini_path,
4388
- "mcpServers",
4389
- "c3",
4390
- {
4391
- "command": mcp_command,
4392
- "args": server_args,
4393
- },
4394
- )
4395
- print(f"{gemini_state.capitalize()} {gemini_path}")
4396
4347
 
4397
4348
 
4398
- def _ensure_global_session_fallbacks(server_script: str, c3_mcp_exe: str | None = None) -> None:
4399
- """Keep user-global Codex/Gemini MCP configs pointing at C3.
4349
+
4350
+ def _ensure_global_session_fallbacks(server_script: str, c3_mcp_exe: str | None = None,
4351
+ primary_profile: str | None = None) -> None:
4352
+ """Keep user-global Codex/Antigravity MCP configs pointing at C3.
4400
4353
 
4401
4354
  These fallback entries omit `--project` so the MCP server can resolve the
4402
4355
  active working directory dynamically when a session starts in a project that
4403
- does not yet have project-local Codex/Gemini config files.
4356
+ does not yet have a project-local Codex config file.
4404
4357
  """
4405
4358
  server_script_posix = Path(server_script).as_posix()
4406
4359
  # With the installed entry point, no script path is needed; --project stays
@@ -4423,20 +4376,25 @@ def _ensure_global_session_fallbacks(server_script: str, c3_mcp_exe: str | None
4423
4376
  except PermissionError:
4424
4377
  print(f"Warning: Could not update {codex_path} (global fallback skipped)")
4425
4378
 
4426
- gemini_path = Path.home() / ".gemini" / "settings.json"
4427
- try:
4428
- gemini_state = _upsert_json_mcp_server(
4429
- gemini_path,
4430
- "mcpServers",
4431
- "c3",
4432
- {
4433
- "command": c3_mcp_exe or sys.executable,
4434
- "args": fallback_args,
4435
- },
4436
- )
4437
- print(f"{gemini_state.capitalize()} {gemini_path} (global fallback)")
4438
- except PermissionError:
4439
- print(f"Warning: Could not update {gemini_path} (global fallback skipped)")
4379
+ # Antigravity shares the ~/.gemini home dir but reads its own MCP config.
4380
+ # When Antigravity is the primary profile, the main install flow already
4381
+ # wrote this file — here we only keep it fresh for codex installs
4382
+ # on machines that have Antigravity (its config dir exists).
4383
+ antigravity_path = Path.home() / ".gemini" / "antigravity" / "mcp_config.json"
4384
+ if primary_profile != "antigravity" and antigravity_path.parent.is_dir():
4385
+ try:
4386
+ ag_state = _upsert_json_mcp_server(
4387
+ antigravity_path,
4388
+ "mcpServers",
4389
+ "c3",
4390
+ {
4391
+ "command": c3_mcp_exe or sys.executable,
4392
+ "args": fallback_args,
4393
+ },
4394
+ )
4395
+ print(f"{ag_state.capitalize()} {antigravity_path} (global fallback)")
4396
+ except PermissionError:
4397
+ print(f"Warning: Could not update {antigravity_path} (global fallback skipped)")
4440
4398
 
4441
4399
 
4442
4400
  def _uninstall_mcp_all(project_path: str):
@@ -4474,8 +4432,8 @@ def _uninstall_mcp_all(project_path: str):
4474
4432
  config_paths.append(Path.home() / profile.config_path)
4475
4433
  else:
4476
4434
  config_paths.append(target / profile.config_path)
4477
- # For Codex and Gemini, also check the global fallback in home dir
4478
- if ide_name in ("codex", "gemini"):
4435
+ # For Codex, also check the global fallback in home dir
4436
+ if ide_name == "codex":
4479
4437
  config_paths.append(Path.home() / profile.config_path)
4480
4438
 
4481
4439
  for mcp_config_path in config_paths:
@@ -4597,6 +4555,22 @@ def _uninstall_mcp_all(project_path: str):
4597
4555
  except Exception as e:
4598
4556
  print(f" Warning: Could not update {vscode_settings_path}: {e}")
4599
4557
 
4558
+ # Legacy Gemini CLI configs (profile removed in v2.52) — still strip the c3 entry.
4559
+ for legacy_cfg in (target / ".gemini" / "settings.json",
4560
+ Path.home() / ".gemini" / "settings.json"):
4561
+ if not legacy_cfg.exists():
4562
+ continue
4563
+ try:
4564
+ with open(legacy_cfg, encoding="utf-8") as f:
4565
+ legacy_data = json.load(f)
4566
+ if "c3" in (legacy_data.get("mcpServers") or {}):
4567
+ del legacy_data["mcpServers"]["c3"]
4568
+ with open(legacy_cfg, "w", encoding="utf-8") as f:
4569
+ json.dump(legacy_data, f, indent=2)
4570
+ print(f" Removed C3 from {legacy_cfg}")
4571
+ except Exception as e:
4572
+ print(f" Warning: Could not update {legacy_cfg}: {e}")
4573
+
4600
4574
  # Final pass: clean up empty IDE directories (.claude, .codex, .gemini, .vscode, .github)
4601
4575
  dirs_to_check = [".claude", ".codex", ".gemini", ".vscode", ".github"]
4602
4576
  for dname in dirs_to_check:
@@ -4715,11 +4689,27 @@ def _ensure_global_claude_md() -> None:
4715
4689
 
4716
4690
 
4717
4691
  def _instruction_documents_for_project() -> list[tuple[str, str]]:
4718
- """Return the project-local instruction documents C3 should keep in sync."""
4692
+ """Return every project-local instruction document C3 has ever managed.
4693
+
4694
+ Used by uninstall/cleanup paths, so it must keep listing legacy docs
4695
+ (GEMINI.md — profile removed in v2.52; empty template, never generated).
4696
+ """
4719
4697
  return [
4720
4698
  ("CLAUDE.md", _CLAUDE_MD_CONTENT),
4721
4699
  ("AGENTS.md", _AGENTS_MD_CONTENT),
4722
- ("GEMINI.md", _GEMINI_MD_CONTENT),
4700
+ ("GEMINI.md", ""),
4701
+ ]
4702
+
4703
+
4704
+ _LEGACY_INSTRUCTION_DOCS = ("GEMINI.md",) # Gemini CLI profile removed in v2.52
4705
+
4706
+
4707
+ def _instruction_documents_to_generate() -> list[tuple[str, str]]:
4708
+ """Instruction docs to write for a project (legacy docs excluded)."""
4709
+ return [
4710
+ (name, template)
4711
+ for name, template in _instruction_documents_for_project()
4712
+ if name not in _LEGACY_INSTRUCTION_DOCS
4723
4713
  ]
4724
4714
 
4725
4715
 
@@ -4727,7 +4717,7 @@ def _sync_project_instruction_docs(project_path: str, sm: SessionManager) -> Non
4727
4717
  """Write the current C3 instruction docs into the project root."""
4728
4718
  repo_root = Path(__file__).resolve().parent.parent
4729
4719
  synced: list[str] = []
4730
- for instructions_file, template in _instruction_documents_for_project():
4720
+ for instructions_file, template in _instruction_documents_to_generate():
4731
4721
  print(f"Generating {instructions_file}...")
4732
4722
  # Resolve placeholder for project-scoped MCP configs
4733
4723
  resolved_template = template.replace("<path-to-c3>", str(repo_root).replace("\\", "/"))
@@ -4993,8 +4983,8 @@ def cmd_install_mcp(args):
4993
4983
  if _found:
4994
4984
  c3_mcp_exe = Path(_found).resolve().as_posix()
4995
4985
 
4996
- # On Windows, Gemini CLI splits command args by space, so the script path stays a
4997
- # single arg. 'python' keeps the source fallback portable across platforms.
4986
+ # Keep the script path as a single arg; 'python' keeps the source fallback
4987
+ # portable across platforms.
4998
4988
  if c3_mcp_exe:
4999
4989
  new_entry = {"command": c3_mcp_exe, "args": ["--project", "."]}
5000
4990
  else:
@@ -5069,9 +5059,9 @@ def cmd_install_mcp(args):
5069
5059
  # instead of letting it surface as anonymous out-of-band drift.
5070
5060
  from services.artifact_defs import note_pending_write
5071
5061
  note_pending_write(target, profile.config_path, "install_mcp")
5072
- if profile.name in {"codex", "gemini"}:
5062
+ if profile.name in {"codex", "antigravity"}:
5073
5063
  _ensure_project_session_configs(target, server_script, primary_profile=profile.name, c3_mcp_exe=c3_mcp_exe)
5074
- _ensure_global_session_fallbacks(server_script, c3_mcp_exe=c3_mcp_exe)
5064
+ _ensure_global_session_fallbacks(server_script, c3_mcp_exe=c3_mcp_exe, primary_profile=profile.name)
5075
5065
 
5076
5066
  # ── Persist IDE choice to .c3/config.json ──
5077
5067
  c3_config_dir = target / ".c3"
@@ -5085,7 +5075,7 @@ def cmd_install_mcp(args):
5085
5075
  with open(c3_config_path, 'w', encoding="utf-8") as f:
5086
5076
  json.dump(c3_config, f, indent=2)
5087
5077
 
5088
- # ── Install hooks (Claude Code + Gemini CLI) ──
5078
+ # ── Install hooks (Claude Code) ──
5089
5079
  if profile.supports_hooks and profile.settings_path:
5090
5080
  settings_dir = target / Path(profile.settings_path).parent
5091
5081
  settings_dir.mkdir(parents=True, exist_ok=True)
@@ -5116,26 +5106,15 @@ def cmd_install_mcp(args):
5116
5106
  hook_stop_cmd = f"{_dispatch_base} stop"
5117
5107
  hook_prompt_cmd = f"{_dispatch_base} prompt"
5118
5108
 
5119
- # Tool matcher names differ by IDE: Gemini uses snake_case built-in names.
5120
- if profile.name == "gemini":
5121
- shell_matcher = "run_shell_command"
5122
- read_matcher = "read_file"
5123
- grep_matcher = "grep"
5124
- glob_matcher = "find_files"
5125
- edit_matcher = "edit_file"
5126
- write_matcher = "write_file"
5127
- # Gemini has no MultiEdit / NotebookEdit equivalents.
5128
- extra_edit_matchers = []
5129
- else:
5130
- shell_matcher = "Bash"
5131
- read_matcher = "Read"
5132
- grep_matcher = "Grep"
5133
- glob_matcher = "Glob"
5134
- edit_matcher = "Edit"
5135
- write_matcher = "Write"
5136
- # Claude Code also exposes MultiEdit (batch edits) and NotebookEdit;
5137
- # both bypass enforcement/logging unless their matchers are registered.
5138
- extra_edit_matchers = ["MultiEdit", "NotebookEdit"]
5109
+ shell_matcher = "Bash"
5110
+ read_matcher = "Read"
5111
+ grep_matcher = "Grep"
5112
+ glob_matcher = "Glob"
5113
+ edit_matcher = "Edit"
5114
+ write_matcher = "Write"
5115
+ # Claude Code also exposes MultiEdit (batch edits) and NotebookEdit;
5116
+ # both bypass enforcement/logging unless their matchers are registered.
5117
+ extra_edit_matchers = ["MultiEdit", "NotebookEdit"]
5139
5118
 
5140
5119
  # ── PostToolUse hooks ──
5141
5120
  # Matcher set is unchanged from pre-v2.42; every matcher now points at
@@ -5193,8 +5172,7 @@ def cmd_install_mcp(args):
5193
5172
  existing_post.extend(desired_post_hooks)
5194
5173
  settings.setdefault("hooks", {})[hook_event] = existing_post
5195
5174
 
5196
- # PreToolUse hooks (Claude Code: "PreToolUse", Gemini: "BeforeTool")
5197
- pre_event = "BeforeTool" if profile.name == "gemini" else "PreToolUse"
5175
+ pre_event = "PreToolUse"
5198
5176
  pre_matchers = {h.get("matcher") for h in desired_pre_hooks}
5199
5177
  existing_pre = [
5200
5178
  h for h in settings.get("hooks", {}).get(pre_event, [])
@@ -5204,7 +5182,6 @@ def cmd_install_mcp(args):
5204
5182
  settings.setdefault("hooks", {})[pre_event] = existing_pre
5205
5183
 
5206
5184
  # ── Stop hooks (auto-snapshot + session stats on session end / Ctrl+C) ──
5207
- # Stop hooks fire for both Claude Code ("Stop") and Gemini ("Stop").
5208
5185
  desired_stop_hooks = [
5209
5186
  {
5210
5187
  "matcher": "",
@@ -5239,26 +5216,25 @@ def cmd_install_mcp(args):
5239
5216
  settings.setdefault("hooks", {})[stop_event] = existing_stop
5240
5217
 
5241
5218
  # ── UserPromptSubmit hook (per-prompt project-memory injection) ──
5242
- # Claude Code only: Gemini CLI has no equivalent hook event. Same
5243
- # merge discipline as Stop: replace only C3's own entries (identified
5244
- # by the dispatcher script in the command), keep user-added ones.
5245
- if profile.name != "gemini":
5246
- prompt_event = "UserPromptSubmit"
5247
-
5248
- def _is_c3_prompt_hook(entry: dict) -> bool:
5249
- return any(
5250
- "hook_dispatch.py" in (hk.get("command") or "")
5251
- for hk in entry.get("hooks", [])
5252
- )
5219
+ # Same merge discipline as Stop: replace only C3's own entries
5220
+ # (identified by the dispatcher script in the command), keep
5221
+ # user-added ones.
5222
+ prompt_event = "UserPromptSubmit"
5253
5223
 
5254
- existing_prompt = [
5255
- h for h in settings.get("hooks", {}).get(prompt_event, [])
5256
- if not _is_c3_prompt_hook(h)
5257
- ]
5258
- existing_prompt.append(
5259
- {"matcher": "", "hooks": [{"type": "command", "command": hook_prompt_cmd}]}
5224
+ def _is_c3_prompt_hook(entry: dict) -> bool:
5225
+ return any(
5226
+ "hook_dispatch.py" in (hk.get("command") or "")
5227
+ for hk in entry.get("hooks", [])
5260
5228
  )
5261
- settings.setdefault("hooks", {})[prompt_event] = existing_prompt
5229
+
5230
+ existing_prompt = [
5231
+ h for h in settings.get("hooks", {}).get(prompt_event, [])
5232
+ if not _is_c3_prompt_hook(h)
5233
+ ]
5234
+ existing_prompt.append(
5235
+ {"matcher": "", "hooks": [{"type": "command", "command": hook_prompt_cmd}]}
5236
+ )
5237
+ settings.setdefault("hooks", {})[prompt_event] = existing_prompt
5262
5238
 
5263
5239
  # Claude Code only: enable MCP server prompt settings
5264
5240
  if profile.name == "claude-code":
@@ -5364,20 +5340,6 @@ def cmd_install_mcp(args):
5364
5340
  print(f"Warning: {global_codex_cfg} has [mcp_servers.c3] enabled = false.")
5365
5341
  print(" This can make C3 look disabled. Set it to true or remove that global c3 section.")
5366
5342
 
5367
- # ── Gemini settings.json enforcement file ──────────────────
5368
- if profile.name == "gemini":
5369
- # Warn about a common conflict: global Gemini config pointing elsewhere.
5370
- global_gemini_cfg = Path.home() / ".gemini" / "settings.json"
5371
- if global_gemini_cfg.exists():
5372
- try:
5373
- with open(global_gemini_cfg, 'r', encoding="utf-8") as f:
5374
- g_data = json.load(f)
5375
- if "mcpServers" in g_data and "c3" in g_data["mcpServers"]:
5376
- print(f"Note: Global config {global_gemini_cfg} also defines 'c3'.")
5377
- print(" The project-local config at .gemini/settings.json should take precedence.")
5378
- except Exception:
5379
- pass
5380
-
5381
5343
  _sync_project_instruction_docs(str(target), sm)
5382
5344
 
5383
5345
  # ── User-global C3 enforcement ──────────────────────────────
@@ -5387,7 +5349,7 @@ def cmd_install_mcp(args):
5387
5349
  print(f"Warning: Could not update global CLAUDE.md: {e}")
5388
5350
 
5389
5351
  # ── Install /terse skill for supported IDEs ──────────────────
5390
- if profile.name in ("claude-code", "codex", "gemini"):
5352
+ if profile.name in ("claude-code", "codex"):
5391
5353
  try:
5392
5354
  _ensure_terse_skill(profile.name)
5393
5355
  except Exception as e:
cli/commands/parser.py CHANGED
@@ -21,7 +21,7 @@ def build_parser(version: str, parse_cli_ide_arg):
21
21
  p_init.add_argument("project_path", nargs="?", default=".")
22
22
  p_init.add_argument("--force", action="store_true", help="Skip prompts and apply update non-interactively")
23
23
  p_init.add_argument("--clear", action="store_true", help="Remove all C3 files and exit without rebuilding")
24
- 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)")
24
+ p_init.add_argument("--ide", default="auto", type=parse_cli_ide_arg, metavar="{auto,claude,vscode,cursor,codex,antigravity}", help="Target IDE for MCP config (default: auto-detect)")
25
25
  p_init.add_argument("--mcp-mode", choices=["direct", "proxy"], default="direct", help="Default MCP mode if install is selected during init (default: direct)")
26
26
  p_init.add_argument("--git", action="store_true", help="Initialize a local Git repository during init/update")
27
27
  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)")
@@ -93,14 +93,14 @@ def build_parser(version: str, parse_cli_ide_arg):
93
93
 
94
94
  p_install_mcp = subparsers.add_parser("install-mcp", help="Generate MCP config for your IDE")
95
95
  p_install_mcp.add_argument("targets", nargs="*", help="Optional project path and/or IDE shorthand (for example: `claude` or `. codex`)")
96
- 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)")
96
+ p_install_mcp.add_argument("--ide", default="auto", type=parse_cli_ide_arg, metavar="{auto,claude,vscode,cursor,codex,antigravity}", help="Target IDE (default: auto-detect)")
97
97
  p_install_mcp.add_argument("--mcp-mode", choices=["direct", "proxy"], default="direct", help="MCP entrypoint mode (default: direct)")
98
98
  p_install_mcp.add_argument("--permissions", choices=["read-only", "c3-strict", "standard", "permissive"], default=None, help="Apply Claude Code permission tier (Claude Code only)")
99
99
  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")
100
100
 
101
101
  p_mcp_install = subparsers.add_parser("mcp-install", help="Alias for install-mcp")
102
102
  p_mcp_install.add_argument("targets", nargs="*", help="Optional project path and/or IDE shorthand")
103
- 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)")
103
+ p_mcp_install.add_argument("--ide", default="auto", type=parse_cli_ide_arg, metavar="{auto,claude,vscode,cursor,codex,antigravity}", help="Target IDE (default: auto-detect)")
104
104
  p_mcp_install.add_argument("--mcp-mode", choices=["direct", "proxy"], default="direct", help="MCP entrypoint mode (default: direct)")
105
105
  p_mcp_install.add_argument("--permissions", choices=["read-only", "c3-strict", "standard", "permissive"], default=None, help="Apply Claude Code permission tier (Claude Code only)")
106
106
  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")
cli/docs.html CHANGED
@@ -790,7 +790,6 @@
790
790
  <div class="subtitle">
791
791
  A local code intelligence system that reduces AI coding assistant token usage through compression,
792
792
  indexing, and persistent memory. Works with <strong>Claude Code</strong>, <strong>Google Antigravity</strong>,
793
- <strong>Gemini CLI</strong>,
794
793
  <strong>VS Code Copilot</strong>, and <strong>Cursor</strong> via MCP. Available as an <strong>MCP
795
794
  server</strong>,
796
795
  <strong>CLI</strong>, and <strong>web dashboard</strong>.
@@ -955,7 +954,7 @@ c3 init /path/to/your/project</code></pre>
955
954
  <code>.c3/</code> directory, builds the code index, and then walks through a guided 3-step setup:
956
955
  choose the IDE profile, optionally run a local <code>git init</code>, and optionally install MCP.
957
956
  When MCP is installed, C3 writes the IDE config plus project-local session files such as
958
- <code>.codex/config.toml</code> and <code>.gemini/settings.json</code>. For VS Code, also generates
957
+ <code>.codex/config.toml</code>. For VS Code, also generates
959
958
  <code>.github/copilot-instructions.md</code> (hard enforcement language) and
960
959
  <code>.vscode/settings.json</code> (Copilot instruction links). For Codex, generates
961
960
  <code>AGENTS.md</code> with the C3 session protocol. Use <code>--ide vscode</code>,
@@ -967,7 +966,7 @@ c3 init /path/to/your/project</code></pre>
967
966
  <div class="step-content">
968
967
  <div class="step-title">Restart your IDE</div>
969
968
  <div class="step-desc">Open your IDE in the project. For Claude Code, run <code>/mcp</code> to verify C3
970
- tools appear. For Google Antigravity and Gemini CLI, the MCP tools will be loaded based on your config.
969
+ tools appear. For Google Antigravity, the MCP tools will be loaded based on your config.
971
970
  For VS Code, check MCP tools in the Copilot agent panel. For Codex, the <code>.codex/config.toml</code> is
972
971
  picked up automatically on next session start.</div>
973
972
  </div>
@@ -1021,19 +1020,11 @@ c3 init /path/to/your/project</code></pre>
1021
1020
  <td>No</td>
1022
1021
  <td>No</td>
1023
1022
  </tr>
1024
- <tr>
1025
- <td><strong>Gemini CLI</strong></td>
1026
- <td><code>.gemini/settings.json</code></td>
1027
- <td><code>GEMINI.md</code></td>
1028
- <td>—</td>
1029
- <td>No</td>
1030
- <td>No</td>
1031
- </tr>
1032
1023
  <tr>
1033
1024
  <td><strong>Google Antigravity</strong></td>
1034
1025
  <td><code>~/.gemini/antigravity/mcp_config.json</code> <span class="badge badge-warn"
1035
1026
  style="font-size:10px">global</span></td>
1036
- <td><code>GEMINI.md</code></td>
1027
+ <td><code>AGENTS.md</code></td>
1037
1028
  <td>—</td>
1038
1029
  <td>No</td>
1039
1030
  <td>No</td>
@@ -1059,13 +1050,12 @@ python cli/c3.py install-mcp /path/to/project --ide vscode
1059
1050
  python cli/c3.py install-mcp /path/to/project --ide cursor
1060
1051
  python cli/c3.py install-mcp /path/to/project --ide claude
1061
1052
  python cli/c3.py install-mcp /path/to/project --ide codex
1062
- python cli/c3.py install-mcp /path/to/project --ide gemini
1063
1053
  python cli/c3.py install-mcp /path/to/project --ide antigravity
1064
1054
 
1065
1055
  # IDE shorthand when already inside the project directory
1066
1056
  python cli/c3.py install-mcp claude
1067
1057
  python cli/c3.py install-mcp codex
1068
- python cli/c3.py install-mcp . gemini</code></pre>
1058
+ python cli/c3.py install-mcp . antigravity</code></pre>
1069
1059
 
1070
1060
  <p><code>--git</code> runs a local-only <code>git init</code>. It does not create remotes or connect to GitHub,
1071
1061
  GitLab, or any other hosted service.</p>
@@ -1104,30 +1094,20 @@ python cli/c3.py install-mcp . gemini</code></pre>
1104
1094
  </li>
1105
1095
  </ul>
1106
1096
 
1107
- <p><strong>Gemini CLI</strong> — <code>install-mcp --ide gemini</code> (and <code>c3 init --ide gemini</code>)
1108
- write a project-scoped JSON config and a GEMINI.md instructions file:</p>
1109
- <ul>
1110
- <li><code>.gemini/settings.json</code> — project-scoped MCP config under the <code>mcpServers</code> key.
1111
- Gemini CLI reads both this file and <code>~/.gemini/settings.json</code> (user-global). Merged with existing
1112
- settings. Auto-detected when a <code>.gemini/</code> directory or <code>.gemini/settings.json</code> already
1113
- exists.</li>
1114
- <li><code>GEMINI.md</code> — Gemini CLI reads this file as project context (loaded from the project root and
1115
- parent directories). Generated with REQUIRED/MANDATORY/NEVER language and the 7-step C3 session protocol. Only
1116
- created if absent.</li>
1117
- </ul>
1097
+ <p><em>The Gemini CLI profile was removed in v2.52 — use Google Antigravity instead. <code>c3 uninstall</code>
1098
+ still cleans up legacy <code>.gemini/settings.json</code> and <code>GEMINI.md</code> files.</em></p>
1118
1099
 
1119
1100
  <p><strong>Google Antigravity</strong> — <code>install-mcp --ide antigravity</code> writes the MCP config to the
1120
- user-global Antigravity config and a project-local <code>GEMINI.md</code>:</p>
1101
+ user-global Antigravity config and a project-local <code>AGENTS.md</code>:</p>
1121
1102
  <ul>
1122
1103
  <li><code>~/.gemini/antigravity/mcp_config.json</code> — user-global Antigravity MCP config under the
1123
1104
  <code>mcpServers</code> key. This is the file managed by Antigravity's <em>Manage MCPs</em> UI. Merged with
1124
1105
  existing config. Each project still passes <code>--project</code> to the MCP server so C3 indexes the correct
1125
1106
  directory.
1126
1107
  </li>
1127
- <li><code>GEMINI.md</code> — Antigravity and Gemini CLI both read <code>GEMINI.md</code> files from the
1128
- project root. Same content as Gemini CLI profile. Only created if absent.</li>
1129
- <li>Auto-detected when <code>~/.gemini/antigravity/</code> directory already exists alongside a
1130
- <code>.gemini/</code> project directory.
1108
+ <li><code>AGENTS.md</code> — Antigravity prefers <code>AGENTS.md</code> read from the project root
1109
+ (shared with the Codex profile). Only created if absent.</li>
1110
+ <li>Auto-detected when a legacy <code>.gemini/</code> project directory exists.
1131
1111
  </li>
1132
1112
  </ul>
1133
1113
 
@@ -172,10 +172,6 @@ c3 init</code></pre>
172
172
  <td><code>AGENTS.md</code></td>
173
173
  <td>Instructions for Codex sessions</td>
174
174
  </tr>
175
- <tr>
176
- <td><code>.gemini/settings.json</code></td>
177
- <td>Instructions for Gemini sessions</td>
178
- </tr>
179
175
  </tbody>
180
176
  </table>
181
177
 
@@ -194,7 +190,7 @@ c3 init</code></pre>
194
190
  <span class="callout-icon">🛡️</span>
195
191
  <div class="callout-body">
196
192
  <strong>Your hand-written content is preserved</strong>
197
- Generated instruction files (<code>CLAUDE.md</code>, <code>AGENTS.md</code>, <code>GEMINI.md</code>) wrap C3 content in a <code>&lt;!-- C3:BEGIN … --&gt;</code> / <code>&lt;!-- C3:END --&gt;</code> block. Re-running init (or <code>c3 claudemd save</code> / the <strong>Compact</strong> action) rewrites only that block — anything you add outside it stays put. An existing hand-written file with no block is kept and the C3 block is appended below it.
193
+ Generated instruction files (<code>CLAUDE.md</code>, <code>AGENTS.md</code>) wrap C3 content in a <code>&lt;!-- C3:BEGIN … --&gt;</code> / <code>&lt;!-- C3:END --&gt;</code> block. Re-running init (or <code>c3 claudemd save</code> / the <strong>Compact</strong> action) rewrites only that block — anything you add outside it stays put. An existing hand-written file with no block is kept and the C3 block is appended below it.
198
194
  </div>
199
195
  </div>
200
196
  </section>
@@ -219,9 +215,11 @@ c3 init</code></pre>
219
215
  <pre><code>c3 install-mcp codex</code></pre>
220
216
  <p>Updates <code>.codex/config.toml</code> and <code>AGENTS.md</code>.</p>
221
217
 
222
- <h3>Gemini CLI</h3>
223
- <pre><code>c3 install-mcp gemini</code></pre>
224
- <p>Updates <code>.gemini/settings.json</code>.</p>
218
+ <h3>Antigravity</h3>
219
+ <pre><code>c3 install-mcp antigravity</code></pre>
220
+ <p>Updates the user-global <code>~/.gemini/antigravity/mcp_config.json</code> and <code>AGENTS.md</code>.</p>
221
+
222
+ <p><em>The Gemini CLI profile was removed in v2.52 — use Antigravity instead. <code>c3 uninstall</code> still cleans up legacy <code>.gemini/settings.json</code> and <code>GEMINI.md</code> files.</em></p>
225
223
 
226
224
  <h3>All IDEs at once</h3>
227
225
  <pre><code>c3 install-mcp all</code></pre>
cli/guide/tools.html CHANGED
@@ -611,7 +611,7 @@ c3_impact(target=<span class="str">'_legacy_compress'</span>)</code></pre>
611
611
  <td class="param-name">cmd</td>
612
612
  <td class="param-type">string</td>
613
613
  <td class="param-default">required</td>
614
- <td class="param-desc">Shell command to execute (runs via <code>subprocess.Popen(shell=True)</code>)</td>
614
+ <td class="param-desc">Shell command to execute (Git Bash on Windows when available; otherwise the platform default shell)</td>
615
615
  </tr>
616
616
  <tr>
617
617
  <td class="param-name">cwd</td>
@@ -648,7 +648,10 @@ c3_shell(cmd=<span class="str">'pytest tests/test_c3_shell.py -v'</span>)
648
648
  c3_shell(cmd=<span class="str">'git commit -m "wire c3_shell"'</span>)
649
649
 
650
650
  <span class="com"># Build with a longer timeout</span>
651
- c3_shell(cmd=<span class="str">'npm run build'</span>, timeout=<span class="str">300</span>)</code></pre>
651
+ c3_shell(cmd=<span class="str">'npm run build'</span>, timeout=<span class="str">300</span>)
652
+
653
+ <span class="com"># Portable JSON formatting — Git Bash may not include jq</span>
654
+ c3_shell(cmd=<span class="str">'curl -s http://localhost:8000/health | python -m json.tool'</span>)</code></pre>
652
655
 
653
656
  <h4>Safety classification</h4>
654
657
  <div class="tag-row">
@@ -661,7 +664,7 @@ c3_shell(cmd=<span class="str">'npm run build'</span>, timeout=<span class="str"
661
664
  <div class="callout callout-tip">
662
665
  <span class="callout-icon">💡</span>
663
666
  <div class="callout-body">
664
- Prefer <code>c3_shell</code> over native <code>Bash</code> for anything noisy, git-mutating, or long-running. You get structured returns, consistent Windows timeout behaviour, free output filtering, and ledger coupling. Fall back to native <code>Bash</code> only for interactive / TTY commands.
667
+ Prefer <code>c3_shell</code> over native <code>Bash</code> for anything noisy, git-mutating, or long-running. You get structured returns, consistent timeout behaviour, free output filtering, and ledger coupling. Git Bash does not guarantee optional utilities such as <code>jq</code>; use Python's stdlib JSON tools for portable commands. Fall back to native <code>Bash</code> only for interactive / TTY commands.
665
668
  </div>
666
669
  </div>
667
670
  </div>
cli/hook_dispatch.py CHANGED
@@ -235,6 +235,9 @@ def dispatch(event: str, payload: dict, project_path: Path | None = None) -> dic
235
235
 
236
236
 
237
237
  def main() -> None:
238
+ # Windows hook subprocesses get cp1252 pipes; sub-hook _text may carry
239
+ # box-drawing chars / emoji, so print(text) below would UnicodeEncodeError.
240
+ _hook_utils.ensure_utf8_stdio()
238
241
  event = sys.argv[1].strip().lower() if len(sys.argv) > 1 else ""
239
242
  if event not in VALID_EVENTS:
240
243
  log_hook_error(