gemcode 0.3.69__tar.gz → 0.3.70__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. {gemcode-0.3.69/src/gemcode.egg-info → gemcode-0.3.70}/PKG-INFO +1 -1
  2. {gemcode-0.3.69 → gemcode-0.3.70}/pyproject.toml +1 -1
  3. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/callbacks.py +34 -0
  4. gemcode-0.3.70/src/gemcode/policy_profile.py +135 -0
  5. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/repl_slash.py +7 -0
  6. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/session_runtime.py +10 -0
  7. {gemcode-0.3.69 → gemcode-0.3.70/src/gemcode.egg-info}/PKG-INFO +1 -1
  8. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode.egg-info/SOURCES.txt +1 -0
  9. {gemcode-0.3.69 → gemcode-0.3.70}/LICENSE +0 -0
  10. {gemcode-0.3.69 → gemcode-0.3.70}/MANIFEST.in +0 -0
  11. {gemcode-0.3.69 → gemcode-0.3.70}/README.md +0 -0
  12. {gemcode-0.3.69 → gemcode-0.3.70}/setup.cfg +0 -0
  13. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/__init__.py +0 -0
  14. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/__main__.py +0 -0
  15. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/agent.py +0 -0
  16. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/audit.py +0 -0
  17. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/autocompact.py +0 -0
  18. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/capability_routing.py +0 -0
  19. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/cli.py +0 -0
  20. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/compaction.py +0 -0
  21. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/computer_use/__init__.py +0 -0
  22. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/computer_use/browser_computer.py +0 -0
  23. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/config.py +0 -0
  24. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/context_budget.py +0 -0
  25. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/context_warning.py +0 -0
  26. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/credentials.py +0 -0
  27. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/dynamic_policy.py +0 -0
  28. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/hitl_session.py +0 -0
  29. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/hooks.py +0 -0
  30. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/intent_classifier.py +0 -0
  31. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/interactions.py +0 -0
  32. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/invoke.py +0 -0
  33. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/kairos_daemon.py +0 -0
  34. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/limits.py +0 -0
  35. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/live_audio_engine.py +0 -0
  36. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/logging_config.py +0 -0
  37. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/mcp_loader.py +0 -0
  38. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/memory/__init__.py +0 -0
  39. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/memory/embedding_memory_service.py +0 -0
  40. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/memory/file_memory_service.py +0 -0
  41. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/modality_tools.py +0 -0
  42. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/model_errors.py +0 -0
  43. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/model_routing.py +0 -0
  44. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/openapi_loader.py +0 -0
  45. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/paths.py +0 -0
  46. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/permissions.py +0 -0
  47. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/plugins/__init__.py +0 -0
  48. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  49. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  50. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/pricing.py +0 -0
  51. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/prompt_suggestions.py +0 -0
  52. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/query/__init__.py +0 -0
  53. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/query/config.py +0 -0
  54. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/query/deps.py +0 -0
  55. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/query/engine.py +0 -0
  56. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/query/stop_hooks.py +0 -0
  57. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/query/token_budget.py +0 -0
  58. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/query/transitions.py +0 -0
  59. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/refine.py +0 -0
  60. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/repl_commands.py +0 -0
  61. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/review_agent.py +0 -0
  62. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/session_store.py +0 -0
  63. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/slash_commands.py +0 -0
  64. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/thinking.py +0 -0
  65. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tool_prompt_manifest.py +0 -0
  66. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tool_registry.py +0 -0
  67. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tool_result_store.py +0 -0
  68. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/__init__.py +0 -0
  69. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/bash.py +0 -0
  70. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/browser.py +0 -0
  71. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/edit.py +0 -0
  72. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/filesystem.py +0 -0
  73. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/notebook.py +0 -0
  74. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/notes.py +0 -0
  75. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/repo_map.py +0 -0
  76. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/search.py +0 -0
  77. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/shell.py +0 -0
  78. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/shell_gate.py +0 -0
  79. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/subtask.py +0 -0
  80. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/tasks.py +0 -0
  81. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/think.py +0 -0
  82. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/todo.py +0 -0
  83. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/web.py +0 -0
  84. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools/web_search.py +0 -0
  85. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tools_inspector.py +0 -0
  86. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/trust.py +0 -0
  87. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tui/input_handler.py +0 -0
  88. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tui/scrollback.py +0 -0
  89. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tui/spinner.py +0 -0
  90. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tui/welcome_banner.py +0 -0
  91. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/tui/welcome_rich.py +0 -0
  92. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/version.py +0 -0
  93. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/vertex.py +0 -0
  94. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/web/__init__.py +0 -0
  95. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/web/claude_sse_adapter.py +0 -0
  96. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/web/terminal_repl.py +0 -0
  97. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode/workspace_hints.py +0 -0
  98. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode.egg-info/dependency_links.txt +0 -0
  99. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode.egg-info/entry_points.txt +0 -0
  100. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode.egg-info/requires.txt +0 -0
  101. {gemcode-0.3.69 → gemcode-0.3.70}/src/gemcode.egg-info/top_level.txt +0 -0
  102. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_agent_instruction.py +0 -0
  103. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_autocompact.py +0 -0
  104. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_capability_routing.py +0 -0
  105. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_claude_web_adapter_sse.py +0 -0
  106. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_cli_init.py +0 -0
  107. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_computer_use_permissions.py +0 -0
  108. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_context_budget.py +0 -0
  109. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_context_warning.py +0 -0
  110. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_credentials.py +0 -0
  111. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_interactive_permission_ask.py +0 -0
  112. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_kairos_scheduler.py +0 -0
  113. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_modality_tools.py +0 -0
  114. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_model_error_retry.py +0 -0
  115. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_model_errors.py +0 -0
  116. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_model_routing.py +0 -0
  117. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_paths.py +0 -0
  118. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_permissions.py +0 -0
  119. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_prompt_suggestions.py +0 -0
  120. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_repl_commands.py +0 -0
  121. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_repl_slash.py +0 -0
  122. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_slash_commands.py +0 -0
  123. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_thinking_config.py +0 -0
  124. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_token_budget.py +0 -0
  125. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_tool_context_circulation.py +0 -0
  126. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_tools.py +0 -0
  127. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_tools_inspector.py +0 -0
  128. {gemcode-0.3.69 → gemcode-0.3.70}/tests/test_workspace_hints.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.69
3
+ Version: 0.3.70
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.3.69"
7
+ version = "0.3.70"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -45,6 +45,9 @@ _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"
48
51
 
49
52
  def _truthy_env(name: str, *, default: bool = False) -> bool:
50
53
  v = os.environ.get(name)
@@ -165,9 +168,11 @@ def make_before_tool_callback(cfg: GemCodeConfig):
165
168
  object.__setattr__(cfg, "_risk_score", cur)
166
169
  # Writes / shell are inherently higher risk; allow more evidence.
167
170
  if name in MUTATING_TOOLS:
171
+ st[_RISK_HAD_WRITE] = True
168
172
  cur = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
169
173
  object.__setattr__(cfg, "_risk_score", min(1.0, cur + 0.12))
170
174
  if name in SHELL_TOOLS:
175
+ st[_RISK_HAD_SHELL] = True
171
176
  cur = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
172
177
  object.__setattr__(cfg, "_risk_score", min(1.0, cur + 0.08))
173
178
  except Exception:
@@ -423,9 +428,17 @@ def make_after_tool_callback(cfg: GemCodeConfig):
423
428
  cur = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
424
429
  bump = 0.0
425
430
  if err:
431
+ try:
432
+ st[_RISK_HAD_FAILURE] = True
433
+ except Exception:
434
+ pass
426
435
  bump += 0.15
427
436
  if isinstance(tool_response, dict) and isinstance(tool_response.get("exit_code"), int):
428
437
  if int(tool_response["exit_code"]) != 0:
438
+ try:
439
+ st[_RISK_HAD_FAILURE] = True
440
+ except Exception:
441
+ pass
429
442
  bump += 0.10
430
443
  # Test/build failures should boost evidence allowance more.
431
444
  if name in ("bash", "run_command"):
@@ -438,6 +451,27 @@ def make_after_tool_callback(cfg: GemCodeConfig):
438
451
  object.__setattr__(cfg, "_risk_score", cur)
439
452
  except Exception:
440
453
  pass
454
+
455
+ # Persist repo calibration profile (best-effort).
456
+ try:
457
+ files = st.get(_RISK_FILES_TOUCHED, []) or []
458
+ files_n = len(files) if isinstance(files, list) else 0
459
+ tool_calls = int(st.get(_RISK_TOOL_CALLS, 0) or 0)
460
+ had_shell = bool(st.get(_RISK_HAD_SHELL, False))
461
+ had_write = bool(st.get(_RISK_HAD_WRITE, False))
462
+ had_failure = bool(st.get(_RISK_HAD_FAILURE, False))
463
+ from gemcode.policy_profile import update_profile
464
+ prof = update_profile(
465
+ cfg.project_root,
466
+ files_touched=files_n,
467
+ tool_calls=tool_calls,
468
+ had_shell=had_shell,
469
+ had_write=had_write,
470
+ had_failure=had_failure,
471
+ )
472
+ object.__setattr__(cfg, "_policy_profile", prof.to_dict())
473
+ except Exception:
474
+ pass
441
475
  # ── Shell hooks: post_tool_use ────────────────────────────────────────
442
476
  try:
443
477
  from gemcode.hooks import run_post_tool_use_hook
@@ -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
+
@@ -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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.69
3
+ Version: 0.3.70
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -32,6 +32,7 @@ src/gemcode/model_routing.py
32
32
  src/gemcode/openapi_loader.py
33
33
  src/gemcode/paths.py
34
34
  src/gemcode/permissions.py
35
+ src/gemcode/policy_profile.py
35
36
  src/gemcode/pricing.py
36
37
  src/gemcode/prompt_suggestions.py
37
38
  src/gemcode/refine.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes