code-context-control 2.34.0__py3-none-any.whl → 2.36.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/c3.py +172 -24
- cli/commands/parser.py +4 -0
- cli/guide/bitbucket.html +527 -0
- cli/guide/getting-started.html +441 -0
- cli/guide/index.html +611 -0
- cli/guide/oracle.html +385 -0
- cli/guide/shared.css +637 -0
- cli/guide/tools.html +1204 -0
- cli/guide/workflow.html +1193 -0
- cli/hub_server.py +10 -0
- cli/mcp_server.py +4 -3
- cli/server.py +11 -1
- cli/tools/edits.py +7 -2
- {code_context_control-2.34.0.dist-info → code_context_control-2.36.0.dist-info}/METADATA +30 -7
- {code_context_control-2.34.0.dist-info → code_context_control-2.36.0.dist-info}/RECORD +26 -18
- core/config.py +6 -0
- services/agents.py +250 -0
- services/context_snapshot.py +37 -0
- services/edit_ledger.py +24 -26
- services/git_context.py +243 -0
- services/session_manager.py +17 -0
- services/version_tracker.py +8 -16
- {code_context_control-2.34.0.dist-info → code_context_control-2.36.0.dist-info}/WHEEL +0 -0
- {code_context_control-2.34.0.dist-info → code_context_control-2.36.0.dist-info}/entry_points.txt +0 -0
- {code_context_control-2.34.0.dist-info → code_context_control-2.36.0.dist-info}/licenses/LICENSE +0 -0
- {code_context_control-2.34.0.dist-info → code_context_control-2.36.0.dist-info}/top_level.txt +0 -0
cli/c3.py
CHANGED
|
@@ -85,7 +85,7 @@ console = Console() if HAS_RICH else None
|
|
|
85
85
|
# Config
|
|
86
86
|
CONFIG_DIR = ".c3"
|
|
87
87
|
CONFIG_FILE = ".c3/config.json"
|
|
88
|
-
__version__ = "2.
|
|
88
|
+
__version__ = "2.36.0"
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def _command_deps() -> CommandDeps:
|
|
@@ -912,6 +912,12 @@ def cmd_init(args):
|
|
|
912
912
|
health["instructions_file"] + " missing" not in " ".join(health["issues"])
|
|
913
913
|
else " [MISSING]"))
|
|
914
914
|
|
|
915
|
+
# Version-skew notice: this project's .c3 was written by an older C3.
|
|
916
|
+
stored_version = _safe_read_json(c3_dir / "config.json", "config").get("version")
|
|
917
|
+
if stored_version and _version_tuple(str(stored_version)) < _version_tuple(__version__):
|
|
918
|
+
print(f"\n [upgrade] Set up with C3 v{stored_version}; now running v{__version__}.")
|
|
919
|
+
print(" Run 'c3 init . --force' to re-apply MCP config, hooks, and docs.")
|
|
920
|
+
|
|
915
921
|
# Permission status (Claude Code only) — surface tier + stale-tool drift
|
|
916
922
|
try:
|
|
917
923
|
from core.ide import load_ide_config as _load_ide
|
|
@@ -3872,8 +3878,8 @@ _AGENTS_MD_CONTENT = _C3_COMPACT_WORKFLOW + """
|
|
|
3872
3878
|
This project uses project-scoped MCP servers. Ensure your `.codex/config.toml` includes:
|
|
3873
3879
|
```toml
|
|
3874
3880
|
[mcp_servers.c3]
|
|
3875
|
-
command = "
|
|
3876
|
-
args = ["
|
|
3881
|
+
command = "c3-mcp"
|
|
3882
|
+
args = ["--project", "."]
|
|
3877
3883
|
enabled = true
|
|
3878
3884
|
```
|
|
3879
3885
|
"""
|
|
@@ -3886,8 +3892,8 @@ This project uses project-scoped MCP servers. Ensure your `.gemini/settings.json
|
|
|
3886
3892
|
{
|
|
3887
3893
|
"mcpServers": {
|
|
3888
3894
|
"c3": {
|
|
3889
|
-
"command": "
|
|
3890
|
-
"args": ["
|
|
3895
|
+
"command": "c3-mcp",
|
|
3896
|
+
"args": ["--project", "."]
|
|
3891
3897
|
}
|
|
3892
3898
|
}
|
|
3893
3899
|
}
|
|
@@ -4215,11 +4221,17 @@ def _upsert_json_mcp_server(config_path: Path, config_key: str, server_name: str
|
|
|
4215
4221
|
return "updated" if previous_entry is not None else "written"
|
|
4216
4222
|
|
|
4217
4223
|
|
|
4218
|
-
def _ensure_project_session_configs(target: Path, server_script: str, primary_profile: str | None = None
|
|
4224
|
+
def _ensure_project_session_configs(target: Path, server_script: str, primary_profile: str | None = None,
|
|
4225
|
+
c3_mcp_exe: str | None = None) -> None:
|
|
4219
4226
|
"""Keep project-local Codex and Gemini MCP configs in sync for new sessions."""
|
|
4220
4227
|
# Ensure forward slashes for config portability and avoid Windows path-splitting issues
|
|
4221
4228
|
server_script_posix = Path(server_script).as_posix()
|
|
4222
|
-
|
|
4229
|
+
if c3_mcp_exe:
|
|
4230
|
+
mcp_command = c3_mcp_exe
|
|
4231
|
+
server_args = ["--project", target.as_posix()]
|
|
4232
|
+
else:
|
|
4233
|
+
mcp_command = "python"
|
|
4234
|
+
server_args = [server_script_posix, "--project", target.as_posix()]
|
|
4223
4235
|
|
|
4224
4236
|
if primary_profile != "codex":
|
|
4225
4237
|
codex_path = target / ".codex" / "config.toml"
|
|
@@ -4228,7 +4240,7 @@ def _ensure_project_session_configs(target: Path, server_script: str, primary_pr
|
|
|
4228
4240
|
codex_path,
|
|
4229
4241
|
"mcp_servers.c3",
|
|
4230
4242
|
{
|
|
4231
|
-
"command":
|
|
4243
|
+
"command": mcp_command,
|
|
4232
4244
|
"args": server_args,
|
|
4233
4245
|
"enabled": True,
|
|
4234
4246
|
},
|
|
@@ -4242,14 +4254,14 @@ def _ensure_project_session_configs(target: Path, server_script: str, primary_pr
|
|
|
4242
4254
|
"mcpServers",
|
|
4243
4255
|
"c3",
|
|
4244
4256
|
{
|
|
4245
|
-
"command":
|
|
4257
|
+
"command": mcp_command,
|
|
4246
4258
|
"args": server_args,
|
|
4247
4259
|
},
|
|
4248
4260
|
)
|
|
4249
4261
|
print(f"{gemini_state.capitalize()} {gemini_path}")
|
|
4250
4262
|
|
|
4251
4263
|
|
|
4252
|
-
def _ensure_global_session_fallbacks(server_script: str) -> None:
|
|
4264
|
+
def _ensure_global_session_fallbacks(server_script: str, c3_mcp_exe: str | None = None) -> None:
|
|
4253
4265
|
"""Keep user-global Codex/Gemini MCP configs pointing at C3.
|
|
4254
4266
|
|
|
4255
4267
|
These fallback entries omit `--project` so the MCP server can resolve the
|
|
@@ -4257,7 +4269,9 @@ def _ensure_global_session_fallbacks(server_script: str) -> None:
|
|
|
4257
4269
|
does not yet have project-local Codex/Gemini config files.
|
|
4258
4270
|
"""
|
|
4259
4271
|
server_script_posix = Path(server_script).as_posix()
|
|
4260
|
-
|
|
4272
|
+
# With the installed entry point, no script path is needed; --project stays
|
|
4273
|
+
# omitted so the server resolves the working directory at session start.
|
|
4274
|
+
fallback_args = [] if c3_mcp_exe else [server_script_posix]
|
|
4261
4275
|
|
|
4262
4276
|
codex_path = Path.home() / ".codex" / "config.toml"
|
|
4263
4277
|
try:
|
|
@@ -4266,7 +4280,7 @@ def _ensure_global_session_fallbacks(server_script: str) -> None:
|
|
|
4266
4280
|
codex_path,
|
|
4267
4281
|
"mcp_servers.c3",
|
|
4268
4282
|
{
|
|
4269
|
-
"command": "python",
|
|
4283
|
+
"command": c3_mcp_exe or "python",
|
|
4270
4284
|
"args": fallback_args,
|
|
4271
4285
|
"enabled": True,
|
|
4272
4286
|
},
|
|
@@ -4282,7 +4296,7 @@ def _ensure_global_session_fallbacks(server_script: str) -> None:
|
|
|
4282
4296
|
"mcpServers",
|
|
4283
4297
|
"c3",
|
|
4284
4298
|
{
|
|
4285
|
-
"command": sys.executable,
|
|
4299
|
+
"command": c3_mcp_exe or sys.executable,
|
|
4286
4300
|
"args": fallback_args,
|
|
4287
4301
|
},
|
|
4288
4302
|
)
|
|
@@ -4825,13 +4839,25 @@ def cmd_install_mcp(args):
|
|
|
4825
4839
|
# Use forward slashes for cross-platform compatibility in config files
|
|
4826
4840
|
server_script = (cli_dir / server_filename).as_posix()
|
|
4827
4841
|
|
|
4828
|
-
#
|
|
4829
|
-
#
|
|
4830
|
-
#
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4842
|
+
# Prefer the installed `c3-mcp` console script for direct mode. It survives C3
|
|
4843
|
+
# upgrades (pip/pipx reinstall to the same launcher path) and keeps the source-tree
|
|
4844
|
+
# location out of every project's MCP config, so upgrading no longer requires
|
|
4845
|
+
# re-running install-mcp per project. Fall back to invoking the source script with
|
|
4846
|
+
# `python` when running from a checkout with no installed entry point, or in proxy
|
|
4847
|
+
# mode (which has no console script).
|
|
4848
|
+
import shutil
|
|
4849
|
+
c3_mcp_exe = None
|
|
4850
|
+
if mcp_mode != "proxy":
|
|
4851
|
+
_found = shutil.which("c3-mcp")
|
|
4852
|
+
if _found:
|
|
4853
|
+
c3_mcp_exe = Path(_found).resolve().as_posix()
|
|
4854
|
+
|
|
4855
|
+
# On Windows, Gemini CLI splits command args by space, so the script path stays a
|
|
4856
|
+
# single arg. 'python' keeps the source fallback portable across platforms.
|
|
4857
|
+
if c3_mcp_exe:
|
|
4858
|
+
new_entry = {"command": c3_mcp_exe, "args": ["--project", "."]}
|
|
4859
|
+
else:
|
|
4860
|
+
new_entry = {"command": "python", "args": [server_script, "--project", "."]}
|
|
4835
4861
|
if profile.needs_type_field:
|
|
4836
4862
|
new_entry["type"] = "stdio"
|
|
4837
4863
|
|
|
@@ -4865,7 +4891,10 @@ def cmd_install_mcp(args):
|
|
|
4865
4891
|
try:
|
|
4866
4892
|
if profile.config_format == "toml":
|
|
4867
4893
|
# Codex uses TOML: [mcp_servers.c3] with command/args
|
|
4868
|
-
|
|
4894
|
+
if c3_mcp_exe:
|
|
4895
|
+
toml_entries = {"command": c3_mcp_exe, "args": ["--project", str(target)]}
|
|
4896
|
+
else:
|
|
4897
|
+
toml_entries = {"command": sys.executable, "args": [server_script, "--project", str(target)]}
|
|
4869
4898
|
if profile.name == "codex":
|
|
4870
4899
|
# Codex supports explicit enable/disable per server.
|
|
4871
4900
|
toml_entries["enabled"] = True
|
|
@@ -4895,8 +4924,8 @@ def cmd_install_mcp(args):
|
|
|
4895
4924
|
|
|
4896
4925
|
print(f"Wrote {mcp_config_path}")
|
|
4897
4926
|
if profile.name in {"codex", "gemini"}:
|
|
4898
|
-
_ensure_project_session_configs(target, server_script, primary_profile=profile.name)
|
|
4899
|
-
_ensure_global_session_fallbacks(server_script)
|
|
4927
|
+
_ensure_project_session_configs(target, server_script, primary_profile=profile.name, c3_mcp_exe=c3_mcp_exe)
|
|
4928
|
+
_ensure_global_session_fallbacks(server_script, c3_mcp_exe=c3_mcp_exe)
|
|
4900
4929
|
|
|
4901
4930
|
# ── Persist IDE choice to .c3/config.json ──
|
|
4902
4931
|
c3_config_dir = target / ".c3"
|
|
@@ -6342,6 +6371,123 @@ def _run_swe_bench_lite(args, project_path):
|
|
|
6342
6371
|
pass
|
|
6343
6372
|
|
|
6344
6373
|
|
|
6374
|
+
def _version_tuple(v: str) -> tuple:
|
|
6375
|
+
"""Best-effort numeric version tuple for comparisons ('2.36.0' -> (2, 36, 0))."""
|
|
6376
|
+
parts = []
|
|
6377
|
+
for chunk in str(v or "").split("."):
|
|
6378
|
+
digits = ""
|
|
6379
|
+
for ch in chunk:
|
|
6380
|
+
if ch.isdigit():
|
|
6381
|
+
digits += ch
|
|
6382
|
+
else:
|
|
6383
|
+
break
|
|
6384
|
+
parts.append(int(digits) if digits else 0)
|
|
6385
|
+
return tuple(parts) or (0,)
|
|
6386
|
+
|
|
6387
|
+
|
|
6388
|
+
def _latest_pypi_version(package: str = "code-context-control", timeout: float = 5.0) -> str | None:
|
|
6389
|
+
"""Best-effort latest release of `package` on PyPI; None if unreachable."""
|
|
6390
|
+
import urllib.request
|
|
6391
|
+
try:
|
|
6392
|
+
url = f"https://pypi.org/pypi/{package}/json"
|
|
6393
|
+
with urllib.request.urlopen(url, timeout=timeout) as resp:
|
|
6394
|
+
data = json.loads(resp.read().decode("utf-8", "replace"))
|
|
6395
|
+
return (data.get("info") or {}).get("version")
|
|
6396
|
+
except Exception:
|
|
6397
|
+
return None
|
|
6398
|
+
|
|
6399
|
+
|
|
6400
|
+
def _installed_distribution(package: str = "code-context-control"):
|
|
6401
|
+
"""Return the installed Distribution for `package`, or None when running from source."""
|
|
6402
|
+
try:
|
|
6403
|
+
from importlib import metadata
|
|
6404
|
+
return metadata.distribution(package)
|
|
6405
|
+
except Exception:
|
|
6406
|
+
return None
|
|
6407
|
+
|
|
6408
|
+
|
|
6409
|
+
def _is_editable_install(package: str = "code-context-control") -> bool:
|
|
6410
|
+
"""True when `package` is pip-installed in editable/development mode."""
|
|
6411
|
+
dist = _installed_distribution(package)
|
|
6412
|
+
if dist is None:
|
|
6413
|
+
return False
|
|
6414
|
+
try:
|
|
6415
|
+
text = dist.read_text("direct_url.json")
|
|
6416
|
+
if text:
|
|
6417
|
+
return bool(json.loads(text).get("dir_info", {}).get("editable"))
|
|
6418
|
+
except Exception:
|
|
6419
|
+
pass
|
|
6420
|
+
return False
|
|
6421
|
+
|
|
6422
|
+
|
|
6423
|
+
def cmd_upgrade(args):
|
|
6424
|
+
"""Upgrade C3 to the latest PyPI release (or just check with --check)."""
|
|
6425
|
+
current = __version__
|
|
6426
|
+
latest = _latest_pypi_version()
|
|
6427
|
+
if latest is None:
|
|
6428
|
+
print(" Could not reach PyPI to check for updates (offline?).")
|
|
6429
|
+
elif _version_tuple(latest) <= _version_tuple(current):
|
|
6430
|
+
print(f" C3 is up to date (v{current}).")
|
|
6431
|
+
return
|
|
6432
|
+
else:
|
|
6433
|
+
print(f" Update available: v{current} -> v{latest}")
|
|
6434
|
+
|
|
6435
|
+
if getattr(args, "check", False):
|
|
6436
|
+
return
|
|
6437
|
+
|
|
6438
|
+
if _installed_distribution() is None:
|
|
6439
|
+
print(" C3 is running from a source checkout (not pip-installed).")
|
|
6440
|
+
print(" Update with: git pull")
|
|
6441
|
+
return
|
|
6442
|
+
if _is_editable_install():
|
|
6443
|
+
print(" C3 is installed in editable/development mode (pip install -e .).")
|
|
6444
|
+
print(" Update with: git pull")
|
|
6445
|
+
return
|
|
6446
|
+
|
|
6447
|
+
print(" Upgrading via pip (this may take a minute)...")
|
|
6448
|
+
cmd = [sys.executable, "-m", "pip", "install", "-U", "code-context-control[tui]"]
|
|
6449
|
+
try:
|
|
6450
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
6451
|
+
except Exception as e:
|
|
6452
|
+
print(f" Upgrade failed to launch pip: {e}")
|
|
6453
|
+
sys.exit(1)
|
|
6454
|
+
if result.returncode != 0:
|
|
6455
|
+
print(" pip upgrade failed:")
|
|
6456
|
+
print((result.stderr or result.stdout or "").strip()[-1000:])
|
|
6457
|
+
sys.exit(1)
|
|
6458
|
+
print(" Upgraded to the latest release. Restart your IDE's MCP server to load it.")
|
|
6459
|
+
print(" In each project, run c3 init . --force to apply any migrations.")
|
|
6460
|
+
|
|
6461
|
+
|
|
6462
|
+
def _launch_tui() -> None:
|
|
6463
|
+
"""Launch the interactive TUI — what `c3` with no arguments does.
|
|
6464
|
+
|
|
6465
|
+
Runs tui/main.py as a subprocess so its bare `from screens...` imports resolve
|
|
6466
|
+
(its own directory lands on sys.path[0]); the package root goes on PYTHONPATH for
|
|
6467
|
+
cli/services imports. Falls back to help text when the optional [tui] extra
|
|
6468
|
+
(textual) is not installed.
|
|
6469
|
+
"""
|
|
6470
|
+
pkg_root = Path(__file__).resolve().parent.parent
|
|
6471
|
+
tui_main = pkg_root / "tui" / "main.py"
|
|
6472
|
+
try:
|
|
6473
|
+
import textual # noqa: F401
|
|
6474
|
+
except Exception:
|
|
6475
|
+
print("The interactive TUI needs the optional 'textual' dependency.")
|
|
6476
|
+
print(' Install it with: pip install "code-context-control[tui]"')
|
|
6477
|
+
print(" Or run c3 --help to see all commands.")
|
|
6478
|
+
return
|
|
6479
|
+
if not tui_main.exists():
|
|
6480
|
+
print("TUI entry point not found. Run c3 --help for commands.")
|
|
6481
|
+
return
|
|
6482
|
+
env = os.environ.copy()
|
|
6483
|
+
existing_pp = env.get("PYTHONPATH")
|
|
6484
|
+
env["PYTHONPATH"] = str(pkg_root) + (os.pathsep + existing_pp if existing_pp else "")
|
|
6485
|
+
try:
|
|
6486
|
+
subprocess.run([sys.executable, str(tui_main)], env=env)
|
|
6487
|
+
except KeyboardInterrupt:
|
|
6488
|
+
pass
|
|
6489
|
+
|
|
6490
|
+
|
|
6345
6491
|
def main():
|
|
6346
6492
|
try:
|
|
6347
6493
|
from services import error_reporting
|
|
@@ -6353,7 +6499,8 @@ def main():
|
|
|
6353
6499
|
args = parser.parse_args()
|
|
6354
6500
|
|
|
6355
6501
|
if not args.command:
|
|
6356
|
-
|
|
6502
|
+
# Bare `c3` launches the interactive TUI (replaces the old c3.bat wrapper).
|
|
6503
|
+
_launch_tui()
|
|
6357
6504
|
return
|
|
6358
6505
|
|
|
6359
6506
|
commands = {
|
|
@@ -6382,6 +6529,7 @@ def main():
|
|
|
6382
6529
|
"hub": cmd_hub,
|
|
6383
6530
|
"bitbucket": cmd_bitbucket,
|
|
6384
6531
|
"oracle": cmd_oracle,
|
|
6532
|
+
"upgrade": cmd_upgrade,
|
|
6385
6533
|
}
|
|
6386
6534
|
|
|
6387
6535
|
cmd_func = commands.get(args.command)
|
cli/commands/parser.py
CHANGED
|
@@ -23,6 +23,10 @@ def build_parser(version: str, parse_cli_ide_arg):
|
|
|
23
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
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
25
|
|
|
26
|
+
p_upgrade = subparsers.add_parser("upgrade", help="Upgrade C3 to the latest PyPI release")
|
|
27
|
+
p_upgrade.add_argument("--check", action="store_true",
|
|
28
|
+
help="Only report whether a newer version exists; don't install")
|
|
29
|
+
|
|
26
30
|
p_index = subparsers.add_parser("index", help="Rebuild code index")
|
|
27
31
|
p_index.add_argument("--max-files", type=int, default=500)
|
|
28
32
|
|