Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
repos:
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
rev: 9.0.0a3
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/psf/black
rev: 23.3.0
rev: 26.3.1
hooks:
- id: black
args: ["--line-length", "100", "--preview"]
- repo: https://github.com/pycqa/flake8
rev: 7.1.0
rev: 7.3.0
hooks:
- id: flake8
args: ["--show-source"]
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
rev: v2.4.2
hooks:
- id: codespell
args: ["--skip", "cecli/website/docs/languages.md"]
Expand Down
4 changes: 1 addition & 3 deletions benchmark/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,7 @@ def simple_namespace_to_dict(obj):

if not dry and "CECLI_DOCKER" not in os.environ:
logger.warning("Warning: Benchmarking runs unvetted code. Run in a docker container.")
logger.warning(
"Set CECLI_DOCKER in the environment to by-pass this check at your own risk."
)
logger.warning("Set CECLI_DOCKER in the environment to bypass this check at your own risk.")
return

# Check dirs exist
Expand Down
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.3.dev"
__version__ = "0.99.6.dev"
safe_version = __version__

try:
Expand Down
1 change: 1 addition & 0 deletions cecli/coders/agent_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ def format_chat_chunks(self):

# Add post-message context blocks (priority 250 - between CUR and REMINDER)
ConversationService.get_chunks(self).add_post_message_context_blocks()
ConversationService.get_chunks(self).add_randomized_cta()

return ConversationService.get_manager(self).get_messages_dict()

Expand Down
5 changes: 3 additions & 2 deletions cecli/coders/base_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1131,8 +1131,9 @@ def _include_in_map(abs_path):
"other_files": other_files,
"mentioned_fnames": mentioned_fnames,
"all_abs_files": all_abs_files,
"read_only_count": len(set(self.abs_read_only_fnames)) + len(
set(self.abs_read_only_stubs_fnames)
"read_only_count": (
len(set(self.abs_read_only_fnames))
+ len(set(self.abs_read_only_stubs_fnames))
),
}
)
Expand Down
43 changes: 30 additions & 13 deletions cecli/commands/load_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class LoadMcpCommand(BaseCommand):
NORM_NAME = "load-mcp"
DESCRIPTION = "Load a MCP server by name"
DESCRIPTION = "Load MCP server(s) by name, or use '*' to load all enabled servers"

@classmethod
async def execute(cls, io, coder, args, **kwargs):
Expand All @@ -21,17 +21,34 @@ async def execute(cls, io, coder, args, **kwargs):

server_names = args.strip().split()
results = []
for server_name in server_names:
server = coder.mcp_manager.get_server(server_name)
if server is None:
results.append(f"MCP server {server_name} does not exist.")
continue

did_connect = await coder.mcp_manager.connect_server(server.name)
if did_connect:
results.append(f"Loaded server: {server_name}")
else:
results.append(f"Unable to load server: {server_name}")
# Handle '*' wildcard to load all servers enabled by default
if server_names == ["*"]:
for server in coder.mcp_manager.servers:
if server in coder.mcp_manager.connected_servers:
results.append(f"Server already loaded: {server.name}")
continue
auto_connect = server.config.get("enabled", True)
if not auto_connect:
results.append(f"Skipping server (not enabled by default): {server.name}")
continue
did_connect = await coder.mcp_manager.connect_server(server.name)
if did_connect:
results.append(f"Loaded server: {server.name}")
else:
results.append(f"Unable to load server: {server.name}")
else:
for server_name in server_names:
server = coder.mcp_manager.get_server(server_name)
if server is None:
results.append(f"MCP server {server_name} does not exist.")
continue

did_connect = await coder.mcp_manager.connect_server(server.name)
if did_connect:
results.append(f"Loaded server: {server_name}")
else:
results.append(f"Unable to load server: {server_name}")

try:
return format_command_result(io, cls.NORM_NAME, "\n".join(results))
Expand Down Expand Up @@ -67,8 +84,8 @@ def get_help(cls) -> str:
help_text = super().get_help()
help_text += "\nUsage:\n"
help_text += " /load-mcp <mcp-name>... # Load one or more mcps by name\n"
help_text += " /load-mcp * # Load all mcps enabled by default\n"
help_text += "\nExamples:\n"
help_text += " /load-mcp context7 # Load the context7 mcp\n"
help_text += " /load-mcp github context7 # Load both github and context7 mcps\n"
help_text += "\nThis command loads one or more MCP servers by name.\n"
return help_text
help_text += " /load-mcp * # Load all mcps enabled by default\n"
26 changes: 19 additions & 7 deletions cecli/commands/remove_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class RemoveMcpCommand(BaseCommand):
NORM_NAME = "remove-mcp"
DESCRIPTION = "Remove a MCP server by name"
DESCRIPTION = "Remove a MCP server by name, or use '*' to remove all"

@classmethod
async def execute(cls, io, coder, args, **kwargs):
Expand All @@ -21,12 +21,23 @@ async def execute(cls, io, coder, args, **kwargs):

server_names = args.strip().split()
results = []
for server_name in server_names:
was_disconnected = await coder.mcp_manager.disconnect_server(server_name)
if was_disconnected:
results.append(f"Removed server: {server_name}")

# Handle '*' wildcard to disconnect all servers
if server_names == ["*"]:
connected = [s for s in coder.mcp_manager.servers if s.is_connected]
if not connected:
results.append("No MCP servers connected, nothing to remove.")
else:
results.append(f"Unable to remove server: {server_name}")
for server in connected:
await coder.mcp_manager.disconnect_server(server.name)
results.append(f"Removed server: {server.name}")
else:
for server_name in server_names:
was_disconnected = await coder.mcp_manager.disconnect_server(server_name)
if was_disconnected:
results.append(f"Removed server: {server_name}")
else:
results.append(f"Unable to remove server: {server_name}")

try:
return format_command_result(io, cls.NORM_NAME, "\n".join(results))
Expand Down Expand Up @@ -59,7 +70,8 @@ def get_help(cls) -> str:
help_text = super().get_help()
help_text += "\nUsage:\n"
help_text += " /remove-mcp <mcp-name>... # Remove one or more mcps by name\n"
help_text += " /remove-mcp * # Remove all connected mcps\n"
help_text += "\nExamples:\n"
help_text += " /remove-mcp context7 # Remove the context7 mcp\n"
help_text += " /remove-mcp github context7 # Remove both github and context7 mcps\n"
help_text += "\nThis command removes one or more MCP servers by name.\n"
help_text += " /remove-mcp * # Remove all connected mcps\n"
90 changes: 89 additions & 1 deletion cecli/helpers/conversation/integration.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import random
import weakref
from typing import Any, Dict, List
from uuid import UUID
Expand Down Expand Up @@ -93,7 +94,9 @@ def add_system_messages(self) -> None:
):
msg = dict(
role="user",
content=coder.fmt_system_prompt(coder.gpt_prompts.system_reminder),
content=self._shuffle_reminders(
coder.fmt_system_prompt(coder.gpt_prompts.system_reminder)
),
)
ConversationService.get_manager(coder).add_message(
message_dict=msg,
Expand All @@ -103,6 +106,62 @@ def add_system_messages(self) -> None:
mark_for_delete=0,
)

def add_randomized_cta(self) -> None:
coder = self.get_coder()
if not coder:
return

message = random.choice(
[
"Given the above, please call any tools necessary to make progress on your task",
(
"Based on the information provided, please execute the appropriate tools to"
" move the task forward."
),
"With this context in mind, please proceed with your work.",
(
"In light of the above, please utilize the required tools to continue with this"
" request."
),
(
"Continue making progress. If you have reached the goal, summarize the results."
" Otherwise, call the next necessary tool."
),
(
"Please use the proper tools to fulfill the next steps of this task based on"
" the current data."
),
(
"You’ve got what you need, please invoke the right tools to keep making"
" progress towards our goal."
),
(
"Considering what we've established, please use the available tools to complete"
" the current objective."
),
(
"Given this information, please use the available tools to proceed with the"
" assignment."
),
"Please take the next logical steps to make headway on this task.",
]
)

msg = dict(
role="user",
content="\n\n" + message,
)

ConversationService.get_manager(coder).add_message(
message_dict=msg,
tag=MessageTag.REMINDER,
hash_key=("main", "randomized_cta"),
force=True,
mark_for_delete=0,
promotion=2 * ConversationService.get_manager(coder).DEFAULT_TAG_PROMOTION_VALUE,
mark_for_demotion=1,
)

def cleanup_files(self) -> None:
"""
Clean up ConversationFiles and remove corresponding messages from ConversationManager
Expand Down Expand Up @@ -903,6 +962,35 @@ def add_post_message_context_blocks(self) -> None:
force=True,
)

def _shuffle_reminders(self, content: str) -> str:
"""
If the string is a critical_reminders block, shuffle all bulleted points
to prevent the model from developing 'boilerplate blindness.'
"""
if not content.strip().startswith('<context name="critical_reminders">'):
return content

lines = content.splitlines()

# 1. Identify indices of lines starting with a hyphen (and the content itself)
# We use strip() to handle indentation within the XML block
list_info = [(i, line) for i, line in enumerate(lines) if line.strip().startswith("-")]

if not list_info:
return content

# 2. Extract and shuffle the list items
indices = [item[0] for item in list_info]
bullet_contents = [item[1] for item in list_info]
random.shuffle(bullet_contents)

# 3. Reconstruct the block by placing shuffled items back into the original indices
new_lines = list(lines)
for index, shuffled_text in zip(indices, bullet_contents):
new_lines[index] = shuffled_text

return "\n".join(new_lines)

def debug_print_conversation_state(self) -> None:
"""
Print debug information about conversation state.
Expand Down
4 changes: 4 additions & 0 deletions cecli/helpers/grep_ast/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# noqa: F401

from .grep_ast import TreeContext # noqa
from .parsers import filename_to_lang # noqa
29 changes: 29 additions & 0 deletions cecli/helpers/grep_ast/dump.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import json
import traceback


def cvt(s):
if isinstance(s, str):
return s
try:
return json.dumps(s, indent=4)
except TypeError:
return str(s)


def dump(*vals):
# http://docs.python.org/library/traceback.html
stack = traceback.extract_stack()
vars = stack[-2][3]

# strip away the call to dump()
vars = "(".join(vars.split("(")[1:])
vars = ")".join(vars.split(")")[:-1])

vals = [cvt(v) for v in vals]
has_newline = sum(1 for v in vals if "\n" in v)
if has_newline:
print("%s:" % vars)
print(", ".join(vals))
else:
print("%s:" % vars, ", ".join(vals))
Loading
Loading