auto-coder 0.1.299__py3-none-any.whl → 0.1.301__py3-none-any.whl

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.

Potentially problematic release.


This version of auto-coder might be problematic. Click here for more details.

autocoder/auto_coder.py CHANGED
@@ -5,6 +5,7 @@ from autocoder.dispacher import Dispacher
5
5
  from autocoder.common import git_utils, code_auto_execute
6
6
  from autocoder.utils.llm_client_interceptors import token_counter_interceptor
7
7
  from autocoder.db.store import Store
8
+ from autocoder.common.action_yml_file_manager import ActionYmlFileManager
8
9
 
9
10
  from autocoder.utils.llms import get_llm_names
10
11
  from autocoder.utils.queue_communicate import (
@@ -196,59 +197,25 @@ def main(input_args: Optional[List[str]] = None):
196
197
  return
197
198
 
198
199
  if raw_args.command == "next":
199
- actions_dir = os.path.join(os.getcwd(), "actions")
200
- if not os.path.exists(actions_dir):
201
- print("Current directory does not have an actions directory")
202
- return
203
-
204
- action_files = [
205
- f for f in os.listdir(actions_dir) if f[:3].isdigit() and "_" in f and f.endswith(".yml")
206
- ]
207
-
208
- def get_old_seq(name):
209
- return name.split("_")[0]
210
-
211
- if not action_files:
212
- max_seq = 0
213
- else:
214
- seqs = [int(get_old_seq(f)) for f in action_files]
215
- max_seq = max(seqs)
216
-
217
- new_seq = str(max_seq + 1).zfill(12)
218
- prev_files = [f for f in action_files if int(
219
- get_old_seq(f)) < int(new_seq)]
220
-
200
+ # 使用 ActionYmlFileManager 创建下一个 action 文件
201
+ action_manager = ActionYmlFileManager(args.source_dir)
202
+
221
203
  if raw_args.from_yaml:
222
- # If --from_yaml is specified, copy content from the matching YAML file
223
- from_files = [
224
- f for f in action_files if f.startswith(raw_args.from_yaml)]
225
- if from_files:
226
- from_file = from_files[0] # Take the first match
227
- with open(os.path.join(actions_dir, from_file), "r",encoding="utf-8") as f:
228
- content = f.read()
229
- new_file = os.path.join(
230
- actions_dir, f"{new_seq}_{raw_args.name}.yml")
231
- with open(new_file, "w",encoding="utf-8") as f:
232
- f.write(content)
233
- else:
234
- print(
235
- f"No YAML file found matching prefix: {raw_args.from_yaml}")
204
+ # 基于指定的 yaml 文件创建新文件
205
+ new_file = action_manager.create_next_action_file(
206
+ name=raw_args.name,
207
+ from_yaml=raw_args.from_yaml
208
+ )
209
+ if not new_file:
210
+ print(f"No YAML file found matching prefix: {raw_args.from_yaml}")
236
211
  return
237
212
  else:
238
- # If --from_yaml is not specified, use the previous logic
239
- if not prev_files:
240
- new_file = os.path.join(
241
- actions_dir, f"{new_seq}_{raw_args.name}.yml")
242
- with open(new_file, "w",encoding="utf-8") as f:
243
- pass
244
- else:
245
- prev_file = sorted(prev_files)[-1] # 取序号最大的文件
246
- with open(os.path.join(actions_dir, prev_file), "r",encoding="utf-8") as f:
247
- content = f.read()
248
- new_file = os.path.join(
249
- actions_dir, f"{new_seq}_{raw_args.name}.yml")
250
- with open(new_file, "w",encoding="utf-8") as f:
251
- f.write(content)
213
+ # 创建新的 action 文件
214
+ new_file = action_manager.create_next_action_file(name=raw_args.name)
215
+ if not new_file:
216
+ print("Failed to create new action file")
217
+ return
218
+
252
219
  # open_yaml_file_in_editor(new_file)
253
220
  return
254
221
 
@@ -883,7 +850,6 @@ def main(input_args: Optional[List[str]] = None):
883
850
  # os.path.join(args.source_dir, "actions")
884
851
  # )
885
852
  # )
886
- # )
887
853
  return
888
854
  elif raw_args.agent_command == "project_reader":
889
855
 
@@ -1062,6 +1028,15 @@ def main(input_args: Optional[List[str]] = None):
1062
1028
 
1063
1029
  elif raw_args.agent_command == "chat":
1064
1030
 
1031
+ # 统一格式
1032
+ # {"command1": {"args": ["arg1", "arg2"], "kwargs": {"key1": "value1", "key2": "value2"}}}
1033
+ if isinstance(args.action, dict):
1034
+ commands_info = args.action
1035
+ else:
1036
+ commands_info = {}
1037
+ for command in args.action:
1038
+ commands_info[command] = {}
1039
+
1065
1040
  memory_dir = os.path.join(args.source_dir, ".auto-coder", "memory")
1066
1041
  os.makedirs(memory_dir, exist_ok=True)
1067
1042
  memory_file = os.path.join(memory_dir, "chat_history.json")
@@ -1167,7 +1142,7 @@ def main(input_args: Optional[List[str]] = None):
1167
1142
  file_path=source.module_name,
1168
1143
  model_name=",".join(get_llm_names(chat_llm)))
1169
1144
 
1170
- if "no_context" not in args.action:
1145
+ if "no_context" not in commands_info:
1171
1146
  s = build_index_and_filter_files(
1172
1147
  llm=llm, args=args, sources=filtered_sources).to_str()
1173
1148
 
@@ -1304,7 +1279,7 @@ def main(input_args: Optional[List[str]] = None):
1304
1279
  ),
1305
1280
  )
1306
1281
 
1307
- if "save" in args.action:
1282
+ if "save" in commands_info:
1308
1283
  save_to_memory_file(ask_conversation=chat_history["ask_conversation"],
1309
1284
  query=args.query,
1310
1285
  response=result)
@@ -1314,8 +1289,9 @@ def main(input_args: Optional[List[str]] = None):
1314
1289
 
1315
1290
  # 计算耗时
1316
1291
  start_time = time.time()
1292
+ commit_file_name = None
1317
1293
 
1318
- if "rag" in args.action:
1294
+ if "rag" in commands_info:
1319
1295
  from autocoder.rag.rag_entry import RAGFactory
1320
1296
  args.enable_rag_search = True
1321
1297
  args.enable_rag_context = False
@@ -1324,25 +1300,35 @@ def main(input_args: Optional[List[str]] = None):
1324
1300
  conversations=loaded_conversations)[0]
1325
1301
  v = (item for item in response)
1326
1302
 
1327
- elif "mcp" in args.action:
1303
+ elif "mcp" in commands_info:
1328
1304
  from autocoder.common.mcp_server import get_mcp_server, McpRequest, McpInstallRequest, McpRemoveRequest, McpListRequest, McpListRunningRequest, McpRefreshRequest
1329
1305
  mcp_server = get_mcp_server()
1306
+
1307
+ pos_args = commands_info["mcp"].get("args", [])
1308
+ final_query = pos_args[0] if pos_args else args.query
1330
1309
  response = mcp_server.send_request(
1331
1310
  McpRequest(
1332
- query=args.query,
1311
+ query=final_query,
1333
1312
  model=args.inference_model or args.model,
1334
1313
  product_mode=args.product_mode
1335
1314
  )
1336
1315
  )
1337
1316
  v = [[response.result,None]]
1338
- elif "review_commit" in args.action:
1339
- from autocoder.agent.auto_review_commit import AutoReviewCommit
1317
+ elif "review" in commands_info:
1318
+ from autocoder.agent.auto_review_commit import AutoReviewCommit
1340
1319
  reviewer = AutoReviewCommit(llm=chat_llm, args=args)
1341
- v = reviewer.review_commit(query=args.query,conversations=loaded_conversations)
1342
- elif "learn_from_commit" in args.action:
1320
+ pos_args = commands_info["review"].get("args", [])
1321
+ final_query = pos_args[0] if pos_args else args.query
1322
+ kwargs = commands_info["review"].get("kwargs", {})
1323
+ commit_id = kwargs.get("commit", None)
1324
+ v = reviewer.review_commit(query=final_query, conversations=loaded_conversations, commit_id=commit_id)
1325
+ elif "learn" in commands_info:
1343
1326
  from autocoder.agent.auto_learn_from_commit import AutoLearnFromCommit
1344
1327
  learner = AutoLearnFromCommit(llm=chat_llm, args=args)
1345
- v = learner.learn_from_commit(query=args.query,conversations=loaded_conversations)
1328
+ pos_args = commands_info["learn"].get("args", [])
1329
+ final_query = pos_args[0] if pos_args else args.query
1330
+ v,tmp_file_name = learner.learn_from_commit(query=final_query,conversations=loaded_conversations)
1331
+ commit_file_name = tmp_file_name
1346
1332
  else:
1347
1333
  # 预估token数量
1348
1334
  dumped_conversations = json.dumps(loaded_conversations, ensure_ascii=False)
@@ -1375,7 +1361,15 @@ def main(input_args: Optional[List[str]] = None):
1375
1361
  "input": {
1376
1362
  "query": args.query
1377
1363
  }
1378
- })
1364
+ })
1365
+
1366
+ if "learn" in commands_info:
1367
+ if commit_file_name:
1368
+ # 使用 ActionYmlFileManager 更新 YAML 文件
1369
+ action_manager = ActionYmlFileManager(args.source_dir)
1370
+ if not action_manager.update_yaml_field(commit_file_name, 'how_to_reproduce', assistant_response):
1371
+ printer = Printer()
1372
+ printer.print_in_terminal("yaml_save_error", style="red", yaml_file=commit_file_name)
1379
1373
 
1380
1374
  # 打印耗时和token统计
1381
1375
  if last_meta:
@@ -1410,7 +1404,7 @@ def main(input_args: Optional[List[str]] = None):
1410
1404
  with open(memory_file, "w",encoding="utf-8") as f:
1411
1405
  json.dump(chat_history, f, ensure_ascii=False)
1412
1406
 
1413
- if "copy" in args.action:
1407
+ if "copy" in commands_info:
1414
1408
  #copy assistant_response to clipboard
1415
1409
  import pyperclip
1416
1410
  try:
@@ -1418,12 +1412,17 @@ def main(input_args: Optional[List[str]] = None):
1418
1412
  except:
1419
1413
  print("pyperclip not installed or clipboard is not supported, instruction will not be copied to clipboard.")
1420
1414
 
1421
- if "save" in args.action:
1422
- save_to_memory_file(ask_conversation=chat_history["ask_conversation"],
1415
+ if "save" in commands_info:
1416
+ tmp_dir = save_to_memory_file(ask_conversation=chat_history["ask_conversation"],
1423
1417
  query=args.query,
1424
1418
  response=assistant_response)
1425
1419
  printer = Printer()
1426
- printer.print_in_terminal("memory_save_success")
1420
+ printer.print_in_terminal("memory_save_success", style="green", path=tmp_dir)
1421
+
1422
+ if len(commands_info["save"]["args"]) > 0:
1423
+ # 保存到指定文件
1424
+ with open(commands_info["save"]["args"][0], "w",encoding="utf-8") as f:
1425
+ f.write(assistant_response)
1427
1426
  return
1428
1427
 
1429
1428
  else:
@@ -51,6 +51,7 @@ from autocoder.common.printer import Printer
51
51
  from autocoder.utils.thread_utils import run_in_raw_thread
52
52
  from autocoder.common.command_completer import CommandCompleter,FileSystemModel as CCFileSystemModel,MemoryConfig as CCMemoryModel
53
53
  from autocoder.common.conf_validator import ConfigValidator
54
+ from autocoder import command_parser as CommandParser
54
55
 
55
56
  class SymbolItem(BaseModel):
56
57
  symbol_name: str
@@ -1598,48 +1599,30 @@ def chat(query: str):
1598
1599
  if "emb_model" in conf:
1599
1600
  yaml_config["emb_model"] = conf["emb_model"]
1600
1601
 
1601
- is_new = "/new" in query
1602
- if is_new:
1603
- query = query.replace("/new", "", 1).strip()
1604
-
1605
- yaml_config["action"] = []
1606
-
1607
- if "/mcp " in query:
1608
- yaml_config["action"].append("mcp")
1609
- query = query.replace("/mcp ", "", 1).strip()
1610
-
1611
- if "/rag " in query:
1612
- yaml_config["action"].append("rag")
1613
- query = query.replace("/rag ", "", 1).strip()
1614
-
1615
- if "/copy" in query:
1616
- yaml_config["action"].append("copy")
1617
- query = query.replace("/copy", "", 1).strip()
1618
-
1619
- if "/save" in query:
1620
- yaml_config["action"].append("save")
1621
- query = query.replace("/save", "", 1).strip()
1622
-
1623
- if "/review" in query and "/commit" in query:
1624
- yaml_config["action"].append("review_commit")
1625
- query = query.replace("/review", "", 1).replace("/commit", "", 1).strip()
1626
- elif "/learn" in query:
1627
- yaml_config["action"].append("learn_from_commit")
1628
- query = query.replace("/learn", "", 1).strip()
1629
- else:
1630
- is_review = query.strip().startswith("/review")
1631
- if is_review:
1632
- query = query.replace("/review", "", 1).strip()
1633
- if "prompt_review" in conf:
1634
- query = format_str_jinja2(conf["prompt_review"], query=query)
1635
- else:
1636
- query = code_review.prompt(query)
1602
+ # 解析命令
1603
+ commands_infos = CommandParser.parse_query(query)
1604
+ if len(commands_infos) > 0:
1605
+ if "query" in commands_infos:
1606
+ query = commands_infos["query"]["args"][-1]
1607
+ else:
1608
+ # 获取第一个command 的最后一个位置参数作为默认query
1609
+ temp_query = ""
1610
+ for (command,command_info) in commands_infos.items():
1611
+ if command_info["args"]:
1612
+ temp_query = command_info["args"][-1]
1613
+ break
1614
+ query = temp_query
1615
+
1616
+ is_new = "new" in commands_infos
1637
1617
 
1638
- is_no_context = "/no_context" in query.strip()
1639
- if is_no_context:
1640
- query = query.replace("/no_context", "", 1).strip()
1641
- yaml_config["action"].append("no_context")
1618
+ if "learn" in commands_infos:
1619
+ commands_infos["no_context"] = {}
1642
1620
 
1621
+ if "review" in commands_infos:
1622
+ commands_infos["no_context"] = {}
1623
+
1624
+ yaml_config["action"] = commands_infos
1625
+
1643
1626
  for key, value in conf.items():
1644
1627
  converted_value = convert_config_value(key, value)
1645
1628
  if converted_value is not None:
@@ -0,0 +1,280 @@
1
+ from typing import Dict, List, Tuple, Any, Optional
2
+ import re
3
+
4
+
5
+ class CommandParser:
6
+ """
7
+ 命令解析器,用于解析命令行格式的查询字符串。
8
+ 支持以下格式:
9
+ 1. /command arg1 arg2
10
+ 2. /command key1=value1 key2=value2
11
+ 3. /command arg1 key1=value1
12
+ 4. /command1 arg1 /command2 arg2
13
+ 5. /command1 /command2 arg2
14
+ 6. /command1 /command2 key=value
15
+ 7. /command key="value with spaces"
16
+ 8. /command key='value with spaces'
17
+
18
+ 注意:路径参数(如/path/to/file)不会被识别为命令。
19
+ """
20
+
21
+ def __init__(self):
22
+ # 匹配命令的正则表达式 - 必须是以/开头,后跟单词字符,且不能后跟/或.
23
+ # (?<!\S) 确保命令前是字符串开头或空白字符
24
+ self.command_pattern = r'(?<!\S)/(\w+)(?!/|\.)'
25
+ # 匹配键值对参数的正则表达式,支持带引号的值
26
+ self.key_value_pattern = r'(\w+)=(?:"([^"]*?)"|\'([^\']*?)\'|([^\s"\']+))(?:\s|$)'
27
+ # 匹配路径模式的正则表达式
28
+ self.path_pattern = r'/\w+(?:/[^/\s]+)+'
29
+
30
+ def parse(self, query: str) -> Dict[str, Any]:
31
+ """
32
+ 解析命令行格式的查询字符串,返回命令和参数的字典。
33
+
34
+ 参数:
35
+ query: 命令行格式的查询字符串
36
+
37
+ 返回:
38
+ Dict[str, Any]: 命令和参数的字典,格式为:
39
+ {
40
+ 'command1': {
41
+ 'args': ['arg1', 'arg2'],
42
+ 'kwargs': {'key1': 'value1', 'key2': 'value with spaces'}
43
+ },
44
+ 'command2': {
45
+ 'args': [],
46
+ 'kwargs': {'key': 'value'}
47
+ }
48
+ }
49
+ """
50
+ if not query or not query.strip():
51
+ return {}
52
+
53
+ # 预处理:标记路径参数,避免被识别为命令
54
+ processed_query = query
55
+ path_matches = re.finditer(self.path_pattern, query)
56
+ placeholders = {}
57
+
58
+ for i, match in enumerate(path_matches):
59
+ path = match.group(0)
60
+ placeholder = f"__PATH_PLACEHOLDER_{i}__"
61
+ placeholders[placeholder] = path
62
+ processed_query = processed_query.replace(path, placeholder, 1)
63
+
64
+ # 找出所有命令
65
+ commands = re.findall(self.command_pattern, processed_query)
66
+ if not commands:
67
+ return {}
68
+
69
+ # 将查询字符串按命令分割
70
+ parts = re.split(self.command_pattern, processed_query)
71
+ # 第一个元素是空字符串或之前的非命令内容,保留它
72
+ first_part = parts[0]
73
+ parts = parts[1:]
74
+
75
+ result = {}
76
+
77
+ # 处理每个命令和它的参数
78
+ for i in range(0, len(parts), 2):
79
+ command = parts[i]
80
+
81
+ # 获取此命令的参数部分
82
+ params_str = parts[i+1].strip() if i+1 < len(parts) else ""
83
+
84
+ # 恢复路径参数的原始值
85
+ for placeholder, path in placeholders.items():
86
+ params_str = params_str.replace(placeholder, path)
87
+
88
+ # 解析参数
89
+ args, kwargs = self._parse_params(params_str)
90
+
91
+ result[command] = {
92
+ 'args': args,
93
+ 'kwargs': kwargs
94
+ }
95
+
96
+ return result
97
+
98
+ def _parse_params(self, params_str: str) -> Tuple[List[str], Dict[str, str]]:
99
+ """
100
+ 解析参数字符串,区分位置参数和键值对参数。
101
+ 支持带引号(双引号或单引号)的值,引号内可以包含空格。
102
+
103
+ 参数:
104
+ params_str: 参数字符串
105
+
106
+ 返回:
107
+ Tuple[List[str], Dict[str, str]]: 位置参数列表和键值对参数字典
108
+ """
109
+ args = []
110
+ kwargs = {}
111
+
112
+ if not params_str:
113
+ return args, kwargs
114
+
115
+ # 找出所有键值对
116
+ key_value_pairs = re.findall(self.key_value_pattern, params_str)
117
+
118
+ # 如果有键值对,处理它们
119
+ if key_value_pairs:
120
+ for match in key_value_pairs:
121
+ key = match[0]
122
+ # 值可能在三个捕获组中的一个,取非空的那个
123
+ value = match[1] or match[2] or match[3]
124
+ kwargs[key] = value.strip()
125
+
126
+ # 替换带引号的键值对
127
+ processed_params_str = params_str
128
+ for match in re.finditer(self.key_value_pattern, params_str):
129
+ full_match = match.group(0)
130
+ processed_params_str = processed_params_str.replace(full_match, "", 1).strip()
131
+
132
+ # 现在 processed_params_str 中应该只剩下位置参数
133
+
134
+ # 处理带引号的位置参数
135
+ quote_pattern = r'(?:"([^"]*?)"|\'([^\']*?)\')'
136
+ quoted_args = re.findall(quote_pattern, processed_params_str)
137
+ for quoted_arg in quoted_args:
138
+ # 取非空的那个捕获组
139
+ arg = quoted_arg[0] or quoted_arg[1]
140
+ args.append(arg)
141
+ # 从参数字符串中移除这个带引号的参数
142
+ quoted_pattern = f'"{arg}"' if quoted_arg[0] else f"'{arg}'"
143
+ processed_params_str = processed_params_str.replace(quoted_pattern, "", 1).strip()
144
+
145
+ # 分割剩余的位置参数(不带引号的)
146
+ remaining_args = [arg.strip() for arg in processed_params_str.split() if arg.strip()]
147
+ args.extend(remaining_args)
148
+ else:
149
+ # 如果没有键值对,处理所有参数作为位置参数
150
+
151
+ # 处理带引号的位置参数
152
+ quote_pattern = r'(?:"([^"]*?)"|\'([^\']*?)\')'
153
+ quoted_args = re.findall(quote_pattern, params_str)
154
+ processed_params_str = params_str
155
+
156
+ for quoted_arg in quoted_args:
157
+ # 取非空的那个捕获组
158
+ arg = quoted_arg[0] or quoted_arg[1]
159
+ args.append(arg)
160
+ # 从参数字符串中移除这个带引号的参数
161
+ quoted_pattern = f'"{arg}"' if quoted_arg[0] else f"'{arg}'"
162
+ processed_params_str = processed_params_str.replace(quoted_pattern, "", 1).strip()
163
+
164
+ # 分割剩余的位置参数(不带引号的)
165
+ remaining_args = [arg.strip() for arg in processed_params_str.split() if arg.strip()]
166
+ args.extend(remaining_args)
167
+
168
+ return args, kwargs
169
+
170
+ def parse_command(self, query: str, command: str) -> Optional[Dict[str, Any]]:
171
+ """
172
+ 解析特定命令的参数。
173
+
174
+ 参数:
175
+ query: 命令行格式的查询字符串
176
+ command: 要解析的命令名
177
+
178
+ 返回:
179
+ Optional[Dict[str, Any]]: 如果找到命令,返回其参数;否则返回None
180
+ """
181
+ commands = self.parse(query)
182
+ return commands.get(command)
183
+
184
+
185
+ def parse_query(query: str) -> Dict[str, Any]:
186
+ """
187
+ 解析命令行格式的查询字符串的便捷函数。
188
+
189
+ 参数:
190
+ query: 命令行格式的查询字符串
191
+
192
+ 返回:
193
+ Dict[str, Any]: 命令和参数的字典
194
+ """
195
+ parser = CommandParser()
196
+ return parser.parse(query)
197
+
198
+
199
+ def has_command(query: str, command: str) -> bool:
200
+ """
201
+ 检查查询字符串中是否包含特定命令。
202
+
203
+ 参数:
204
+ query: 命令行格式的查询字符串
205
+ command: 要检查的命令名
206
+
207
+ 返回:
208
+ bool: 如果包含命令返回True,否则返回False
209
+ """
210
+ parser = CommandParser()
211
+ commands = parser.parse(query)
212
+ return command in commands
213
+
214
+
215
+ def get_command_args(query: str, command: str) -> List[str]:
216
+ """
217
+ 获取特定命令的位置参数。
218
+
219
+ 参数:
220
+ query: 命令行格式的查询字符串
221
+ command: 要获取参数的命令名
222
+
223
+ 返回:
224
+ List[str]: 命令的位置参数列表,如果命令不存在返回空列表
225
+ """
226
+ parser = CommandParser()
227
+ command_info = parser.parse_command(query, command)
228
+ if command_info:
229
+ return command_info['args']
230
+ return []
231
+
232
+
233
+ def get_command_kwargs(query: str, command: str) -> Dict[str, str]:
234
+ """
235
+ 获取特定命令的键值对参数。
236
+
237
+ 参数:
238
+ query: 命令行格式的查询字符串
239
+ command: 要获取参数的命令名
240
+
241
+ 返回:
242
+ Dict[str, str]: 命令的键值对参数字典,如果命令不存在返回空字典
243
+ """
244
+ parser = CommandParser()
245
+ command_info = parser.parse_command(query, command)
246
+ if command_info:
247
+ return command_info['kwargs']
248
+ return {}
249
+
250
+
251
+ # 示例用法
252
+ if __name__ == "__main__":
253
+ # 测试各种格式的查询
254
+ test_queries = [
255
+ "/learn hello world /commit 123456",
256
+ "/learn /commit 123456",
257
+ "/learn /commit commit_id=123456",
258
+ "/learn msg=hello /commit commit_id=123456",
259
+ "/learn hello key=value /commit",
260
+ # 带引号的值
261
+ '/learn msg="hello world" /commit message="Fix bug #123"',
262
+ "/learn 'quoted arg' key='value with spaces' /commit",
263
+ # 路径参数测试
264
+ "/learn /path/to/file.txt",
265
+ "/commit message='Added /path/to/file.txt'",
266
+ "Check /path/to/file.txt and also /another/path/file.md",
267
+ "/clone /path/to/repo /checkout branch",
268
+ "Use the file at /usr/local/bin/python with /learn"
269
+ ]
270
+
271
+ for query in test_queries:
272
+ print(f"\nQuery: {query}")
273
+ result = parse_query(query)
274
+ print(f"Parsed: {result}")
275
+
276
+ if has_command(query, "commit"):
277
+ args = get_command_args(query, "commit")
278
+ kwargs = get_command_kwargs(query, "commit")
279
+ print(f"Commit args: {args}")
280
+ print(f"Commit kwargs: {kwargs}")