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.
- {gemcode-0.3.81/src/gemcode.egg-info → gemcode-0.3.85}/PKG-INFO +16 -2
- {gemcode-0.3.81 → gemcode-0.3.85}/README.md +15 -1
- {gemcode-0.3.81 → gemcode-0.3.85}/pyproject.toml +1 -1
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/agent.py +15 -1
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/callbacks.py +20 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/cli.py +101 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/config.py +3 -1
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/curated_memory.py +12 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/ide_stdio.py +7 -1
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/modality_tools.py +12 -1
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/output_styles.py +8 -1
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/plugins/terminal_hooks_plugin.py +31 -0
- gemcode-0.3.85/src/gemcode/query_sanitizer.py +87 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/repl_commands.py +4 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/repl_slash.py +155 -12
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/session_runtime.py +15 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/skills.py +85 -6
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/__init__.py +4 -0
- gemcode-0.3.85/src/gemcode/tools/compress_memory.py +333 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/web_search.py +9 -1
- gemcode-0.3.85/src/gemcode/veomem_bridge.py +105 -0
- gemcode-0.3.85/src/gemcode/wal.py +62 -0
- {gemcode-0.3.81 → gemcode-0.3.85/src/gemcode.egg-info}/PKG-INFO +16 -2
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode.egg-info/SOURCES.txt +5 -0
- gemcode-0.3.85/tests/test_compress_memory_tool.py +52 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_model_routing.py +4 -4
- {gemcode-0.3.81 → gemcode-0.3.85}/LICENSE +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/MANIFEST.in +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/setup.cfg +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/__init__.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/__main__.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/audit.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/autotune.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/checkpoints.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/compaction.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/credentials.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/dynamic_policy.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/evals/harness.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/hooks.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/ide_protocol.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/intent_classifier.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/interactions.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/invoke.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/kaira_daemon.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/learning.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/limits.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/multimodal_input.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/openapi_loader.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/paths.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/permissions.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/policy_profile.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/pricing.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/config.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/refine.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/review_agent.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/rules.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/session_store.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/thinking.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tool_prompt_manifest.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tool_result_store.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/browser.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/curated_memory.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/notebook.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/notes.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/repo_map.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/skills.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/subtask.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/tasks.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/think.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/trust.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tui/input_handler.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tui/scrollback.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/version.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/vertex.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/web/sse_adapter.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/web/web_sse_compat.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_add_dir.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_autocompact.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_capability_routing.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_checkpoint_diff_command.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_cli_init.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_context_budget.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_context_warning.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_credentials.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_eval_harness_layout.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_ide_stdio_attachments.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_kaira_scheduler.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_modality_tools.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_model_errors.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_multimodal_input.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_output_styles_and_rules.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_paths.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_permissions.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_repl_commands.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_repl_slash.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_skills.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_slash_commands.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_slash_completion_registry.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_thinking_config.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_token_budget.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_tools.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.3.81 → gemcode-0.3.85}/tests/test_web_sse_adapter.py +0 -0
- {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.
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
|
@@ -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/)",
|