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.
Files changed (154) hide show
  1. {gemcode-0.3.80/src/gemcode.egg-info → gemcode-0.3.82}/PKG-INFO +10 -2
  2. {gemcode-0.3.80 → gemcode-0.3.82}/README.md +9 -1
  3. {gemcode-0.3.80 → gemcode-0.3.82}/pyproject.toml +1 -1
  4. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/agent.py +6 -5
  5. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/curated_memory.py +12 -0
  6. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/modality_tools.py +12 -1
  7. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/output_styles.py +8 -1
  8. gemcode-0.3.82/src/gemcode/query_sanitizer.py +87 -0
  9. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/repl_commands.py +3 -0
  10. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/repl_slash.py +87 -0
  11. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/skills.py +34 -0
  12. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/__init__.py +4 -0
  13. gemcode-0.3.82/src/gemcode/tools/compress_memory.py +333 -0
  14. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/edit.py +10 -5
  15. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/web_search.py +9 -1
  16. gemcode-0.3.82/src/gemcode/wal.py +62 -0
  17. {gemcode-0.3.80 → gemcode-0.3.82/src/gemcode.egg-info}/PKG-INFO +10 -2
  18. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode.egg-info/SOURCES.txt +4 -0
  19. gemcode-0.3.82/tests/test_compress_memory_tool.py +52 -0
  20. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_tools.py +11 -0
  21. {gemcode-0.3.80 → gemcode-0.3.82}/LICENSE +0 -0
  22. {gemcode-0.3.80 → gemcode-0.3.82}/MANIFEST.in +0 -0
  23. {gemcode-0.3.80 → gemcode-0.3.82}/setup.cfg +0 -0
  24. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/__init__.py +0 -0
  25. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/__main__.py +0 -0
  26. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/audit.py +0 -0
  27. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/autocompact.py +0 -0
  28. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/autotune.py +0 -0
  29. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/callbacks.py +0 -0
  30. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/capability_routing.py +0 -0
  31. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/checkpoints.py +0 -0
  32. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/cli.py +0 -0
  33. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/compaction.py +0 -0
  34. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/computer_use/__init__.py +0 -0
  35. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/computer_use/browser_computer.py +0 -0
  36. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/config.py +0 -0
  37. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/context_budget.py +0 -0
  38. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/context_warning.py +0 -0
  39. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/credentials.py +0 -0
  40. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/dynamic_policy.py +0 -0
  41. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/evals/harness.py +0 -0
  42. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/hitl_session.py +0 -0
  43. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/hooks.py +0 -0
  44. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/ide_protocol.py +0 -0
  45. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/ide_stdio.py +0 -0
  46. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/intent_classifier.py +0 -0
  47. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/interactions.py +0 -0
  48. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/invoke.py +0 -0
  49. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/kaira_daemon.py +0 -0
  50. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/learning.py +0 -0
  51. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/limits.py +0 -0
  52. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/live_audio_engine.py +0 -0
  53. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/logging_config.py +0 -0
  54. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/mcp_loader.py +0 -0
  55. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/memory/__init__.py +0 -0
  56. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/memory/embedding_memory_service.py +0 -0
  57. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/memory/file_memory_service.py +0 -0
  58. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/model_errors.py +0 -0
  59. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/model_routing.py +0 -0
  60. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/multimodal_input.py +0 -0
  61. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/openapi_loader.py +0 -0
  62. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/paths.py +0 -0
  63. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/permissions.py +0 -0
  64. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/plugins/__init__.py +0 -0
  65. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  66. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  67. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/policy_profile.py +0 -0
  68. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/pricing.py +0 -0
  69. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/prompt_suggestions.py +0 -0
  70. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/__init__.py +0 -0
  71. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/config.py +0 -0
  72. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/deps.py +0 -0
  73. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/engine.py +0 -0
  74. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/stop_hooks.py +0 -0
  75. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/token_budget.py +0 -0
  76. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/query/transitions.py +0 -0
  77. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/refine.py +0 -0
  78. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/review_agent.py +0 -0
  79. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/rules.py +0 -0
  80. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/session_runtime.py +0 -0
  81. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/session_store.py +0 -0
  82. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/slash_commands.py +0 -0
  83. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/thinking.py +0 -0
  84. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tool_prompt_manifest.py +0 -0
  85. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tool_registry.py +0 -0
  86. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tool_result_store.py +0 -0
  87. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/bash.py +0 -0
  88. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/browser.py +0 -0
  89. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/curated_memory.py +0 -0
  90. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/filesystem.py +0 -0
  91. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/notebook.py +0 -0
  92. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/notes.py +0 -0
  93. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/repo_map.py +0 -0
  94. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/search.py +0 -0
  95. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/shell.py +0 -0
  96. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/shell_gate.py +0 -0
  97. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/skills.py +0 -0
  98. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/subtask.py +0 -0
  99. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/tasks.py +0 -0
  100. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/think.py +0 -0
  101. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/todo.py +0 -0
  102. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools/web.py +0 -0
  103. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tools_inspector.py +0 -0
  104. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/trust.py +0 -0
  105. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tui/input_handler.py +0 -0
  106. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tui/scrollback.py +0 -0
  107. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tui/spinner.py +0 -0
  108. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tui/welcome_banner.py +0 -0
  109. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/tui/welcome_rich.py +0 -0
  110. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/version.py +0 -0
  111. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/vertex.py +0 -0
  112. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/web/__init__.py +0 -0
  113. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/web/sse_adapter.py +0 -0
  114. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/web/terminal_repl.py +0 -0
  115. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/web/web_sse_compat.py +0 -0
  116. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode/workspace_hints.py +0 -0
  117. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode.egg-info/dependency_links.txt +0 -0
  118. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode.egg-info/entry_points.txt +0 -0
  119. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode.egg-info/requires.txt +0 -0
  120. {gemcode-0.3.80 → gemcode-0.3.82}/src/gemcode.egg-info/top_level.txt +0 -0
  121. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_add_dir.py +0 -0
  122. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_agent_instruction.py +0 -0
  123. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_autocompact.py +0 -0
  124. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_capability_routing.py +0 -0
  125. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_checkpoint_diff_command.py +0 -0
  126. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_cli_init.py +0 -0
  127. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_computer_use_permissions.py +0 -0
  128. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_context_budget.py +0 -0
  129. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_context_warning.py +0 -0
  130. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_credentials.py +0 -0
  131. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_eval_harness_layout.py +0 -0
  132. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_ide_stdio_attachments.py +0 -0
  133. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_interactive_permission_ask.py +0 -0
  134. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_kaira_scheduler.py +0 -0
  135. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_modality_tools.py +0 -0
  136. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_model_error_retry.py +0 -0
  137. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_model_errors.py +0 -0
  138. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_model_routing.py +0 -0
  139. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_multimodal_input.py +0 -0
  140. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_output_styles_and_rules.py +0 -0
  141. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_paths.py +0 -0
  142. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_permissions.py +0 -0
  143. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_prompt_suggestions.py +0 -0
  144. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_repl_commands.py +0 -0
  145. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_repl_slash.py +0 -0
  146. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_skills.py +0 -0
  147. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_slash_commands.py +0 -0
  148. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_slash_completion_registry.py +0 -0
  149. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_thinking_config.py +0 -0
  150. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_token_budget.py +0 -0
  151. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_tool_context_circulation.py +0 -0
  152. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_tools_inspector.py +0 -0
  153. {gemcode-0.3.80 → gemcode-0.3.82}/tests/test_web_sse_adapter.py +0 -0
  154. {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.80
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 vendor-specific instruction filenames (e.g. `CLAUDE.md`, `AGENTS.md`) are blocked; use project conventions like `GEMINI.md` and curated memory files instead.
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 vendor-specific instruction filenames (e.g. `CLAUDE.md`, `AGENTS.md`) are blocked; use project conventions like `GEMINI.md` and curated memory files instead.
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.3.80"
7
+ version = "0.3.82"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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 {"query": query, "backend": "embeddings", "matches": matches}
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
- # Project has priority over personal.
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,