gemcode 0.3.80__tar.gz → 0.3.82__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.
- {gemcode-0.3.80/src/gemcode.egg-info → gemcode-0.3.82}/PKG-INFO +10 -2
- {gemcode-0.3.80 → gemcode-0.3.82}/README.md +9 -1
- {gemcode-0.3.80 → gemcode-0.3.82}/pyproject.toml +1 -1
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/agent.py +6 -5
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/curated_memory.py +12 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/modality_tools.py +12 -1
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/output_styles.py +8 -1
- gemcode-0.3.82/src/gemcode/query_sanitizer.py +87 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/repl_commands.py +3 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/repl_slash.py +87 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/skills.py +34 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/__init__.py +4 -0
- gemcode-0.3.82/src/gemcode/tools/compress_memory.py +333 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/edit.py +10 -5
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/web_search.py +9 -1
- gemcode-0.3.82/src/gemcode/wal.py +62 -0
- {gemcode-0.3.80 → gemcode-0.3.82/src/gemcode.egg-info}/PKG-INFO +10 -2
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode.egg-info/SOURCES.txt +4 -0
- gemcode-0.3.82/tests/test_compress_memory_tool.py +52 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_tools.py +11 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/LICENSE +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/MANIFEST.in +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/setup.cfg +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/__init__.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/__main__.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/audit.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/autotune.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/checkpoints.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/cli.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/compaction.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/config.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/credentials.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/dynamic_policy.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/evals/harness.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/hooks.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/ide_protocol.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/ide_stdio.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/intent_classifier.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/interactions.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/invoke.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/kaira_daemon.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/learning.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/limits.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/multimodal_input.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/openapi_loader.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/paths.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/permissions.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/policy_profile.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/pricing.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/config.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/refine.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/review_agent.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/rules.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/session_runtime.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/session_store.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/thinking.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tool_prompt_manifest.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tool_result_store.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/browser.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/curated_memory.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/notebook.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/notes.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/repo_map.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/skills.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/subtask.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/tasks.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/think.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/trust.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tui/input_handler.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tui/scrollback.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/version.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/vertex.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/web/sse_adapter.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/web/web_sse_compat.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_add_dir.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_autocompact.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_capability_routing.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_checkpoint_diff_command.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_cli_init.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_context_budget.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_context_warning.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_credentials.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_eval_harness_layout.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_ide_stdio_attachments.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_kaira_scheduler.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_modality_tools.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_model_errors.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_model_routing.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_multimodal_input.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_output_styles_and_rules.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_paths.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_permissions.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_repl_commands.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_repl_slash.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_skills.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_slash_commands.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_slash_completion_registry.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_thinking_config.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_token_budget.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_web_sse_adapter.py +0 -0
- {gemcode-0.3.80 → gemcode-0.3.82}/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.
|
|
3
|
+
Version: 0.3.82
|
|
4
4
|
Summary: Local-first coding agent on Google Gemini + ADK
|
|
5
5
|
Author: GemCode Contributors
|
|
6
6
|
License: Apache License
|
|
@@ -381,6 +381,7 @@ State is **project-local** (unless noted).
|
|
|
381
381
|
| `policy.json` | Self-tuning profile for dynamic token / evidence budgets. |
|
|
382
382
|
| `memories.jsonl` | Embedding-backed memory when `GEMCODE_ENABLE_MEMORY=1`. |
|
|
383
383
|
| `notes.md` | Agent notes surfaced via `/notes`. |
|
|
384
|
+
| `wal.jsonl` | Write-ahead log for curated memory + compression actions (metadata only). |
|
|
384
385
|
| `evals/last_eval.json` | Latest `gemcode eval` record. |
|
|
385
386
|
| `evals/autotune_ledger.jsonl` | Rows from `gemcode autotune eval`. |
|
|
386
387
|
| `skills/<name>/SKILL.md` | **GemSkills** (project-scoped; see [GemSkills](#gemskills)). |
|
|
@@ -473,7 +474,7 @@ Tools are registered in `gemcode/tools/` and exposed to the model as ADK functio
|
|
|
473
474
|
- **Computer use:** ADK `ComputerUseToolset` + Playwright (separate install and flags).
|
|
474
475
|
- **MCP:** Tools loaded from configured servers.
|
|
475
476
|
|
|
476
|
-
**Vendor file policy:** Writes to certain
|
|
477
|
+
**Vendor file policy:** Writes to certain third-party instruction filenames (`CLAUDE.md`, `AGENTS.md`, `*.local` variants, `.cursorrules`, …) are blocked; use `GEMINI.md` and `.gemcode/notes.md` instead. The agent instruction always states this; `write_file` / `search_replace` enforce it.
|
|
477
478
|
|
|
478
479
|
---
|
|
479
480
|
|
|
@@ -590,6 +591,13 @@ Legacy filenames **`.gemcode/MEMORY.md`** and **`.gemcode/USER.md`** are still r
|
|
|
590
591
|
|
|
591
592
|
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.
|
|
592
593
|
|
|
594
|
+
### Query sanitization (tooling)
|
|
595
|
+
|
|
596
|
+
Some tools sanitize long “contaminated” queries (e.g. accidental system-prompt prefixes) down to a short, likely-intended query. This currently applies to:
|
|
597
|
+
|
|
598
|
+
- `web_search(query=...)`
|
|
599
|
+
- `semantic_search_files(query=...)` (when `--embeddings` is on)
|
|
600
|
+
|
|
593
601
|
---
|
|
594
602
|
|
|
595
603
|
## Workspace trust
|
|
@@ -192,6 +192,7 @@ State is **project-local** (unless noted).
|
|
|
192
192
|
| `policy.json` | Self-tuning profile for dynamic token / evidence budgets. |
|
|
193
193
|
| `memories.jsonl` | Embedding-backed memory when `GEMCODE_ENABLE_MEMORY=1`. |
|
|
194
194
|
| `notes.md` | Agent notes surfaced via `/notes`. |
|
|
195
|
+
| `wal.jsonl` | Write-ahead log for curated memory + compression actions (metadata only). |
|
|
195
196
|
| `evals/last_eval.json` | Latest `gemcode eval` record. |
|
|
196
197
|
| `evals/autotune_ledger.jsonl` | Rows from `gemcode autotune eval`. |
|
|
197
198
|
| `skills/<name>/SKILL.md` | **GemSkills** (project-scoped; see [GemSkills](#gemskills)). |
|
|
@@ -284,7 +285,7 @@ Tools are registered in `gemcode/tools/` and exposed to the model as ADK functio
|
|
|
284
285
|
- **Computer use:** ADK `ComputerUseToolset` + Playwright (separate install and flags).
|
|
285
286
|
- **MCP:** Tools loaded from configured servers.
|
|
286
287
|
|
|
287
|
-
**Vendor file policy:** Writes to certain
|
|
288
|
+
**Vendor file policy:** Writes to certain third-party instruction filenames (`CLAUDE.md`, `AGENTS.md`, `*.local` variants, `.cursorrules`, …) are blocked; use `GEMINI.md` and `.gemcode/notes.md` instead. The agent instruction always states this; `write_file` / `search_replace` enforce it.
|
|
288
289
|
|
|
289
290
|
---
|
|
290
291
|
|
|
@@ -401,6 +402,13 @@ Legacy filenames **`.gemcode/MEMORY.md`** and **`.gemcode/USER.md`** are still r
|
|
|
401
402
|
|
|
402
403
|
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.
|
|
403
404
|
|
|
405
|
+
### Query sanitization (tooling)
|
|
406
|
+
|
|
407
|
+
Some tools sanitize long “contaminated” queries (e.g. accidental system-prompt prefixes) down to a short, likely-intended query. This currently applies to:
|
|
408
|
+
|
|
409
|
+
- `web_search(query=...)`
|
|
410
|
+
- `semantic_search_files(query=...)` (when `--embeddings` is on)
|
|
411
|
+
|
|
404
412
|
---
|
|
405
413
|
|
|
406
414
|
## Workspace trust
|
|
@@ -77,7 +77,8 @@ def build_global_instruction() -> str:
|
|
|
77
77
|
"Think deeply about what the person actually wants before you do anything. "
|
|
78
78
|
"Use exactly as many tools as the task genuinely requires — no more. "
|
|
79
79
|
"Act fully and autonomously when action is needed. "
|
|
80
|
-
"Always use read-only tools before shell or write tools."
|
|
80
|
+
"Always use read-only tools before shell or write tools. "
|
|
81
|
+
"Never create CLAUDE.md or AGENTS.md; use GEMINI.md for project instructions."
|
|
81
82
|
)
|
|
82
83
|
|
|
83
84
|
|
|
@@ -580,6 +581,10 @@ You have native deep thinking capability — use it actively:
|
|
|
580
581
|
Keep tool usage minimal. Prefer short, targeted calls and keep tool outputs small.
|
|
581
582
|
If you need more tool usage examples, set `GEMCODE_VERBOSE_INSTRUCTIONS=1`.
|
|
582
583
|
|
|
584
|
+
## Instruction files (GemCode — always follow)
|
|
585
|
+
- **Do not** create or modify `CLAUDE.md`, `AGENTS.md`, `claude.local.md`, `agents.local.md`, or `.cursorrules` unless the user **explicitly** asks for that exact filename. Those are for other assistants; GemCode reads **`GEMINI.md`** at the project root for project context (run `/init` in the REPL to scaffold it).
|
|
586
|
+
- If you need to capture project conventions, edit **`GEMINI.md`** or append to **`.gemcode/notes.md`** via the notes tools — not vendor-specific instruction filenames.
|
|
587
|
+
|
|
583
588
|
"""
|
|
584
589
|
|
|
585
590
|
if not verbose_tools_guide:
|
|
@@ -895,10 +900,6 @@ You have two tools to persist project insights across sessions (auto-memory styl
|
|
|
895
900
|
Notes are loaded at session start so future sessions inherit this knowledge.
|
|
896
901
|
|
|
897
902
|
- **`read_project_notes()`** — read current notes **only when starting a real engineering task** (editing, debugging, building). Do NOT call this for greetings or general questions. If notes exist and you're about to work on a task, read them once to avoid re-discovering known information.
|
|
898
|
-
|
|
899
|
-
## Do not create vendor-specific instruction files
|
|
900
|
-
- Do NOT create or modify `CLAUDE.md` or `AGENTS.md`. GemCode does not use these.
|
|
901
|
-
- If project instructions are needed and the user asked for it, use `GEMINI.md` (repo root).
|
|
902
903
|
"""
|
|
903
904
|
|
|
904
905
|
# Inject capability-specific strategy sections only when those caps are on.
|
|
@@ -22,6 +22,8 @@ from datetime import datetime
|
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
from typing import Any
|
|
24
24
|
|
|
25
|
+
from gemcode.wal import append_wal_event, wal_text_fingerprint
|
|
26
|
+
|
|
25
27
|
|
|
26
28
|
_SUSPICIOUS = [
|
|
27
29
|
"api_key",
|
|
@@ -106,5 +108,15 @@ def append_fact(project_root: Path, *, target: str, text: str) -> dict[str, Any]
|
|
|
106
108
|
ts = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
107
109
|
entry = f"\n<!-- {ts} -->\n- {stripped}\n"
|
|
108
110
|
p.write_text(cur + entry, encoding="utf-8")
|
|
111
|
+
# Best-effort WAL: do not store raw text.
|
|
112
|
+
append_wal_event(
|
|
113
|
+
project_root,
|
|
114
|
+
type="curated_memory.append_fact",
|
|
115
|
+
data={
|
|
116
|
+
"target": (target or "").strip().lower() or "memory",
|
|
117
|
+
"path": str(p),
|
|
118
|
+
"fingerprint": wal_text_fingerprint(stripped),
|
|
119
|
+
},
|
|
120
|
+
)
|
|
109
121
|
return {"status": "appended", "path": str(p)}
|
|
110
122
|
|
|
@@ -13,6 +13,7 @@ from pathlib import Path
|
|
|
13
13
|
from typing import Any
|
|
14
14
|
|
|
15
15
|
from gemcode.config import GemCodeConfig
|
|
16
|
+
from gemcode.query_sanitizer import sanitize_tool_query
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def _get_embedding_client():
|
|
@@ -76,6 +77,10 @@ async def semantic_search_files(
|
|
|
76
77
|
"""
|
|
77
78
|
if not isinstance(query, str) or not query.strip():
|
|
78
79
|
return {"error": "query must be a non-empty string"}
|
|
80
|
+
s = sanitize_tool_query(query)
|
|
81
|
+
query = str(s.get("clean_query") or "").strip()
|
|
82
|
+
if not query:
|
|
83
|
+
return {"error": "query must be a non-empty string"}
|
|
79
84
|
|
|
80
85
|
root = Path(project_root).resolve() if project_root else None
|
|
81
86
|
if root is None:
|
|
@@ -165,7 +170,13 @@ async def semantic_search_files(
|
|
|
165
170
|
snippet = chunks[idx][:500].replace("\n", " ")
|
|
166
171
|
matches.append({"path": rel, "snippet": snippet, "score": score})
|
|
167
172
|
|
|
168
|
-
return {
|
|
173
|
+
return {
|
|
174
|
+
"query": query,
|
|
175
|
+
"query_sanitized": bool(s.get("was_sanitized")),
|
|
176
|
+
"query_sanitizer_method": str(s.get("method")),
|
|
177
|
+
"backend": "embeddings",
|
|
178
|
+
"matches": matches,
|
|
179
|
+
}
|
|
169
180
|
|
|
170
181
|
|
|
171
182
|
def build_extra_tools(cfg: GemCodeConfig) -> list[Any]:
|
|
@@ -16,11 +16,18 @@ def _is_valid_name(name: str) -> bool:
|
|
|
16
16
|
return bool(re.fullmatch(r"[a-z0-9][a-z0-9-]{0,63}", name or ""))
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
def _builtin_style_dir() -> Path:
|
|
20
|
+
# Built-in styles shipped with GemCode (lowest priority).
|
|
21
|
+
# Located under the python package so they work out-of-the-box.
|
|
22
|
+
return Path(__file__).resolve().parent / "builtin" / "output-styles"
|
|
23
|
+
|
|
24
|
+
|
|
19
25
|
def _style_dirs_for_project(project_root: Path) -> list[Path]:
|
|
20
|
-
#
|
|
26
|
+
# Priority (highest last): project > personal > built-in.
|
|
21
27
|
return [
|
|
22
28
|
project_root / ".gemcode" / "output-styles",
|
|
23
29
|
Path.home() / ".gemcode" / "output-styles",
|
|
30
|
+
_builtin_style_dir(),
|
|
24
31
|
]
|
|
25
32
|
|
|
26
33
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mitigate "system prompt contamination" in tool queries.
|
|
3
|
+
|
|
4
|
+
Inspired by MemPalace's query sanitizer. Goal: prevent catastrophic search degradation
|
|
5
|
+
when an agent accidentally prefixes a long system instruction to a short query.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
MAX_QUERY_LENGTH = 250
|
|
13
|
+
SAFE_QUERY_LENGTH = 200
|
|
14
|
+
MIN_QUERY_LENGTH = 10
|
|
15
|
+
|
|
16
|
+
_SENTENCE_SPLIT = re.compile(r"[.!?。!?\n]+")
|
|
17
|
+
_QUESTION_MARK = re.compile(r'[??]\s*["\']?\s*$')
|
|
18
|
+
_QUOTE_CHARS = {"'", '"'}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def sanitize_tool_query(raw_query: str) -> dict[str, object]:
|
|
22
|
+
"""
|
|
23
|
+
Return a best-effort clean query.
|
|
24
|
+
|
|
25
|
+
Returns dict:
|
|
26
|
+
clean_query: str
|
|
27
|
+
was_sanitized: bool
|
|
28
|
+
method: str
|
|
29
|
+
"""
|
|
30
|
+
if not raw_query or not str(raw_query).strip():
|
|
31
|
+
return {"clean_query": "", "was_sanitized": False, "method": "empty"}
|
|
32
|
+
|
|
33
|
+
raw_query = str(raw_query).strip()
|
|
34
|
+
n = len(raw_query)
|
|
35
|
+
|
|
36
|
+
def _strip_wrapping_quotes(s: str) -> str:
|
|
37
|
+
s = (s or "").strip()
|
|
38
|
+
while len(s) >= 2 and s[:1] in _QUOTE_CHARS and s[:1] == s[-1:]:
|
|
39
|
+
s = s[1:-1].strip()
|
|
40
|
+
if not s:
|
|
41
|
+
return ""
|
|
42
|
+
if s[:1] in _QUOTE_CHARS:
|
|
43
|
+
s = s[1:].strip()
|
|
44
|
+
if s[-1:] in _QUOTE_CHARS:
|
|
45
|
+
s = s[:-1].strip()
|
|
46
|
+
return s
|
|
47
|
+
|
|
48
|
+
def _trim_candidate(s: str) -> str:
|
|
49
|
+
s = _strip_wrapping_quotes(s)
|
|
50
|
+
if len(s) <= MAX_QUERY_LENGTH:
|
|
51
|
+
return s
|
|
52
|
+
frags = [_strip_wrapping_quotes(x) for x in _SENTENCE_SPLIT.split(s) if x.strip()]
|
|
53
|
+
for frag in reversed(frags):
|
|
54
|
+
if MIN_QUERY_LENGTH <= len(frag) <= MAX_QUERY_LENGTH:
|
|
55
|
+
return frag
|
|
56
|
+
return s[-MAX_QUERY_LENGTH:].strip()
|
|
57
|
+
|
|
58
|
+
if n <= SAFE_QUERY_LENGTH:
|
|
59
|
+
return {"clean_query": raw_query, "was_sanitized": False, "method": "passthrough"}
|
|
60
|
+
|
|
61
|
+
# Prefer last question-looking segment.
|
|
62
|
+
segments = [s.strip() for s in raw_query.split("\n") if s.strip()]
|
|
63
|
+
question_candidates: list[str] = []
|
|
64
|
+
for seg in reversed(segments):
|
|
65
|
+
if _QUESTION_MARK.search(seg):
|
|
66
|
+
question_candidates.append(seg)
|
|
67
|
+
if not question_candidates:
|
|
68
|
+
sentences = [s.strip() for s in _SENTENCE_SPLIT.split(raw_query) if s.strip()]
|
|
69
|
+
for sent in reversed(sentences):
|
|
70
|
+
if "?" in sent or "?" in sent:
|
|
71
|
+
question_candidates.append(sent)
|
|
72
|
+
|
|
73
|
+
if question_candidates:
|
|
74
|
+
cand = _trim_candidate(question_candidates[0])
|
|
75
|
+
if len(cand) >= MIN_QUERY_LENGTH:
|
|
76
|
+
return {"clean_query": cand, "was_sanitized": True, "method": "question_extraction"}
|
|
77
|
+
|
|
78
|
+
# Otherwise take the last meaningful segment.
|
|
79
|
+
for seg in reversed(segments):
|
|
80
|
+
cand = _trim_candidate(seg)
|
|
81
|
+
if len(cand) >= MIN_QUERY_LENGTH:
|
|
82
|
+
return {"clean_query": cand, "was_sanitized": True, "method": "tail_sentence"}
|
|
83
|
+
|
|
84
|
+
# Fallback: tail truncation.
|
|
85
|
+
cand = raw_query[-MAX_QUERY_LENGTH:].strip()
|
|
86
|
+
return {"clean_query": cand, "was_sanitized": True, "method": "tail_truncation"}
|
|
87
|
+
|
|
@@ -208,6 +208,8 @@ SLASH_COMMANDS: list[tuple[str, str]] = [
|
|
|
208
208
|
("audit", "Tail audit.log · /logs same"),
|
|
209
209
|
("autotune", "Branch + eval ledger · /autotune init <tag> · /autotune eval"),
|
|
210
210
|
("batch", "Built-in batch GemSkill (large parallel changes)"),
|
|
211
|
+
("caveman", "Terse output mode · /caveman lite|full|ultra|wenyan|off"),
|
|
212
|
+
("caveman:compress", "Compress memory file · /caveman:compress <path> [lite|full|ultra]"),
|
|
211
213
|
("budget", "Per-turn token budget · /token-budget same"),
|
|
212
214
|
("caps", "Capabilities · /capabilities /capability same"),
|
|
213
215
|
("clear", "Fresh session · same as /session new"),
|
|
@@ -338,6 +340,7 @@ def slash_help_lines() -> list[str]:
|
|
|
338
340
|
" /gemskill <name> Load an existing GemSkill into this session (system prompt)",
|
|
339
341
|
" /gemskill list|clear List skills or unload all session-loaded skills",
|
|
340
342
|
" /append gemskill <name> <request> Ask the agent to edit that skill file",
|
|
343
|
+
" /caveman [level]|off Terse output mode (like caveman-speak). Levels: lite|full|ultra|wenyan-lite|wenyan|wenyan-ultra",
|
|
341
344
|
" /style List available output styles",
|
|
342
345
|
" /style <name>|off Activate an output style for this session",
|
|
343
346
|
" /rules Show loaded rule files (from .gemcode/rules/)",
|
|
@@ -419,6 +419,91 @@ async def process_repl_slash(
|
|
|
419
419
|
out()
|
|
420
420
|
return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
|
|
421
421
|
|
|
422
|
+
# ── /caveman (shortcut to built-in output styles) ─────────────────────────
|
|
423
|
+
if name == "caveman":
|
|
424
|
+
args = (sc.args or "").strip().lower()
|
|
425
|
+
# Levels map to built-in output styles (still overridable by project/user styles).
|
|
426
|
+
mapping = {
|
|
427
|
+
"": "caveman",
|
|
428
|
+
"full": "caveman",
|
|
429
|
+
"lite": "caveman-lite",
|
|
430
|
+
"ultra": "caveman-ultra",
|
|
431
|
+
"wenyan": "caveman-wenyan",
|
|
432
|
+
"wenyan-full": "caveman-wenyan",
|
|
433
|
+
"wenyan-lite": "caveman-wenyan-lite",
|
|
434
|
+
"wenyan-ultra": "caveman-wenyan-ultra",
|
|
435
|
+
}
|
|
436
|
+
if args in ("off", "stop", "normal", "none", "clear", "reset", "default"):
|
|
437
|
+
setattr(cfg, "output_style", None)
|
|
438
|
+
out("caveman: off (output_style cleared)")
|
|
439
|
+
out("Runner will rebuild on next turn to apply changes.")
|
|
440
|
+
out()
|
|
441
|
+
return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
|
|
442
|
+
if args not in mapping:
|
|
443
|
+
out("Usage:")
|
|
444
|
+
out(" /caveman (full)")
|
|
445
|
+
out(" /caveman lite|full|ultra")
|
|
446
|
+
out(" /caveman wenyan-lite|wenyan|wenyan-ultra")
|
|
447
|
+
out(" /caveman off")
|
|
448
|
+
out()
|
|
449
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
450
|
+
|
|
451
|
+
choice = mapping[args]
|
|
452
|
+
styles = discover_output_styles(cfg.project_root)
|
|
453
|
+
if choice not in styles or load_output_style(cfg.project_root, choice) is None:
|
|
454
|
+
out(f"caveman: style unavailable: {choice}")
|
|
455
|
+
out("Tip: update GemCode, or create a custom style at .gemcode/output-styles/")
|
|
456
|
+
out()
|
|
457
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
458
|
+
|
|
459
|
+
setattr(cfg, "output_style", choice)
|
|
460
|
+
out(f"caveman: on — output_style: {choice}")
|
|
461
|
+
out("Runner will rebuild on next turn to apply changes.")
|
|
462
|
+
out()
|
|
463
|
+
return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
|
|
464
|
+
|
|
465
|
+
# ── /caveman:compress (alias for /compress-memory) ────────────────────────
|
|
466
|
+
if name in ("caveman:compress", "caveman-compress", "caveman:compress-memory"):
|
|
467
|
+
args = (sc.args or "").strip()
|
|
468
|
+
parts = args.split()
|
|
469
|
+
if not parts:
|
|
470
|
+
out("Usage:")
|
|
471
|
+
out(" /caveman:compress <path> [lite|full|ultra]")
|
|
472
|
+
out("Note: mode defaults based on active /caveman level (or full).")
|
|
473
|
+
out()
|
|
474
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
475
|
+
|
|
476
|
+
target = parts[0]
|
|
477
|
+
mode = (parts[1].strip().lower() if len(parts) >= 2 else "")
|
|
478
|
+
if mode and mode not in ("lite", "full", "ultra"):
|
|
479
|
+
out(f"Unknown mode: {mode}")
|
|
480
|
+
out("Use: lite|full|ultra (or omit to auto-pick from current caveman level)")
|
|
481
|
+
out()
|
|
482
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
483
|
+
|
|
484
|
+
# Auto-pick mode from current output_style if not provided.
|
|
485
|
+
if not mode:
|
|
486
|
+
os_ = (getattr(cfg, "output_style", None) or "").lower()
|
|
487
|
+
if os_ in ("caveman-lite",):
|
|
488
|
+
mode = "lite"
|
|
489
|
+
elif os_ in ("caveman-ultra",):
|
|
490
|
+
mode = "ultra"
|
|
491
|
+
else:
|
|
492
|
+
mode = "full"
|
|
493
|
+
|
|
494
|
+
# Dispatch as a model turn so the agent runs the tool and reports results.
|
|
495
|
+
prompt = (
|
|
496
|
+
"Compress a memory file now.\n\n"
|
|
497
|
+
f"- target: `{target}`\n"
|
|
498
|
+
f"- mode: `{mode}`\n\n"
|
|
499
|
+
"Call `compress_memory_file(path=..., mode=...)` and report:\n"
|
|
500
|
+
"- ok/error\n"
|
|
501
|
+
"- path + backup_path\n"
|
|
502
|
+
"- warnings (if any)\n"
|
|
503
|
+
"- chars_before/chars_after\n"
|
|
504
|
+
)
|
|
505
|
+
return ReplSlashResult(model_prompt=prompt)
|
|
506
|
+
|
|
422
507
|
# ── /rules ────────────────────────────────────────────────────────────────
|
|
423
508
|
if name == "rules":
|
|
424
509
|
rules = _load_rules(cfg.project_root, touched_paths=None)
|
|
@@ -1045,6 +1130,8 @@ async def process_repl_slash(
|
|
|
1045
1130
|
"3. Look at the source directory structure (src/, lib/, app/, etc.)\n"
|
|
1046
1131
|
"4. Check for test directories and test runner config\n"
|
|
1047
1132
|
"5. Look for linting/formatting config files (.eslintrc, .prettierrc, ruff.toml, etc.)\n\n"
|
|
1133
|
+
"Write **only** to `GEMINI.md` at the project root. Do **not** create "
|
|
1134
|
+
"`CLAUDE.md`, `AGENTS.md`, `.cursorrules`, or similar.\n\n"
|
|
1048
1135
|
"Then write a GEMINI.md file at the project root containing:\n"
|
|
1049
1136
|
"# Project Name\n"
|
|
1050
1137
|
"One-sentence description.\n\n"
|
|
@@ -71,6 +71,40 @@ _BUILTIN_SKILLS: dict[str, tuple[GemSkillMeta, str]] = {
|
|
|
71
71
|
"- **Final**: verification + next steps\n"
|
|
72
72
|
),
|
|
73
73
|
),
|
|
74
|
+
"compress-memory": (
|
|
75
|
+
GemSkillMeta(
|
|
76
|
+
name="compress-memory",
|
|
77
|
+
description=(
|
|
78
|
+
"Compress a markdown memory file (GEMINI.md, .gemcode notes, todos) into a terse style "
|
|
79
|
+
"to reduce input tokens, while preserving code blocks, headings, and URLs."
|
|
80
|
+
),
|
|
81
|
+
disable_model_invocation=False,
|
|
82
|
+
user_invocable=True,
|
|
83
|
+
),
|
|
84
|
+
(
|
|
85
|
+
"## Compress memory file\n"
|
|
86
|
+
"Use this skill to rewrite a markdown-like memory file into a more token-efficient form.\n\n"
|
|
87
|
+
"### When to use\n"
|
|
88
|
+
"- The user asks to compress GEMINI.md, .gemcode notes, preferences, or other prose-heavy markdown.\n"
|
|
89
|
+
"- The user wants fewer input tokens each session.\n\n"
|
|
90
|
+
"### Safety and boundaries\n"
|
|
91
|
+
"- ONLY run on markdown-like files (.md/.txt/.rst, or extensionless files under .gemcode/).\n"
|
|
92
|
+
"- NEVER run on secret/credential/key files (.env, credentials, .ssh, .aws, *.pem, etc.).\n"
|
|
93
|
+
"- This operation sends file content to the Gemini API.\n"
|
|
94
|
+
"- Tool will create a backup: `<stem>.original.md` and abort if backup already exists.\n"
|
|
95
|
+
"- Tool validates: headings, fenced code blocks, URLs. On failure, it restores the original.\n\n"
|
|
96
|
+
"### How to run\n"
|
|
97
|
+
"1. Confirm target file path from `$ARGUMENTS`.\n"
|
|
98
|
+
"2. Pick mode: `lite`, `full`, or `ultra` (default `full`).\n"
|
|
99
|
+
"3. Call the tool:\n\n"
|
|
100
|
+
"```python\n"
|
|
101
|
+
"compress_memory_file(path=\"$ARGUMENTS\", mode=\"$ARGUMENTS[1]\")\n"
|
|
102
|
+
"```\n\n"
|
|
103
|
+
"If no mode provided, call with `mode=\"full\"`.\n\n"
|
|
104
|
+
"### Output\n"
|
|
105
|
+
"- Report: ok/error, file path, backup path, and any warnings.\n"
|
|
106
|
+
),
|
|
107
|
+
),
|
|
74
108
|
}
|
|
75
109
|
|
|
76
110
|
|
|
@@ -18,6 +18,7 @@ from gemcode.tools.web import make_web_fetch_tool
|
|
|
18
18
|
from gemcode.tools.web_search import make_web_search_tool
|
|
19
19
|
from gemcode.checkpoints import list_checkpoints as _list_checkpoints, undo_checkpoint as _undo_checkpoint
|
|
20
20
|
from gemcode.tools.curated_memory import make_curated_memory_tools
|
|
21
|
+
from gemcode.tools.compress_memory import make_compress_memory_tool
|
|
21
22
|
from gemcode.tools.skills import make_skill_tools
|
|
22
23
|
|
|
23
24
|
|
|
@@ -87,6 +88,7 @@ def build_function_tools(cfg: GemCodeConfig, *, include_subtask: bool = True) ->
|
|
|
87
88
|
load_tool_result = _make_load_tool_result_tool(cfg)
|
|
88
89
|
repo_map = make_repo_map_tool(cfg)
|
|
89
90
|
remember_fact, read_curated_memory = make_curated_memory_tools(cfg)
|
|
91
|
+
compress_memory_file = make_compress_memory_tool(cfg)
|
|
90
92
|
list_skills, load_skill, skills_manifest = make_skill_tools(cfg)
|
|
91
93
|
|
|
92
94
|
def checkpoints_list(limit: int = 20) -> dict:
|
|
@@ -149,6 +151,8 @@ def build_function_tools(cfg: GemCodeConfig, *, include_subtask: bool = True) ->
|
|
|
149
151
|
# Evolving: curated memory (safe-to-inject facts)
|
|
150
152
|
remember_fact,
|
|
151
153
|
read_curated_memory,
|
|
154
|
+
# Optional: compress memory files (markdown only; safe guards apply)
|
|
155
|
+
compress_memory_file,
|
|
152
156
|
# GemSkills (on-demand playbooks)
|
|
153
157
|
list_skills,
|
|
154
158
|
load_skill,
|