jarvis-ai-assistant 0.1.134__py3-none-any.whl → 0.1.138__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 jarvis-ai-assistant might be problematic. Click here for more details.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +201 -79
- jarvis/jarvis_agent/builtin_input_handler.py +16 -6
- jarvis/jarvis_agent/file_input_handler.py +9 -9
- jarvis/jarvis_agent/jarvis.py +10 -10
- jarvis/jarvis_agent/main.py +12 -11
- jarvis/jarvis_agent/output_handler.py +3 -3
- jarvis/jarvis_agent/patch.py +86 -62
- jarvis/jarvis_agent/shell_input_handler.py +5 -3
- jarvis/jarvis_code_agent/code_agent.py +134 -99
- jarvis/jarvis_code_agent/file_select.py +24 -24
- jarvis/jarvis_dev/main.py +45 -51
- jarvis/jarvis_git_details/__init__.py +0 -0
- jarvis/jarvis_git_details/main.py +179 -0
- jarvis/jarvis_git_squash/main.py +7 -7
- jarvis/jarvis_lsp/base.py +11 -11
- jarvis/jarvis_lsp/cpp.py +14 -14
- jarvis/jarvis_lsp/go.py +13 -13
- jarvis/jarvis_lsp/python.py +8 -8
- jarvis/jarvis_lsp/registry.py +21 -21
- jarvis/jarvis_lsp/rust.py +15 -15
- jarvis/jarvis_methodology/main.py +101 -0
- jarvis/jarvis_multi_agent/__init__.py +11 -11
- jarvis/jarvis_multi_agent/main.py +6 -6
- jarvis/jarvis_platform/__init__.py +1 -1
- jarvis/jarvis_platform/ai8.py +67 -89
- jarvis/jarvis_platform/base.py +14 -13
- jarvis/jarvis_platform/kimi.py +25 -28
- jarvis/jarvis_platform/ollama.py +24 -26
- jarvis/jarvis_platform/openai.py +15 -19
- jarvis/jarvis_platform/oyi.py +48 -50
- jarvis/jarvis_platform/registry.py +27 -28
- jarvis/jarvis_platform/yuanbao.py +38 -42
- jarvis/jarvis_platform_manager/main.py +81 -81
- jarvis/jarvis_platform_manager/openai_test.py +21 -21
- jarvis/jarvis_rag/file_processors.py +18 -18
- jarvis/jarvis_rag/main.py +261 -277
- jarvis/jarvis_smart_shell/main.py +12 -12
- jarvis/jarvis_tools/ask_codebase.py +28 -28
- jarvis/jarvis_tools/ask_user.py +8 -8
- jarvis/jarvis_tools/base.py +4 -4
- jarvis/jarvis_tools/chdir.py +9 -9
- jarvis/jarvis_tools/code_review.py +19 -19
- jarvis/jarvis_tools/create_code_agent.py +15 -15
- jarvis/jarvis_tools/execute_python_script.py +3 -3
- jarvis/jarvis_tools/execute_shell.py +11 -11
- jarvis/jarvis_tools/execute_shell_script.py +3 -3
- jarvis/jarvis_tools/file_analyzer.py +29 -29
- jarvis/jarvis_tools/file_operation.py +22 -20
- jarvis/jarvis_tools/find_caller.py +25 -25
- jarvis/jarvis_tools/find_methodolopy.py +65 -0
- jarvis/jarvis_tools/find_symbol.py +24 -24
- jarvis/jarvis_tools/function_analyzer.py +27 -27
- jarvis/jarvis_tools/git_commiter.py +9 -9
- jarvis/jarvis_tools/lsp_get_diagnostics.py +19 -19
- jarvis/jarvis_tools/methodology.py +23 -62
- jarvis/jarvis_tools/project_analyzer.py +29 -33
- jarvis/jarvis_tools/rag.py +15 -15
- jarvis/jarvis_tools/read_code.py +24 -22
- jarvis/jarvis_tools/read_webpage.py +31 -31
- jarvis/jarvis_tools/registry.py +72 -52
- jarvis/jarvis_tools/tool_generator.py +18 -18
- jarvis/jarvis_utils/config.py +23 -23
- jarvis/jarvis_utils/embedding.py +83 -83
- jarvis/jarvis_utils/git_utils.py +20 -20
- jarvis/jarvis_utils/globals.py +18 -6
- jarvis/jarvis_utils/input.py +10 -9
- jarvis/jarvis_utils/methodology.py +140 -136
- jarvis/jarvis_utils/output.py +11 -11
- jarvis/jarvis_utils/utils.py +22 -70
- {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/METADATA +1 -1
- jarvis_ai_assistant-0.1.138.dist-info/RECORD +85 -0
- {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/entry_points.txt +2 -0
- jarvis/jarvis_tools/select_code_files.py +0 -62
- jarvis_ai_assistant-0.1.134.dist-info/RECORD +0 -82
- {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
"""Jarvis代码代理模块。
|
|
2
|
+
|
|
3
|
+
该模块提供CodeAgent类,用于处理代码修改任务。
|
|
4
|
+
"""
|
|
5
|
+
|
|
2
6
|
import os
|
|
7
|
+
import sys
|
|
8
|
+
import subprocess
|
|
3
9
|
import argparse
|
|
4
|
-
from token import OP
|
|
5
10
|
from typing import Optional
|
|
6
11
|
|
|
7
12
|
from yaspin import yaspin
|
|
@@ -13,41 +18,51 @@ from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
|
|
|
13
18
|
from jarvis.jarvis_agent.patch import PatchOutputHandler
|
|
14
19
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
15
20
|
from jarvis.jarvis_tools.git_commiter import GitCommitTool
|
|
16
|
-
|
|
17
21
|
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
18
|
-
from jarvis.jarvis_utils.git_utils import
|
|
22
|
+
from jarvis.jarvis_utils.git_utils import (
|
|
23
|
+
find_git_root,
|
|
24
|
+
get_commits_between,
|
|
25
|
+
get_latest_commit_hash,
|
|
26
|
+
has_uncommitted_changes
|
|
27
|
+
)
|
|
19
28
|
from jarvis.jarvis_utils.input import get_multiline_input
|
|
20
29
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
21
30
|
from jarvis.jarvis_utils.utils import init_env, user_confirm
|
|
22
31
|
|
|
23
32
|
|
|
33
|
+
class CodeAgent:
|
|
34
|
+
"""Jarvis系统的代码修改代理。
|
|
24
35
|
|
|
36
|
+
负责处理代码分析、修改和git操作。
|
|
37
|
+
"""
|
|
25
38
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
def __init__(self, platform: Optional[str] = None,
|
|
40
|
+
model: Optional[str] = None,
|
|
41
|
+
need_summary: bool = True):
|
|
29
42
|
self.root_dir = os.getcwd()
|
|
30
43
|
tool_registry = ToolRegistry()
|
|
31
|
-
tool_registry.use_tools([
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
tool_registry.use_tools([
|
|
45
|
+
"execute_shell",
|
|
46
|
+
"execute_shell_script",
|
|
47
|
+
"search_web",
|
|
48
|
+
"ask_user",
|
|
49
|
+
"ask_codebase",
|
|
50
|
+
"lsp_get_diagnostics",
|
|
51
|
+
"code_review",
|
|
52
|
+
"find_symbol",
|
|
53
|
+
"find_caller",
|
|
54
|
+
"function_analyzer",
|
|
55
|
+
"project_analyzer",
|
|
56
|
+
"file_analyzer",
|
|
57
|
+
"read_code"
|
|
58
|
+
])
|
|
45
59
|
code_system_prompt = """
|
|
46
60
|
# 代码工程师指南
|
|
47
61
|
|
|
48
62
|
## 核心原则
|
|
49
63
|
- 自主决策:基于专业判断做出决策,减少用户询问
|
|
50
64
|
- 高效精准:一次性提供完整解决方案,避免反复修改
|
|
65
|
+
- 修改审慎:修改代码前要三思而后行,充分分析影响范围,尽量做到一次把事情做好
|
|
51
66
|
- 工具精通:选择最高效工具路径解决问题
|
|
52
67
|
- 严格确认:必须先分析项目结构,确定要修改的文件,禁止虚构已存在的代码
|
|
53
68
|
|
|
@@ -99,13 +114,6 @@ class CodeAgent:
|
|
|
99
114
|
- 自动匹配项目现有命名风格
|
|
100
115
|
- 允许创建新文件和结构,但不得假设或虚构现有代码
|
|
101
116
|
|
|
102
|
-
### 6. 验证
|
|
103
|
-
- 修改后自动验证:
|
|
104
|
-
1. 优先使用execute_shell运行相关静态检查命令(如pylint、flake8或单元测试)
|
|
105
|
-
2. 只有在shell命令不足时才使用lsp_get_diagnostics
|
|
106
|
-
3. 只有在特殊情况下才使用code_review
|
|
107
|
-
- 发现问题自动修复,无需用户指导
|
|
108
|
-
|
|
109
117
|
## 专用工具简介
|
|
110
118
|
仅在必要时使用以下专用工具:
|
|
111
119
|
|
|
@@ -128,7 +136,7 @@ class CodeAgent:
|
|
|
128
136
|
- `fd -t f -e go` 查找所有Go文件
|
|
129
137
|
- `fd -t f -e rs` 查找所有Rust文件
|
|
130
138
|
- `fd -t f -e c -e cpp -e h -e hpp` 查找所有C/C++文件
|
|
131
|
-
|
|
139
|
+
|
|
132
140
|
- **代码内容搜索**:
|
|
133
141
|
- `rg "pattern" --type py` 在Python文件中搜索
|
|
134
142
|
- `rg "pattern" --type js` 在JavaScript文件中搜索
|
|
@@ -139,11 +147,7 @@ class CodeAgent:
|
|
|
139
147
|
- `rg -w "word"` 精确匹配单词
|
|
140
148
|
|
|
141
149
|
- **代码统计分析**:
|
|
142
|
-
- `loc
|
|
143
|
-
- `loc --include="*.py"` 统计所有Python文件
|
|
144
|
-
- `loc --include="*.js" --include="*.ts"` 统计所有JavaScript/TypeScript文件
|
|
145
|
-
- `loc --exclude="test"` 排除测试文件
|
|
146
|
-
- `loc --sort=code` 按代码量排序
|
|
150
|
+
- `loc` 统计当前目录代码行数
|
|
147
151
|
|
|
148
152
|
- **代码质量检查**:
|
|
149
153
|
- Python: `pylint <file_path>`, `flake8 <file_path>`
|
|
@@ -174,27 +178,37 @@ class CodeAgent:
|
|
|
174
178
|
- fd比find更快更易用,应优先使用
|
|
175
179
|
- loc比wc -l提供更多代码统计信息,应优先使用
|
|
176
180
|
- 针对不同编程语言选择对应的代码质量检查工具
|
|
181
|
+
- 不要留下未实现的代码
|
|
177
182
|
"""
|
|
178
183
|
# Dynamically add ask_codebase based on task complexity if really needed
|
|
179
184
|
# 处理platform参数
|
|
180
|
-
platform_instance = (PlatformRegistry().create_platform(platform)
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
platform_instance = (PlatformRegistry().create_platform(platform)
|
|
186
|
+
if platform
|
|
187
|
+
else PlatformRegistry().get_normal_platform())
|
|
183
188
|
if model:
|
|
184
|
-
platform_instance.set_model_name(model)
|
|
185
|
-
|
|
189
|
+
platform_instance.set_model_name(model) # type: ignore
|
|
190
|
+
|
|
186
191
|
self.agent = Agent(system_prompt=code_system_prompt,
|
|
187
192
|
name="CodeAgent",
|
|
188
|
-
auto_complete=False,
|
|
189
|
-
output_handler=[tool_registry,
|
|
193
|
+
auto_complete=False,
|
|
194
|
+
output_handler=[tool_registry,
|
|
195
|
+
PatchOutputHandler()],
|
|
190
196
|
platform=platform_instance,
|
|
191
|
-
input_handler=[
|
|
197
|
+
input_handler=[
|
|
198
|
+
shell_input_handler, file_input_handler, builtin_input_handler],
|
|
192
199
|
need_summary=need_summary)
|
|
200
|
+
self.agent.set_addon_prompt("请使用工具充分理解用户需求,然后根据需求一步步执行代码修改/开发")
|
|
201
|
+
|
|
202
|
+
def get_root_dir(self) -> str:
|
|
203
|
+
"""获取项目根目录
|
|
193
204
|
|
|
194
|
-
|
|
205
|
+
返回:
|
|
206
|
+
str: 项目根目录路径
|
|
207
|
+
"""
|
|
208
|
+
return self.root_dir
|
|
195
209
|
|
|
196
210
|
def _init_env(self):
|
|
197
|
-
with yaspin(text="正在初始化环境...", color="cyan") as spinner:
|
|
211
|
+
with yaspin(text="正在初始化环境...", color="cyan") as spinner:
|
|
198
212
|
curr_dir = os.getcwd()
|
|
199
213
|
git_dir = find_git_root(curr_dir)
|
|
200
214
|
self.root_dir = git_dir
|
|
@@ -205,62 +219,86 @@ class CodeAgent:
|
|
|
205
219
|
spinner.text = "环境初始化完成"
|
|
206
220
|
spinner.ok("✅")
|
|
207
221
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
222
|
+
def _handle_uncommitted_changes(self):
|
|
223
|
+
"""处理未提交的修改"""
|
|
224
|
+
if has_uncommitted_changes():
|
|
225
|
+
PrettyOutput.print("检测到未提交的修改,是否要提交?", OutputType.WARNING)
|
|
226
|
+
if user_confirm("是否要提交?", True):
|
|
227
|
+
git_commiter = GitCommitTool()
|
|
228
|
+
git_commiter.execute({})
|
|
229
|
+
|
|
230
|
+
def _show_commit_history(self, start_commit, end_commit):
|
|
231
|
+
"""显示提交历史"""
|
|
232
|
+
if start_commit and end_commit:
|
|
233
|
+
commits = get_commits_between(start_commit, end_commit)
|
|
234
|
+
else:
|
|
235
|
+
commits = []
|
|
236
|
+
|
|
237
|
+
if commits:
|
|
238
|
+
commit_messages = "检测到以下提交记录:\n" + \
|
|
239
|
+
"\n".join(
|
|
240
|
+
[f"- {commit_hash[:7]}: {message}" for commit_hash, message in commits])
|
|
241
|
+
PrettyOutput.print(commit_messages, OutputType.INFO)
|
|
242
|
+
return commits
|
|
243
|
+
|
|
244
|
+
def _handle_commit_confirmation(self, commits, start_commit):
|
|
245
|
+
"""处理提交确认和可能的重置"""
|
|
246
|
+
if commits and user_confirm("是否接受以上提交记录?", True):
|
|
247
|
+
if len(commits) > 1 and user_confirm(
|
|
248
|
+
"是否要合并为一个更清晰的提交记录?", True
|
|
249
|
+
):
|
|
250
|
+
# Reset to start commit
|
|
251
|
+
subprocess.run(
|
|
252
|
+
["git", "reset", "--mixed", start_commit],
|
|
253
|
+
stdout=subprocess.DEVNULL,
|
|
254
|
+
stderr=subprocess.DEVNULL,
|
|
255
|
+
check=True
|
|
256
|
+
)
|
|
257
|
+
# Create new commit
|
|
258
|
+
git_commiter = GitCommitTool()
|
|
259
|
+
git_commiter.execute({})
|
|
260
|
+
elif start_commit:
|
|
261
|
+
os.system(f"git reset --hard {start_commit}")
|
|
262
|
+
PrettyOutput.print("已重置到初始提交", OutputType.INFO)
|
|
263
|
+
|
|
264
|
+
def run(self, user_input: str) -> Optional[str]:
|
|
265
|
+
"""使用给定的用户输入运行代码代理。
|
|
266
|
+
|
|
267
|
+
参数:
|
|
268
|
+
user_input: 用户的需求/请求
|
|
269
|
+
|
|
270
|
+
返回:
|
|
271
|
+
str: 描述执行结果的输出,成功时返回None
|
|
218
272
|
"""
|
|
219
273
|
try:
|
|
220
274
|
self._init_env()
|
|
221
|
-
|
|
222
275
|
start_commit = get_latest_commit_hash()
|
|
223
|
-
|
|
276
|
+
|
|
224
277
|
try:
|
|
225
278
|
self.agent.run(user_input)
|
|
226
|
-
except
|
|
279
|
+
except RuntimeError as e:
|
|
227
280
|
PrettyOutput.print(f"执行失败: {str(e)}", OutputType.WARNING)
|
|
228
|
-
|
|
281
|
+
return str(e)
|
|
282
|
+
|
|
283
|
+
self._handle_uncommitted_changes()
|
|
229
284
|
end_commit = get_latest_commit_hash()
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if commits:
|
|
237
|
-
commit_messages = "检测到以下提交记录:\n" + "\n".join([f"- {commit_hash[:7]}: {message}" for commit_hash, message in commits])
|
|
238
|
-
PrettyOutput.print(commit_messages, OutputType.INFO)
|
|
239
|
-
|
|
240
|
-
if commits and user_confirm("是否接受以上提交记录?", True):
|
|
241
|
-
if len(commits) > 1 and user_confirm("是否要合并为一个更清晰的提交记录?", True):
|
|
242
|
-
# Reset to start commit
|
|
243
|
-
subprocess.run(["git", "reset", "--mixed", start_commit], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
244
|
-
# Create new commit
|
|
245
|
-
git_commiter = GitCommitTool()
|
|
246
|
-
git_commiter.execute({})
|
|
247
|
-
elif start_commit:
|
|
248
|
-
os.system(f"git reset --hard {start_commit}")
|
|
249
|
-
PrettyOutput.print("已重置到初始提交", OutputType.INFO)
|
|
250
|
-
|
|
251
|
-
except Exception as e:
|
|
285
|
+
commits = self._show_commit_history(start_commit, end_commit)
|
|
286
|
+
self._handle_commit_confirmation(commits, start_commit)
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
except RuntimeError as e:
|
|
252
290
|
return f"Error during execution: {str(e)}"
|
|
253
|
-
|
|
254
291
|
|
|
255
292
|
|
|
256
293
|
def main():
|
|
257
|
-
"""Jarvis
|
|
258
|
-
# Add argument parser
|
|
294
|
+
"""Jarvis主入口点。"""
|
|
259
295
|
init_env()
|
|
260
296
|
|
|
261
297
|
parser = argparse.ArgumentParser(description='Jarvis Code Agent')
|
|
262
|
-
parser.add_argument('-p', '--platform', type=str,
|
|
263
|
-
|
|
298
|
+
parser.add_argument('-p', '--platform', type=str,
|
|
299
|
+
help='Target platform name', default=None)
|
|
300
|
+
parser.add_argument('-m', '--model', type=str,
|
|
301
|
+
help='Model name to use', default=None)
|
|
264
302
|
args = parser.parse_args()
|
|
265
303
|
|
|
266
304
|
curr_dir = os.getcwd()
|
|
@@ -268,21 +306,18 @@ def main():
|
|
|
268
306
|
PrettyOutput.print(f"当前目录: {git_dir}", OutputType.INFO)
|
|
269
307
|
|
|
270
308
|
try:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
except Exception as e:
|
|
279
|
-
PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
|
|
309
|
+
user_input = get_multiline_input("请输入你的需求(输入空行退出):")
|
|
310
|
+
if not user_input:
|
|
311
|
+
sys.exit(0)
|
|
312
|
+
agent = CodeAgent(platform=args.platform,
|
|
313
|
+
model=args.model,
|
|
314
|
+
need_summary=False)
|
|
315
|
+
agent.run(user_input)
|
|
280
316
|
|
|
281
|
-
except
|
|
282
|
-
PrettyOutput.print(f"
|
|
283
|
-
|
|
317
|
+
except RuntimeError as e:
|
|
318
|
+
PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
|
|
319
|
+
sys.exit(1)
|
|
284
320
|
|
|
285
|
-
return 0
|
|
286
321
|
|
|
287
322
|
if __name__ == "__main__":
|
|
288
|
-
|
|
323
|
+
main()
|
|
@@ -11,15 +11,15 @@ from jarvis.jarvis_utils.utils import user_confirm
|
|
|
11
11
|
|
|
12
12
|
def _parse_file_selection(input_str: str, max_index: int) -> List[int]:
|
|
13
13
|
selected = set()
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
# Remove all whitespace characters
|
|
16
16
|
input_str = "".join(input_str.split())
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
# Process comma-separated parts
|
|
19
19
|
for part in input_str.split(","):
|
|
20
20
|
if not part:
|
|
21
21
|
continue
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
# Process range (e.g.: 3-6)
|
|
24
24
|
if "-" in part:
|
|
25
25
|
try:
|
|
@@ -41,7 +41,7 @@ def _parse_file_selection(input_str: str, max_index: int) -> List[int]:
|
|
|
41
41
|
PrettyOutput.print(f"忽略超出范围的索引: {part}", OutputType.WARNING)
|
|
42
42
|
except ValueError:
|
|
43
43
|
PrettyOutput.print(f"忽略无效的数字: {part}", OutputType.WARNING)
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
return sorted(list(selected))
|
|
46
46
|
|
|
47
47
|
def _get_file_completer(root_dir: str) -> Completer:
|
|
@@ -49,58 +49,58 @@ def _get_file_completer(root_dir: str) -> Completer:
|
|
|
49
49
|
class FileCompleter(Completer):
|
|
50
50
|
def __init__(self, root_dir: str):
|
|
51
51
|
self.root_dir = root_dir
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
def get_completions(self, document, complete_event):
|
|
54
54
|
text = document.text_before_cursor
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
if not text:
|
|
57
57
|
for path in self._list_files(""):
|
|
58
58
|
yield Completion(path, start_position=0)
|
|
59
59
|
return
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
# Generate fuzzy matching pattern
|
|
62
62
|
pattern = '.*'.join(map(re.escape, text))
|
|
63
63
|
try:
|
|
64
64
|
regex = re.compile(pattern, re.IGNORECASE)
|
|
65
65
|
except re.error:
|
|
66
66
|
return
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
for path in self._list_files(""):
|
|
69
69
|
if regex.search(path):
|
|
70
70
|
yield Completion(path, start_position=-len(text))
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
def _list_files(self, current_dir: str) -> List[str]:
|
|
73
73
|
"""List all files in the specified directory (recursively)"""
|
|
74
74
|
files = []
|
|
75
75
|
search_dir = os.path.join(self.root_dir, current_dir)
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
for root, _, filenames in os.walk(search_dir):
|
|
78
78
|
for filename in filenames:
|
|
79
79
|
full_path = os.path.join(root, filename)
|
|
80
80
|
rel_path = os.path.relpath(full_path, self.root_dir)
|
|
81
81
|
if not any(part.startswith('.') for part in rel_path.split(os.sep)):
|
|
82
82
|
files.append(rel_path)
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
return sorted(files)
|
|
85
85
|
|
|
86
86
|
return FileCompleter(root_dir)
|
|
87
87
|
|
|
88
88
|
def _fuzzy_match_files(root_dir: str, pattern: str) -> List[str]:
|
|
89
89
|
"""Fuzzy match file path
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
Args:
|
|
92
92
|
pattern: Matching pattern
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
Returns:
|
|
95
95
|
List[str]: List of matching file paths
|
|
96
96
|
"""
|
|
97
97
|
matches = []
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
# 将模式转换为正则表达式
|
|
100
100
|
pattern = pattern.replace('.', r'\.').replace('*', '.*').replace('?', '.')
|
|
101
101
|
pattern = f".*{pattern}.*" # 允许部分匹配
|
|
102
102
|
regex = re.compile(pattern, re.IGNORECASE)
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
# 遍历所有文件
|
|
105
105
|
for root, _, files in os.walk(root_dir):
|
|
106
106
|
for file in files:
|
|
@@ -110,7 +110,7 @@ def _fuzzy_match_files(root_dir: str, pattern: str) -> List[str]:
|
|
|
110
110
|
if not any(part.startswith('.') for part in rel_path.split(os.sep)):
|
|
111
111
|
if regex.match(rel_path):
|
|
112
112
|
matches.append(rel_path)
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
return sorted(matches)
|
|
115
115
|
|
|
116
116
|
def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dict[str, str]]:
|
|
@@ -127,7 +127,7 @@ def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dic
|
|
|
127
127
|
if output:
|
|
128
128
|
PrettyOutput.section("相关文件", OutputType.INFO)
|
|
129
129
|
PrettyOutput.print(output, OutputType.INFO, lang="markdown")
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
if len(related_files) > 0:
|
|
132
132
|
# Ask the user if they need to adjust the file list
|
|
133
133
|
if user_confirm("是否需要调整文件列表?", False):
|
|
@@ -139,7 +139,7 @@ def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dic
|
|
|
139
139
|
selected_files = [related_files[i] for i in selected_indices]
|
|
140
140
|
else:
|
|
141
141
|
PrettyOutput.print("没有有效的文件被选择, 保持当前选择", OutputType.WARNING)
|
|
142
|
-
|
|
142
|
+
|
|
143
143
|
tips = ""
|
|
144
144
|
# Ask if they need to supplement files
|
|
145
145
|
if user_confirm("是否需要补充其他文件?", False):
|
|
@@ -154,23 +154,23 @@ def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dic
|
|
|
154
154
|
file_path = session.prompt(">>> ").strip()
|
|
155
155
|
except KeyboardInterrupt:
|
|
156
156
|
break
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
if not file_path:
|
|
159
159
|
break
|
|
160
|
-
|
|
160
|
+
|
|
161
161
|
# Process wildcard matching
|
|
162
162
|
if '*' in file_path or '?' in file_path:
|
|
163
163
|
matches = _fuzzy_match_files(root_dir, file_path)
|
|
164
164
|
if not matches:
|
|
165
165
|
PrettyOutput.print("没有找到匹配的文件", OutputType.WARNING)
|
|
166
166
|
continue
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
# Display matching files
|
|
169
169
|
tips = "找到以下匹配的文件:"
|
|
170
170
|
for i, path in enumerate(matches, 1):
|
|
171
171
|
tips += f"\n[{i}] {path}"
|
|
172
172
|
PrettyOutput.print(tips, OutputType.INFO)
|
|
173
|
-
|
|
173
|
+
|
|
174
174
|
# Let the user select
|
|
175
175
|
numbers = get_single_line_input("请选择要添加的文件编号(支持: 1,3-6格式, 按回车选择所有)").strip()
|
|
176
176
|
if numbers:
|
|
@@ -182,7 +182,7 @@ def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dic
|
|
|
182
182
|
paths_to_add = matches
|
|
183
183
|
else:
|
|
184
184
|
paths_to_add = [file_path]
|
|
185
|
-
|
|
185
|
+
|
|
186
186
|
# Add selected files
|
|
187
187
|
tips = "添加以下文件:"
|
|
188
188
|
for path in paths_to_add:
|
|
@@ -190,7 +190,7 @@ def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dic
|
|
|
190
190
|
if not os.path.isfile(full_path):
|
|
191
191
|
tips += f"\n文件不存在: {path}"
|
|
192
192
|
continue
|
|
193
|
-
|
|
193
|
+
|
|
194
194
|
try:
|
|
195
195
|
selected_files.append({"file": path, "reason": "I Added"})
|
|
196
196
|
tips += f"\n文件已添加: {path}"
|