Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2b884b9
fix: Ring bell when tool call info is printed
szmania Apr 10, 2026
c93147c
fix: Add notifications for TUI confirmation prompts
szmania Apr 11, 2026
7c3372a
fix: Trigger user input notification in TUI mode
szmania Apr 11, 2026
9ad7c23
fix: Refactor notification logic to separate user input notification
szmania Apr 11, 2026
21342c2
fix: Ensure single notification for confirmations
szmania Apr 11, 2026
04a3df7
fix: Remove redundant ring_bell call in cecli/io.py
szmania Apr 11, 2026
a5c9d99
fix: Remove unnecessary ring_bell call in base_coder
szmania Apr 11, 2026
e36519c
fix: Control notification triggering based on agent reflection
szmania Apr 11, 2026
646348f
fix: Control notification triggering in agent coder
szmania Apr 11, 2026
8e5ae7f
fix: Restore notification for user input prompts
szmania Apr 11, 2026
680f4f2
fix: Restore user input required notification
szmania Apr 11, 2026
fc3cf61
fix
szmania Apr 12, 2026
3ce8727
cli-5: fixed black format issues
szmania Apr 12, 2026
f088bf3
cli-5: fixed merge conflcits
szmania Apr 12, 2026
5de9b85
cli-5: fixed merge conflcits
szmania Apr 12, 2026
df389a9
cli-5: fixed merge conflcits
szmania Apr 12, 2026
d00a2b2
cli-5: fixed merge conflcits
szmania Apr 12, 2026
5899fce
fixed black format issues
szmania Apr 12, 2026
76414a4
cli-6: added interruption fix
szmania Apr 14, 2026
f651f99
docs: Add evals directory to skill structure
szmania Apr 14, 2026
8f43cac
docs: Add evals.json structure to skills documentation
szmania Apr 14, 2026
0f1d9d3
docs: Update skills documentation to support assertion-based evals fo…
szmania Apr 15, 2026
0371003
cli-7: added evals directory for skills for evaluating the correct in…
szmania Apr 15, 2026
6f7ffe5
Bump Version
Apr 15, 2026
b3986ae
Update model metadata
Apr 15, 2026
d00b709
#484: presence_penalty crashes some model APIs with no way to detect …
Apr 15, 2026
4aabcf2
Remove empty trace back on tool call duplication error
Apr 15, 2026
e13d71f
Update agent mode system prompt
Apr 15, 2026
7c1a631
Fix repo re-initialization modifying workspace working directory
Apr 15, 2026
766d851
Allow passing in custom ignore files to workspaces to be able to thin…
Apr 15, 2026
8ad46fd
Merge pull request #483 from szmania/cli-5-notifications
dwash96 Apr 15, 2026
cc70892
Update workspaces documentation for custom ignore files
Apr 15, 2026
9688876
Merge pull request #485 from szmania/cli-6-interruption-fix
dwash96 Apr 15, 2026
c452982
Fix system initialization
Apr 15, 2026
5b35a22
Fix typo in main file making workspaces override hooks config
Apr 15, 2026
d7d26ab
Disambiguate what "workspaces" means inside of the system
Apr 15, 2026
881f92e
Merge pull request #486 from szmania/cli-7-add-evals
dwash96 Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cecli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from packaging import version

__version__ = "0.99.1.dev"
__version__ = "0.99.2.dev"
safe_version = __version__

try:
Expand Down
4 changes: 2 additions & 2 deletions cecli/coders/agent_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def _setup_agent(self):
os.makedirs(".cecli/workspace", exist_ok=True)
os.makedirs(".cecli/temp", exist_ok=True)

def _get_agent_config(self):
"""
Expand Down Expand Up @@ -1020,7 +1020,7 @@ def _generate_tool_context(self, repetitive_tools):
self.model_kwargs = {
"temperature": default_temp + 0.1,
"frequency_penalty": default_fp + 0.2,
"presence_penalty": 0.1,
# "presence_penalty": 0.1,
}
else:
temperature = nested.getter(self.model_kwargs, "temperature", default_temp)
Expand Down
39 changes: 31 additions & 8 deletions cecli/coders/base_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ async def create(
file_watcher=from_coder.file_watcher,
mcp_manager=from_coder.mcp_manager,
uuid=from_coder.uuid,
repo=from_coder.repo,
)
use_kwargs.update(update) # override to complete the switch
use_kwargs.update(kwargs) # override passed kwargs
Expand Down Expand Up @@ -328,6 +329,7 @@ def __init__(
uuid="",
):
# initialize from args.map_cache_dir
self.interrupt_event = asyncio.Event()
self.uuid = generate_unique_id()
if uuid:
self.uuid = uuid
Expand Down Expand Up @@ -1735,6 +1737,7 @@ def keyboard_interrupt(self):

self.io.tool_warning("\n\n^C KeyboardInterrupt")

self.interrupt_event.set()
self.last_keyboard_interrupt = time.time()

# Old summarization system removed - using context compaction logic instead
Expand Down Expand Up @@ -2768,6 +2771,7 @@ async def process_tool_calls(self, tool_call_response):

def _print_tool_call_info(self, server_tool_calls):
"""Print information about an MCP tool call."""
self.io.ring_bell()
# self.io.tool_output("Preparing to run MCP tools", bold=False)

for server, tool_calls in server_tool_calls.items():
Expand Down Expand Up @@ -3039,6 +3043,7 @@ async def check_for_file_mentions(self, content):
return prompts.added_files.format(fnames=", ".join(added_fnames))

async def send(self, messages, model=None, functions=None, tools=None):
self.interrupt_event.clear()
self.got_reasoning_content = False
self.ended_reasoning_content = False

Expand All @@ -3058,15 +3063,33 @@ async def send(self, messages, model=None, functions=None, tools=None):
self.token_profiler.start()

try:
hash_object, completion = await model.send_completion(
messages,
functions,
self.stream,
self.temperature,
# This could include any tools, but for now it is just MCP tools
tools=tools,
override_kwargs=self.model_kwargs.copy(),
completion_task = asyncio.create_task(
model.send_completion(
messages,
functions,
self.stream,
self.temperature,
# This could include any tools, but for now it is just MCP tools
tools=tools,
override_kwargs=self.model_kwargs.copy(),
)
)
interrupt_task = asyncio.create_task(self.interrupt_event.wait())

done, pending = await asyncio.wait(
{completion_task, interrupt_task},
return_when=asyncio.FIRST_COMPLETED,
)

if interrupt_task in done:
completion_task.cancel()
try:
await completion_task
except asyncio.CancelledError:
pass
raise KeyboardInterrupt

hash_object, completion = completion_task.result()
self.chat_completion_call_hashes.append(hash_object.hexdigest())

if not isinstance(completion, ModelResponse):
Expand Down
1 change: 1 addition & 0 deletions cecli/helpers/monorepo/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def __init__(self, workspace_path: Path, config: Dict[str, Any]):
self.config = config
self.name = config["name"]
self.repo_url = config["repo"]
self.ignore_file = config.get("ignore")
self.base_path = workspace_path / self.name
self.main_path = self.base_path / "main"

Expand Down
11 changes: 11 additions & 0 deletions cecli/helpers/monorepo/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ def initialize(self) -> None:
project = Project(self.path, proj_cfg)
project.initialize()

# Copy ignore files to workspace root
for proj_cfg in projects_config:
ignore_file = proj_cfg.get("ignore")
if ignore_file:
ignore_path = Path(ignore_file).expanduser()
if ignore_path.exists():
import shutil

dest_path = self.path / f"{proj_cfg['name']}.ignore"
shutil.copy2(ignore_path, dest_path)

# Write metadata
import json

Expand Down
34 changes: 33 additions & 1 deletion cecli/helpers/skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class SkillContent:
references: Dict[str, Path] = field(default_factory=dict)
scripts: Dict[str, Path] = field(default_factory=dict)
assets: Dict[str, Path] = field(default_factory=dict)
evals: Dict[str, Path] = field(default_factory=dict)


class SkillsManager:
Expand Down Expand Up @@ -227,13 +228,17 @@ def _load_complete_skill(self, metadata: SkillMetadata) -> SkillContent:
# Load assets
assets = self._load_assets(skill_dir)

# Load evals
evals = self._load_evals(skill_dir)

return SkillContent(
metadata=metadata,
frontmatter=frontmatter,
instructions=instructions,
references=references,
scripts=scripts,
assets=assets,
evals=evals,
)

def _load_references(self, skill_dir: Path) -> Dict[str, Path]:
Expand Down Expand Up @@ -286,6 +291,23 @@ def _load_assets(self, skill_dir: Path) -> Dict[str, Path]:

return assets

def _load_evals(self, skill_dir: Path) -> Dict[str, Path]:
"""Load eval files from the evals/ directory."""
evals = {}
evals_dir = skill_dir / "evals"

if evals_dir.exists():
for eval_file in evals_dir.glob("**/*"):
if eval_file.is_file():
try:
# Use relative path as key, store the Path object
rel_path = eval_file.relative_to(evals_dir)
evals[str(rel_path)] = eval_file
except Exception:
continue

return evals

def get_skill_summary(self, skill_name: str) -> Optional[str]:
"""
Get a summary of a skill for display purposes.
Expand Down Expand Up @@ -315,9 +337,11 @@ def get_skill_summary(self, skill_name: str) -> Optional[str]:
ref_count = len(skill.references)
script_count = len(skill.scripts)
asset_count = len(skill.assets)
eval_count = len(skill.evals)

summary += (
f"Resources: {ref_count} references, {script_count} scripts, {asset_count} assets\n"
f"Resources: {ref_count} references, {script_count} scripts, {asset_count} assets,"
f" {eval_count} evals\n"
)

return summary
Expand Down Expand Up @@ -540,6 +564,14 @@ def get_skills_content(self) -> Optional[str]:
result += f"- **{asset_name}**: `{asset_path}`\n"
result += "\n"

# Add evals file paths
if skill_content.evals:
result += f"#### Evals ({len(skill_content.evals)} file(s))\n\n"
result += "Available eval files:\n\n"
for eval_name, eval_path in skill_content.evals.items():
result += f"- **{eval_name}**: `{eval_path}`\n"
result += "\n"

result += "---\n\n"

result += "</context>"
Expand Down
38 changes: 25 additions & 13 deletions cecli/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,11 @@ def rule(self):
print()

def interrupt_input(self):
if self.coder:
coder = self.coder()
if coder and hasattr(coder, "interrupt_event"):
coder.interrupt_event.set()

if self.prompt_session and self.prompt_session.app:
# Store any partial input before interrupting
self.placeholder = self.prompt_session.app.current_buffer.text
Expand Down Expand Up @@ -1301,6 +1306,7 @@ async def _confirm_ask(
self.user_input(f"{question} - {res}", log_only=False)
else:
# Ring the bell if needed
self.notify_user_input_required()
self.ring_bell()
self.start_spinner("Awaiting Confirmation...", False)

Expand Down Expand Up @@ -1708,22 +1714,28 @@ def get_default_notification_command(self):

return None # Unknown system

def _send_notification(self):
if self.notifications_command:
try:
result = subprocess.run(self.notifications_command, shell=True, capture_output=True)
if result.returncode != 0 and result.stderr:
error_msg = result.stderr.decode("utf-8", errors="replace")
self.tool_warning(f"Failed to run notifications command: {error_msg}")
except Exception as e:
self.tool_warning(f"Failed to run notifications command: {e}")
else:
print("\a", end="", flush=True) # Ring the bell

def notify_user_input_required(self):
"""Send a notification that user input is required."""
if self.notifications:
self._send_notification()

def ring_bell(self):
"""Ring the terminal bell if needed and clear the flag"""
if self.bell_on_next_input and self.notifications:
if self.notifications_command:
try:
result = subprocess.run(
self.notifications_command, shell=True, capture_output=True
)
if result.returncode != 0 and result.stderr:
error_msg = result.stderr.decode("utf-8", errors="replace")
self.tool_warning(f"Failed to run notifications command: {error_msg}")
except Exception as e:
self.tool_warning(f"Failed to run notifications command: {e}")
else:
print("\a", end="", flush=True) # Ring the bell
self.bell_on_next_input = False # Clear the flag
self._send_notification()
self.bell_on_next_input = False

def toggle_multiline_mode(self):
"""Toggle between normal and multiline input modes"""
Expand Down
2 changes: 1 addition & 1 deletion cecli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ async def main_async(argv=None, input=None, output=None, force_git_root=None, re
if hasattr(args, "hooks") and args.hooks is not None:
args.hooks = convert_yaml_to_json_string(args.hooks)
if hasattr(args, "workspaces") and args.workspaces is not None:
args.hooks = convert_yaml_to_json_string(args.workspaces)
args.workspaces = convert_yaml_to_json_string(args.workspaces)

# Interpolate environment variables in all string arguments
for key, value in vars(args).items():
Expand Down
25 changes: 12 additions & 13 deletions cecli/prompts/agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ main_system: |
<context name="role_and_directives">
## Core Directives
**Act Proactively**: Autonomously use discovery and management tools (`ViewFilesAtGlob`, `ViewFilesMatching`, `Ls`, `ContextManager`) to fulfill the request. Chain tool calls across multiple turns for continuous exploration.
**Be Decisive**: Trust your findings. Do not repeat identical searches or ask redundant questions once a path is established.
**Be Decisive**: Trust your findings. Do not repeat identical searches or ask redundant questions.
**Be Efficient**: Batch tool calls when tools allow you to. Respect usage limits while maximizing the utility of each turn.
**Be Persistent**: Do not take short cuts. Work through your task until completion. No task takes too long as long as you are making progress towards the goal.
</context>

<context name="file_format">
### 1. FILE FORMAT
Files are provided in "hashline" format. Each line starts with a case-sensitive content hash followed by `::`.
Do not attempt to write these content hashes. They are automatically generated, maintained, and subject to change.

**Example File Format :**
il9n::#!/usr/bin/env python3
Expand All @@ -43,33 +45,30 @@ main_system: |
5. **Finished**: Use the `Finished` tool only after verifying the solution. Briefly summarize the changes for the user.

## Todo List Management
- Use `UpdateTodoList` every 3-10 tool calls to keep the state synchronized.
- Break complex tasks into granular steps so they remain tractable and context-efficient
- Use `UpdateTodoList` to keep the state synchronized as you complete subtasks.
- Break complex tasks and long edits into granular steps so they remain tractable and context-efficient

### Editing Tools (Precision Protocol)
Files use leading hashline content id prefixes inside brackets, i.e. `[{{4 char hash}}]{{line content}}`.
Do not attempt to write these content ids. They are automatically generated.
### Editing Tool Protocol

**MANDATORY Two-Phase Safety Protocol**:
1. **Phase 1**: Use `ShowContext` to get the hashline-prefixed content around the pattern to modify. Capture entire functions, logical blocks and closures. You may use multiple calls.
1. **Phase 1**: Use `ShowContext` to gather the hashline-prefixed content of the section to modify. Capture entire functions, logical blocks and closures.
2. **Phase 2**: Execute the edit (`ReplaceText`, `InsertText`, `DeleteText`) using the verified hashlines prefixes from the `ShowContext` tool.

**Atomic Scope:** Include the **entire function or logical block**. Never return partial syntax or broken closures. Do not attempt to replace just the beginning or end of a closure.
**Indentation**: Preserve all whitespace (spaces, tabs, and newlines).
**Indentation**: Preserve all necessary whitespace (spaces, tabs, and newlines) and stylistic indentation.
</context>

Use the `.cecli/workspace` directory for all temporary, test, or scratch files.
Use the `.cecli/temp` directory for all temporary, test, or scratch files.
Always reply to the user in {language}.

system_reminder: |
<context name="critical_reminders">
## Reminders
**Strict Scope**: Stay on task. Do not alter functionality and syntax that is out of scope or pursue unrequested refactors. Do not attempt to modify large files in one shot. Work step by step.
**Context Hygiene**: Remove files or skills from context using `ContextManager` or `RemoveSkill` once they are no longer needed to save tokens and prevent confusion.
**Context Hygiene**: Remove files and loaded skills from context using `ContextManager` or `RemoveSkill` once they are no longer needed to save tokens and prevent confusion.
**Turn Management**: Tool calls trigger the next turn. Do not include tool calls in your final summary to the user. You must use `ShowContext` to view the relevant hashline range before each edit.
**Sandbox**: Use `.cecli/workspace` for all verification and temporary logic.
**Novelty**: Do not repeat phrases in your responses to the user. You do not need to declare you understand the task. Simply proceed. Only give status when you have new information.
**Patience**: Do not take short cuts. Work through your task until completion. No task takes too long as long as you are making progress towards the goal.
**Sandbox**: Use `.cecli/temp` for all verification and temporary logic.
**Novelty**: Do not repeat phrases in your responses to the user. You do not need to declare you understand the task. Simply proceed. Only give status updates when you have new information.

{lazy_prompt}
{shell_cmd_reminder}
Expand Down
2 changes: 1 addition & 1 deletion cecli/prompts/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ compaction_prompt: |
- (e.g., "Discovered that the connection timeout error is triggered by the `RetryPolicy` class.")
- (e.g., "Successfully refactored the `validate_input` function to handle null bytes.")
- (e.g., "Reverted changes to `db.py` after determining the issue was in the environment config instead.")
- (e.g., "Verified that the fix works in isolation using a temporary script in `.cecli/workspace`.")
- (e.g., "Verified that the fix works in isolation using a temporary script in `.cecli/temp`.")
### 3. Current Technical Context
- **Files In-Scope**: List paths currently being edited or actively referenced.
Expand Down
Loading
Loading