jarvis-ai-assistant 0.1.222__py3-none-any.whl → 0.7.0__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +1143 -245
- jarvis/jarvis_agent/agent_manager.py +97 -0
- jarvis/jarvis_agent/builtin_input_handler.py +12 -10
- jarvis/jarvis_agent/config_editor.py +57 -0
- jarvis/jarvis_agent/edit_file_handler.py +392 -99
- jarvis/jarvis_agent/event_bus.py +48 -0
- jarvis/jarvis_agent/events.py +157 -0
- jarvis/jarvis_agent/file_context_handler.py +79 -0
- jarvis/jarvis_agent/file_methodology_manager.py +117 -0
- jarvis/jarvis_agent/jarvis.py +1117 -147
- jarvis/jarvis_agent/main.py +78 -34
- jarvis/jarvis_agent/memory_manager.py +195 -0
- jarvis/jarvis_agent/methodology_share_manager.py +174 -0
- jarvis/jarvis_agent/prompt_manager.py +82 -0
- jarvis/jarvis_agent/prompts.py +46 -9
- jarvis/jarvis_agent/protocols.py +4 -1
- jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
- jarvis/jarvis_agent/run_loop.py +146 -0
- jarvis/jarvis_agent/session_manager.py +9 -9
- jarvis/jarvis_agent/share_manager.py +228 -0
- jarvis/jarvis_agent/shell_input_handler.py +23 -3
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +212 -0
- jarvis/jarvis_agent/task_manager.py +154 -0
- jarvis/jarvis_agent/task_planner.py +496 -0
- jarvis/jarvis_agent/tool_executor.py +8 -4
- jarvis/jarvis_agent/tool_share_manager.py +139 -0
- jarvis/jarvis_agent/user_interaction.py +42 -0
- jarvis/jarvis_agent/utils.py +54 -0
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +751 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +613 -0
- jarvis/jarvis_c2rust/collector.py +258 -0
- jarvis/jarvis_c2rust/library_replacer.py +1122 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
- jarvis/jarvis_c2rust/optimizer.py +960 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2325 -0
- jarvis/jarvis_code_agent/build_validation_config.py +133 -0
- jarvis/jarvis_code_agent/code_agent.py +1605 -178
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
- jarvis/jarvis_code_agent/lint.py +275 -13
- jarvis/jarvis_code_agent/utils.py +142 -0
- jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
- jarvis/jarvis_code_analysis/code_review.py +583 -548
- jarvis/jarvis_data/config_schema.json +339 -28
- jarvis/jarvis_git_squash/main.py +22 -13
- jarvis/jarvis_git_utils/git_commiter.py +171 -55
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
- jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
- jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
- jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
- jarvis/jarvis_methodology/main.py +48 -63
- jarvis/jarvis_multi_agent/__init__.py +302 -43
- jarvis/jarvis_multi_agent/main.py +70 -24
- jarvis/jarvis_platform/ai8.py +40 -23
- jarvis/jarvis_platform/base.py +210 -49
- jarvis/jarvis_platform/human.py +11 -1
- jarvis/jarvis_platform/kimi.py +82 -76
- jarvis/jarvis_platform/openai.py +73 -1
- jarvis/jarvis_platform/registry.py +8 -15
- jarvis/jarvis_platform/tongyi.py +115 -101
- jarvis/jarvis_platform/yuanbao.py +89 -63
- jarvis/jarvis_platform_manager/main.py +194 -132
- jarvis/jarvis_platform_manager/service.py +122 -86
- jarvis/jarvis_rag/cli.py +156 -53
- jarvis/jarvis_rag/embedding_manager.py +155 -12
- jarvis/jarvis_rag/llm_interface.py +10 -13
- jarvis/jarvis_rag/query_rewriter.py +63 -12
- jarvis/jarvis_rag/rag_pipeline.py +222 -40
- jarvis/jarvis_rag/reranker.py +26 -3
- jarvis/jarvis_rag/retriever.py +270 -14
- jarvis/jarvis_sec/__init__.py +3605 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +116 -0
- jarvis/jarvis_sec/report.py +257 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/workflow.py +219 -0
- jarvis/jarvis_smart_shell/main.py +405 -137
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +387 -0
- jarvis/jarvis_stats/stats.py +711 -0
- jarvis/jarvis_stats/storage.py +612 -0
- jarvis/jarvis_stats/visualizer.py +282 -0
- jarvis/jarvis_tools/ask_user.py +1 -0
- jarvis/jarvis_tools/base.py +18 -2
- jarvis/jarvis_tools/clear_memory.py +239 -0
- jarvis/jarvis_tools/cli/main.py +220 -144
- jarvis/jarvis_tools/execute_script.py +52 -12
- jarvis/jarvis_tools/file_analyzer.py +17 -12
- jarvis/jarvis_tools/generate_new_tool.py +46 -24
- jarvis/jarvis_tools/read_code.py +277 -18
- jarvis/jarvis_tools/read_symbols.py +141 -0
- jarvis/jarvis_tools/read_webpage.py +86 -13
- jarvis/jarvis_tools/registry.py +294 -90
- jarvis/jarvis_tools/retrieve_memory.py +227 -0
- jarvis/jarvis_tools/save_memory.py +194 -0
- jarvis/jarvis_tools/search_web.py +62 -28
- jarvis/jarvis_tools/sub_agent.py +205 -0
- jarvis/jarvis_tools/sub_code_agent.py +217 -0
- jarvis/jarvis_tools/virtual_tty.py +330 -62
- jarvis/jarvis_utils/builtin_replace_map.py +4 -5
- jarvis/jarvis_utils/clipboard.py +90 -0
- jarvis/jarvis_utils/config.py +607 -50
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/fzf.py +57 -0
- jarvis/jarvis_utils/git_utils.py +251 -29
- jarvis/jarvis_utils/globals.py +174 -17
- jarvis/jarvis_utils/http.py +58 -79
- jarvis/jarvis_utils/input.py +899 -153
- jarvis/jarvis_utils/methodology.py +210 -83
- jarvis/jarvis_utils/output.py +220 -137
- jarvis/jarvis_utils/utils.py +1906 -135
- jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
- jarvis/jarvis_git_details/main.py +0 -265
- jarvis/jarvis_platform/oyi.py +0 -357
- jarvis/jarvis_tools/edit_file.py +0 -255
- jarvis/jarvis_tools/rewrite_file.py +0 -195
- jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
- jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
- /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
jarvis/jarvis_utils/embedding.py
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""FZF selector utility."""
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
from typing import List, Optional, Union, Dict, Any, cast
|
|
6
|
+
|
|
7
|
+
def fzf_select(
|
|
8
|
+
options: Union[List[str], List[Dict[str, Any]]],
|
|
9
|
+
prompt: str = "SELECT> ",
|
|
10
|
+
key: Optional[str] = None,
|
|
11
|
+
) -> Optional[str]:
|
|
12
|
+
"""
|
|
13
|
+
Uses fzf to select an item from a list.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
options: A list of strings or dicts to choose from.
|
|
17
|
+
prompt: The prompt to display in fzf.
|
|
18
|
+
key: If options is a list of dicts, this is the key to display.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
The selected item, or None if fzf is not available or the selection is cancelled.
|
|
22
|
+
"""
|
|
23
|
+
if shutil.which("fzf") is None:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
if not options:
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
if isinstance(options[0], dict):
|
|
30
|
+
if key is None:
|
|
31
|
+
raise ValueError("A key must be provided for a list of dicts.")
|
|
32
|
+
options_dict = cast(List[Dict[str, Any]], options)
|
|
33
|
+
input_lines = [str(item.get(key, "")) for item in options_dict]
|
|
34
|
+
else:
|
|
35
|
+
input_lines = [str(item) for item in options]
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
process = subprocess.run(
|
|
39
|
+
[
|
|
40
|
+
"fzf",
|
|
41
|
+
"--prompt",
|
|
42
|
+
prompt,
|
|
43
|
+
"--height",
|
|
44
|
+
"40%",
|
|
45
|
+
"--border",
|
|
46
|
+
"--layout=reverse",
|
|
47
|
+
],
|
|
48
|
+
input="\n".join(input_lines),
|
|
49
|
+
stdout=subprocess.PIPE,
|
|
50
|
+
stderr=subprocess.PIPE,
|
|
51
|
+
text=True,
|
|
52
|
+
check=True,
|
|
53
|
+
)
|
|
54
|
+
selected = process.stdout.strip()
|
|
55
|
+
return selected if selected else None
|
|
56
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
57
|
+
return None
|
jarvis/jarvis_utils/git_utils.py
CHANGED
|
@@ -14,11 +14,12 @@ import os
|
|
|
14
14
|
import re
|
|
15
15
|
import subprocess
|
|
16
16
|
import sys
|
|
17
|
-
from typing import Any, Dict, List, Set, Tuple
|
|
17
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
18
18
|
|
|
19
|
-
from jarvis.jarvis_utils.config import is_confirm_before_apply_patch
|
|
19
|
+
from jarvis.jarvis_utils.config import get_data_dir, is_confirm_before_apply_patch
|
|
20
20
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
21
21
|
from jarvis.jarvis_utils.input import user_confirm
|
|
22
|
+
from jarvis.jarvis_utils.utils import is_rag_installed
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def find_git_root_and_cd(start_dir: str = ".") -> str:
|
|
@@ -33,7 +34,13 @@ def find_git_root_and_cd(start_dir: str = ".") -> str:
|
|
|
33
34
|
"""
|
|
34
35
|
os.chdir(start_dir)
|
|
35
36
|
try:
|
|
36
|
-
|
|
37
|
+
result = subprocess.run(
|
|
38
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
39
|
+
capture_output=True,
|
|
40
|
+
text=True,
|
|
41
|
+
check=True,
|
|
42
|
+
)
|
|
43
|
+
git_root = result.stdout.strip()
|
|
37
44
|
if not git_root:
|
|
38
45
|
subprocess.run(["git", "init"], check=True)
|
|
39
46
|
git_root = os.path.abspath(".")
|
|
@@ -208,6 +215,126 @@ def revert_change() -> None:
|
|
|
208
215
|
PrettyOutput.print(f"恢复更改失败: {str(e)}", OutputType.ERROR)
|
|
209
216
|
|
|
210
217
|
|
|
218
|
+
def detect_large_code_deletion(threshold: int = 200) -> Optional[Dict[str, int]]:
|
|
219
|
+
"""检测是否有大量代码删除
|
|
220
|
+
|
|
221
|
+
参数:
|
|
222
|
+
threshold: 净删除行数阈值,默认200行
|
|
223
|
+
|
|
224
|
+
返回:
|
|
225
|
+
Optional[Dict[str, int]]: 如果检测到大量删除,返回包含统计信息的字典:
|
|
226
|
+
{
|
|
227
|
+
'insertions': int, # 新增行数
|
|
228
|
+
'deletions': int, # 删除行数
|
|
229
|
+
'net_deletions': int # 净删除行数
|
|
230
|
+
}
|
|
231
|
+
如果没有大量删除或发生错误,返回None
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
# 临时暂存所有文件以便获取完整的diff统计
|
|
235
|
+
subprocess.run(["git", "add", "-N", "."], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
236
|
+
|
|
237
|
+
# 检查是否有HEAD
|
|
238
|
+
head_check = subprocess.run(
|
|
239
|
+
["git", "rev-parse", "--verify", "HEAD"],
|
|
240
|
+
stderr=subprocess.DEVNULL,
|
|
241
|
+
stdout=subprocess.DEVNULL,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if head_check.returncode == 0:
|
|
245
|
+
# 有HEAD,获取相对于HEAD的diff统计
|
|
246
|
+
diff_result = subprocess.run(
|
|
247
|
+
["git", "diff", "HEAD", "--shortstat"],
|
|
248
|
+
capture_output=True,
|
|
249
|
+
text=True,
|
|
250
|
+
encoding="utf-8",
|
|
251
|
+
errors="replace",
|
|
252
|
+
check=False,
|
|
253
|
+
)
|
|
254
|
+
else:
|
|
255
|
+
# 空仓库,获取工作区diff统计
|
|
256
|
+
diff_result = subprocess.run(
|
|
257
|
+
["git", "diff", "--shortstat"],
|
|
258
|
+
capture_output=True,
|
|
259
|
+
text=True,
|
|
260
|
+
encoding="utf-8",
|
|
261
|
+
errors="replace",
|
|
262
|
+
check=False,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# 重置暂存区
|
|
266
|
+
subprocess.run(["git", "reset"], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
267
|
+
|
|
268
|
+
# 解析插入和删除行数
|
|
269
|
+
if diff_result.returncode == 0 and diff_result.stdout:
|
|
270
|
+
insertions = 0
|
|
271
|
+
deletions = 0
|
|
272
|
+
insertions_match = re.search(r"(\d+)\s+insertions?\(\+\)", diff_result.stdout)
|
|
273
|
+
deletions_match = re.search(r"(\d+)\s+deletions?\(\-\)", diff_result.stdout)
|
|
274
|
+
if insertions_match:
|
|
275
|
+
insertions = int(insertions_match.group(1))
|
|
276
|
+
if deletions_match:
|
|
277
|
+
deletions = int(deletions_match.group(1))
|
|
278
|
+
|
|
279
|
+
# 检查是否有大量代码删除(净删除超过阈值)
|
|
280
|
+
net_deletions = deletions - insertions
|
|
281
|
+
if net_deletions > threshold:
|
|
282
|
+
return {
|
|
283
|
+
'insertions': insertions,
|
|
284
|
+
'deletions': deletions,
|
|
285
|
+
'net_deletions': net_deletions
|
|
286
|
+
}
|
|
287
|
+
return None
|
|
288
|
+
except Exception:
|
|
289
|
+
# 如果检查过程中出错,返回None
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def confirm_large_code_deletion(detection_result: Dict[str, int]) -> bool:
|
|
294
|
+
"""询问用户是否确认大量代码删除
|
|
295
|
+
|
|
296
|
+
参数:
|
|
297
|
+
detection_result: 检测结果字典,包含 'insertions', 'deletions', 'net_deletions'
|
|
298
|
+
|
|
299
|
+
返回:
|
|
300
|
+
bool: 如果用户确认,返回True;如果用户拒绝,返回False
|
|
301
|
+
"""
|
|
302
|
+
insertions = detection_result['insertions']
|
|
303
|
+
deletions = detection_result['deletions']
|
|
304
|
+
net_deletions = detection_result['net_deletions']
|
|
305
|
+
|
|
306
|
+
PrettyOutput.print(
|
|
307
|
+
f"⚠️ 检测到大量代码删除:净删除 {net_deletions} 行(删除 {deletions} 行,新增 {insertions} 行)",
|
|
308
|
+
OutputType.WARNING,
|
|
309
|
+
)
|
|
310
|
+
if not user_confirm(
|
|
311
|
+
"此补丁包含大量代码删除,是否合理?", default=True
|
|
312
|
+
):
|
|
313
|
+
# 用户认为不合理,拒绝提交
|
|
314
|
+
revert_change()
|
|
315
|
+
PrettyOutput.print(
|
|
316
|
+
"已拒绝本次提交(用户认为补丁不合理)", OutputType.INFO
|
|
317
|
+
)
|
|
318
|
+
return False
|
|
319
|
+
return True
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def check_large_code_deletion(threshold: int = 200) -> bool:
|
|
323
|
+
"""检查是否有大量代码删除并询问用户确认
|
|
324
|
+
|
|
325
|
+
参数:
|
|
326
|
+
threshold: 净删除行数阈值,默认200行
|
|
327
|
+
|
|
328
|
+
返回:
|
|
329
|
+
bool: 如果检测到大量删除且用户拒绝提交,返回False;否则返回True
|
|
330
|
+
"""
|
|
331
|
+
detection_result = detect_large_code_deletion(threshold)
|
|
332
|
+
if detection_result is None:
|
|
333
|
+
return True
|
|
334
|
+
|
|
335
|
+
return confirm_large_code_deletion(detection_result)
|
|
336
|
+
|
|
337
|
+
|
|
211
338
|
def handle_commit_workflow() -> bool:
|
|
212
339
|
"""Handle the git commit workflow and return the commit details.
|
|
213
340
|
|
|
@@ -228,6 +355,10 @@ def handle_commit_workflow() -> bool:
|
|
|
228
355
|
if not has_uncommitted_changes():
|
|
229
356
|
return False
|
|
230
357
|
|
|
358
|
+
# 在提交前检查是否有大量代码删除
|
|
359
|
+
if not check_large_code_deletion():
|
|
360
|
+
return False
|
|
361
|
+
|
|
231
362
|
# 获取当前分支的提交总数
|
|
232
363
|
commit_result = subprocess.run(
|
|
233
364
|
["git", "rev-list", "--count", "HEAD"], capture_output=True, text=True
|
|
@@ -245,7 +376,7 @@ def handle_commit_workflow() -> bool:
|
|
|
245
376
|
["git", "commit", "-m", f"CheckPoint #{commit_count + 1}"], check=True
|
|
246
377
|
)
|
|
247
378
|
return True
|
|
248
|
-
except subprocess.CalledProcessError
|
|
379
|
+
except subprocess.CalledProcessError:
|
|
249
380
|
return False
|
|
250
381
|
|
|
251
382
|
|
|
@@ -282,7 +413,7 @@ def get_latest_commit_hash() -> str:
|
|
|
282
413
|
return ""
|
|
283
414
|
|
|
284
415
|
|
|
285
|
-
def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
|
|
416
|
+
def get_modified_line_ranges() -> Dict[str, List[Tuple[int, int]]]:
|
|
286
417
|
"""从Git差异中获取所有更改文件的修改行范围
|
|
287
418
|
|
|
288
419
|
返回:
|
|
@@ -290,10 +421,16 @@ def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
|
|
|
290
421
|
行号从1开始。
|
|
291
422
|
"""
|
|
292
423
|
# 获取所有文件的Git差异
|
|
293
|
-
|
|
424
|
+
# 仅用于解析修改行范围,减少上下文以降低输出体积和解析成本
|
|
425
|
+
proc = subprocess.run(
|
|
426
|
+
["git", "show", "--no-color"],
|
|
427
|
+
capture_output=True,
|
|
428
|
+
text=True,
|
|
429
|
+
)
|
|
430
|
+
diff_output = proc.stdout
|
|
294
431
|
|
|
295
432
|
# 解析差异以获取修改的文件及其行范围
|
|
296
|
-
result = {}
|
|
433
|
+
result: Dict[str, List[Tuple[int, int]]] = {}
|
|
297
434
|
current_file = None
|
|
298
435
|
|
|
299
436
|
for line in diff_output.splitlines():
|
|
@@ -309,7 +446,9 @@ def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
|
|
|
309
446
|
start_line = int(range_match.group(1)) # 保持从1开始
|
|
310
447
|
line_count = int(range_match.group(2)) if range_match.group(2) else 1
|
|
311
448
|
end_line = start_line + line_count - 1
|
|
312
|
-
|
|
449
|
+
if current_file not in result:
|
|
450
|
+
result[current_file] = []
|
|
451
|
+
result[current_file].append((start_line, end_line))
|
|
313
452
|
|
|
314
453
|
return result
|
|
315
454
|
|
|
@@ -326,7 +465,7 @@ def is_file_in_git_repo(filepath: str) -> bool:
|
|
|
326
465
|
|
|
327
466
|
# 检查文件路径是否在仓库根目录下
|
|
328
467
|
return os.path.abspath(filepath).startswith(os.path.abspath(repo_root))
|
|
329
|
-
except:
|
|
468
|
+
except Exception:
|
|
330
469
|
return False
|
|
331
470
|
|
|
332
471
|
|
|
@@ -339,25 +478,21 @@ def check_and_update_git_repo(repo_path: str) -> bool:
|
|
|
339
478
|
返回:
|
|
340
479
|
bool: 是否执行了更新
|
|
341
480
|
"""
|
|
481
|
+
# 检查上次检查日期
|
|
482
|
+
last_check_file = os.path.join(get_data_dir(), "last_git_check")
|
|
483
|
+
today_str = datetime.date.today().strftime("%Y-%m-%d")
|
|
484
|
+
if os.path.exists(last_check_file):
|
|
485
|
+
with open(last_check_file, "r") as f:
|
|
486
|
+
last_check_date = f.read().strip()
|
|
487
|
+
if last_check_date == today_str:
|
|
488
|
+
return False
|
|
489
|
+
|
|
342
490
|
curr_dir = os.path.abspath(os.getcwd())
|
|
343
491
|
git_root = find_git_root_and_cd(repo_path)
|
|
344
492
|
if git_root is None:
|
|
345
493
|
return False
|
|
346
494
|
|
|
347
495
|
try:
|
|
348
|
-
# 检查最新提交时间是否为今天
|
|
349
|
-
commit_date_result = subprocess.run(
|
|
350
|
-
["git", "log", "-1", "--format=%cd", "--date=short"],
|
|
351
|
-
cwd=git_root,
|
|
352
|
-
capture_output=True,
|
|
353
|
-
text=True,
|
|
354
|
-
)
|
|
355
|
-
if commit_date_result.returncode == 0:
|
|
356
|
-
commit_date = commit_date_result.stdout.strip()
|
|
357
|
-
today = datetime.date.today().strftime("%Y-%m-%d")
|
|
358
|
-
if commit_date == today:
|
|
359
|
-
return False
|
|
360
|
-
|
|
361
496
|
# 检查是否有未提交的修改
|
|
362
497
|
if has_uncommitted_changes():
|
|
363
498
|
return False
|
|
@@ -418,8 +553,51 @@ def check_and_update_git_repo(repo_path: str) -> bool:
|
|
|
418
553
|
hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix
|
|
419
554
|
)
|
|
420
555
|
|
|
421
|
-
#
|
|
422
|
-
|
|
556
|
+
# 检测 uv 可用性:优先虚拟环境内的 uv,其次 PATH 中的 uv
|
|
557
|
+
from shutil import which as _which
|
|
558
|
+
uv_executable = None
|
|
559
|
+
if sys.platform == "win32":
|
|
560
|
+
venv_uv = os.path.join(sys.prefix, "Scripts", "uv.exe")
|
|
561
|
+
else:
|
|
562
|
+
venv_uv = os.path.join(sys.prefix, "bin", "uv")
|
|
563
|
+
if os.path.exists(venv_uv):
|
|
564
|
+
uv_executable = venv_uv
|
|
565
|
+
else:
|
|
566
|
+
path_uv = _which("uv")
|
|
567
|
+
if path_uv:
|
|
568
|
+
uv_executable = path_uv
|
|
569
|
+
|
|
570
|
+
# 根据环境选择安装命令
|
|
571
|
+
# 检测是否安装了 RAG 特性(更精确)
|
|
572
|
+
rag_installed = is_rag_installed()
|
|
573
|
+
|
|
574
|
+
# 根据 uv 可用性与 RAG 特性选择安装命令(优先使用 uv)
|
|
575
|
+
if uv_executable:
|
|
576
|
+
if rag_installed:
|
|
577
|
+
install_cmd = [uv_executable, "pip", "install", "-e", ".[rag]"]
|
|
578
|
+
else:
|
|
579
|
+
install_cmd = [uv_executable, "pip", "install", "-e", "."]
|
|
580
|
+
else:
|
|
581
|
+
if rag_installed:
|
|
582
|
+
install_cmd = [
|
|
583
|
+
sys.executable,
|
|
584
|
+
"-m",
|
|
585
|
+
"pip",
|
|
586
|
+
"install",
|
|
587
|
+
"-e",
|
|
588
|
+
".[rag]",
|
|
589
|
+
]
|
|
590
|
+
else:
|
|
591
|
+
install_cmd = [
|
|
592
|
+
sys.executable,
|
|
593
|
+
"-m",
|
|
594
|
+
"pip",
|
|
595
|
+
"install",
|
|
596
|
+
"-e",
|
|
597
|
+
".",
|
|
598
|
+
]
|
|
599
|
+
|
|
600
|
+
# 尝试安装
|
|
423
601
|
result = subprocess.run(
|
|
424
602
|
install_cmd, cwd=git_root, capture_output=True, text=True
|
|
425
603
|
)
|
|
@@ -454,6 +632,9 @@ def check_and_update_git_repo(repo_path: str) -> bool:
|
|
|
454
632
|
f"安装过程中发生意外错误: {str(e)}", OutputType.ERROR
|
|
455
633
|
)
|
|
456
634
|
return False
|
|
635
|
+
# 更新检查日期文件
|
|
636
|
+
with open(last_check_file, "w") as f:
|
|
637
|
+
f.write(today_str)
|
|
457
638
|
return False
|
|
458
639
|
except Exception as e:
|
|
459
640
|
PrettyOutput.print(f"Git仓库更新检查失败: {e}", OutputType.WARNING)
|
|
@@ -534,17 +715,19 @@ def get_recent_commits_with_files() -> List[Dict[str, Any]]:
|
|
|
534
715
|
],
|
|
535
716
|
capture_output=True,
|
|
536
717
|
text=True,
|
|
718
|
+
encoding="utf-8",
|
|
719
|
+
errors="replace",
|
|
537
720
|
)
|
|
538
|
-
if result.returncode != 0:
|
|
721
|
+
if result.returncode != 0 or result.stdout is None:
|
|
539
722
|
return []
|
|
540
723
|
|
|
541
724
|
# 解析提交信息
|
|
542
|
-
commits = []
|
|
725
|
+
commits: List[Dict[str, Any]] = []
|
|
543
726
|
lines = result.stdout.splitlines()
|
|
544
727
|
for i in range(0, len(lines), 4):
|
|
545
728
|
if i + 3 >= len(lines):
|
|
546
729
|
break
|
|
547
|
-
commit = {
|
|
730
|
+
commit: Dict[str, Any] = {
|
|
548
731
|
"hash": lines[i],
|
|
549
732
|
"message": lines[i + 1],
|
|
550
733
|
"author": lines[i + 2],
|
|
@@ -563,7 +746,7 @@ def get_recent_commits_with_files() -> List[Dict[str, Any]]:
|
|
|
563
746
|
if files_result.returncode == 0:
|
|
564
747
|
file_lines = files_result.stdout.splitlines()
|
|
565
748
|
unique_files: Set[str] = set(filter(None, file_lines))
|
|
566
|
-
commit["files"] = list(unique_files)[:20] #
|
|
749
|
+
commit["files"] = list(unique_files)[:20] # 限制最多20个文件
|
|
567
750
|
|
|
568
751
|
return commits
|
|
569
752
|
|
|
@@ -658,8 +841,47 @@ def confirm_add_new_files() -> None:
|
|
|
658
841
|
|
|
659
842
|
if not user_confirm(
|
|
660
843
|
"是否要添加这些变更(如果不需要请修改.gitignore文件以忽略不需要的文件)?",
|
|
661
|
-
|
|
844
|
+
True,
|
|
662
845
|
):
|
|
846
|
+
# 用户选择 N:自动将未跟踪文件列表添加到仓库根目录的 .gitignore
|
|
847
|
+
try:
|
|
848
|
+
repo_root_result = subprocess.run(
|
|
849
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
850
|
+
capture_output=True,
|
|
851
|
+
text=True,
|
|
852
|
+
check=True,
|
|
853
|
+
)
|
|
854
|
+
repo_root = repo_root_result.stdout.strip() or "."
|
|
855
|
+
except Exception:
|
|
856
|
+
repo_root = "."
|
|
857
|
+
gitignore_path = os.path.join(repo_root, ".gitignore")
|
|
858
|
+
|
|
859
|
+
# 仅对未跟踪的新文件进行忽略(已跟踪文件无法通过 .gitignore 忽略)
|
|
860
|
+
files_to_ignore = sorted(set(new_files))
|
|
861
|
+
|
|
862
|
+
# 读取已存在的 .gitignore 以避免重复添加
|
|
863
|
+
existing_lines: Set[str] = set()
|
|
864
|
+
try:
|
|
865
|
+
if os.path.exists(gitignore_path):
|
|
866
|
+
with open(gitignore_path, "r", encoding="utf-8") as f:
|
|
867
|
+
existing_lines = set(line.strip() for line in f if line.strip())
|
|
868
|
+
except Exception:
|
|
869
|
+
existing_lines = set()
|
|
870
|
+
|
|
871
|
+
# 追加未存在的文件路径到 .gitignore(使用相对于仓库根目录的路径)
|
|
872
|
+
try:
|
|
873
|
+
with open(gitignore_path, "a", encoding="utf-8") as f:
|
|
874
|
+
for file in files_to_ignore:
|
|
875
|
+
abs_path = os.path.abspath(file)
|
|
876
|
+
rel_path = os.path.relpath(abs_path, repo_root)
|
|
877
|
+
# 避免无效的相对路径(不应出现 .. 前缀),有则回退用原始值
|
|
878
|
+
entry = rel_path if not rel_path.startswith("..") else file
|
|
879
|
+
if entry not in existing_lines:
|
|
880
|
+
f.write(entry + "\n")
|
|
881
|
+
PrettyOutput.print("已将未跟踪文件添加到 .gitignore,正在重新检测...", OutputType.INFO)
|
|
882
|
+
except Exception as e:
|
|
883
|
+
PrettyOutput.print(f"更新 .gitignore 失败: {str(e)}", OutputType.WARNING)
|
|
884
|
+
|
|
663
885
|
continue
|
|
664
886
|
|
|
665
887
|
break
|