gemcode 0.3.69__py3-none-any.whl → 0.3.71__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.
gemcode/callbacks.py CHANGED
@@ -45,6 +45,12 @@ _LAST_CONTEXT_PCT = "gemcode:last_context_percent_left"
45
45
  _LAST_CONTEXT_LEVEL = "gemcode:last_context_alert_level"
46
46
  _RISK_FILES_TOUCHED = "gemcode:risk_files_touched"
47
47
  _RISK_TOOL_CALLS = "gemcode:risk_tool_calls"
48
+ _RISK_HAD_SHELL = "gemcode:risk_had_shell"
49
+ _RISK_HAD_WRITE = "gemcode:risk_had_write"
50
+ _RISK_HAD_FAILURE = "gemcode:risk_had_failure"
51
+ _TOOL_GROUP_CHARS = "gemcode:tool_group_chars"
52
+ _TOOL_GROUP_EXCEEDED = "gemcode:tool_group_budget_exceeded"
53
+ _TOOL_SEQ = "gemcode:tool_seq"
48
54
 
49
55
  def _truthy_env(name: str, *, default: bool = False) -> bool:
50
56
  v = os.environ.get(name)
@@ -147,6 +153,8 @@ def make_before_tool_callback(cfg: GemCodeConfig):
147
153
  try:
148
154
  if tool_context is not None:
149
155
  st = tool_context.state
156
+ # Per-turn tool sequence (used for stable tool-result replacement keys).
157
+ st[_TOOL_SEQ] = int(st.get(_TOOL_SEQ, 0) or 0) + 1
150
158
  st[_RISK_TOOL_CALLS] = int(st.get(_RISK_TOOL_CALLS, 0) or 0) + 1
151
159
  if name == "read_file":
152
160
  p = (args or {}).get("path")
@@ -165,9 +173,11 @@ def make_before_tool_callback(cfg: GemCodeConfig):
165
173
  object.__setattr__(cfg, "_risk_score", cur)
166
174
  # Writes / shell are inherently higher risk; allow more evidence.
167
175
  if name in MUTATING_TOOLS:
176
+ st[_RISK_HAD_WRITE] = True
168
177
  cur = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
169
178
  object.__setattr__(cfg, "_risk_score", min(1.0, cur + 0.12))
170
179
  if name in SHELL_TOOLS:
180
+ st[_RISK_HAD_SHELL] = True
171
181
  cur = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
172
182
  object.__setattr__(cfg, "_risk_score", min(1.0, cur + 0.08))
173
183
  except Exception:
@@ -355,18 +365,40 @@ def make_after_tool_callback(cfg: GemCodeConfig):
355
365
  except Exception:
356
366
  pass
357
367
 
368
+ # Aggregate per-turn tool-result budget: if we already exceeded the budget,
369
+ # tighten caps further for the rest of this user message.
370
+ try:
371
+ if tool_context is not None:
372
+ st = tool_context.state
373
+ if bool(st.get(_TOOL_GROUP_EXCEEDED, False)):
374
+ effective_tool_chars = max(1500, int(effective_tool_chars * 0.5))
375
+ except Exception:
376
+ pass
377
+
358
378
  if (
359
379
  isinstance(tool_response, dict)
360
380
  and getattr(cfg, "tool_result_offload_enabled", False)
361
381
  and effective_tool_chars > 0
362
382
  ):
363
383
  try:
364
- from gemcode.tool_result_store import maybe_offload_tool_result
365
- new_payload, did = maybe_offload_tool_result(
384
+ from gemcode.tool_result_store import maybe_offload_tool_result_stable
385
+ seq = None
386
+ st = None
387
+ try:
388
+ if tool_context is not None:
389
+ st = tool_context.state
390
+ seq = int(st.get(_TOOL_SEQ, 0) or 0)
391
+ except Exception:
392
+ st = None
393
+ seq = None
394
+ new_payload, did = maybe_offload_tool_result_stable(
366
395
  project_root=cfg.project_root,
367
396
  tool_name=name,
397
+ args=args or {},
368
398
  payload=tool_response,
369
399
  max_inline_chars=int(effective_tool_chars),
400
+ state=st,
401
+ seq=seq,
370
402
  )
371
403
  if did and isinstance(new_payload, dict):
372
404
  tool_response = new_payload
@@ -387,6 +419,19 @@ def make_after_tool_callback(cfg: GemCodeConfig):
387
419
  st = tool_context.state
388
420
  except Exception:
389
421
  return tool_response if (truncated or offloaded) else None
422
+
423
+ # Update aggregate per-turn budget counters (best-effort).
424
+ try:
425
+ from gemcode.context_budget import estimate_obj_string_chars
426
+ budget = int(getattr(cfg, "tool_result_group_budget_chars", 0) or 0)
427
+ if budget > 0:
428
+ used = int(st.get(_TOOL_GROUP_CHARS, 0) or 0)
429
+ used += int(estimate_obj_string_chars(tool_response))
430
+ st[_TOOL_GROUP_CHARS] = used
431
+ if used >= budget:
432
+ st[_TOOL_GROUP_EXCEEDED] = True
433
+ except Exception:
434
+ pass
390
435
  err = isinstance(tool_response, dict) and tool_response.get("error")
391
436
  err_kind = (
392
437
  isinstance(tool_response, dict) and tool_response.get("error_kind")
@@ -423,9 +468,17 @@ def make_after_tool_callback(cfg: GemCodeConfig):
423
468
  cur = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
424
469
  bump = 0.0
425
470
  if err:
471
+ try:
472
+ st[_RISK_HAD_FAILURE] = True
473
+ except Exception:
474
+ pass
426
475
  bump += 0.15
427
476
  if isinstance(tool_response, dict) and isinstance(tool_response.get("exit_code"), int):
428
477
  if int(tool_response["exit_code"]) != 0:
478
+ try:
479
+ st[_RISK_HAD_FAILURE] = True
480
+ except Exception:
481
+ pass
429
482
  bump += 0.10
430
483
  # Test/build failures should boost evidence allowance more.
431
484
  if name in ("bash", "run_command"):
@@ -438,6 +491,27 @@ def make_after_tool_callback(cfg: GemCodeConfig):
438
491
  object.__setattr__(cfg, "_risk_score", cur)
439
492
  except Exception:
440
493
  pass
494
+
495
+ # Persist repo calibration profile (best-effort).
496
+ try:
497
+ files = st.get(_RISK_FILES_TOUCHED, []) or []
498
+ files_n = len(files) if isinstance(files, list) else 0
499
+ tool_calls = int(st.get(_RISK_TOOL_CALLS, 0) or 0)
500
+ had_shell = bool(st.get(_RISK_HAD_SHELL, False))
501
+ had_write = bool(st.get(_RISK_HAD_WRITE, False))
502
+ had_failure = bool(st.get(_RISK_HAD_FAILURE, False))
503
+ from gemcode.policy_profile import update_profile
504
+ prof = update_profile(
505
+ cfg.project_root,
506
+ files_touched=files_n,
507
+ tool_calls=tool_calls,
508
+ had_shell=had_shell,
509
+ had_write=had_write,
510
+ had_failure=had_failure,
511
+ )
512
+ object.__setattr__(cfg, "_policy_profile", prof.to_dict())
513
+ except Exception:
514
+ pass
441
515
  # ── Shell hooks: post_tool_use ────────────────────────────────────────
442
516
  try:
443
517
  from gemcode.hooks import run_post_tool_use_hook
gemcode/config.py CHANGED
@@ -60,6 +60,10 @@ def token_budget_invocation_reset() -> dict:
60
60
  "gemcode:bt_t0": t,
61
61
  "gemcode:bt_base_total_tokens": -1,
62
62
  "gemcode:bt_token_budget_stop": False,
63
+ # Tool-result aggregate budget (per user message)
64
+ "gemcode:tool_group_chars": 0,
65
+ "gemcode:tool_group_budget_exceeded": False,
66
+ "gemcode:tool_seq": 0,
63
67
  }
64
68
 
65
69
 
@@ -131,6 +135,12 @@ class GemCodeConfig:
131
135
  )
132
136
  )
133
137
 
138
+ # Aggregate tool-result budget per user message (approx. characters of tool payloads).
139
+ # When exceeded, GemCode tightens subsequent tool output caps for the remainder of the turn.
140
+ tool_result_group_budget_chars: int = field(
141
+ default_factory=lambda: int(os.environ.get("GEMCODE_TOOL_RESULT_GROUP_BUDGET_CHARS", "60000"))
142
+ )
143
+
134
144
  # When enabled, oversized tool outputs are offloaded to disk under
135
145
  # .gemcode/tool-results/ and replaced in history with stable refs + previews.
136
146
  # This reduces context bloat and improves prompt-cache stability.
@@ -0,0 +1,135 @@
1
+ """
2
+ Persistent per-repo policy profile.
3
+
4
+ Goal: make dynamic budgets self-tuning per repository without requiring manual
5
+ configuration. This stores lightweight rolling stats under `.gemcode/policy.json`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import time
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+
17
+ def _path(root: Path) -> Path:
18
+ d = root / ".gemcode"
19
+ d.mkdir(parents=True, exist_ok=True)
20
+ return d / "policy.json"
21
+
22
+
23
+ def _clamp(x: float, lo: float, hi: float) -> float:
24
+ return lo if x < lo else hi if x > hi else x
25
+
26
+
27
+ def _ema(prev: float, x: float, *, alpha: float) -> float:
28
+ return (alpha * x) + ((1.0 - alpha) * prev)
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class PolicyProfile:
33
+ # Rolling averages in [0,1] where possible.
34
+ failure_rate_ema: float = 0.0
35
+ shell_rate_ema: float = 0.0
36
+ write_rate_ema: float = 0.0
37
+ files_touched_ema: float = 0.0 # scaled 0..1 (e.g. 0.5 ~ 10 files)
38
+ updated_at: int = 0
39
+
40
+ def to_dict(self) -> dict[str, Any]:
41
+ return {
42
+ "failure_rate_ema": self.failure_rate_ema,
43
+ "shell_rate_ema": self.shell_rate_ema,
44
+ "write_rate_ema": self.write_rate_ema,
45
+ "files_touched_ema": self.files_touched_ema,
46
+ "updated_at": self.updated_at,
47
+ "version": 1,
48
+ }
49
+
50
+ @staticmethod
51
+ def from_dict(d: dict[str, Any]) -> "PolicyProfile":
52
+ try:
53
+ return PolicyProfile(
54
+ failure_rate_ema=float(d.get("failure_rate_ema", 0.0) or 0.0),
55
+ shell_rate_ema=float(d.get("shell_rate_ema", 0.0) or 0.0),
56
+ write_rate_ema=float(d.get("write_rate_ema", 0.0) or 0.0),
57
+ files_touched_ema=float(d.get("files_touched_ema", 0.0) or 0.0),
58
+ updated_at=int(d.get("updated_at", 0) or 0),
59
+ )
60
+ except Exception:
61
+ return PolicyProfile()
62
+
63
+
64
+ def load_profile(project_root: Path) -> PolicyProfile:
65
+ p = _path(project_root)
66
+ if not p.exists():
67
+ return PolicyProfile()
68
+ try:
69
+ raw = p.read_text(encoding="utf-8", errors="replace")
70
+ d = json.loads(raw) if raw.strip() else {}
71
+ if isinstance(d, dict):
72
+ return PolicyProfile.from_dict(d)
73
+ except Exception:
74
+ return PolicyProfile()
75
+ return PolicyProfile()
76
+
77
+
78
+ def save_profile(project_root: Path, profile: PolicyProfile) -> None:
79
+ p = _path(project_root)
80
+ p.write_text(
81
+ json.dumps(profile.to_dict(), ensure_ascii=False, indent=2),
82
+ encoding="utf-8",
83
+ errors="replace",
84
+ )
85
+
86
+
87
+ def update_profile(
88
+ project_root: Path,
89
+ *,
90
+ files_touched: int,
91
+ tool_calls: int,
92
+ had_shell: bool,
93
+ had_write: bool,
94
+ had_failure: bool,
95
+ alpha: float = 0.08,
96
+ ) -> PolicyProfile:
97
+ """
98
+ Update profile with a single-turn observation.
99
+
100
+ We scale files_touched into [0,1] via min(files/20, 1).
101
+ """
102
+ prof = load_profile(project_root)
103
+ alpha = _clamp(alpha, 0.01, 0.3)
104
+ ft_scaled = _clamp(float(files_touched) / 20.0, 0.0, 1.0)
105
+ fail = 1.0 if had_failure else 0.0
106
+ shell = 1.0 if had_shell else 0.0
107
+ write = 1.0 if had_write else 0.0
108
+ # tool_calls unused for now, but reserved for future calibration.
109
+ _ = tool_calls
110
+ updated = PolicyProfile(
111
+ failure_rate_ema=_ema(prof.failure_rate_ema, fail, alpha=alpha),
112
+ shell_rate_ema=_ema(prof.shell_rate_ema, shell, alpha=alpha),
113
+ write_rate_ema=_ema(prof.write_rate_ema, write, alpha=alpha),
114
+ files_touched_ema=_ema(prof.files_touched_ema, ft_scaled, alpha=alpha),
115
+ updated_at=int(time.time()),
116
+ )
117
+ save_profile(project_root, updated)
118
+ return updated
119
+
120
+
121
+ def calibrated_baseline_risk(profile: PolicyProfile) -> float:
122
+ """
123
+ Convert profile into a baseline risk prior for a repo.
124
+
125
+ Repos with frequent failures, many writes, and lots of files touched tend to
126
+ benefit from higher evidence budgets by default.
127
+ """
128
+ r = (
129
+ 0.55 * profile.failure_rate_ema
130
+ + 0.20 * profile.write_rate_ema
131
+ + 0.15 * profile.shell_rate_ema
132
+ + 0.10 * profile.files_touched_ema
133
+ )
134
+ return _clamp(r, 0.0, 0.8)
135
+
gemcode/repl_slash.py CHANGED
@@ -395,6 +395,7 @@ async def process_repl_slash(
395
395
  try:
396
396
  risk = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
397
397
  pct = getattr(cfg, "_context_percent_left", None)
398
+ prof = getattr(cfg, "_policy_profile", None)
398
399
  out(" Dynamic policy:")
399
400
  out(f" dynamic_token_policy: {getattr(cfg, 'dynamic_token_policy', True)}")
400
401
  out(f" dynamic_risk_policy: {getattr(cfg, 'dynamic_risk_policy', True)}")
@@ -402,6 +403,12 @@ async def process_repl_slash(
402
403
  out(f" risk_score: {risk:.2f}")
403
404
  if isinstance(pct, int):
404
405
  out(f" context_percent_left: {pct}%")
406
+ if isinstance(prof, dict):
407
+ try:
408
+ out(f" profile.failure_rate_ema: {float(prof.get('failure_rate_ema', 0.0) or 0.0):.2f}")
409
+ out(f" profile.files_touched_ema: {float(prof.get('files_touched_ema', 0.0) or 0.0):.2f}")
410
+ except Exception:
411
+ pass
405
412
  out()
406
413
  except Exception:
407
414
  pass
@@ -314,6 +314,16 @@ def _build_artifact_service(cfg: GemCodeConfig):
314
314
 
315
315
  def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None) -> Runner:
316
316
  """Construct Runner + SQLite session service + root LlmAgent."""
317
+ # Load per-repo calibration profile (self-tuning dynamic policy).
318
+ try:
319
+ from gemcode.policy_profile import calibrated_baseline_risk, load_profile
320
+ prof = load_profile(cfg.project_root)
321
+ base = calibrated_baseline_risk(prof)
322
+ cur = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
323
+ object.__setattr__(cfg, "_risk_score", max(cur, base))
324
+ object.__setattr__(cfg, "_policy_profile", prof.to_dict())
325
+ except Exception:
326
+ pass
317
327
  modality_tools = build_modality_extra_tools(cfg)
318
328
  merged_extra_tools: list | None
319
329
  if extra_tools:
@@ -18,6 +18,73 @@ from pathlib import Path
18
18
  from typing import Any
19
19
 
20
20
  _REF_PREFIX = "tool_result:"
21
+ _REPL_STATE_KEY = "gemcode:tool_replacement_state"
22
+
23
+
24
+ def _stable_key(tool_name: str, args: dict[str, Any] | None, seq: int | None) -> str:
25
+ """
26
+ Build a stable key for replacement decisions.
27
+
28
+ ADK does not expose tool_use_id directly to callbacks in all versions, so we use:
29
+ - per-turn tool sequence number (preferred when available)
30
+ - tool name
31
+ - a stable hash of args (best-effort)
32
+ """
33
+ import json
34
+ try:
35
+ args_s = json.dumps(args or {}, sort_keys=True, ensure_ascii=False)
36
+ except Exception:
37
+ args_s = str(args or {})
38
+ b = (f"{seq or 0}:{tool_name}:{args_s}").encode("utf-8", errors="replace")
39
+ return _sha256_bytes(b)
40
+
41
+
42
+ def maybe_offload_tool_result_stable(
43
+ *,
44
+ project_root: Path,
45
+ tool_name: str,
46
+ args: dict[str, Any] | None,
47
+ payload: Any,
48
+ max_inline_chars: int,
49
+ state: dict[str, Any] | None,
50
+ seq: int | None,
51
+ ) -> tuple[Any, bool]:
52
+ """
53
+ Stable offload wrapper.
54
+
55
+ - If we've already processed an identical tool call in this session, we re-apply
56
+ the exact same replacement structure to preserve prompt byte stability.
57
+ - Otherwise, we apply offload and remember the replacement result.
58
+ """
59
+ if state is None:
60
+ return maybe_offload_tool_result(
61
+ project_root=project_root,
62
+ tool_name=tool_name,
63
+ payload=payload,
64
+ max_inline_chars=max_inline_chars,
65
+ )
66
+
67
+ repl_state = state.get(_REPL_STATE_KEY)
68
+ if not isinstance(repl_state, dict):
69
+ repl_state = {}
70
+ state[_REPL_STATE_KEY] = repl_state
71
+
72
+ key = _stable_key(tool_name, args, seq)
73
+ if key in repl_state:
74
+ return repl_state[key], False
75
+
76
+ new_payload, did = maybe_offload_tool_result(
77
+ project_root=project_root,
78
+ tool_name=tool_name,
79
+ payload=payload,
80
+ max_inline_chars=max_inline_chars,
81
+ )
82
+ if did:
83
+ repl_state[key] = new_payload
84
+ else:
85
+ # Freeze "no replacement" decision too (prevents later shape drift).
86
+ repl_state[key] = payload
87
+ return new_payload, did
21
88
 
22
89
 
23
90
  def _store_dir(project_root: Path) -> Path:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.69
3
+ Version: 0.3.71
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -217,6 +217,16 @@ gemcode --yes "Add a module docstring to src/foo.py"
217
217
  gemcode --session mysess --yes "Continue: run tests and fix failures"
218
218
  ```
219
219
 
220
+ ### What GemCode writes to `.gemcode/`
221
+
222
+ GemCode keeps project-local state under `.gemcode/`:
223
+
224
+ - **`sessions.sqlite`**: session events/history (ADK `SqliteSessionService`)
225
+ - **`audit.log`**: JSONL audit trail for tool usage + model usage + stop reasons
226
+ - **`tool-results/`**: oversized tool outputs offloaded to stable refs (`tool_result:<sha>`)
227
+ - **`artifacts/`**: file artifacts (ADK `FileArtifactService`)
228
+ - **`policy.json`**: self-tuning per-repo profile used to calibrate dynamic budgets
229
+
220
230
  - **`--yes`**: allow mutating tools (`write_file`, `search_replace`). Shell execution is still restricted by the `.env.example` allowlist.
221
231
  - **`--session`**: Conversation history is stored under `.gemcode/sessions.sqlite` (ADK `SqliteSessionService`). Reuse the same `--session` id to continue.
222
232
  - **`--max-llm-calls`**: cap model↔tool iterations for this message (maps to ADK `RunConfig.max_llm_calls`). You can also set `GEMCODE_MAX_LLM_CALLS`.
@@ -243,6 +253,33 @@ gemcode --session mysess --yes "Continue: run tests and fix failures"
243
253
  - **Recovery-loop**: ADK `ReflectAndRetryToolPlugin`-based retries on tool failures.
244
254
  - Set `GEMCODE_ENABLE_TOOL_RECOVERY_RETRY=0` to disable.
245
255
  - Set `GEMCODE_TOOL_REFLECT_MAX_RETRIES=1` to control retries per tool.
256
+
257
+ ### Token efficiency (dynamic + intelligent)
258
+
259
+ GemCode optimizes tokens without losing capability by using a dynamic policy:
260
+
261
+ - **Context-pressure aware**: tool caps tighten when context is tight, loosen when there is room.
262
+ - **Risk/complexity aware**: caps increase for risky tasks (writes, shell, failures, many files).
263
+ - **Self-tuning per repo**: `.gemcode/policy.json` calibrates baseline evidence budgets over time.
264
+
265
+ Key env toggles:
266
+
267
+ - `GEMCODE_DYNAMIC_TOKEN_POLICY=0|1`
268
+ - `GEMCODE_DYNAMIC_RISK_POLICY=0|1`
269
+ - `GEMCODE_DYNAMIC_RISK_BOOST=<float>` (default `0.6`)
270
+ - `GEMCODE_TOOL_RESULT_OFFLOAD=0|1` (default `1`)
271
+
272
+ Live telemetry:
273
+
274
+ - `/status` shows `risk_score`, `context_percent_left`, and profile EMAs.
275
+
276
+ ### Stable tool output offloading (OpenClaude-style)
277
+
278
+ Oversized tool outputs are automatically offloaded and replaced with stable refs:
279
+
280
+ - Stored in: `.gemcode/tool-results/`
281
+ - References: `tool_result:<sha256>`
282
+ - Load on demand: `load_tool_result(ref)`
246
283
  - **Gemini thinking controls (Claude-like)**:
247
284
  - By default GemCode lets Gemini use its dynamic/adaptive thinking behavior.
248
285
  - Set `GEMCODE_DISABLE_THINKING=1` to force a best-effort “low thinking” mode:
@@ -301,12 +338,24 @@ the user’s project.
301
338
  - `list_directory`
302
339
  - `glob_files`
303
340
  - `grep_content`
341
+ - `repo_map`
342
+ - `web_search`
343
+ - `web_fetch`
344
+ - `notebook_read`
345
+ - `notebook_edit`
304
346
  - Mutating tools (require `--yes` unless your policy blocks them):
305
347
  - `write_file`
306
348
  - `search_replace`
307
349
  - Shell execution:
308
350
  - `run_command` (guarded by `GEMCODE_ALLOW_COMMANDS` from `.env.example` and
309
351
  `GEMCODE_PERMISSION_MODE`).
352
+ - `bash` (pipelines + redirects; supports `background=True`)
353
+
354
+ - Background task management (for processes started via `bash(..., background=True)`):
355
+ - `list_tasks`, `task_output`, `kill_task`
356
+
357
+ - Tool offload loader:
358
+ - `load_tool_result(ref)`
310
359
 
311
360
  Tool execution is still controlled by permission gates and then governed by
312
361
  GemCode’s circuit breaker + recovery behavior.
@@ -434,6 +483,23 @@ pip install -e ".[dev]"
434
483
  pytest
435
484
  ```
436
485
 
486
+ ## Release workflow (GitHub Actions → PyPI)
487
+
488
+ This repository publishes to PyPI on tag pushes:
489
+
490
+ - `.github/workflows/publish-pypi.yml` triggers on tags matching `v*`
491
+
492
+ Typical release steps:
493
+
494
+ ```bash
495
+ # 1) bump gemcode/pyproject.toml version
496
+ git add -A
497
+ git commit -m "release: vX.Y.Z"
498
+ git tag -a vX.Y.Z -m "vX.Y.Z"
499
+ git push origin HEAD
500
+ git push origin vX.Y.Z
501
+ ```
502
+
437
503
  ## References (local only)
438
504
 
439
505
  Do not commit proprietary leaked trees into this package. Keep `claude-code-leaked/` and similar folders outside version control or in a private mirror.
@@ -3,11 +3,11 @@ gemcode/__main__.py,sha256=EX2s1hxq2Yvli_-tnBN3w5Qv4bOjsBBbjyISF0pDIQw,37
3
3
  gemcode/agent.py,sha256=yft-8lLJbX4abM_bUW156JWfVT3b4OHO-_hIoKdVaOs,52032
4
4
  gemcode/audit.py,sha256=bh9uhXaeh8wqxqoZtz3ZAowd8Ndk1ss-mw9993Vlrgo,469
5
5
  gemcode/autocompact.py,sha256=77h5tgFzJ2rjrhlCL2oIc28IHwLbP4Pqlo7cSNgDwiA,6727
6
- gemcode/callbacks.py,sha256=JW9jiYBZwgqqZnX4zehd55dGHeVLCVQVU9zNE3SYS_s,28430
6
+ gemcode/callbacks.py,sha256=EYkJTRcHjCeqTgVZGat2Y3BYWg4tNtHl-cgl5cdUxpo,31076
7
7
  gemcode/capability_routing.py,sha256=yvQXwKtrfHXbgbNunU0Dxh9GCDN4cbySXIeccrdzr2o,3471
8
8
  gemcode/cli.py,sha256=kBXb4b4JCG3u0XewCJn8lCyOT62Y8bOvlVoDc2R-GKQ,25320
9
9
  gemcode/compaction.py,sha256=9YtA_qa23_8dHWVHx7AJwUduuI7jJQtq-m6sT8jgPWI,1186
10
- gemcode/config.py,sha256=it_F4haaUlveJXZkX8mGI_nmx1JHsh56XBSkLyUfQ4I,15101
10
+ gemcode/config.py,sha256=APBKPIpGvKd46Xt0ZGOZfVr8Tl6EAPbwqDsuM45jc1U,15609
11
11
  gemcode/context_budget.py,sha256=Nhox9vFBtLbb7jtO7cyGW1MxtN7SVjlIeQ7d-cgGyKM,10544
12
12
  gemcode/context_warning.py,sha256=Q8mg5Vojj7EglPhsGAVL7vb8ROLuHVPgdzw25yw-Q2c,4263
13
13
  gemcode/credentials.py,sha256=04v-rLD8_Ams69FQdof2FwcL3ZgsroGUnMcHNQFuBZo,1296
@@ -28,19 +28,20 @@ gemcode/model_routing.py,sha256=8oSgz4qjueEkuZ-ZLme2C5a_nAKUMAwMrV2vq634QyY,5174
28
28
  gemcode/openapi_loader.py,sha256=g_NZD8YL9_9iIJJ9qykhdbBrylJ1195A4FyHGC0mroc,4157
29
29
  gemcode/paths.py,sha256=U6cEH9jfIcSc4NO8Ke0jniZSiJTfCIJPvSMue3hR0ZU,768
30
30
  gemcode/permissions.py,sha256=0gQ63Ll-KPlZVU6KigIpwSwKL5-OWqYMB6a0x2wpc28,6766
31
+ gemcode/policy_profile.py,sha256=kcaKJQwLxAo3RjqfJJHl_G7B5GgTYKco0z3k5QcXsVY,3861
31
32
  gemcode/pricing.py,sha256=lftp0SwyDqOzHqC2-6XzgZZhjif5PLdCe1Q3wY-p6kQ,3558
32
33
  gemcode/prompt_suggestions.py,sha256=h-W_9LlfagS91PyoMEjEjsCqoG4XmIh3QBypA59HyGw,2553
33
34
  gemcode/refine.py,sha256=BijEZ4Z32wGa9aK_WottyAhZF-j0xEqRg5UpjedNv2A,7653
34
35
  gemcode/repl_commands.py,sha256=AHjDUhlq7IuSL6Fla17y0aeGZyp6snxwbMG7gz1PrRc,11330
35
- gemcode/repl_slash.py,sha256=y_nb-0PYRpW2At7mlA-hkpEybe6vdyjMiVSrjOTPb_g,48434
36
+ gemcode/repl_slash.py,sha256=4YZ4ZsPoWWQ6oV6WKxkXS7C6R55On0E4H1SH0oxqB88,48778
36
37
  gemcode/review_agent.py,sha256=4t7_5-aE60b4-EheJ_eSB_H2eQYf9GppKoui6jw0TME,5264
37
- gemcode/session_runtime.py,sha256=crpKBVYZnWeRkBHcbvBGuCc2Kw2hGhVHuM4uRCmXpOI,18771
38
+ gemcode/session_runtime.py,sha256=o65TYRkCiNKDk2o7CiTs7Trpm41RLI2g0HiK1QyvqQc,19216
38
39
  gemcode/session_store.py,sha256=POUT_QQf715c74jbXj0s5vCd4dlAgJz_CLsIWuEUoO0,6051
39
40
  gemcode/slash_commands.py,sha256=Qylzsj1notk0xN_hvd3CR4HD8g-l99UENDMcg1pKeBA,794
40
41
  gemcode/thinking.py,sha256=RanBf_x9fKv1o4DNyNXPLfOdn2xT0KybJb65nYgmMEE,4885
41
42
  gemcode/tool_prompt_manifest.py,sha256=MS_eSJg2BTp6yv1Ih2p93okPmnK3B2dYAMjnG6yaEVY,8695
42
43
  gemcode/tool_registry.py,sha256=ifqxtr2uLwEUwnJLKYLza_tIz-paaZediJa75y9MiyA,1795
43
- gemcode/tool_result_store.py,sha256=Wfm_JHLdYAI4jfjTLHOEAeK2yho9OCLUF_qErhj-wV0,4428
44
+ gemcode/tool_result_store.py,sha256=pkeV5ekvsp6v7679yOTB2Rof4vPv3RdSl8urzawofq4,6351
44
45
  gemcode/tools_inspector.py,sha256=okmu4PDYAQQ7nthDvuzSHmy2zArFTG4ftIPRadzLnxA,4100
45
46
  gemcode/trust.py,sha256=fxe57Xg6aL_KU24bQDUtD-rXjsNpaq7g-eQTInZnudE,1336
46
47
  gemcode/version.py,sha256=uwynYS-RmK8CDoqGtt8976kFkJv0zELkEAlwebnp_io,380
@@ -86,9 +87,9 @@ gemcode/tui/welcome_rich.py,sha256=8FEZzLXrzqly5JWiDgV9ooRV1LNXDk-CXV1a7K6ua-U,4
86
87
  gemcode/web/__init__.py,sha256=EysmUAWs6g-lmMk4VFljKfaHVrEgb_FiIzwQmBdORJc,40
87
88
  gemcode/web/claude_sse_adapter.py,sha256=HcNp0Lh4DdBZBLOpstsqa-VzfqAUrRngZ6FSuJ-mIMg,8609
88
89
  gemcode/web/terminal_repl.py,sha256=k2irvFGbCY8gDm_pbirR7b_cakaeafcctoTIvnJkVXk,3902
89
- gemcode-0.3.69.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
90
- gemcode-0.3.69.dist-info/METADATA,sha256=2yfwQj99pN5poyQL_zvyHGzXqmqWj-pCFYZuxj8KsX0,23695
91
- gemcode-0.3.69.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
92
- gemcode-0.3.69.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
93
- gemcode-0.3.69.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
94
- gemcode-0.3.69.dist-info/RECORD,,
90
+ gemcode-0.3.71.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
91
+ gemcode-0.3.71.dist-info/METADATA,sha256=OkyL8aZtBhHoGZhMZslyEHdaEeMQf8uwUAU75YTXHYY,25852
92
+ gemcode-0.3.71.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
93
+ gemcode-0.3.71.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
94
+ gemcode-0.3.71.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
95
+ gemcode-0.3.71.dist-info/RECORD,,