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 +22 -0
- cli/c3.py +99 -137
- cli/commands/parser.py +3 -3
- cli/docs.html +10 -30
- cli/guide/getting-started.html +6 -8
- cli/guide/tools.html +6 -3
- cli/hook_dispatch.py +3 -0
- cli/hook_edit_ledger.py +7 -1
- cli/hook_terse_advisor.py +3 -1
- cli/mcp_server.py +21 -3
- cli/tools/shell.py +141 -20
- cli/ui/components/settings.js +0 -2
- {code_context_control-2.51.0.dist-info → code_context_control-2.52.0.dist-info}/METADATA +9 -5
- {code_context_control-2.51.0.dist-info → code_context_control-2.52.0.dist-info}/RECORD +25 -25
- {code_context_control-2.51.0.dist-info → code_context_control-2.52.0.dist-info}/WHEEL +1 -1
- core/ide.py +5 -23
- services/artifact_defs.py +6 -0
- services/embedding_index.py +16 -5
- services/vector_store.py +21 -5
- tui/screens/claudemd_view.py +1 -1
- tui/screens/init_view.py +1 -1
- tui/screens/mcp_view.py +1 -1
- {code_context_control-2.51.0.dist-info → code_context_control-2.52.0.dist-info}/entry_points.txt +0 -0
- {code_context_control-2.51.0.dist-info → code_context_control-2.52.0.dist-info}/licenses/LICENSE +0 -0
- {code_context_control-2.51.0.dist-info → code_context_control-2.52.0.dist-info}/top_level.txt +0 -0
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.
|
|
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
|
-
"
|
|
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]: "
|
|
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,
|
|
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
|
|
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
|
-
|
|
4399
|
-
|
|
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
|
|
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
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
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
|
|
4478
|
-
if ide_name
|
|
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
|
|
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",
|
|
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
|
|
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
|
-
#
|
|
4997
|
-
#
|
|
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", "
|
|
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
|
|
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
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
5243
|
-
#
|
|
5244
|
-
#
|
|
5245
|
-
|
|
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
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
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
|
-
|
|
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"
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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>
|
|
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 .
|
|
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><
|
|
1108
|
-
|
|
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>
|
|
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>
|
|
1128
|
-
|
|
1129
|
-
<li>Auto-detected when <code
|
|
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
|
|
cli/guide/getting-started.html
CHANGED
|
@@ -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
|
|
193
|
+
Generated instruction files (<code>CLAUDE.md</code>, <code>AGENTS.md</code>) wrap C3 content in a <code><!-- C3:BEGIN … --></code> / <code><!-- C3:END --></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>
|
|
223
|
-
<pre><code>c3 install-mcp
|
|
224
|
-
<p>Updates <code
|
|
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 (
|
|
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>)
|
|
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
|
|
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(
|