gemcode 0.3.81__tar.gz → 0.3.85__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 (155) hide show
  1. {gemcode-0.3.81/src/gemcode.egg-info → gemcode-0.3.85}/PKG-INFO +16 -2
  2. {gemcode-0.3.81 → gemcode-0.3.85}/README.md +15 -1
  3. {gemcode-0.3.81 → gemcode-0.3.85}/pyproject.toml +1 -1
  4. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/agent.py +15 -1
  5. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/callbacks.py +20 -0
  6. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/cli.py +101 -0
  7. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/config.py +3 -1
  8. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/curated_memory.py +12 -0
  9. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/ide_stdio.py +7 -1
  10. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/modality_tools.py +12 -1
  11. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/output_styles.py +8 -1
  12. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/plugins/terminal_hooks_plugin.py +31 -0
  13. gemcode-0.3.85/src/gemcode/query_sanitizer.py +87 -0
  14. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/repl_commands.py +4 -0
  15. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/repl_slash.py +155 -12
  16. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/session_runtime.py +15 -0
  17. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/skills.py +85 -6
  18. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/__init__.py +4 -0
  19. gemcode-0.3.85/src/gemcode/tools/compress_memory.py +333 -0
  20. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/web_search.py +9 -1
  21. gemcode-0.3.85/src/gemcode/veomem_bridge.py +105 -0
  22. gemcode-0.3.85/src/gemcode/wal.py +62 -0
  23. {gemcode-0.3.81 → gemcode-0.3.85/src/gemcode.egg-info}/PKG-INFO +16 -2
  24. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode.egg-info/SOURCES.txt +5 -0
  25. gemcode-0.3.85/tests/test_compress_memory_tool.py +52 -0
  26. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_model_routing.py +4 -4
  27. {gemcode-0.3.81 → gemcode-0.3.85}/LICENSE +0 -0
  28. {gemcode-0.3.81 → gemcode-0.3.85}/MANIFEST.in +0 -0
  29. {gemcode-0.3.81 → gemcode-0.3.85}/setup.cfg +0 -0
  30. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/__init__.py +0 -0
  31. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/__main__.py +0 -0
  32. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/audit.py +0 -0
  33. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/autocompact.py +0 -0
  34. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/autotune.py +0 -0
  35. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/capability_routing.py +0 -0
  36. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/checkpoints.py +0 -0
  37. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/compaction.py +0 -0
  38. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/computer_use/__init__.py +0 -0
  39. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/computer_use/browser_computer.py +0 -0
  40. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/context_budget.py +0 -0
  41. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/context_warning.py +0 -0
  42. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/credentials.py +0 -0
  43. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/dynamic_policy.py +0 -0
  44. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/evals/harness.py +0 -0
  45. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/hitl_session.py +0 -0
  46. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/hooks.py +0 -0
  47. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/ide_protocol.py +0 -0
  48. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/intent_classifier.py +0 -0
  49. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/interactions.py +0 -0
  50. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/invoke.py +0 -0
  51. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/kaira_daemon.py +0 -0
  52. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/learning.py +0 -0
  53. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/limits.py +0 -0
  54. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/live_audio_engine.py +0 -0
  55. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/logging_config.py +0 -0
  56. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/mcp_loader.py +0 -0
  57. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/memory/__init__.py +0 -0
  58. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/memory/embedding_memory_service.py +0 -0
  59. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/memory/file_memory_service.py +0 -0
  60. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/model_errors.py +0 -0
  61. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/model_routing.py +0 -0
  62. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/multimodal_input.py +0 -0
  63. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/openapi_loader.py +0 -0
  64. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/paths.py +0 -0
  65. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/permissions.py +0 -0
  66. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/plugins/__init__.py +0 -0
  67. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  68. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/policy_profile.py +0 -0
  69. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/pricing.py +0 -0
  70. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/prompt_suggestions.py +0 -0
  71. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/__init__.py +0 -0
  72. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/config.py +0 -0
  73. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/deps.py +0 -0
  74. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/engine.py +0 -0
  75. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/stop_hooks.py +0 -0
  76. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/token_budget.py +0 -0
  77. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/transitions.py +0 -0
  78. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/refine.py +0 -0
  79. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/review_agent.py +0 -0
  80. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/rules.py +0 -0
  81. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/session_store.py +0 -0
  82. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/slash_commands.py +0 -0
  83. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/thinking.py +0 -0
  84. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tool_prompt_manifest.py +0 -0
  85. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tool_registry.py +0 -0
  86. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tool_result_store.py +0 -0
  87. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/bash.py +0 -0
  88. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/browser.py +0 -0
  89. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/curated_memory.py +0 -0
  90. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/edit.py +0 -0
  91. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/filesystem.py +0 -0
  92. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/notebook.py +0 -0
  93. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/notes.py +0 -0
  94. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/repo_map.py +0 -0
  95. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/search.py +0 -0
  96. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/shell.py +0 -0
  97. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/shell_gate.py +0 -0
  98. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/skills.py +0 -0
  99. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/subtask.py +0 -0
  100. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/tasks.py +0 -0
  101. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/think.py +0 -0
  102. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/todo.py +0 -0
  103. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/web.py +0 -0
  104. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools_inspector.py +0 -0
  105. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/trust.py +0 -0
  106. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tui/input_handler.py +0 -0
  107. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tui/scrollback.py +0 -0
  108. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tui/spinner.py +0 -0
  109. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tui/welcome_banner.py +0 -0
  110. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tui/welcome_rich.py +0 -0
  111. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/version.py +0 -0
  112. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/vertex.py +0 -0
  113. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/web/__init__.py +0 -0
  114. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/web/sse_adapter.py +0 -0
  115. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/web/terminal_repl.py +0 -0
  116. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/web/web_sse_compat.py +0 -0
  117. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/workspace_hints.py +0 -0
  118. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode.egg-info/dependency_links.txt +0 -0
  119. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode.egg-info/entry_points.txt +0 -0
  120. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode.egg-info/requires.txt +0 -0
  121. {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode.egg-info/top_level.txt +0 -0
  122. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_add_dir.py +0 -0
  123. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_agent_instruction.py +0 -0
  124. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_autocompact.py +0 -0
  125. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_capability_routing.py +0 -0
  126. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_checkpoint_diff_command.py +0 -0
  127. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_cli_init.py +0 -0
  128. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_computer_use_permissions.py +0 -0
  129. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_context_budget.py +0 -0
  130. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_context_warning.py +0 -0
  131. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_credentials.py +0 -0
  132. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_eval_harness_layout.py +0 -0
  133. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_ide_stdio_attachments.py +0 -0
  134. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_interactive_permission_ask.py +0 -0
  135. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_kaira_scheduler.py +0 -0
  136. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_modality_tools.py +0 -0
  137. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_model_error_retry.py +0 -0
  138. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_model_errors.py +0 -0
  139. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_multimodal_input.py +0 -0
  140. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_output_styles_and_rules.py +0 -0
  141. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_paths.py +0 -0
  142. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_permissions.py +0 -0
  143. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_prompt_suggestions.py +0 -0
  144. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_repl_commands.py +0 -0
  145. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_repl_slash.py +0 -0
  146. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_skills.py +0 -0
  147. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_slash_commands.py +0 -0
  148. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_slash_completion_registry.py +0 -0
  149. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_thinking_config.py +0 -0
  150. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_token_budget.py +0 -0
  151. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_tool_context_circulation.py +0 -0
  152. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_tools.py +0 -0
  153. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_tools_inspector.py +0 -0
  154. {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_web_sse_adapter.py +0 -0
  155. {gemcode-0.3.81 → gemcode-0.3.85}/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.81
3
+ Version: 0.3.85
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)). |
@@ -573,10 +574,16 @@ The TUI (when `GEMCODE_TUI=1` and terminal supports it) provides **slash complet
573
574
  - **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`.
574
575
  - **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`).
575
576
 
576
- - **`/create gemskill <name>`** — create a new skill directory.
577
+ - **`/create gemskill <name>`** — create a new skill directory (scaffold).
578
+ - **Interactive wizard (CLI)**: if you type something like “I want to make a new skill” in the REPL, GemCode will switch into a short **GemSkill wizard** (name/description/spec) and then generate the full skill folder for you.
577
579
  - **`/gemskill <name>`** — pin the full skill body into the system prompt for this session.
578
580
  - **`/append gemskill <name> <text>`** — one-shot turn for the model to revise that skill on disk.
579
581
 
582
+ ### Frontmatter notes (compatibility)
583
+ GemCode supports common YAML frontmatter styles including multi-line scalars like:
584
+ - `description: >` (folded)
585
+ - `description: |` (literal)
586
+
580
587
  ---
581
588
 
582
589
  ## Curated memory
@@ -590,6 +597,13 @@ Legacy filenames **`.gemcode/MEMORY.md`** and **`.gemcode/USER.md`** are still r
590
597
 
591
598
  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
599
 
600
+ ### Query sanitization (tooling)
601
+
602
+ Some tools sanitize long “contaminated” queries (e.g. accidental system-prompt prefixes) down to a short, likely-intended query. This currently applies to:
603
+
604
+ - `web_search(query=...)`
605
+ - `semantic_search_files(query=...)` (when `--embeddings` is on)
606
+
593
607
  ---
594
608
 
595
609
  ## 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)). |
@@ -384,10 +385,16 @@ The TUI (when `GEMCODE_TUI=1` and terminal supports it) provides **slash complet
384
385
  - **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`.
385
386
  - **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`).
386
387
 
387
- - **`/create gemskill <name>`** — create a new skill directory.
388
+ - **`/create gemskill <name>`** — create a new skill directory (scaffold).
389
+ - **Interactive wizard (CLI)**: if you type something like “I want to make a new skill” in the REPL, GemCode will switch into a short **GemSkill wizard** (name/description/spec) and then generate the full skill folder for you.
388
390
  - **`/gemskill <name>`** — pin the full skill body into the system prompt for this session.
389
391
  - **`/append gemskill <name> <text>`** — one-shot turn for the model to revise that skill on disk.
390
392
 
393
+ ### Frontmatter notes (compatibility)
394
+ GemCode supports common YAML frontmatter styles including multi-line scalars like:
395
+ - `description: >` (folded)
396
+ - `description: |` (literal)
397
+
391
398
  ---
392
399
 
393
400
  ## Curated memory
@@ -401,6 +408,13 @@ Legacy filenames **`.gemcode/MEMORY.md`** and **`.gemcode/USER.md`** are still r
401
408
 
402
409
  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
410
 
411
+ ### Query sanitization (tooling)
412
+
413
+ Some tools sanitize long “contaminated” queries (e.g. accidental system-prompt prefixes) down to a short, likely-intended query. This currently applies to:
414
+
415
+ - `web_search(query=...)`
416
+ - `semantic_search_files(query=...)` (when `--embeddings` is on)
417
+
404
418
  ---
405
419
 
406
420
  ## Workspace trust
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.3.81"
7
+ version = "0.3.85"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -303,6 +303,20 @@ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
303
303
  except Exception:
304
304
  curated_section = ""
305
305
 
306
+ # ── VeoMem recall (optional) ─────────────────────────────────────────────
307
+ veomem_section = ""
308
+ try:
309
+ t = getattr(cfg, "_veomem_wakeup_text", None)
310
+ if isinstance(t, str) and t.strip():
311
+ veomem_section = (
312
+ "\n\n## VeoMem recall (auto-captured, progressive)\n"
313
+ "This section is automatically generated from prior tool usage and summaries. "
314
+ "Treat it as helpful context; do not restate it verbatim to the user.\n"
315
+ f"{t.strip()}\n"
316
+ )
317
+ except Exception:
318
+ veomem_section = ""
319
+
306
320
  return f"""## Runtime facts (authoritative for this session)
307
321
  - **Today's date:** {today}
308
322
  - **Project root** — every filesystem tool path is relative to: `{root}`
@@ -316,7 +330,7 @@ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
316
330
  {kaira_section}
317
331
  - **UI banner** phrases like "GemCode Pro" are terminal marketing, not a separate API tier.
318
332
  - **Env toggles** (`GEMCODE_ENABLE_COMPUTER_USE`, `GEMCODE_MODEL`, etc.) affect only the OS process that launched gemcode. Pasting `VAR=1` in chat does NOT reconfigure a running session—tell the user to export in their shell, use project `.env`, or restart the CLI.
319
- - **Working in subfolders** — call `list_directory(\"Desktop\")`, `glob_files(\"**/query.ts\")`, `read_file(\"testing/ai-edtech-app/src/app/page.tsx\")` directly. Never claim access is blocked unless a tool returned an explicit error.{git_section}{curated_section}"""
333
+ - **Working in subfolders** — call `list_directory(\"Desktop\")`, `glob_files(\"**/query.ts\")`, `read_file(\"testing/ai-edtech-app/src/app/page.tsx\")` directly. Never claim access is blocked unless a tool returned an explicit error.{git_section}{curated_section}{veomem_section}"""
320
334
 
321
335
 
322
336
  def _build_memory_section(cfg: GemCodeConfig) -> str:
@@ -525,6 +525,26 @@ def make_after_tool_callback(cfg: GemCodeConfig):
525
525
  except Exception:
526
526
  pass
527
527
 
528
+ # ── VeoMem (optional): auto-capture tool usage ────────────────────────
529
+ try:
530
+ from gemcode.veomem_bridge import record_tool_use
531
+ sid = None
532
+ try:
533
+ if tool_context is not None and getattr(tool_context, "session", None) is not None:
534
+ sid = getattr(tool_context.session, "id", None)
535
+ except Exception:
536
+ sid = None
537
+ record_tool_use(
538
+ cfg.project_root,
539
+ session_id=str(sid) if sid else None,
540
+ tool_name=name,
541
+ args=args or {},
542
+ result=tool_response if isinstance(tool_response, dict) else {},
543
+ paths=(st.get(_RISK_FILES_TOUCHED, []) or []) if isinstance(st, dict) else None,
544
+ )
545
+ except Exception:
546
+ pass
547
+
528
548
  if _maybe_tool_summary_enabled():
529
549
  summary: dict[str, Any] = {
530
550
  "phase": "tool_result",
@@ -7,6 +7,7 @@ import asyncio
7
7
  import getpass
8
8
  import json
9
9
  import os
10
+ import re
10
11
  import sys
11
12
  import uuid
12
13
  import warnings
@@ -262,6 +263,100 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
262
263
  "GemCode CLI is running. Type your prompt and press Enter. (Ctrl+D to exit)",
263
264
  file=sys.stderr,
264
265
  )
266
+
267
+ def _looks_like_new_skill_request(s: str) -> bool:
268
+ t = (s or "").strip().lower()
269
+ if not t or t.startswith("/"):
270
+ return False
271
+ # Natural language trigger: "make/create/build a new skill/gemskill"
272
+ return bool(
273
+ re.search(r"\b(new|create|make|build)\b", t)
274
+ and re.search(r"\b(gem\s*skill|gemskill|skill)\b", t)
275
+ )
276
+
277
+ def _prompt_nonempty(label: str, default: str | None = None) -> str:
278
+ while True:
279
+ try:
280
+ v = input(label).strip()
281
+ except EOFError:
282
+ return default or ""
283
+ if v:
284
+ return v
285
+ if default is not None:
286
+ return default
287
+
288
+ def _wizard_create_gemskill() -> str | None:
289
+ """
290
+ Interactive CLI wizard that collects a GemSkill spec, then returns a single
291
+ model prompt instructing the agent to generate the skill folder/files.
292
+ """
293
+ if not (hasattr(sys.stdin, "isatty") and sys.stdin.isatty()):
294
+ return None
295
+ print("\n── GemSkill wizard ──", file=sys.stderr)
296
+ print("We'll create a new skill under `.gemcode/skills/<name>/`.\n", file=sys.stderr)
297
+ name = _prompt_nonempty("skill name (kebab-case, e.g. api-review): ")
298
+ name = (name or "").strip().lower()
299
+ if not re.fullmatch(r"[a-z0-9][a-z0-9-]{0,63}", name):
300
+ print("Invalid name. Use lowercase letters/numbers/hyphens (max 64 chars).", file=sys.stderr)
301
+ return None
302
+ desc = _prompt_nonempty("one-line description: ")
303
+ when = _prompt_nonempty("when should it be used (1-2 sentences)? ")
304
+ inputs = _prompt_nonempty("inputs it should accept (bullets or short text): ", default="User request + $ARGUMENTS")
305
+ outputs = _prompt_nonempty("expected output format (short): ", default="Concise checklist + actionable steps")
306
+ tools_pref = _prompt_nonempty(
307
+ "tools: (a)uto, (r)ead-only, (w)eb-research-heavy? [a]: ",
308
+ default="a",
309
+ ).strip().lower()
310
+ use_web = tools_pref.startswith("w")
311
+ read_only = tools_pref.startswith("r")
312
+ examples = _prompt_nonempty("example command(s) user will type (optional): ", default="")
313
+
314
+ root = cfg.project_root.resolve()
315
+ skill_dir = root / ".gemcode" / "skills" / name
316
+ skill_md = skill_dir / "SKILL.md"
317
+
318
+ prompt_parts: list[str] = [
319
+ "You are creating a **GemCode GemSkill**. Generate a new skill folder and files.\n\n",
320
+ "## Target\n",
321
+ f"- Project root: {root}\n",
322
+ f"- Skill directory: {skill_dir}\n",
323
+ f"- Primary file: {skill_md}\n\n",
324
+ "## Requirements\n",
325
+ f"- Skill name: {name}\n",
326
+ f"- Description: {desc}\n",
327
+ f"- When to use: {when}\n",
328
+ f"- Inputs: {inputs}\n",
329
+ f"- Output expectations: {outputs}\n",
330
+ ]
331
+ if examples:
332
+ prompt_parts.append(f"- Example invocations: {examples}\n")
333
+ prompt_parts.extend(
334
+ [
335
+ "\n",
336
+ "- Write `SKILL.md` with YAML frontmatter using **multiline-friendly** fields when needed.\n",
337
+ "- Include: Purpose, When to use, When NOT to use (guardrails), Inputs, Output format, Workflow, Examples.\n",
338
+ "- Make it **token-efficient**: prefer short checklists and explicit decision gates.\n",
339
+ "- Avoid vague 'ALWAYS trigger' language; provide precise triggers.\n",
340
+ "- If you need templates/checklists, create supporting files in a `references/` subfolder and keep them small.\n",
341
+ "- Do not create CLAUDE.md/AGENTS.md or other vendor-specific files.\n\n",
342
+ "## Tooling / research policy\n",
343
+ (
344
+ "- You MAY use web research to find best practices, but only if it materially improves the skill.\n"
345
+ if use_web
346
+ else "- Avoid web research unless strictly necessary.\n"
347
+ ),
348
+ ("- Operate in read-only mode: do not write files.\n" if read_only else "- You are allowed to write the skill files.\n"),
349
+ "\n",
350
+ "## Execution steps\n",
351
+ "1. Create the skill directory if missing.\n",
352
+ "2. Write `SKILL.md` (and any supporting files).\n",
353
+ "3. Validate the YAML frontmatter parses and the skill is usable.\n",
354
+ f"4. Print a short confirmation: created files + how to invoke (e.g. `/skills list`, `/{name} ...`, `/gemskill {name}`).\n",
355
+ ]
356
+ )
357
+ prompt = "".join(prompt_parts)
358
+ return prompt
359
+
265
360
  while True:
266
361
  try:
267
362
  raw = input("> ")
@@ -274,6 +369,12 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
274
369
  if prompt_text in (":q", "quit", "exit", "/exit"):
275
370
  break
276
371
 
372
+ # Natural language shortcut: "I want to make a new skill" => wizard.
373
+ if _looks_like_new_skill_request(prompt_text):
374
+ wizard_prompt = _wizard_create_gemskill()
375
+ if wizard_prompt:
376
+ prompt_text = wizard_prompt
377
+
277
378
  cfg.session_skill_expand_session_id = session_id
278
379
  slash = await process_repl_slash(
279
380
  cfg=cfg,
@@ -232,7 +232,9 @@ class GemCodeConfig:
232
232
  # Deep research model id used when routing selects deep research.
233
233
  model_deep_research: str = field(
234
234
  default_factory=lambda: os.environ.get(
235
- "GEMCODE_MODEL_DEEP_RESEARCH", "travel_explore"
235
+ # Leave empty by default so routing falls back to `cfg.model` unless the
236
+ # user explicitly configures a deep-research-capable model id.
237
+ "GEMCODE_MODEL_DEEP_RESEARCH", ""
236
238
  )
237
239
  )
238
240
 
@@ -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
 
@@ -322,7 +322,13 @@ async def run_stdio_loop() -> int:
322
322
  continue
323
323
  out_text = "".join(txt_parts).strip()
324
324
  if out_text:
325
- emitter.send(make_event(event="text", id=req_id, text=out_text))
325
+ # Pseudo-streaming: emit deltas so IDE chat can feel alive even though
326
+ # we only have the final assistant text today.
327
+ emitter.send(make_event(event="text_start", id=req_id))
328
+ chunk_size = 450
329
+ for i in range(0, len(out_text), chunk_size):
330
+ emitter.send(make_event(event="text_delta", id=req_id, text=out_text[i : i + chunk_size]))
331
+ emitter.send(make_event(event="text_end", id=req_id))
326
332
  emitter.send(make_response(id=req_id, ok=True, session=session_id))
327
333
 
328
334
  finally:
@@ -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
 
@@ -189,5 +189,36 @@ class GemCodeTerminalHooksPlugin(BasePlugin):
189
189
  {"phase": "post_turn_hook", "ok": False, "error": str(e)},
190
190
  )
191
191
 
192
+ # ── VeoMem (optional): store final assistant summary ───────────────────
193
+ try:
194
+ # Best-effort: extract last assistant text from session events.
195
+ txt = ""
196
+ try:
197
+ events = callback_context.session.events or []
198
+ for ev in reversed(events):
199
+ if getattr(ev, "author", None) == getattr(agent, "name", "gemcode"):
200
+ content = getattr(ev, "content", None)
201
+ parts = getattr(content, "parts", None) if content is not None else None
202
+ if parts:
203
+ out_parts = []
204
+ for p in parts:
205
+ t = getattr(p, "text", None)
206
+ if t:
207
+ out_parts.append(t)
208
+ if out_parts:
209
+ txt = "".join(out_parts).strip()
210
+ break
211
+ except Exception:
212
+ txt = ""
213
+ if txt:
214
+ from gemcode.veomem_bridge import record_turn_summary
215
+ record_turn_summary(
216
+ self.cfg.project_root,
217
+ session_id=str(callback_context.session.id),
218
+ text=txt,
219
+ )
220
+ except Exception:
221
+ pass
222
+
192
223
  return None
193
224
 
@@ -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"),
@@ -335,9 +337,11 @@ def slash_help_lines() -> list[str]:
335
337
  " /notes clear Delete all notes",
336
338
  " /notes edit Open notes in $EDITOR",
337
339
  " /create gemskill <name> [description] Create a new GemSkill (SKILL.md scaffold)",
340
+ " Tip: you can also type “I want to make a new skill” and follow the wizard",
338
341
  " /gemskill <name> Load an existing GemSkill into this session (system prompt)",
339
342
  " /gemskill list|clear List skills or unload all session-loaded skills",
340
343
  " /append gemskill <name> <request> Ask the agent to edit that skill file",
344
+ " /caveman [level]|off Terse output mode (like caveman-speak). Levels: lite|full|ultra|wenyan-lite|wenyan|wenyan-ultra",
341
345
  " /style List available output styles",
342
346
  " /style <name>|off Activate an output style for this session",
343
347
  " /rules Show loaded rule files (from .gemcode/rules/)",