Skip to content

Commit 835b8b5

Browse files
authored
Merge pull request #494 from cecli-dev/v0.99.6
V0.99.6
2 parents 4a4e977 + 566a615 commit 835b8b5

30 files changed

Lines changed: 1543 additions & 108 deletions

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
repos:
22
- repo: https://github.com/PyCQA/isort
3-
rev: 5.12.0
3+
rev: 9.0.0a3
44
hooks:
55
- id: isort
66
args: ["--profile", "black"]
77
- repo: https://github.com/psf/black
8-
rev: 23.3.0
8+
rev: 26.3.1
99
hooks:
1010
- id: black
1111
args: ["--line-length", "100", "--preview"]
1212
- repo: https://github.com/pycqa/flake8
13-
rev: 7.1.0
13+
rev: 7.3.0
1414
hooks:
1515
- id: flake8
1616
args: ["--show-source"]
1717
- repo: https://github.com/codespell-project/codespell
18-
rev: v2.2.6
18+
rev: v2.4.2
1919
hooks:
2020
- id: codespell
2121
args: ["--skip", "cecli/website/docs/languages.md"]

benchmark/benchmark.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,7 @@ def simple_namespace_to_dict(obj):
292292

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

300298
# Check dirs exist

cecli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from packaging import version
22

3-
__version__ = "0.99.3.dev"
3+
__version__ = "0.99.6.dev"
44
safe_version = __version__
55

66
try:

cecli/coders/agent_coder.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,7 @@ def format_chat_chunks(self):
582582

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

586587
return ConversationService.get_manager(self).get_messages_dict()
587588

cecli/coders/base_coder.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,8 +1131,9 @@ def _include_in_map(abs_path):
11311131
"other_files": other_files,
11321132
"mentioned_fnames": mentioned_fnames,
11331133
"all_abs_files": all_abs_files,
1134-
"read_only_count": len(set(self.abs_read_only_fnames)) + len(
1135-
set(self.abs_read_only_stubs_fnames)
1134+
"read_only_count": (
1135+
len(set(self.abs_read_only_fnames))
1136+
+ len(set(self.abs_read_only_stubs_fnames))
11361137
),
11371138
}
11381139
)

cecli/commands/load_mcp.py

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

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

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

2222
server_names = args.strip().split()
2323
results = []
24-
for server_name in server_names:
25-
server = coder.mcp_manager.get_server(server_name)
26-
if server is None:
27-
results.append(f"MCP server {server_name} does not exist.")
28-
continue
2924

30-
did_connect = await coder.mcp_manager.connect_server(server.name)
31-
if did_connect:
32-
results.append(f"Loaded server: {server_name}")
33-
else:
34-
results.append(f"Unable to load server: {server_name}")
25+
# Handle '*' wildcard to load all servers enabled by default
26+
if server_names == ["*"]:
27+
for server in coder.mcp_manager.servers:
28+
if server in coder.mcp_manager.connected_servers:
29+
results.append(f"Server already loaded: {server.name}")
30+
continue
31+
auto_connect = server.config.get("enabled", True)
32+
if not auto_connect:
33+
results.append(f"Skipping server (not enabled by default): {server.name}")
34+
continue
35+
did_connect = await coder.mcp_manager.connect_server(server.name)
36+
if did_connect:
37+
results.append(f"Loaded server: {server.name}")
38+
else:
39+
results.append(f"Unable to load server: {server.name}")
40+
else:
41+
for server_name in server_names:
42+
server = coder.mcp_manager.get_server(server_name)
43+
if server is None:
44+
results.append(f"MCP server {server_name} does not exist.")
45+
continue
46+
47+
did_connect = await coder.mcp_manager.connect_server(server.name)
48+
if did_connect:
49+
results.append(f"Loaded server: {server_name}")
50+
else:
51+
results.append(f"Unable to load server: {server_name}")
3552

3653
try:
3754
return format_command_result(io, cls.NORM_NAME, "\n".join(results))
@@ -67,8 +84,8 @@ def get_help(cls) -> str:
6784
help_text = super().get_help()
6885
help_text += "\nUsage:\n"
6986
help_text += " /load-mcp <mcp-name>... # Load one or more mcps by name\n"
87+
help_text += " /load-mcp * # Load all mcps enabled by default\n"
7088
help_text += "\nExamples:\n"
7189
help_text += " /load-mcp context7 # Load the context7 mcp\n"
7290
help_text += " /load-mcp github context7 # Load both github and context7 mcps\n"
73-
help_text += "\nThis command loads one or more MCP servers by name.\n"
74-
return help_text
91+
help_text += " /load-mcp * # Load all mcps enabled by default\n"

cecli/commands/remove_mcp.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

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

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

2222
server_names = args.strip().split()
2323
results = []
24-
for server_name in server_names:
25-
was_disconnected = await coder.mcp_manager.disconnect_server(server_name)
26-
if was_disconnected:
27-
results.append(f"Removed server: {server_name}")
24+
25+
# Handle '*' wildcard to disconnect all servers
26+
if server_names == ["*"]:
27+
connected = [s for s in coder.mcp_manager.servers if s.is_connected]
28+
if not connected:
29+
results.append("No MCP servers connected, nothing to remove.")
2830
else:
29-
results.append(f"Unable to remove server: {server_name}")
31+
for server in connected:
32+
await coder.mcp_manager.disconnect_server(server.name)
33+
results.append(f"Removed server: {server.name}")
34+
else:
35+
for server_name in server_names:
36+
was_disconnected = await coder.mcp_manager.disconnect_server(server_name)
37+
if was_disconnected:
38+
results.append(f"Removed server: {server_name}")
39+
else:
40+
results.append(f"Unable to remove server: {server_name}")
3041

3142
try:
3243
return format_command_result(io, cls.NORM_NAME, "\n".join(results))
@@ -59,7 +70,8 @@ def get_help(cls) -> str:
5970
help_text = super().get_help()
6071
help_text += "\nUsage:\n"
6172
help_text += " /remove-mcp <mcp-name>... # Remove one or more mcps by name\n"
73+
help_text += " /remove-mcp * # Remove all connected mcps\n"
6274
help_text += "\nExamples:\n"
6375
help_text += " /remove-mcp context7 # Remove the context7 mcp\n"
6476
help_text += " /remove-mcp github context7 # Remove both github and context7 mcps\n"
65-
help_text += "\nThis command removes one or more MCP servers by name.\n"
77+
help_text += " /remove-mcp * # Remove all connected mcps\n"

cecli/helpers/conversation/integration.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import random
23
import weakref
34
from typing import Any, Dict, List
45
from uuid import UUID
@@ -93,7 +94,9 @@ def add_system_messages(self) -> None:
9394
):
9495
msg = dict(
9596
role="user",
96-
content=coder.fmt_system_prompt(coder.gpt_prompts.system_reminder),
97+
content=self._shuffle_reminders(
98+
coder.fmt_system_prompt(coder.gpt_prompts.system_reminder)
99+
),
97100
)
98101
ConversationService.get_manager(coder).add_message(
99102
message_dict=msg,
@@ -103,6 +106,62 @@ def add_system_messages(self) -> None:
103106
mark_for_delete=0,
104107
)
105108

109+
def add_randomized_cta(self) -> None:
110+
coder = self.get_coder()
111+
if not coder:
112+
return
113+
114+
message = random.choice(
115+
[
116+
"Given the above, please call any tools necessary to make progress on your task",
117+
(
118+
"Based on the information provided, please execute the appropriate tools to"
119+
" move the task forward."
120+
),
121+
"With this context in mind, please proceed with your work.",
122+
(
123+
"In light of the above, please utilize the required tools to continue with this"
124+
" request."
125+
),
126+
(
127+
"Continue making progress. If you have reached the goal, summarize the results."
128+
" Otherwise, call the next necessary tool."
129+
),
130+
(
131+
"Please use the proper tools to fulfill the next steps of this task based on"
132+
" the current data."
133+
),
134+
(
135+
"You’ve got what you need, please invoke the right tools to keep making"
136+
" progress towards our goal."
137+
),
138+
(
139+
"Considering what we've established, please use the available tools to complete"
140+
" the current objective."
141+
),
142+
(
143+
"Given this information, please use the available tools to proceed with the"
144+
" assignment."
145+
),
146+
"Please take the next logical steps to make headway on this task.",
147+
]
148+
)
149+
150+
msg = dict(
151+
role="user",
152+
content="\n\n" + message,
153+
)
154+
155+
ConversationService.get_manager(coder).add_message(
156+
message_dict=msg,
157+
tag=MessageTag.REMINDER,
158+
hash_key=("main", "randomized_cta"),
159+
force=True,
160+
mark_for_delete=0,
161+
promotion=2 * ConversationService.get_manager(coder).DEFAULT_TAG_PROMOTION_VALUE,
162+
mark_for_demotion=1,
163+
)
164+
106165
def cleanup_files(self) -> None:
107166
"""
108167
Clean up ConversationFiles and remove corresponding messages from ConversationManager
@@ -903,6 +962,35 @@ def add_post_message_context_blocks(self) -> None:
903962
force=True,
904963
)
905964

965+
def _shuffle_reminders(self, content: str) -> str:
966+
"""
967+
If the string is a critical_reminders block, shuffle all bulleted points
968+
to prevent the model from developing 'boilerplate blindness.'
969+
"""
970+
if not content.strip().startswith('<context name="critical_reminders">'):
971+
return content
972+
973+
lines = content.splitlines()
974+
975+
# 1. Identify indices of lines starting with a hyphen (and the content itself)
976+
# We use strip() to handle indentation within the XML block
977+
list_info = [(i, line) for i, line in enumerate(lines) if line.strip().startswith("-")]
978+
979+
if not list_info:
980+
return content
981+
982+
# 2. Extract and shuffle the list items
983+
indices = [item[0] for item in list_info]
984+
bullet_contents = [item[1] for item in list_info]
985+
random.shuffle(bullet_contents)
986+
987+
# 3. Reconstruct the block by placing shuffled items back into the original indices
988+
new_lines = list(lines)
989+
for index, shuffled_text in zip(indices, bullet_contents):
990+
new_lines[index] = shuffled_text
991+
992+
return "\n".join(new_lines)
993+
906994
def debug_print_conversation_state(self) -> None:
907995
"""
908996
Print debug information about conversation state.

cecli/helpers/grep_ast/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# noqa: F401
2+
3+
from .grep_ast import TreeContext # noqa
4+
from .parsers import filename_to_lang # noqa

cecli/helpers/grep_ast/dump.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import json
2+
import traceback
3+
4+
5+
def cvt(s):
6+
if isinstance(s, str):
7+
return s
8+
try:
9+
return json.dumps(s, indent=4)
10+
except TypeError:
11+
return str(s)
12+
13+
14+
def dump(*vals):
15+
# http://docs.python.org/library/traceback.html
16+
stack = traceback.extract_stack()
17+
vars = stack[-2][3]
18+
19+
# strip away the call to dump()
20+
vars = "(".join(vars.split("(")[1:])
21+
vars = ")".join(vars.split(")")[:-1])
22+
23+
vals = [cvt(v) for v in vals]
24+
has_newline = sum(1 for v in vals if "\n" in v)
25+
if has_newline:
26+
print("%s:" % vars)
27+
print(", ".join(vals))
28+
else:
29+
print("%s:" % vars, ", ".join(vals))

0 commit comments

Comments
 (0)