gemcode 0.3.77__py3-none-any.whl → 0.3.78__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/agent.py +42 -1
- gemcode/autotune.py +17 -3
- gemcode/cli.py +17 -0
- gemcode/config.py +6 -0
- gemcode/evals/harness.py +74 -20
- gemcode/repl_commands.py +131 -1
- gemcode/repl_slash.py +326 -3
- gemcode/trust.py +5 -0
- gemcode/tui/input_handler.py +8 -42
- gemcode/tui/scrollback.py +1 -0
- {gemcode-0.3.77.dist-info → gemcode-0.3.78.dist-info}/METADATA +119 -30
- {gemcode-0.3.77.dist-info → gemcode-0.3.78.dist-info}/RECORD +16 -16
- {gemcode-0.3.77.dist-info → gemcode-0.3.78.dist-info}/WHEEL +0 -0
- {gemcode-0.3.77.dist-info → gemcode-0.3.78.dist-info}/entry_points.txt +0 -0
- {gemcode-0.3.77.dist-info → gemcode-0.3.78.dist-info}/licenses/LICENSE +0 -0
- {gemcode-0.3.77.dist-info → gemcode-0.3.78.dist-info}/top_level.txt +0 -0
gemcode/agent.py
CHANGED
|
@@ -27,11 +27,49 @@ from gemcode.limits import make_before_model_limits_callback, make_before_model_
|
|
|
27
27
|
from gemcode.thinking import build_thinking_config
|
|
28
28
|
from gemcode.tools import build_function_tools
|
|
29
29
|
from gemcode.tool_prompt_manifest import build_tool_manifest
|
|
30
|
-
from gemcode.skills import
|
|
30
|
+
from gemcode.skills import (
|
|
31
|
+
build_skill_manifest_text,
|
|
32
|
+
expand_skill_text,
|
|
33
|
+
list_supporting_files,
|
|
34
|
+
load_skill,
|
|
35
|
+
)
|
|
31
36
|
from gemcode.output_styles import build_output_style_section
|
|
32
37
|
from gemcode.rules import build_rules_section
|
|
33
38
|
|
|
34
39
|
|
|
40
|
+
def _build_session_loaded_skills_section(cfg: GemCodeConfig) -> str:
|
|
41
|
+
"""Full bodies for GemSkills the user loaded with /gemskill (session-scoped)."""
|
|
42
|
+
names = list(getattr(cfg, "session_loaded_skill_names", None) or [])
|
|
43
|
+
if not names:
|
|
44
|
+
return ""
|
|
45
|
+
sid = getattr(cfg, "session_skill_expand_session_id", None) or ""
|
|
46
|
+
chunks: list[str] = []
|
|
47
|
+
seen: set[str] = set()
|
|
48
|
+
for raw in names:
|
|
49
|
+
sk_name = (raw or "").strip().lower()
|
|
50
|
+
if not sk_name or sk_name in seen:
|
|
51
|
+
continue
|
|
52
|
+
seen.add(sk_name)
|
|
53
|
+
s = load_skill(cfg.project_root, sk_name)
|
|
54
|
+
if s is None:
|
|
55
|
+
continue
|
|
56
|
+
expanded = expand_skill_text(s, arguments="", session_id=sid)
|
|
57
|
+
files = list_supporting_files(s)
|
|
58
|
+
head = f"### GemSkill: `/{s.meta.name}` (loaded for this session)\n\n"
|
|
59
|
+
chunk = head + expanded
|
|
60
|
+
if files:
|
|
61
|
+
chunk += f"\n\nSupporting files: {', '.join(files)}"
|
|
62
|
+
chunks.append(chunk)
|
|
63
|
+
if not chunks:
|
|
64
|
+
return ""
|
|
65
|
+
return (
|
|
66
|
+
"## Loaded GemSkills (this session)\n"
|
|
67
|
+
"The user explicitly loaded these skills with `/gemskill`. Follow their workflows "
|
|
68
|
+
"when the task matches their purpose; do not force them on unrelated requests.\n\n"
|
|
69
|
+
+ "\n\n---\n\n".join(chunks)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
35
73
|
def build_global_instruction() -> str:
|
|
36
74
|
"""Global instruction applied to the entire agent tree (via ADK plugin)."""
|
|
37
75
|
return (
|
|
@@ -888,6 +926,9 @@ You have two tools to persist project insights across sessions (auto-memory styl
|
|
|
888
926
|
skill_manifest = build_skill_manifest_text(cfg.project_root)
|
|
889
927
|
if skill_manifest:
|
|
890
928
|
base = f"{base}\n\n{skill_manifest}"
|
|
929
|
+
loaded_skills = _build_session_loaded_skills_section(cfg)
|
|
930
|
+
if loaded_skills:
|
|
931
|
+
base = f"{base}\n\n{loaded_skills}"
|
|
891
932
|
extra = _load_gemini_md(cfg.project_root)
|
|
892
933
|
if extra.strip():
|
|
893
934
|
return f"{base}\n\n## Project instructions (GEMINI.md)\n{extra}"
|
gemcode/autotune.py
CHANGED
|
@@ -3,8 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
import subprocess
|
|
4
4
|
import time
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any, Iterable
|
|
7
7
|
|
|
8
|
+
from gemcode.config import GemCodeConfig
|
|
8
9
|
from gemcode.evals.harness import run_eval_suite, write_eval_record
|
|
9
10
|
|
|
10
11
|
|
|
@@ -47,11 +48,24 @@ def init_autotune(*, project_root: Path, tag: str) -> dict[str, Any]:
|
|
|
47
48
|
return {"status": "created", "branch": branch}
|
|
48
49
|
|
|
49
50
|
|
|
50
|
-
def run_autotune_eval(
|
|
51
|
+
def run_autotune_eval(
|
|
52
|
+
*,
|
|
53
|
+
project_root: Path,
|
|
54
|
+
include_llm: bool,
|
|
55
|
+
model: str | None = None,
|
|
56
|
+
session_cfg: GemCodeConfig | None = None,
|
|
57
|
+
extra_tools: Iterable[Any] | None = None,
|
|
58
|
+
) -> dict[str, Any]:
|
|
51
59
|
"""
|
|
52
60
|
Run eval suite and persist last result to .gemcode/evals/last_eval.json.
|
|
53
61
|
"""
|
|
54
|
-
res = run_eval_suite(
|
|
62
|
+
res = run_eval_suite(
|
|
63
|
+
project_root=project_root,
|
|
64
|
+
include_llm=include_llm,
|
|
65
|
+
model=model,
|
|
66
|
+
session_cfg=session_cfg,
|
|
67
|
+
extra_tools=extra_tools,
|
|
68
|
+
)
|
|
55
69
|
meta = {
|
|
56
70
|
"ts": time.time(),
|
|
57
71
|
"git_sha": _git_head_sha(project_root),
|
gemcode/cli.py
CHANGED
|
@@ -245,6 +245,13 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
|
|
|
245
245
|
file=sys.stderr,
|
|
246
246
|
)
|
|
247
247
|
|
|
248
|
+
try:
|
|
249
|
+
from gemcode.repl_commands import install_readline_slash_completion
|
|
250
|
+
|
|
251
|
+
install_readline_slash_completion()
|
|
252
|
+
except Exception:
|
|
253
|
+
pass
|
|
254
|
+
|
|
248
255
|
print(
|
|
249
256
|
"GemCode CLI is running. Type your prompt and press Enter. (Ctrl+D to exit)",
|
|
250
257
|
file=sys.stderr,
|
|
@@ -261,6 +268,7 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
|
|
|
261
268
|
if prompt_text in (":q", "quit", "exit", "/exit"):
|
|
262
269
|
break
|
|
263
270
|
|
|
271
|
+
cfg.session_skill_expand_session_id = session_id
|
|
264
272
|
slash = await process_repl_slash(
|
|
265
273
|
cfg=cfg,
|
|
266
274
|
runner=runner,
|
|
@@ -273,7 +281,16 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
|
|
|
273
281
|
break
|
|
274
282
|
if slash.new_session_id is not None:
|
|
275
283
|
session_id = slash.new_session_id
|
|
284
|
+
cfg.session_skill_expand_session_id = session_id
|
|
276
285
|
if slash.skip_model_turn:
|
|
286
|
+
if slash.force_rebuild_runner:
|
|
287
|
+
try:
|
|
288
|
+
_c = runner.close()
|
|
289
|
+
if asyncio.iscoroutine(_c):
|
|
290
|
+
await _c
|
|
291
|
+
except Exception:
|
|
292
|
+
pass
|
|
293
|
+
runner = create_runner(cfg, extra_tools=None)
|
|
277
294
|
continue
|
|
278
295
|
prompt_text = slash.model_prompt or prompt_text
|
|
279
296
|
|
gemcode/config.py
CHANGED
|
@@ -212,6 +212,12 @@ class GemCodeConfig:
|
|
|
212
212
|
default_factory=lambda: os.environ.get("GEMCODE_OUTPUT_STYLE") or None
|
|
213
213
|
)
|
|
214
214
|
|
|
215
|
+
# GemSkills explicitly loaded via /gemskill — full bodies injected into the
|
|
216
|
+
# system instruction until cleared or the session is reset/resumed.
|
|
217
|
+
session_loaded_skill_names: list[str] = field(default_factory=list)
|
|
218
|
+
# Substitutes ${GEMCODE_SESSION_ID} when expanding loaded skills for prompts.
|
|
219
|
+
session_skill_expand_session_id: str | None = None
|
|
220
|
+
|
|
215
221
|
# Modality toggles (tool injection + routing).
|
|
216
222
|
enable_deep_research: bool = field(
|
|
217
223
|
default_factory=lambda: _truthy_env("GEMCODE_ENABLE_DEEP_RESEARCH", default=False)
|
gemcode/evals/harness.py
CHANGED
|
@@ -3,14 +3,17 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
6
8
|
import time
|
|
7
|
-
from dataclasses import dataclass
|
|
9
|
+
from dataclasses import dataclass, replace
|
|
8
10
|
from pathlib import Path
|
|
9
|
-
from typing import Any,
|
|
11
|
+
from typing import Any, Iterable
|
|
10
12
|
|
|
11
13
|
from gemcode.config import GemCodeConfig, load_cli_environment
|
|
12
14
|
from gemcode.invoke import run_turn
|
|
13
15
|
from gemcode.session_runtime import create_runner
|
|
16
|
+
from gemcode.tools_inspector import inspect_tools, smoke_tools
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
@dataclass
|
|
@@ -21,11 +24,25 @@ class EvalResult:
|
|
|
21
24
|
details: str = ""
|
|
22
25
|
|
|
23
26
|
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
def _discover_pytest_cwd(project_root: Path) -> tuple[Path, dict[str, str] | None] | None:
|
|
28
|
+
"""
|
|
29
|
+
Return (cwd, env) for running pytest, or None if no tests tree found.
|
|
30
|
+
|
|
31
|
+
``env`` is ``None`` to inherit the process environment; otherwise a full env dict.
|
|
32
|
+
|
|
33
|
+
Supports:
|
|
34
|
+
- Monorepo layout: <root>/gemcode/tests → cwd gemcode, PYTHONPATH=src
|
|
35
|
+
- Single-package layout: <root>/tests → cwd root
|
|
36
|
+
"""
|
|
37
|
+
if (project_root / "tests").is_dir():
|
|
38
|
+
return project_root, None
|
|
39
|
+
gc = project_root / "gemcode"
|
|
40
|
+
if (gc / "tests").is_dir():
|
|
41
|
+
env = os.environ.copy()
|
|
42
|
+
prev = env.get("PYTHONPATH", "")
|
|
43
|
+
env["PYTHONPATH"] = "src" + (os.pathsep + prev if prev else "")
|
|
44
|
+
return gc, env
|
|
45
|
+
return None
|
|
29
46
|
|
|
30
47
|
|
|
31
48
|
def _events_to_text(events: list) -> str:
|
|
@@ -72,28 +89,65 @@ def run_eval_suite(
|
|
|
72
89
|
project_root: Path,
|
|
73
90
|
include_llm: bool,
|
|
74
91
|
model: str | None = None,
|
|
92
|
+
session_cfg: GemCodeConfig | None = None,
|
|
93
|
+
extra_tools: Iterable[Any] | None = None,
|
|
75
94
|
) -> dict[str, Any]:
|
|
76
95
|
"""
|
|
77
96
|
Fixed evaluation harness (AutoResearch-style): deterministic gates + optional LLM golden prompts.
|
|
97
|
+
|
|
98
|
+
When ``session_cfg`` is set (e.g. from the REPL), tool smoke uses that config so flags match the live session.
|
|
78
99
|
"""
|
|
79
100
|
t0 = time.time()
|
|
80
101
|
load_cli_environment()
|
|
81
|
-
|
|
82
|
-
if
|
|
83
|
-
cfg
|
|
84
|
-
|
|
102
|
+
root = session_cfg.project_root if session_cfg is not None else project_root.resolve()
|
|
103
|
+
if session_cfg is not None:
|
|
104
|
+
cfg = replace(session_cfg, model=model, model_overridden=True) if model else session_cfg
|
|
105
|
+
else:
|
|
106
|
+
cfg = GemCodeConfig(project_root=project_root.resolve())
|
|
107
|
+
if model:
|
|
108
|
+
cfg.model = model
|
|
109
|
+
cfg.model_overridden = True
|
|
85
110
|
|
|
86
111
|
results: list[EvalResult] = []
|
|
87
112
|
|
|
88
|
-
# Gate 1: tool
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
# Gate 1: tool declaration smoke (in-process; matches REPL config when session_cfg is passed)
|
|
114
|
+
inspections = inspect_tools(cfg, extra_tools=extra_tools)
|
|
115
|
+
failures = smoke_tools(inspections)
|
|
116
|
+
ok_smoke = len(failures) == 0
|
|
117
|
+
smoke_details = ""
|
|
118
|
+
if failures:
|
|
119
|
+
smoke_details = "\n".join(
|
|
120
|
+
f"{f.name}: {f.declaration_error}" for f in failures[:40]
|
|
121
|
+
)
|
|
122
|
+
results.append(
|
|
123
|
+
EvalResult(
|
|
124
|
+
name="tools_smoke",
|
|
125
|
+
ok=ok_smoke,
|
|
126
|
+
score=1.0 if ok_smoke else 0.0,
|
|
127
|
+
details=smoke_details[-1200:],
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Gate 2: pytest if a tests/ tree exists under root or root/gemcode
|
|
132
|
+
pytest_target = _discover_pytest_cwd(root)
|
|
133
|
+
if pytest_target is not None:
|
|
134
|
+
cwd, env = pytest_target
|
|
135
|
+
p = subprocess.run(
|
|
136
|
+
[sys.executable, "-m", "pytest", "-q"],
|
|
137
|
+
cwd=str(cwd),
|
|
138
|
+
env=env,
|
|
139
|
+
capture_output=True,
|
|
140
|
+
text=True,
|
|
141
|
+
)
|
|
142
|
+
out2 = (p.stdout or "") + (p.stderr or "")
|
|
143
|
+
results.append(
|
|
144
|
+
EvalResult(
|
|
145
|
+
name="pytest",
|
|
146
|
+
ok=(p.returncode == 0),
|
|
147
|
+
score=1.0 if p.returncode == 0 else 0.0,
|
|
148
|
+
details=out2[-1200:],
|
|
149
|
+
)
|
|
150
|
+
)
|
|
97
151
|
|
|
98
152
|
if include_llm:
|
|
99
153
|
goldens = [
|
gemcode/repl_commands.py
CHANGED
|
@@ -196,6 +196,123 @@ def format_tools_lines(
|
|
|
196
196
|
return lines
|
|
197
197
|
|
|
198
198
|
|
|
199
|
+
# ---------------------------------------------------------------------------
|
|
200
|
+
# Slash command registry for TUI (prompt_toolkit) + plain REPL (readline Tab).
|
|
201
|
+
# One canonical name per feature where possible; a few shortcuts (e.g.
|
|
202
|
+
# ``gemskill``) stay as their own row so Tab/TUI shows them. Other aliases
|
|
203
|
+
# still work in ``process_repl_slash`` but are omitted here (see descriptions).
|
|
204
|
+
# ---------------------------------------------------------------------------
|
|
205
|
+
SLASH_COMMANDS: list[tuple[str, str]] = [
|
|
206
|
+
("add-dir", "Extra read/search roots · /add_dir works too"),
|
|
207
|
+
("append", "Iterate a file · /append gemskill <name> <request>"),
|
|
208
|
+
("audit", "Tail audit.log · /logs same"),
|
|
209
|
+
("autotune", "Branch + eval ledger · /autotune init <tag> · /autotune eval"),
|
|
210
|
+
("batch", "Built-in batch GemSkill (large parallel changes)"),
|
|
211
|
+
("budget", "Per-turn token budget · /token-budget same"),
|
|
212
|
+
("caps", "Capabilities · /capabilities /capability same"),
|
|
213
|
+
("clear", "Fresh session · same as /session new"),
|
|
214
|
+
("code", "Toggle ADK BuiltInCodeExecutor (sandboxed Python)"),
|
|
215
|
+
("compact", "Context compaction / summarization"),
|
|
216
|
+
("computer", "Browser automation · /browser same"),
|
|
217
|
+
("config", "Dump active configuration"),
|
|
218
|
+
("context", "Context pressure + token breakdown"),
|
|
219
|
+
("cost", "Session token usage + estimated cost"),
|
|
220
|
+
("create", "New GemSkill file · /create gemskill <name> [description]"),
|
|
221
|
+
("gemskill", "Load skill into session prompt · /gemskill <name> · list · clear"),
|
|
222
|
+
("curated", "Curated memory snapshot · /memory-files /memoryfiles same"),
|
|
223
|
+
("diff", "Git diff or checkpoint diff"),
|
|
224
|
+
("doctor", "Environment sanity check"),
|
|
225
|
+
("embeddings", "Semantic file search · /embed same"),
|
|
226
|
+
("eval", "Eval gates (tools + pytest) · /eval llm optional"),
|
|
227
|
+
("exit", "Leave the REPL · /quit same"),
|
|
228
|
+
("help", "Short help · /? same"),
|
|
229
|
+
("hooks", "Post-turn hook configuration"),
|
|
230
|
+
("init", "Generate GEMINI.md project instructions"),
|
|
231
|
+
("kaira", "Background job scheduler — how to run gemcode kaira"),
|
|
232
|
+
("limits", "Execution limits (calls, context, …)"),
|
|
233
|
+
("live-audio", "How to run gemcode live-audio · /liveaudio same"),
|
|
234
|
+
("login", "How to run gemcode login (API key)"),
|
|
235
|
+
("maps", "Maps grounding · /maps on|off · /map same"),
|
|
236
|
+
("memory", "Persistent memory · /memory on|off"),
|
|
237
|
+
("mode", "Model mode: fast|balanced|quality|auto"),
|
|
238
|
+
("model", "Model info / override · /models same"),
|
|
239
|
+
("notes", ".gemcode/notes.md · /notes clear · /notes edit"),
|
|
240
|
+
("permissions", "Permission + HITL · /perm /permission same"),
|
|
241
|
+
("plan", "Plan-before-act mode"),
|
|
242
|
+
("research", "Deep research tools · /research on|off"),
|
|
243
|
+
("review", "Parallel code review"),
|
|
244
|
+
("rewind", "Checkpoints · /checkpoint same"),
|
|
245
|
+
("rules", "Rule files from .gemcode/rules/"),
|
|
246
|
+
("session", "Session id / list / resume / new"),
|
|
247
|
+
("skill", "Load or show a GemSkill"),
|
|
248
|
+
("skills", "List GemSkills"),
|
|
249
|
+
("status", "Model, capabilities, thinking, limits"),
|
|
250
|
+
("style", "Output styles · /style <name>|off"),
|
|
251
|
+
("thinking", "Thinking verbose/brief/off, budget, level"),
|
|
252
|
+
("tools", "Tool inventory · /tools smoke"),
|
|
253
|
+
("trust", "Workspace trust · /trust on|off"),
|
|
254
|
+
("version", "GemCode version"),
|
|
255
|
+
]
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def install_readline_slash_completion() -> bool:
|
|
259
|
+
"""
|
|
260
|
+
Enable Tab completion for slash commands in the plain REPL (``input("> ")``).
|
|
261
|
+
|
|
262
|
+
Returns False if readline is unavailable or stdin is not a TTY.
|
|
263
|
+
"""
|
|
264
|
+
try:
|
|
265
|
+
import readline
|
|
266
|
+
except ImportError:
|
|
267
|
+
return False
|
|
268
|
+
if not hasattr(sys.stdin, "isatty") or not sys.stdin.isatty():
|
|
269
|
+
return False
|
|
270
|
+
|
|
271
|
+
# Treat ``/foo`` as one word so Tab completes after ``/``.
|
|
272
|
+
try:
|
|
273
|
+
delims = readline.get_completer_delims()
|
|
274
|
+
if "/" in delims:
|
|
275
|
+
readline.set_completer_delims(delims.replace("/", ""))
|
|
276
|
+
except Exception:
|
|
277
|
+
pass
|
|
278
|
+
|
|
279
|
+
ordered = [name for name, _ in SLASH_COMMANDS]
|
|
280
|
+
_matches: list[str] = []
|
|
281
|
+
|
|
282
|
+
def completer(text: str, state: int) -> str | None:
|
|
283
|
+
nonlocal _matches
|
|
284
|
+
if state == 0:
|
|
285
|
+
_matches = []
|
|
286
|
+
if not text.startswith("/"):
|
|
287
|
+
return None
|
|
288
|
+
# Only complete the first token (/command …); skip when typing args.
|
|
289
|
+
if " " in text[1:]:
|
|
290
|
+
return None
|
|
291
|
+
frag = text[1:].lower()
|
|
292
|
+
for n in ordered:
|
|
293
|
+
if not frag or n.startswith(frag) or frag in n:
|
|
294
|
+
_matches.append(f"/{n} ")
|
|
295
|
+
if not _matches:
|
|
296
|
+
return None
|
|
297
|
+
try:
|
|
298
|
+
return _matches[state]
|
|
299
|
+
except IndexError:
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
readline.set_completer(completer)
|
|
303
|
+
# GNU readline: double-Tab lists matches. libedit (macOS) may ignore unknown "set" directives.
|
|
304
|
+
for spec in (
|
|
305
|
+
"set show-all-if-ambiguous on",
|
|
306
|
+
"set completion-ignore-case on",
|
|
307
|
+
"tab: complete",
|
|
308
|
+
):
|
|
309
|
+
try:
|
|
310
|
+
readline.parse_and_bind(spec)
|
|
311
|
+
except Exception:
|
|
312
|
+
pass
|
|
313
|
+
return True
|
|
314
|
+
|
|
315
|
+
|
|
199
316
|
def slash_help_lines() -> list[str]:
|
|
200
317
|
return [
|
|
201
318
|
"Slash commands:",
|
|
@@ -203,13 +320,18 @@ def slash_help_lines() -> list[str]:
|
|
|
203
320
|
" (CLI) gemcode login Save or change API key (~/.gemcode/credentials.json)",
|
|
204
321
|
"",
|
|
205
322
|
" Project setup:",
|
|
323
|
+
" /trust Show workspace trust status (file/shell tools)",
|
|
324
|
+
" /trust on|off Trust or revoke trust for this project root (~/.gemcode/trust.json)",
|
|
206
325
|
" /init Analyze project structure and generate GEMINI.md",
|
|
207
326
|
" /init force Regenerate GEMINI.md even if it already exists",
|
|
208
327
|
" /cost Show token usage and estimated cost for this session",
|
|
209
328
|
" /notes Show agent auto-notes (.gemcode/notes.md)",
|
|
210
329
|
" /notes clear Delete all notes",
|
|
211
330
|
" /notes edit Open notes in $EDITOR",
|
|
212
|
-
" /create gemskill <name> [description] Create a GemSkill scaffold
|
|
331
|
+
" /create gemskill <name> [description] Create a new GemSkill (SKILL.md scaffold)",
|
|
332
|
+
" /gemskill <name> Load an existing GemSkill into this session (system prompt)",
|
|
333
|
+
" /gemskill list|clear List skills or unload all session-loaded skills",
|
|
334
|
+
" /append gemskill <name> <request> Ask the agent to edit that skill file",
|
|
213
335
|
" /style List available output styles",
|
|
214
336
|
" /style <name>|off Activate an output style for this session",
|
|
215
337
|
" /rules Show loaded rule files (from .gemcode/rules/)",
|
|
@@ -237,6 +359,13 @@ def slash_help_lines() -> list[str]:
|
|
|
237
359
|
" /context Show context pressure + last prompt tokens",
|
|
238
360
|
" /audit [N] Tail of .gemcode/audit.log (default 40 lines)",
|
|
239
361
|
" /tools List tool inventory for this config",
|
|
362
|
+
" /tools smoke Declaration compile check only (failures listed)",
|
|
363
|
+
" /eval [llm] Run tools_smoke (+ pytest if tests/ exist); optional LLM goldens",
|
|
364
|
+
" /autotune init <tag> Git branch autotune/<tag> for experiment tracking",
|
|
365
|
+
" /autotune eval [llm] Eval + append .gemcode/evals/autotune_ledger.jsonl",
|
|
366
|
+
" /curated Show GEMCODE_MEMORY.md / GEMCODE_USER.md snapshot",
|
|
367
|
+
" /login How to run gemcode login (API key outside REPL)",
|
|
368
|
+
" /live-audio How to run gemcode live-audio (mic → Gemini Live)",
|
|
240
369
|
" /doctor Environment sanity check",
|
|
241
370
|
" /version Print GemCode version hint",
|
|
242
371
|
" /exit Exit the REPL",
|
|
@@ -254,6 +383,7 @@ def slash_help_lines() -> list[str]:
|
|
|
254
383
|
" /computer url Show current browser URL",
|
|
255
384
|
" /research Show deep-research status",
|
|
256
385
|
" /research on|off Enable/disable Google Search + URL Context tools",
|
|
386
|
+
" /maps on|off Enable/disable Maps grounding (runner rebuild)",
|
|
257
387
|
" /embeddings on|off Enable/disable semantic file search (Embeddings API)",
|
|
258
388
|
" /caps View all capability flags",
|
|
259
389
|
" /caps <research|embeddings|all|reset> Bulk capability control",
|
gemcode/repl_slash.py
CHANGED
|
@@ -28,10 +28,12 @@ from gemcode.repl_commands import (
|
|
|
28
28
|
format_tools_lines,
|
|
29
29
|
slash_help_lines,
|
|
30
30
|
)
|
|
31
|
+
from gemcode.curated_memory import load_snapshot as _curated_load_snapshot
|
|
31
32
|
from gemcode.slash_commands import parse_slash_command
|
|
32
33
|
from gemcode.skills import discover_skill_metas, expand_skill_text, list_supporting_files, load_skill
|
|
33
34
|
from gemcode.output_styles import discover_output_styles, load_output_style
|
|
34
35
|
from gemcode.rules import load_rules as _load_rules
|
|
36
|
+
from gemcode.trust import is_trusted_root, trust_json_path, trust_root
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
@dataclass
|
|
@@ -45,6 +47,12 @@ class ReplSlashResult:
|
|
|
45
47
|
force_rebuild_runner: bool = False # True when agent config changed (thinking, etc.)
|
|
46
48
|
|
|
47
49
|
|
|
50
|
+
def _clear_session_loaded_skills(cfg: GemCodeConfig) -> None:
|
|
51
|
+
raw = getattr(cfg, "session_loaded_skill_names", None)
|
|
52
|
+
if isinstance(raw, list):
|
|
53
|
+
raw.clear()
|
|
54
|
+
|
|
55
|
+
|
|
48
56
|
def _parse_tail_n(args: str, *, default: int = 40) -> int:
|
|
49
57
|
parts = (args or "").strip().split()
|
|
50
58
|
if not parts:
|
|
@@ -116,6 +124,61 @@ async def process_repl_slash(
|
|
|
116
124
|
)
|
|
117
125
|
return ReplSlashResult(model_prompt=prompt)
|
|
118
126
|
|
|
127
|
+
# ── /gemskill (load full skill into session system prompt) ────────────────
|
|
128
|
+
if name == "gemskill":
|
|
129
|
+
args_gs = (sc.args or "").strip()
|
|
130
|
+
if not args_gs or args_gs.lower() in ("help", "?"):
|
|
131
|
+
out("Usage:")
|
|
132
|
+
out(" /gemskill <name> Load an existing GemSkill into this session (system prompt).")
|
|
133
|
+
out(" /gemskill list List skills you can load")
|
|
134
|
+
out(" /gemskill clear Unload all session-loaded skills")
|
|
135
|
+
out()
|
|
136
|
+
out("Create a new skill: /create gemskill <name> [description]")
|
|
137
|
+
out("Edit an existing one: /append gemskill <name> <what to change>")
|
|
138
|
+
out()
|
|
139
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
140
|
+
al = args_gs.lower()
|
|
141
|
+
if al in ("list", "ls", "show"):
|
|
142
|
+
metas_gs = discover_skill_metas(cfg.project_root)
|
|
143
|
+
if not metas_gs:
|
|
144
|
+
out("No GemSkills found.")
|
|
145
|
+
out("Create one: /create gemskill <name> [description]")
|
|
146
|
+
out()
|
|
147
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
148
|
+
out("GemSkills (load with /gemskill <name>):")
|
|
149
|
+
for k in sorted(metas_gs.keys()):
|
|
150
|
+
m, _ = metas_gs[k]
|
|
151
|
+
inv = "manual-only" if m.disable_model_invocation else "auto-eligible"
|
|
152
|
+
out(f" {m.name} ({inv}) — {m.description}")
|
|
153
|
+
out()
|
|
154
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
155
|
+
if al == "clear":
|
|
156
|
+
_clear_session_loaded_skills(cfg)
|
|
157
|
+
cfg.session_skill_expand_session_id = session_id
|
|
158
|
+
out("Session-loaded GemSkills cleared.")
|
|
159
|
+
out("Runner will rebuild on the next turn.")
|
|
160
|
+
out()
|
|
161
|
+
return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
|
|
162
|
+
sk_part = args_gs.split()[0].strip().lower()
|
|
163
|
+
s_gs = load_skill(cfg.project_root, sk_part)
|
|
164
|
+
if s_gs is None:
|
|
165
|
+
out(f"Unknown skill: {sk_part}")
|
|
166
|
+
out("Tip: /gemskill list · Create: /create gemskill <name>")
|
|
167
|
+
out()
|
|
168
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
169
|
+
loaded = cfg.session_loaded_skill_names
|
|
170
|
+
if sk_part in loaded:
|
|
171
|
+
out(f"GemSkill `/{sk_part}` is already loaded for this session.")
|
|
172
|
+
out()
|
|
173
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
174
|
+
loaded.append(sk_part)
|
|
175
|
+
cfg.session_skill_expand_session_id = session_id
|
|
176
|
+
out(f"Loaded GemSkill into session: /{sk_part}")
|
|
177
|
+
out("Full skill body is now in the system prompt until /gemskill clear or a new session.")
|
|
178
|
+
out("Runner will rebuild on the next turn.")
|
|
179
|
+
out()
|
|
180
|
+
return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
|
|
181
|
+
|
|
119
182
|
# ── /batch (built-in GemSkill) ─────────────────────────────────────────────
|
|
120
183
|
if name == "batch":
|
|
121
184
|
goal = (sc.args or "").strip()
|
|
@@ -230,10 +293,42 @@ async def process_repl_slash(
|
|
|
230
293
|
|
|
231
294
|
out(f"Created GemSkill: /{skill_name}")
|
|
232
295
|
out(f"Path: {skill_md}")
|
|
233
|
-
out("Try: /
|
|
296
|
+
out("Try: /gemskill " + skill_name + " or /" + skill_name + " <args> for a one-shot turn")
|
|
234
297
|
out()
|
|
235
298
|
return ReplSlashResult(skip_model_turn=True)
|
|
236
299
|
|
|
300
|
+
# ── /append gemskill (iterate existing SKILL.md) ───────────────────────────
|
|
301
|
+
if name == "append":
|
|
302
|
+
raw_ap = (sc.args or "").strip()
|
|
303
|
+
parts_ap = raw_ap.split(None, 2)
|
|
304
|
+
if len(parts_ap) < 3 or parts_ap[0].lower() != "gemskill":
|
|
305
|
+
out("Usage: /append gemskill <name> <what to add or change>")
|
|
306
|
+
out("Example: /append gemskill review-code Add a checklist for API security")
|
|
307
|
+
out()
|
|
308
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
309
|
+
sk_ap = parts_ap[1].strip().lower()
|
|
310
|
+
instruction_ap = parts_ap[2].strip()
|
|
311
|
+
s_ap = load_skill(cfg.project_root, sk_ap)
|
|
312
|
+
if s_ap is None:
|
|
313
|
+
out(f"Unknown skill: {sk_ap}")
|
|
314
|
+
out("Tip: /gemskill list")
|
|
315
|
+
out()
|
|
316
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
317
|
+
files_ap = list_supporting_files(s_ap)
|
|
318
|
+
prompt_ap = (
|
|
319
|
+
f"The user wants to **iterate** on an existing GemSkill ({sk_ap!r}).\n\n"
|
|
320
|
+
f"## Skill file (primary edit target)\n`{s_ap.skill_md}`\n\n"
|
|
321
|
+
f"## User request\n{instruction_ap}\n\n"
|
|
322
|
+
"## Instructions\n"
|
|
323
|
+
"1. Read the full SKILL.md (and supporting files only if needed).\n"
|
|
324
|
+
"2. Apply the user's request: clarify steps, add sections, improve examples, fix mistakes.\n"
|
|
325
|
+
"3. Preserve valid YAML frontmatter (`name`, `description`, etc.) unless the user asked to rename.\n"
|
|
326
|
+
"4. Save changes with `search_replace` or `write_file`.\n"
|
|
327
|
+
"5. Summarize what you changed.\n\n"
|
|
328
|
+
+ (f"## Supporting files (optional)\n{', '.join(files_ap)}\n\n" if files_ap else "")
|
|
329
|
+
)
|
|
330
|
+
return ReplSlashResult(model_prompt=prompt_ap)
|
|
331
|
+
|
|
237
332
|
# ── /style ────────────────────────────────────────────────────────────────
|
|
238
333
|
if name == "style":
|
|
239
334
|
args = (sc.args or "").strip()
|
|
@@ -293,6 +388,44 @@ async def process_repl_slash(
|
|
|
293
388
|
out()
|
|
294
389
|
return ReplSlashResult(skip_model_turn=True)
|
|
295
390
|
|
|
391
|
+
# ── /trust (workspace permission for tools) ─────────────────────────────────
|
|
392
|
+
if name == "trust":
|
|
393
|
+
args_s = (sc.args or "").strip().lower()
|
|
394
|
+
root = cfg.project_root.resolve()
|
|
395
|
+
tpath = trust_json_path()
|
|
396
|
+
|
|
397
|
+
if args_s in ("", "status", "show"):
|
|
398
|
+
if is_trusted_root(root):
|
|
399
|
+
out(f"Workspace is trusted:\n {root}")
|
|
400
|
+
else:
|
|
401
|
+
out(f"Workspace is NOT trusted:\n {root}")
|
|
402
|
+
out("File, shell, and git tools require trust. Use: /trust on")
|
|
403
|
+
out(f"Trust database: {tpath}")
|
|
404
|
+
out()
|
|
405
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
406
|
+
|
|
407
|
+
if args_s in ("on", "yes", "y", "1", "true", "enable"):
|
|
408
|
+
trust_root(root, trusted=True)
|
|
409
|
+
out(f"Trusted:\n {root}")
|
|
410
|
+
out(f"Saved to {tpath}")
|
|
411
|
+
out()
|
|
412
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
413
|
+
|
|
414
|
+
if args_s in ("off", "no", "n", "0", "false", "disable", "revoke"):
|
|
415
|
+
trust_root(root, trusted=False)
|
|
416
|
+
out(f"Removed trust for:\n {root}")
|
|
417
|
+
out("Tools will refuse until you run /trust on (or approve on next CLI start).")
|
|
418
|
+
out()
|
|
419
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
420
|
+
|
|
421
|
+
out("Usage:")
|
|
422
|
+
out(" /trust Show whether this project root is trusted")
|
|
423
|
+
out(" /trust on Trust this workspace (required for file/shell/git tools)")
|
|
424
|
+
out(" /trust off Stop trusting this workspace")
|
|
425
|
+
out(f" (stored under {tpath.parent}/)")
|
|
426
|
+
out()
|
|
427
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
428
|
+
|
|
296
429
|
# ── /add-dir (safe multi-root access) ──────────────────────────────────────
|
|
297
430
|
if name in ("add-dir", "add_dir", "adddir"):
|
|
298
431
|
import os
|
|
@@ -539,6 +672,141 @@ async def process_repl_slash(
|
|
|
539
672
|
out()
|
|
540
673
|
return ReplSlashResult(skip_model_turn=True)
|
|
541
674
|
|
|
675
|
+
# ── /eval ─────────────────────────────────────────────────────────────────
|
|
676
|
+
if name == "eval":
|
|
677
|
+
raw_args = (sc.args or "").strip().lower()
|
|
678
|
+
include_llm = "llm" in raw_args or "--llm" in raw_args
|
|
679
|
+
from gemcode.evals.harness import run_eval_suite, write_eval_record
|
|
680
|
+
|
|
681
|
+
try:
|
|
682
|
+
res = run_eval_suite(
|
|
683
|
+
project_root=cfg.project_root,
|
|
684
|
+
include_llm=include_llm,
|
|
685
|
+
model=None,
|
|
686
|
+
session_cfg=cfg,
|
|
687
|
+
extra_tools=extra_tools,
|
|
688
|
+
)
|
|
689
|
+
except Exception as e:
|
|
690
|
+
out(f"eval failed: {type(e).__name__}: {e}")
|
|
691
|
+
out()
|
|
692
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
693
|
+
try:
|
|
694
|
+
rec_path = write_eval_record(cfg.project_root, res)
|
|
695
|
+
out(f"eval: ok={res.get('ok')} score={float(res.get('score', 0)):.2f} elapsed_s={float(res.get('elapsed_s', 0)):.1f} → {rec_path}")
|
|
696
|
+
except OSError:
|
|
697
|
+
out(f"eval: ok={res.get('ok')} score={float(res.get('score', 0)):.2f} elapsed_s={float(res.get('elapsed_s', 0)):.1f}")
|
|
698
|
+
for row in res.get("results") or []:
|
|
699
|
+
nm = row.get("name", "?")
|
|
700
|
+
ok = row.get("ok", False)
|
|
701
|
+
out(f" {nm}: {'ok' if ok else 'FAIL'}")
|
|
702
|
+
if not ok and row.get("details"):
|
|
703
|
+
det = str(row["details"]).strip()
|
|
704
|
+
if det:
|
|
705
|
+
snippet = det[-600:] if len(det) > 600 else det
|
|
706
|
+
out(f" {snippet}")
|
|
707
|
+
out()
|
|
708
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
709
|
+
|
|
710
|
+
# ── /autotune ───────────────────────────────────────────────────────────────
|
|
711
|
+
if name == "autotune":
|
|
712
|
+
parts = (sc.args or "").strip().split()
|
|
713
|
+
if not parts:
|
|
714
|
+
out("Usage:")
|
|
715
|
+
out(" /autotune init <tag> Create git branch autotune/<tag> (requires git repo)")
|
|
716
|
+
out(" /autotune eval [llm] Run eval suite and append .gemcode/evals/autotune_ledger.jsonl")
|
|
717
|
+
out()
|
|
718
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
719
|
+
sub = parts[0].lower()
|
|
720
|
+
if sub == "init":
|
|
721
|
+
if len(parts) < 2:
|
|
722
|
+
out("Usage: /autotune init <tag>")
|
|
723
|
+
out()
|
|
724
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
725
|
+
tag = parts[1].strip()
|
|
726
|
+
from gemcode.autotune import init_autotune
|
|
727
|
+
|
|
728
|
+
r = init_autotune(project_root=cfg.project_root, tag=tag)
|
|
729
|
+
if r.get("error"):
|
|
730
|
+
out(f"autotune init: {r.get('error')}")
|
|
731
|
+
if r.get("output"):
|
|
732
|
+
out(str(r["output"])[-800:])
|
|
733
|
+
else:
|
|
734
|
+
out(f"autotune init: {r.get('status')} branch={r.get('branch')}")
|
|
735
|
+
out()
|
|
736
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
737
|
+
if sub == "eval":
|
|
738
|
+
include_llm = any(p.lower() in ("llm", "--llm") for p in parts[1:])
|
|
739
|
+
from gemcode.autotune import run_autotune_eval
|
|
740
|
+
|
|
741
|
+
try:
|
|
742
|
+
r = run_autotune_eval(
|
|
743
|
+
project_root=cfg.project_root,
|
|
744
|
+
include_llm=include_llm,
|
|
745
|
+
model=None,
|
|
746
|
+
session_cfg=cfg,
|
|
747
|
+
extra_tools=extra_tools,
|
|
748
|
+
)
|
|
749
|
+
except Exception as e:
|
|
750
|
+
out(f"autotune eval failed: {type(e).__name__}: {e}")
|
|
751
|
+
out()
|
|
752
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
753
|
+
out(f"autotune eval: ok={r.get('ok')} score={float(r.get('score', 0)):.2f}")
|
|
754
|
+
if r.get("record_path"):
|
|
755
|
+
out(f" record: {r['record_path']}")
|
|
756
|
+
if r.get("ledger_path"):
|
|
757
|
+
out(f" ledger: {r['ledger_path']}")
|
|
758
|
+
out()
|
|
759
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
760
|
+
out(f"Unknown /autotune subcommand: {sub}")
|
|
761
|
+
out("Usage: /autotune init <tag> · /autotune eval [llm]")
|
|
762
|
+
out()
|
|
763
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
764
|
+
|
|
765
|
+
# ── /curated (curated memory files) ────────────────────────────────────────
|
|
766
|
+
if name in ("curated", "memory-files", "memoryfiles"):
|
|
767
|
+
snap = _curated_load_snapshot(cfg.project_root, max_chars=8000)
|
|
768
|
+
out("Curated memory (injected when memory is on):")
|
|
769
|
+
out(f" project: {snap.get('memory_path')}")
|
|
770
|
+
out(f" user: {snap.get('user_path')}")
|
|
771
|
+
out(f" loaded: {snap.get('chars', 0)} chars exists={snap.get('exists')}")
|
|
772
|
+
out()
|
|
773
|
+
txt = (snap.get("text") or "").strip()
|
|
774
|
+
if txt:
|
|
775
|
+
out("--- snapshot ---")
|
|
776
|
+
out(txt)
|
|
777
|
+
out("--- end ---")
|
|
778
|
+
else:
|
|
779
|
+
out("(empty — create .gemcode/GEMCODE_MEMORY.md and GEMCODE_USER.md)")
|
|
780
|
+
out()
|
|
781
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
782
|
+
|
|
783
|
+
# ── /login ─────────────────────────────────────────────────────────────────
|
|
784
|
+
if name == "login":
|
|
785
|
+
from gemcode.credentials import credentials_path
|
|
786
|
+
|
|
787
|
+
out("API keys are not stored inside the REPL. Use a separate terminal:")
|
|
788
|
+
out()
|
|
789
|
+
out(" gemcode login")
|
|
790
|
+
out()
|
|
791
|
+
out("Creates or updates your key at:")
|
|
792
|
+
out(f" {credentials_path()}")
|
|
793
|
+
out()
|
|
794
|
+
out("Get a key: https://aistudio.google.com/app/apikey")
|
|
795
|
+
out()
|
|
796
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
797
|
+
|
|
798
|
+
# ── /live-audio ────────────────────────────────────────────────────────────
|
|
799
|
+
if name in ("live-audio", "liveaudio"):
|
|
800
|
+
out("Live audio (microphone → Gemini Live) runs as a dedicated CLI, not inside this REPL.")
|
|
801
|
+
out()
|
|
802
|
+
out("Example:")
|
|
803
|
+
out(f" gemcode live-audio -C {cfg.project_root}")
|
|
804
|
+
out()
|
|
805
|
+
out("Flags: --seconds N --rate 24000 --language en-US --model <id>")
|
|
806
|
+
out(" --yes --deep-research --embeddings --session <uuid>")
|
|
807
|
+
out()
|
|
808
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
809
|
+
|
|
542
810
|
if name in ("model", "models"):
|
|
543
811
|
args = (sc.args or "").strip()
|
|
544
812
|
if not args:
|
|
@@ -772,6 +1040,22 @@ async def process_repl_slash(
|
|
|
772
1040
|
return ReplSlashResult(skip_model_turn=True)
|
|
773
1041
|
|
|
774
1042
|
if name == "tools":
|
|
1043
|
+
args_t = (sc.args or "").strip().lower()
|
|
1044
|
+
if args_t in ("smoke", "decl", "declarations"):
|
|
1045
|
+
from gemcode.tools_inspector import inspect_tools, smoke_tools
|
|
1046
|
+
|
|
1047
|
+
inspections = inspect_tools(cfg, extra_tools=extra_tools)
|
|
1048
|
+
bad = smoke_tools(inspections)
|
|
1049
|
+
if not bad:
|
|
1050
|
+
out(f"tools smoke: OK ({len(inspections)} tools, declarations compile)")
|
|
1051
|
+
else:
|
|
1052
|
+
out(f"tools smoke: {len(bad)} failure(s) of {len(inspections)} tools")
|
|
1053
|
+
for i in bad[:60]:
|
|
1054
|
+
out(f" {i.name}: {i.declaration_error}")
|
|
1055
|
+
if len(bad) > 60:
|
|
1056
|
+
out(f" … ({len(bad) - 60} more)")
|
|
1057
|
+
out()
|
|
1058
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
775
1059
|
out("\n".join(format_tools_lines(cfg, extra_tools=extra_tools)))
|
|
776
1060
|
out()
|
|
777
1061
|
return ReplSlashResult(skip_model_turn=True)
|
|
@@ -787,6 +1071,8 @@ async def process_repl_slash(
|
|
|
787
1071
|
out(f"model_mode: {cfg.model_mode}")
|
|
788
1072
|
out(f"session_id: {session_id}")
|
|
789
1073
|
out(f"project_root: {cfg.project_root}")
|
|
1074
|
+
_lg = getattr(cfg, "session_loaded_skill_names", None) or []
|
|
1075
|
+
out(f"loaded_skills: {', '.join(_lg) if _lg else '(none)'} (/gemskill)")
|
|
790
1076
|
out()
|
|
791
1077
|
out("Capabilities:")
|
|
792
1078
|
out(f" deep_research: {'on ✓' if cfg.enable_deep_research else 'off'}")
|
|
@@ -891,10 +1177,15 @@ async def process_repl_slash(
|
|
|
891
1177
|
|
|
892
1178
|
# /clear or /session new — start fresh session
|
|
893
1179
|
if name == "clear" or args_lower in ("new", "reset"):
|
|
1180
|
+
_clear_session_loaded_skills(cfg)
|
|
894
1181
|
new_id = str(uuid.uuid4())
|
|
895
1182
|
out(f"new session_id: {new_id}")
|
|
896
1183
|
out()
|
|
897
|
-
return ReplSlashResult(
|
|
1184
|
+
return ReplSlashResult(
|
|
1185
|
+
skip_model_turn=True,
|
|
1186
|
+
new_session_id=new_id,
|
|
1187
|
+
force_rebuild_runner=True,
|
|
1188
|
+
)
|
|
898
1189
|
|
|
899
1190
|
# /session list — show recent sessions
|
|
900
1191
|
if args_lower in ("list", "ls", "history"):
|
|
@@ -940,9 +1231,14 @@ async def process_repl_slash(
|
|
|
940
1231
|
out(f"Already in session {found[:8]}.")
|
|
941
1232
|
out()
|
|
942
1233
|
return ReplSlashResult(skip_model_turn=True)
|
|
1234
|
+
_clear_session_loaded_skills(cfg)
|
|
943
1235
|
out(f"Resuming session {found[:8]}…")
|
|
944
1236
|
out()
|
|
945
|
-
return ReplSlashResult(
|
|
1237
|
+
return ReplSlashResult(
|
|
1238
|
+
skip_model_turn=True,
|
|
1239
|
+
new_session_id=found,
|
|
1240
|
+
force_rebuild_runner=True,
|
|
1241
|
+
)
|
|
946
1242
|
|
|
947
1243
|
# Default: show current session info
|
|
948
1244
|
from gemcode.session_store import get_session_name, touch_session
|
|
@@ -1327,6 +1623,33 @@ async def process_repl_slash(
|
|
|
1327
1623
|
out()
|
|
1328
1624
|
return ReplSlashResult(skip_model_turn=True)
|
|
1329
1625
|
|
|
1626
|
+
# ── /maps (Maps grounding, deep-research stack) ────────────────────────────
|
|
1627
|
+
if name in ("maps", "map"):
|
|
1628
|
+
args_s = (sc.args or "").strip().lower()
|
|
1629
|
+
if not args_s or args_s in ("status", "show"):
|
|
1630
|
+
status = "on ✓" if cfg.enable_maps_grounding else "off"
|
|
1631
|
+
out(f"maps_grounding: {status}")
|
|
1632
|
+
out()
|
|
1633
|
+
out("Commands: /maps on · /maps off")
|
|
1634
|
+
out("When on: Maps-backed grounding is available alongside deep-research tools (if enabled).")
|
|
1635
|
+
out()
|
|
1636
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
1637
|
+
if args_s == "on":
|
|
1638
|
+
cfg.enable_maps_grounding = True
|
|
1639
|
+
out("maps_grounding: on")
|
|
1640
|
+
out(" Runner will rebuild on next turn.")
|
|
1641
|
+
out()
|
|
1642
|
+
return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
|
|
1643
|
+
if args_s == "off":
|
|
1644
|
+
cfg.enable_maps_grounding = False
|
|
1645
|
+
out("maps_grounding: off")
|
|
1646
|
+
out()
|
|
1647
|
+
return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
|
|
1648
|
+
out(f"Unknown /maps subcommand: '{args_s}'")
|
|
1649
|
+
out("Usage: /maps [on|off]")
|
|
1650
|
+
out()
|
|
1651
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
1652
|
+
|
|
1330
1653
|
# ── /mode ─────────────────────────────────────────────────────────────────
|
|
1331
1654
|
if name == "mode":
|
|
1332
1655
|
args_s = (sc.args or "").strip().lower()
|
gemcode/trust.py
CHANGED
|
@@ -10,6 +10,11 @@ def _trust_file_path() -> Path:
|
|
|
10
10
|
return base / "trust.json"
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def trust_json_path() -> Path:
|
|
14
|
+
"""Path to the trust database (respects GEMCODE_HOME)."""
|
|
15
|
+
return _trust_file_path()
|
|
16
|
+
|
|
17
|
+
|
|
13
18
|
def load_trusted_roots() -> set[str]:
|
|
14
19
|
"""
|
|
15
20
|
Returns a set of resolved absolute paths (as strings) that are trusted.
|
gemcode/tui/input_handler.py
CHANGED
|
@@ -36,48 +36,7 @@ try:
|
|
|
36
36
|
except ImportError:
|
|
37
37
|
pass
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
# Slash command registry
|
|
41
|
-
# (name without /, description shown in the autocomplete popup)
|
|
42
|
-
# ---------------------------------------------------------------------------
|
|
43
|
-
SLASH_COMMANDS: list[tuple[str, str]] = [
|
|
44
|
-
("help", "List all available commands"),
|
|
45
|
-
("init", "Analyze project and generate GEMINI.md project instructions"),
|
|
46
|
-
("cost", "Show session token usage and estimated USD cost breakdown"),
|
|
47
|
-
("notes", "View agent auto-generated project notes (.gemcode/notes.md)"),
|
|
48
|
-
("diff", "Show git diff (or checkpoint diff fallback)"),
|
|
49
|
-
("rewind", "Restore files to a previous checkpoint · alias: /checkpoint"),
|
|
50
|
-
("add-dir", "Add extra directory for read/search access · /add-dir list"),
|
|
51
|
-
("batch", "Parallel large-change orchestrator (built-in GemSkill)"),
|
|
52
|
-
("review", "Parallel code review: security + style + correctness simultaneously"),
|
|
53
|
-
("compact", "Compact conversation history to free context window"),
|
|
54
|
-
("clear", "Start a fresh session (clears history) · alias: /session new"),
|
|
55
|
-
("model", "View or switch model · /model use <id> · /model list"),
|
|
56
|
-
("mode", "Set model mode · /mode fast|balanced|quality|auto"),
|
|
57
|
-
("thinking", "Thinking config · /thinking verbose · /thinking brief · /thinking budget <N>"),
|
|
58
|
-
("status", "Show full session status: model, capabilities, thinking, limits"),
|
|
59
|
-
("context", "Show context window usage and token counts"),
|
|
60
|
-
("compact", "Summarise conversation history to reclaim context space"),
|
|
61
|
-
("research", "Toggle Google Search + URL Context · /research on|off"),
|
|
62
|
-
("embeddings", "Toggle semantic file search via Embeddings API · /embeddings on|off"),
|
|
63
|
-
("computer", "Browser automation (Playwright Chromium) · /computer on|off|url"),
|
|
64
|
-
("caps", "View/toggle all capabilities · /caps · /caps all · /caps reset"),
|
|
65
|
-
("memory", "Toggle persistent memory · /memory on|off"),
|
|
66
|
-
("budget", "Set per-turn token budget · /budget <N> · /budget off"),
|
|
67
|
-
("limits", "Show/set execution limits (max_llm_calls, context, etc.)"),
|
|
68
|
-
("kaira", "Background parallel job scheduler — how to run gemcode kaira"),
|
|
69
|
-
("code", "Toggle sandboxed Python code executor (ADK BuiltInCodeExecutor)"),
|
|
70
|
-
("plan", "Toggle plan mode — agent writes explicit plan before executing tools"),
|
|
71
|
-
("tools", "List all tools and their permission categories"),
|
|
72
|
-
("config", "Show full active configuration (all fields)"),
|
|
73
|
-
("permissions", "Show current permission mode (default / strict / yes)"),
|
|
74
|
-
("session", "Show current session ID · /session new to reset"),
|
|
75
|
-
("audit", "Show recent audit log · /audit [N lines]"),
|
|
76
|
-
("doctor", "Run diagnostics and validate the environment"),
|
|
77
|
-
("hooks", "Show post-turn hook configuration"),
|
|
78
|
-
("version", "Show installed GemCode version"),
|
|
79
|
-
("exit", "Exit GemCode"),
|
|
80
|
-
]
|
|
39
|
+
from gemcode.repl_commands import SLASH_COMMANDS
|
|
81
40
|
|
|
82
41
|
|
|
83
42
|
# ---------------------------------------------------------------------------
|
|
@@ -136,6 +95,13 @@ class GemCodeInputHandler:
|
|
|
136
95
|
|
|
137
96
|
if _PT_AVAILABLE and sys.stdin.isatty() and sys.stdout.isatty():
|
|
138
97
|
self._build_session()
|
|
98
|
+
elif sys.stdin.isatty():
|
|
99
|
+
try:
|
|
100
|
+
from gemcode.repl_commands import install_readline_slash_completion
|
|
101
|
+
|
|
102
|
+
install_readline_slash_completion()
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
139
105
|
|
|
140
106
|
def _build_session(self) -> None:
|
|
141
107
|
style = Style.from_dict(
|
gemcode/tui/scrollback.py
CHANGED
|
@@ -530,6 +530,7 @@ async def run_gemcode_scrollback_tui(
|
|
|
530
530
|
old_model = getattr(cfg, "model", "")
|
|
531
531
|
old_model_overridden = bool(getattr(cfg, "model_overridden", False))
|
|
532
532
|
|
|
533
|
+
cfg.session_skill_expand_session_id = current_session_id
|
|
533
534
|
slash = await process_repl_slash(
|
|
534
535
|
cfg=cfg,
|
|
535
536
|
runner=runner,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gemcode
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.78
|
|
4
4
|
Summary: Local-first coding agent on Google Gemini + ADK
|
|
5
5
|
Author: GemCode Contributors
|
|
6
6
|
License: Apache License
|
|
@@ -197,32 +197,82 @@ This document is the **authoritative reference** for CLI behavior, configuration
|
|
|
197
197
|
|
|
198
198
|
## Table of contents
|
|
199
199
|
|
|
200
|
-
1. [
|
|
201
|
-
2. [
|
|
202
|
-
3. [
|
|
203
|
-
4. [
|
|
204
|
-
5. [
|
|
205
|
-
6. [
|
|
206
|
-
7. [
|
|
207
|
-
8. [
|
|
208
|
-
9. [
|
|
209
|
-
10. [
|
|
210
|
-
11. [
|
|
211
|
-
12. [
|
|
212
|
-
13. [
|
|
213
|
-
14. [
|
|
214
|
-
15. [
|
|
215
|
-
16. [
|
|
216
|
-
17. [
|
|
217
|
-
18. [
|
|
218
|
-
19. [
|
|
219
|
-
20. [
|
|
220
|
-
21. [
|
|
221
|
-
22. [
|
|
222
|
-
23. [
|
|
223
|
-
24. [
|
|
224
|
-
25. [
|
|
225
|
-
26. [
|
|
200
|
+
1. [What GemCode is (detailed)](#what-gemcode-is-detailed)
|
|
201
|
+
2. [Architecture](#architecture)
|
|
202
|
+
3. [Requirements and install](#requirements-and-install)
|
|
203
|
+
4. [First run](#first-run)
|
|
204
|
+
5. [CLI commands](#cli-commands)
|
|
205
|
+
6. [Main CLI flags](#main-cli-flags)
|
|
206
|
+
7. [The `.gemcode/` directory](#the-gemcode-directory)
|
|
207
|
+
8. [Project context: `GEMINI.md`](#project-context-geminimd)
|
|
208
|
+
9. [Function tools (catalog)](#function-tools-catalog)
|
|
209
|
+
10. [REPL: slash commands](#repl-slash-commands)
|
|
210
|
+
11. [GemSkills](#gemskills)
|
|
211
|
+
12. [Curated memory](#curated-memory)
|
|
212
|
+
13. [Workspace trust](#workspace-trust)
|
|
213
|
+
14. [Output styles and rules](#output-styles-and-rules)
|
|
214
|
+
15. [Checkpoints, diff, and rewind](#checkpoints-diff-and-rewind)
|
|
215
|
+
16. [Multi-root workspaces (`/add-dir`)](#multi-root-workspaces-add-dir)
|
|
216
|
+
17. [Model routing and thinking](#model-routing-and-thinking)
|
|
217
|
+
18. [Capabilities](#capabilities)
|
|
218
|
+
19. [Permissions and interactive approval](#permissions-and-interactive-approval)
|
|
219
|
+
20. [Hooks](#hooks)
|
|
220
|
+
21. [Token budget, context, and compaction](#token-budget-context-and-compaction)
|
|
221
|
+
22. [MCP](#mcp)
|
|
222
|
+
23. [IDE bridge: `gemcode ide --stdio`](#ide-bridge-gemcode-ide-stdio)
|
|
223
|
+
24. [Eval harness and autotune](#eval-harness-and-autotune)
|
|
224
|
+
25. [Kaira scheduler](#kaira-scheduler)
|
|
225
|
+
26. [Live audio](#live-audio)
|
|
226
|
+
27. [Related components](#related-components)
|
|
227
|
+
28. [Environment variables](#environment-variables)
|
|
228
|
+
29. [Development and release](#development-and-release)
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## What GemCode is (detailed)
|
|
233
|
+
|
|
234
|
+
**GemCode** is a single Python package that exposes a **`gemcode`** CLI. Every invocation is anchored to a **project root** (`-C`, default current directory). From there it:
|
|
235
|
+
|
|
236
|
+
- Builds a **GemCodeConfig** (model, capabilities, permissions, limits, paths).
|
|
237
|
+
- Optionally loads **MCP** toolsets from `.gemcode/mcp.json`.
|
|
238
|
+
- Constructs an ADK **Runner** with a root **LlmAgent** whose **system instruction** aggregates: global behavior, `GEMINI.md`, capability sections, tool manifest, optional **output style** and **rules**, the **skills manifest** (metadata only), and any **session-loaded GemSkills** (full bodies after `/gemskill`).
|
|
239
|
+
- Persists conversation state in **SQLite** (`.gemcode/sessions.sqlite`) keyed by **`--session`** / REPL session id.
|
|
240
|
+
|
|
241
|
+
### Modes of use
|
|
242
|
+
|
|
243
|
+
| Mode | How | Best for |
|
|
244
|
+
|------|-----|----------|
|
|
245
|
+
| **One-shot CLI** | `gemcode -C repo "prompt"` | Scripts, CI-style runs, quick questions. |
|
|
246
|
+
| **REPL** | `gemcode -C repo` (TTY, no prompt arg) | Long sessions, slash commands, exploration. |
|
|
247
|
+
| **TUI** | Same as REPL with `GEMCODE_TUI=1` (default when supported) | Scrollback, styled output, slash **completion menu**. |
|
|
248
|
+
| **IDE** | VS Code extension → `gemcode ide --stdio` | Editor-native Chat, diff apply, proposals. |
|
|
249
|
+
| **Kaira** | `gemcode kaira` | Background queue of independent agent jobs. |
|
|
250
|
+
| **Live audio** | `gemcode live-audio` | Voice → Gemini Live (separate from file-editing REPL). |
|
|
251
|
+
|
|
252
|
+
### “Memory” in GemCode (three different things)
|
|
253
|
+
|
|
254
|
+
| Mechanism | Where | Purpose |
|
|
255
|
+
|-----------|--------|---------|
|
|
256
|
+
| **Session history** | `sessions.sqlite` | Full turn-by-turn chat for a session id. |
|
|
257
|
+
| **Curated memory** | `GEMCODE_MEMORY.md`, `GEMCODE_USER.md` (under `.gemcode/`) | Small, **human-approved** facts injected when memory features are on; distinct from noisy auto-memory. |
|
|
258
|
+
| **Embedding memory** | `memories.jsonl` + ADK hooks (when enabled) | Retrieval-oriented storage when **`GEMCODE_ENABLE_MEMORY`** (and related) are on. |
|
|
259
|
+
|
|
260
|
+
Use **`/curated`** in the REPL to preview the curated snapshot; tools such as **`read_curated_memory`** / **`remember_fact`** operate on that layer.
|
|
261
|
+
|
|
262
|
+
### GemSkills at a glance (four ways)
|
|
263
|
+
|
|
264
|
+
| Mechanism | Effect |
|
|
265
|
+
|-----------|--------|
|
|
266
|
+
| **`/create gemskill <name> [description]`** | Creates **`.gemcode/skills/<name>/SKILL.md`** scaffold (new skill on disk). |
|
|
267
|
+
| **`/gemskill <name>`** | **Pins** the skill’s **full body** into the **system instruction** for the **current session** (until `/gemskill clear`, new session, or resume). Rebuilds the runner. |
|
|
268
|
+
| **`/append gemskill <name> <request>`** | One model turn instructed to **edit** that skill file according to `<request>`. |
|
|
269
|
+
| **`/skill <name>`**, **`/<name>`**, or tools **`load_skill`** | **One-shot** turn: inject expanded skill into the **user message** for that turn only (does not pin to system prompt). |
|
|
270
|
+
|
|
271
|
+
The built-in **`batch`** skill is manual-only (`disable_model_invocation`); use **`/batch <goal>`** to run it.
|
|
272
|
+
|
|
273
|
+
### Slash completion
|
|
274
|
+
|
|
275
|
+
The REPL and TUI use a **canonical** command list for Tab completion (`repl_commands.SLASH_COMMANDS`). Some spellings (e.g. `/quit`, `/logs`, `/embed`) still work in **`process_repl_slash`** but may not appear as separate menu rows—see descriptions in **`/help`**.
|
|
226
276
|
|
|
227
277
|
---
|
|
228
278
|
|
|
@@ -311,6 +361,7 @@ State is **project-local** (unless noted).
|
|
|
311
361
|
| Path / artifact | Purpose |
|
|
312
362
|
|-----------------|---------|
|
|
313
363
|
| `sessions.sqlite` | ADK session service: conversation history for `--session` ids. |
|
|
364
|
+
| `GEMCODE_MEMORY.md`, `GEMCODE_USER.md` | **Curated memory** (see [Curated memory](#curated-memory)). |
|
|
314
365
|
| `audit.log` | JSONL audit: tool usage, model usage, terminal reasons, optional tool-use summaries. |
|
|
315
366
|
| `tool-results/` | Offloaded large tool outputs; references like `tool_result:<sha256>`. |
|
|
316
367
|
| `artifacts/` | File artifacts (ADK `FileArtifactService`). |
|
|
@@ -421,10 +472,15 @@ In interactive mode, lines starting with `/` are **slash commands** (see `repl_c
|
|
|
421
472
|
|
|
422
473
|
| Command | Purpose |
|
|
423
474
|
|---------|---------|
|
|
475
|
+
| `/trust`, `/trust on`, `/trust off` | Show, grant, or revoke **workspace trust** for the project root (stored in `~/.gemcode/trust.json`; required for file/shell/git tools). |
|
|
424
476
|
| `/init` \| `/init force` | Analyze the repo and generate or overwrite `GEMINI.md`. |
|
|
425
477
|
| `/cost` | Token usage and estimated cost for the session. |
|
|
426
478
|
| `/notes`, `/notes clear`, `/notes edit` | View, clear, or edit `.gemcode/notes.md`. |
|
|
427
|
-
| `/create gemskill <name> [description]` |
|
|
479
|
+
| `/create gemskill <name> [description]` | **Create** a new GemSkill (scaffold `SKILL.md`). |
|
|
480
|
+
| `/gemskill <name>` | **Load** an existing skill into the **session system prompt** (until `/gemskill clear` or new session). |
|
|
481
|
+
| `/gemskill list` \| `/gemskill clear` | List skills or unload all session-loaded skills. |
|
|
482
|
+
| `/append gemskill <name> <request>` | **Iterate** the skill file: model edits `SKILL.md` per your request. |
|
|
483
|
+
| `/curated` | Show **curated memory** snapshot (`.gemcode/GEMCODE_MEMORY.md`, `GEMCODE_USER.md`). |
|
|
428
484
|
| `/style`, `/style <name>\|off` | List or activate **output styles** (`.gemcode/output-styles/*.md`). |
|
|
429
485
|
| `/rules` | Show **rule** files from `.gemcode/rules/` (with path gating). |
|
|
430
486
|
| `/diff`, `/diff last`, `/diff cp_…` | Git diff, or **checkpoint → workspace** diff. |
|
|
@@ -437,7 +493,7 @@ In interactive mode, lines starting with `/` are **slash commands** (see `repl_c
|
|
|
437
493
|
| Command | Purpose |
|
|
438
494
|
|---------|---------|
|
|
439
495
|
| `/help` | Short help. |
|
|
440
|
-
| `/status` | Model, capabilities, thinking, limits, risk/context telemetry. |
|
|
496
|
+
| `/status` | Model, capabilities, thinking, limits, risk/context telemetry; **`loaded_skills`** (session-pinned GemSkills from `/gemskill`). |
|
|
441
497
|
| `/config` | Dump active config fields. |
|
|
442
498
|
| `/session`, `/session list`, `/session name`, `/session resume`, `/session new` | Session management; `/clear` aliases `/session new`. |
|
|
443
499
|
| `/compact`, `/compact <focus>` | Force context compaction / summarization. |
|
|
@@ -445,6 +501,12 @@ In interactive mode, lines starting with `/` are **slash commands** (see `repl_c
|
|
|
445
501
|
| `/context` | Context pressure and token breakdown (includes styles, rules, skills manifest, touched paths). |
|
|
446
502
|
| `/audit [N]` | Tail of `audit.log`. |
|
|
447
503
|
| `/tools` | Tool inventory for current config. |
|
|
504
|
+
| `/tools smoke` | Declaration compile check only (lists failures). |
|
|
505
|
+
| `/eval`, `/eval llm` | **Eval harness**: tool smoke + `pytest` if a `tests/` tree exists; optional LLM golden prompts (costs tokens). Writes `.gemcode/evals/last_eval.json`. |
|
|
506
|
+
| `/autotune init <tag>` | Create git branch `autotune/<tag>` (requires a git repo). |
|
|
507
|
+
| `/autotune eval`, `… llm` | Run eval and append `.gemcode/evals/autotune_ledger.jsonl`. |
|
|
508
|
+
| `/login` | How to run **`gemcode login`** (API key is set outside the REPL). |
|
|
509
|
+
| `/live-audio` | How to run **`gemcode live-audio`** (mic → Gemini Live). |
|
|
448
510
|
| `/doctor` | Environment sanity check. |
|
|
449
511
|
| `/version` | Version string. |
|
|
450
512
|
| `/exit` | Leave the REPL. |
|
|
@@ -462,6 +524,7 @@ In interactive mode, lines starting with `/` are **slash commands** (see `repl_c
|
|
|
462
524
|
|---------|---------|
|
|
463
525
|
| `/computer`, `/computer on\|off`, `/computer url` | Browser automation (Playwright). |
|
|
464
526
|
| `/research`, `/research on\|off` | Deep research tools. |
|
|
527
|
+
| `/maps`, `/maps on\|off` | Maps **grounding** toggle (runner rebuild). |
|
|
465
528
|
| `/embeddings on\|off` | Semantic search tool. |
|
|
466
529
|
| `/caps`, `/caps …` | View or bulk-toggle capabilities. |
|
|
467
530
|
| `/memory`, `/memory on\|off` | Persistent memory. |
|
|
@@ -496,7 +559,33 @@ The TUI (when `GEMCODE_TUI=1` and terminal supports it) provides **slash complet
|
|
|
496
559
|
- **Discovery:** Only **metadata** (name + description) is preloaded into context for token efficiency. Full body loads **on demand** via `/skill <name>`, `/<name>`, or tools `load_skill` / `list_skills`.
|
|
497
560
|
- **Built-in:** **`batch`** — parallel large-change workflow (map → units → `run_subtask` → verify). Exposed as `/batch <goal>`; not auto-invoked by the model (`disable_model_invocation`).
|
|
498
561
|
|
|
499
|
-
|
|
562
|
+
- **`/create gemskill <name>`** — create a new skill directory.
|
|
563
|
+
- **`/gemskill <name>`** — pin the full skill body into the system prompt for this session.
|
|
564
|
+
- **`/append gemskill <name> <text>`** — one-shot turn for the model to revise that skill on disk.
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
## Curated memory
|
|
569
|
+
|
|
570
|
+
**Curated memory** is a small, **intentional** text layer separate from raw session logs or embedding retrieval:
|
|
571
|
+
|
|
572
|
+
- **Project facts:** `.gemcode/GEMCODE_MEMORY.md` (conventions, commands, architecture notes the team wants the agent to see).
|
|
573
|
+
- **User preferences (per project):** `.gemcode/GEMCODE_USER.md`.
|
|
574
|
+
|
|
575
|
+
Legacy filenames **`.gemcode/MEMORY.md`** and **`.gemcode/USER.md`** are still read if the new names are absent.
|
|
576
|
+
|
|
577
|
+
Content is **sanitized** before append (length and sensitivity heuristics). The REPL command **`/curated`** prints a bounded snapshot of what would be injected. When the **memory** capability is enabled, curated material can be combined with broader memory tools—see **`remember_fact`**, **`read_curated_memory`** in the [function tools](#function-tools-catalog) table.
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## Workspace trust
|
|
582
|
+
|
|
583
|
+
File, shell, and related tools require the **project root** to be **trusted**. Trust is stored in **`~/.gemcode/trust.json`** (path configurable via **`GEMCODE_HOME`**), not inside the repo.
|
|
584
|
+
|
|
585
|
+
- **`/trust`** — show whether the current root is trusted.
|
|
586
|
+
- **`/trust on`** / **`/trust off`** — grant or revoke trust for **`cfg.project_root`**.
|
|
587
|
+
|
|
588
|
+
Without trust, mutating and shell tools remain blocked even if **`--yes`** is set—this reduces accidental execution when the cwd is wrong (e.g. home directory).
|
|
500
589
|
|
|
501
590
|
---
|
|
502
591
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
gemcode/__init__.py,sha256=l0DCRYqK7KM7Fb7u49fqh-5_SlpeIL7r3LjMeJWMgSg,112
|
|
2
2
|
gemcode/__main__.py,sha256=EX2s1hxq2Yvli_-tnBN3w5Qv4bOjsBBbjyISF0pDIQw,37
|
|
3
|
-
gemcode/agent.py,sha256=
|
|
3
|
+
gemcode/agent.py,sha256=_v00pQSOzAI9nyaaKC1wn-aTalD4SV52KDzwlyIHJAo,56867
|
|
4
4
|
gemcode/audit.py,sha256=bh9uhXaeh8wqxqoZtz3ZAowd8Ndk1ss-mw9993Vlrgo,469
|
|
5
5
|
gemcode/autocompact.py,sha256=OE3QbGx2gWN2WVXy28Sd0xyfAJgVR1x6ml9HrX2CB7I,6719
|
|
6
|
-
gemcode/autotune.py,sha256=
|
|
6
|
+
gemcode/autotune.py,sha256=zcTGDKC8LSnw0fHuoOcnnh1rz0by8K6MTUKl0_GhT9s,2704
|
|
7
7
|
gemcode/callbacks.py,sha256=QR98bz-FeK2kp9N9JdzaeHRD2Ga2_rgz0XAPiYFafU0,31038
|
|
8
8
|
gemcode/capability_routing.py,sha256=ZScRb2Bn41ZJUEmw_QaYYDVUPaNnbGBbK216tm5G4mI,3456
|
|
9
9
|
gemcode/checkpoints.py,sha256=ptz2YW7P56mkqOrAf7-MXA0_Op3y4fgAg5ly_etuKvw,3936
|
|
10
|
-
gemcode/cli.py,sha256=
|
|
10
|
+
gemcode/cli.py,sha256=2R5pvpx3T8sUnfgbBRmkFjv5-uxCPeo_HuXMsaz23s8,28429
|
|
11
11
|
gemcode/compaction.py,sha256=9YtA_qa23_8dHWVHx7AJwUduuI7jJQtq-m6sT8jgPWI,1186
|
|
12
|
-
gemcode/config.py,sha256=
|
|
12
|
+
gemcode/config.py,sha256=OTJrLFIX-sr6kMWVI4l8rM2kJqkt3X2NcNFBEg6Nfdg,18042
|
|
13
13
|
gemcode/context_budget.py,sha256=nctVwzpUg6kyBL0FHM7xcrvVvZpX9qj7cZK4IczfKAg,10534
|
|
14
14
|
gemcode/context_warning.py,sha256=YwkkNx6AM2ugOckg8QhRWYcU6D3UJXUOHiBwjKQT2rs,4199
|
|
15
15
|
gemcode/credentials.py,sha256=o1gQ4oQXAjjA1IANXgCalM3WjTkfVkbkXsf-lNVQ55I,1300
|
|
@@ -39,8 +39,8 @@ gemcode/policy_profile.py,sha256=kcaKJQwLxAo3RjqfJJHl_G7B5GgTYKco0z3k5QcXsVY,386
|
|
|
39
39
|
gemcode/pricing.py,sha256=lftp0SwyDqOzHqC2-6XzgZZhjif5PLdCe1Q3wY-p6kQ,3558
|
|
40
40
|
gemcode/prompt_suggestions.py,sha256=RNEclxtoorRqu-wUlzuyUJ7OLFVOOryGOZBpbaCducI,2544
|
|
41
41
|
gemcode/refine.py,sha256=BijEZ4Z32wGa9aK_WottyAhZF-j0xEqRg5UpjedNv2A,7653
|
|
42
|
-
gemcode/repl_commands.py,sha256=
|
|
43
|
-
gemcode/repl_slash.py,sha256=
|
|
42
|
+
gemcode/repl_commands.py,sha256=iGF68JdJiwryoZ69EvLSNAg7BN-ivopctJqNWe4UjWk,18467
|
|
43
|
+
gemcode/repl_slash.py,sha256=zRBFJYnaNfO3yAfHQ5S9X-OyiZSWDePoR62OV5LbG5M,83743
|
|
44
44
|
gemcode/review_agent.py,sha256=4t7_5-aE60b4-EheJ_eSB_H2eQYf9GppKoui6jw0TME,5264
|
|
45
45
|
gemcode/rules.py,sha256=Itg02VpifOo6jqGj5xwna_ahaPPb0OVtaeR2cNI0pLE,3018
|
|
46
46
|
gemcode/session_runtime.py,sha256=MQr34s6dw4bmk_VPSgBvkJTMwUBZQhvWDXl_iC5Pp6s,20867
|
|
@@ -52,13 +52,13 @@ gemcode/tool_prompt_manifest.py,sha256=BbM88yj6hN5CTDBzyHa7DZ1S6gpzIZQe9G7dFqkMm
|
|
|
52
52
|
gemcode/tool_registry.py,sha256=DqHshoOG_8U5Ijey407DFxp3xYq3bVBLwUVxFgGgELs,1799
|
|
53
53
|
gemcode/tool_result_store.py,sha256=50ufSP03F1D8mWFX7HFimTVh-fMWpshOtw4AP-qf-cU,6351
|
|
54
54
|
gemcode/tools_inspector.py,sha256=ZAuD1oVIsZvyD_vrzaWBws2ezpFT6cIsU_w4yc_1PPA,4086
|
|
55
|
-
gemcode/trust.py,sha256=
|
|
55
|
+
gemcode/trust.py,sha256=kZi1dll1T8RUZtSY42WbsEh-ZIkMRup_IZPfYlJMbw4,1457
|
|
56
56
|
gemcode/version.py,sha256=uwynYS-RmK8CDoqGtt8976kFkJv0zELkEAlwebnp_io,380
|
|
57
57
|
gemcode/vertex.py,sha256=Fy8zxuU8jWkObt0WDRI0XmgnjNznILXVLVwKjImNz9Q,643
|
|
58
58
|
gemcode/workspace_hints.py,sha256=WQFwyoGnVrzzYSl0s5MNcd1-UP11Ao8KGgrdFIB5m9g,621
|
|
59
59
|
gemcode/computer_use/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
60
|
gemcode/computer_use/browser_computer.py,sha256=bWXXgj7K6ZrDqT49iPEcnBd_ZJBwRxSgKhzKaJpn4cQ,13212
|
|
61
|
-
gemcode/evals/harness.py,sha256=
|
|
61
|
+
gemcode/evals/harness.py,sha256=qf2L77cdghBE6uPUTkQQxL9TmgDpN54NwTmyJUFcF14,5232
|
|
62
62
|
gemcode/memory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
63
|
gemcode/memory/embedding_memory_service.py,sha256=4iMZUw80GY8SPrJcuT4CwOsTmZ600SOuYkm-nv2c634,9422
|
|
64
64
|
gemcode/memory/file_memory_service.py,sha256=yAXCspfSPBfQXDrwPX8ZZsqaprnC_CKvgmN_GiQVzuo,6092
|
|
@@ -91,8 +91,8 @@ gemcode/tools/think.py,sha256=bch9bsz1bs24uia5l3utnNSkT84mIyU_EzMgi82e60Y,1624
|
|
|
91
91
|
gemcode/tools/todo.py,sha256=dlGfcNce1WsJ5Y9txrDL3SoF6Hv2rms9r1cvGPs6qIs,5798
|
|
92
92
|
gemcode/tools/web.py,sha256=I-6-GgCVKblc9zVFfilWLHoJZfri7_pC2MpT52ZZarE,5078
|
|
93
93
|
gemcode/tools/web_search.py,sha256=YqIvwAoOXK3TMqrrMVeSrg5Nyt7Ou5nRljrYQ4784_4,8816
|
|
94
|
-
gemcode/tui/input_handler.py,sha256=
|
|
95
|
-
gemcode/tui/scrollback.py,sha256=
|
|
94
|
+
gemcode/tui/input_handler.py,sha256=Az8SbPaPHksIoibjph8gevMnfjagR1b-34_wpKbEhgQ,8259
|
|
95
|
+
gemcode/tui/scrollback.py,sha256=BA_hjqdEggnpbq5TZtJbojB7sRRgZ7evQMEQEfeCKoU,33137
|
|
96
96
|
gemcode/tui/spinner.py,sha256=dExs_enBPOWjkmRtodDzRw3E-MYh-xgtqDo54Q82sco,4892
|
|
97
97
|
gemcode/tui/welcome_banner.py,sha256=aocl1lnoyLIM6RN4f65g3i0wRA71RqUlgPrGsXeVLW4,4387
|
|
98
98
|
gemcode/tui/welcome_rich.py,sha256=VERTr2PB8hCdxNftJ827OdilFnnJMMDnqkaqzLne_lQ,4059
|
|
@@ -100,9 +100,9 @@ gemcode/web/__init__.py,sha256=EysmUAWs6g-lmMk4VFljKfaHVrEgb_FiIzwQmBdORJc,40
|
|
|
100
100
|
gemcode/web/sse_adapter.py,sha256=fXhKxn_bdJJUGqlmvkxLNSYL-ZiIZDaLHtQCF_BheRc,7108
|
|
101
101
|
gemcode/web/terminal_repl.py,sha256=fQt895g0qcr6VBhXfv_5b_bsC5zHT5-MO0ysBdgi2Fg,3886
|
|
102
102
|
gemcode/web/web_sse_compat.py,sha256=9A2s-GI7El7AotJqhO263FrLwppCXXkdydZ5EiOQbao,504
|
|
103
|
-
gemcode-0.3.
|
|
104
|
-
gemcode-0.3.
|
|
105
|
-
gemcode-0.3.
|
|
106
|
-
gemcode-0.3.
|
|
107
|
-
gemcode-0.3.
|
|
108
|
-
gemcode-0.3.
|
|
103
|
+
gemcode-0.3.78.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
|
|
104
|
+
gemcode-0.3.78.dist-info/METADATA,sha256=LEGVRi0Lfgz12djaXYe11PAzIEpce21QAIzpPQx3tvM,40109
|
|
105
|
+
gemcode-0.3.78.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
106
|
+
gemcode-0.3.78.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
|
|
107
|
+
gemcode-0.3.78.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
|
|
108
|
+
gemcode-0.3.78.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|