jarvis-ai-assistant 0.3.30__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.
Files changed (115) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +289 -87
  3. jarvis/jarvis_agent/agent_manager.py +17 -8
  4. jarvis/jarvis_agent/edit_file_handler.py +374 -86
  5. jarvis/jarvis_agent/event_bus.py +1 -1
  6. jarvis/jarvis_agent/file_context_handler.py +79 -0
  7. jarvis/jarvis_agent/jarvis.py +601 -43
  8. jarvis/jarvis_agent/main.py +32 -2
  9. jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
  10. jarvis/jarvis_agent/run_loop.py +38 -5
  11. jarvis/jarvis_agent/share_manager.py +8 -1
  12. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  13. jarvis/jarvis_agent/task_analyzer.py +5 -2
  14. jarvis/jarvis_agent/task_planner.py +496 -0
  15. jarvis/jarvis_agent/utils.py +5 -1
  16. jarvis/jarvis_agent/web_bridge.py +189 -0
  17. jarvis/jarvis_agent/web_output_sink.py +53 -0
  18. jarvis/jarvis_agent/web_server.py +751 -0
  19. jarvis/jarvis_c2rust/__init__.py +26 -0
  20. jarvis/jarvis_c2rust/cli.py +613 -0
  21. jarvis/jarvis_c2rust/collector.py +258 -0
  22. jarvis/jarvis_c2rust/library_replacer.py +1122 -0
  23. jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
  24. jarvis/jarvis_c2rust/optimizer.py +960 -0
  25. jarvis/jarvis_c2rust/scanner.py +1681 -0
  26. jarvis/jarvis_c2rust/transpiler.py +2325 -0
  27. jarvis/jarvis_code_agent/build_validation_config.py +133 -0
  28. jarvis/jarvis_code_agent/code_agent.py +1171 -94
  29. jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
  30. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  31. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  32. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
  33. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
  34. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  35. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
  36. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
  37. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
  38. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
  39. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
  40. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
  41. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
  42. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
  43. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
  44. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  45. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
  46. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  47. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  48. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  49. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  50. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  51. jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
  52. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
  53. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
  54. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
  55. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
  56. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
  57. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
  58. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
  59. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
  60. jarvis/jarvis_code_agent/lint.py +270 -8
  61. jarvis/jarvis_code_agent/utils.py +142 -0
  62. jarvis/jarvis_code_analysis/code_review.py +483 -569
  63. jarvis/jarvis_data/config_schema.json +97 -8
  64. jarvis/jarvis_git_utils/git_commiter.py +38 -26
  65. jarvis/jarvis_mcp/sse_mcp_client.py +2 -2
  66. jarvis/jarvis_mcp/stdio_mcp_client.py +1 -1
  67. jarvis/jarvis_memory_organizer/memory_organizer.py +1 -1
  68. jarvis/jarvis_multi_agent/__init__.py +239 -25
  69. jarvis/jarvis_multi_agent/main.py +37 -1
  70. jarvis/jarvis_platform/base.py +103 -51
  71. jarvis/jarvis_platform/openai.py +26 -1
  72. jarvis/jarvis_platform/yuanbao.py +1 -1
  73. jarvis/jarvis_platform_manager/service.py +2 -2
  74. jarvis/jarvis_rag/cli.py +4 -4
  75. jarvis/jarvis_sec/__init__.py +3605 -0
  76. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  77. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  78. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  79. jarvis/jarvis_sec/cli.py +116 -0
  80. jarvis/jarvis_sec/report.py +257 -0
  81. jarvis/jarvis_sec/status.py +264 -0
  82. jarvis/jarvis_sec/types.py +20 -0
  83. jarvis/jarvis_sec/workflow.py +219 -0
  84. jarvis/jarvis_stats/cli.py +1 -1
  85. jarvis/jarvis_stats/stats.py +1 -1
  86. jarvis/jarvis_stats/visualizer.py +1 -1
  87. jarvis/jarvis_tools/cli/main.py +1 -0
  88. jarvis/jarvis_tools/execute_script.py +46 -9
  89. jarvis/jarvis_tools/generate_new_tool.py +3 -1
  90. jarvis/jarvis_tools/read_code.py +275 -12
  91. jarvis/jarvis_tools/read_symbols.py +141 -0
  92. jarvis/jarvis_tools/read_webpage.py +5 -3
  93. jarvis/jarvis_tools/registry.py +73 -35
  94. jarvis/jarvis_tools/search_web.py +15 -11
  95. jarvis/jarvis_tools/sub_agent.py +24 -42
  96. jarvis/jarvis_tools/sub_code_agent.py +14 -13
  97. jarvis/jarvis_tools/virtual_tty.py +1 -1
  98. jarvis/jarvis_utils/config.py +187 -35
  99. jarvis/jarvis_utils/embedding.py +3 -0
  100. jarvis/jarvis_utils/git_utils.py +181 -6
  101. jarvis/jarvis_utils/globals.py +3 -3
  102. jarvis/jarvis_utils/http.py +1 -1
  103. jarvis/jarvis_utils/input.py +78 -2
  104. jarvis/jarvis_utils/methodology.py +25 -19
  105. jarvis/jarvis_utils/utils.py +644 -359
  106. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/METADATA +85 -1
  107. jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
  108. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +4 -0
  109. jarvis/jarvis_agent/config.py +0 -92
  110. jarvis/jarvis_tools/edit_file.py +0 -179
  111. jarvis/jarvis_tools/rewrite_file.py +0 -191
  112. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  113. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
  114. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
  115. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,9 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import os
3
3
  from functools import lru_cache
4
- from typing import Any, Dict, List, Optional
4
+ from typing import Any, Dict, List, Optional, cast
5
5
 
6
- import yaml # type: ignore
6
+ import yaml
7
7
 
8
8
  from jarvis.jarvis_utils.builtin_replace_map import BUILTIN_REPLACE_MAP
9
9
 
@@ -37,7 +37,7 @@ def get_git_commit_prompt() -> str:
37
37
  返回:
38
38
  str: Git提交信息生成提示模板,如果未配置则返回空字符串
39
39
  """
40
- return GLOBAL_CONFIG_DATA.get("JARVIS_GIT_COMMIT_PROMPT", "")
40
+ return cast(str, GLOBAL_CONFIG_DATA.get("JARVIS_GIT_COMMIT_PROMPT", ""))
41
41
 
42
42
 
43
43
  # 输出窗口预留大小
@@ -76,17 +76,6 @@ def get_replace_map() -> dict:
76
76
  return {**BUILTIN_REPLACE_MAP, **file_map}
77
77
 
78
78
 
79
- def get_max_token_count(model_group_override: Optional[str] = None) -> int:
80
- """
81
- 获取模型允许的最大token数量。
82
-
83
- 返回:
84
- int: 模型能处理的最大token数量,为最大输入token数量的100倍。
85
- """
86
- max_input_tokens = get_max_input_token_count(model_group_override)
87
- return max_input_tokens * 100
88
-
89
-
90
79
  def get_max_input_token_count(model_group_override: Optional[str] = None) -> int:
91
80
  """
92
81
  获取模型允许的最大输入token数量。
@@ -112,7 +101,7 @@ def get_shell_name() -> str:
112
101
  4. 如果都未配置,则默认返回bash
113
102
  """
114
103
  shell_path = GLOBAL_CONFIG_DATA.get("SHELL", os.getenv("SHELL", "/bin/bash"))
115
- return os.path.basename(shell_path).lower()
104
+ return cast(str, os.path.basename(shell_path).lower())
116
105
 
117
106
 
118
107
  def _apply_llm_group_env_override(group_config: Dict[str, Any]) -> None:
@@ -190,7 +179,7 @@ def get_normal_platform_name(model_group_override: Optional[str] = None) -> str:
190
179
  str: 平台名称,默认为'yuanbao'
191
180
  """
192
181
  config = _get_resolved_model_config(model_group_override)
193
- return config.get("JARVIS_PLATFORM", "yuanbao")
182
+ return cast(str, config.get("JARVIS_PLATFORM", "yuanbao"))
194
183
 
195
184
 
196
185
  def get_normal_model_name(model_group_override: Optional[str] = None) -> str:
@@ -201,7 +190,7 @@ def get_normal_model_name(model_group_override: Optional[str] = None) -> str:
201
190
  str: 模型名称,默认为'deep_seek_v3'
202
191
  """
203
192
  config = _get_resolved_model_config(model_group_override)
204
- return config.get("JARVIS_MODEL", "deep_seek_v3")
193
+ return cast(str, config.get("JARVIS_MODEL", "deep_seek_v3"))
205
194
 
206
195
 
207
196
  def _deprecated_platform_name_v1(model_group_override: Optional[str] = None) -> str:
@@ -235,7 +224,7 @@ def is_execute_tool_confirm() -> bool:
235
224
  返回:
236
225
  bool: 如果需要确认则返回True,默认为False
237
226
  """
238
- return GLOBAL_CONFIG_DATA.get("JARVIS_EXECUTE_TOOL_CONFIRM", False)
227
+ return cast(bool, GLOBAL_CONFIG_DATA.get("JARVIS_EXECUTE_TOOL_CONFIRM", False))
239
228
 
240
229
 
241
230
  def is_confirm_before_apply_patch() -> bool:
@@ -245,7 +234,24 @@ def is_confirm_before_apply_patch() -> bool:
245
234
  返回:
246
235
  bool: 如果需要确认则返回True,默认为False
247
236
  """
248
- return GLOBAL_CONFIG_DATA.get("JARVIS_CONFIRM_BEFORE_APPLY_PATCH", False)
237
+ return cast(bool, GLOBAL_CONFIG_DATA.get("JARVIS_CONFIRM_BEFORE_APPLY_PATCH", False))
238
+
239
+
240
+ def get_patch_format() -> str:
241
+ """
242
+ 获取补丁格式。
243
+
244
+ - "search": 仅使用精确匹配的 `SEARCH` 模式。此模式对能力较弱的模型更稳定,因为它要求代码片段完全匹配。
245
+ - "search_range": 仅使用 `SEARCH_START` 和 `SEARCH_END` 的范围匹配模式。此模式对能力较强的模型更灵活,因为它允许在代码块内部进行修改,而不要求整个块完全匹配。
246
+ - "all": 同时支持以上两种模式(默认)。
247
+
248
+ 返回:
249
+ str: "all", "search", or "search_range"
250
+ """
251
+ mode = GLOBAL_CONFIG_DATA.get("JARVIS_PATCH_FORMAT", "all")
252
+ if mode in ["all", "search", "search_range"]:
253
+ return cast(str, mode)
254
+ return "all"
249
255
 
250
256
 
251
257
  def get_data_dir() -> str:
@@ -257,7 +263,7 @@ def get_data_dir() -> str:
257
263
  如果未设置或为空,则使用~/.jarvis作为默认值
258
264
  """
259
265
  return os.path.expanduser(
260
- GLOBAL_CONFIG_DATA.get("JARVIS_DATA_PATH", "~/.jarvis").strip()
266
+ cast(str, GLOBAL_CONFIG_DATA.get("JARVIS_DATA_PATH", "~/.jarvis")).strip()
261
267
  )
262
268
 
263
269
 
@@ -285,7 +291,7 @@ def get_pretty_output() -> bool:
285
291
  if platform.system() == "Windows":
286
292
  return False
287
293
 
288
- return GLOBAL_CONFIG_DATA.get("JARVIS_PRETTY_OUTPUT", True)
294
+ return cast(bool, GLOBAL_CONFIG_DATA.get("JARVIS_PRETTY_OUTPUT", True))
289
295
 
290
296
 
291
297
  def is_use_methodology() -> bool:
@@ -295,7 +301,7 @@ def is_use_methodology() -> bool:
295
301
  返回:
296
302
  bool: 如果启用方法论则返回True,默认为True
297
303
  """
298
- return GLOBAL_CONFIG_DATA.get("JARVIS_USE_METHODOLOGY", True)
304
+ return cast(bool, GLOBAL_CONFIG_DATA.get("JARVIS_USE_METHODOLOGY", True))
299
305
 
300
306
 
301
307
  def is_use_analysis() -> bool:
@@ -305,7 +311,7 @@ def is_use_analysis() -> bool:
305
311
  返回:
306
312
  bool: 如果启用任务分析则返回True,默认为True
307
313
  """
308
- return GLOBAL_CONFIG_DATA.get("JARVIS_USE_ANALYSIS", True)
314
+ return cast(bool, GLOBAL_CONFIG_DATA.get("JARVIS_USE_ANALYSIS", True))
309
315
 
310
316
 
311
317
  def get_tool_load_dirs() -> List[str]:
@@ -399,7 +405,7 @@ def get_central_methodology_repo() -> str:
399
405
  返回:
400
406
  str: 中心方法论Git仓库地址,如果未配置则返回空字符串
401
407
  """
402
- return GLOBAL_CONFIG_DATA.get("JARVIS_CENTRAL_METHODOLOGY_REPO", "")
408
+ return cast(str, GLOBAL_CONFIG_DATA.get("JARVIS_CENTRAL_METHODOLOGY_REPO", ""))
403
409
 
404
410
 
405
411
  def get_central_tool_repo() -> str:
@@ -409,7 +415,7 @@ def get_central_tool_repo() -> str:
409
415
  返回:
410
416
  str: 中心工具Git仓库地址,如果未配置则返回空字符串
411
417
  """
412
- return GLOBAL_CONFIG_DATA.get("JARVIS_CENTRAL_TOOL_REPO", "")
418
+ return cast(str, GLOBAL_CONFIG_DATA.get("JARVIS_CENTRAL_TOOL_REPO", ""))
413
419
 
414
420
 
415
421
  def is_print_prompt() -> bool:
@@ -419,7 +425,7 @@ def is_print_prompt() -> bool:
419
425
  返回:
420
426
  bool: 如果打印提示则返回True,默认为True
421
427
  """
422
- return GLOBAL_CONFIG_DATA.get("JARVIS_PRINT_PROMPT", False)
428
+ return cast(bool, GLOBAL_CONFIG_DATA.get("JARVIS_PRINT_PROMPT", False))
423
429
 
424
430
 
425
431
  def is_print_error_traceback() -> bool:
@@ -452,6 +458,36 @@ def is_enable_static_analysis() -> bool:
452
458
  return GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_STATIC_ANALYSIS", True) is True
453
459
 
454
460
 
461
+ def is_enable_build_validation() -> bool:
462
+ """
463
+ 获取是否启用构建验证。
464
+
465
+ 返回:
466
+ bool: 如果启用构建验证则返回True,默认为True
467
+ """
468
+ return GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_BUILD_VALIDATION", True) is True
469
+
470
+
471
+ def is_enable_impact_analysis() -> bool:
472
+ """
473
+ 获取是否启用编辑影响范围分析。
474
+
475
+ 返回:
476
+ bool: 如果启用影响范围分析则返回True,默认为True
477
+ """
478
+ return GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_IMPACT_ANALYSIS", True) is True
479
+
480
+
481
+ def get_build_validation_timeout() -> int:
482
+ """
483
+ 获取构建验证的超时时间(秒)。
484
+
485
+ 返回:
486
+ int: 超时时间,默认为30秒
487
+ """
488
+ return int(GLOBAL_CONFIG_DATA.get("JARVIS_BUILD_VALIDATION_TIMEOUT", 30))
489
+
490
+
455
491
  def get_git_check_mode() -> str:
456
492
  """
457
493
  获取Git校验模式。
@@ -473,7 +509,7 @@ def get_mcp_config() -> List[Dict[str, Any]]:
473
509
  返回:
474
510
  List[Dict[str, Any]]: MCP配置项列表,如果未配置则返回空列表
475
511
  """
476
- return GLOBAL_CONFIG_DATA.get("JARVIS_MCP", [])
512
+ return cast(List[Dict[str, Any]], GLOBAL_CONFIG_DATA.get("JARVIS_MCP", []))
477
513
 
478
514
 
479
515
  # ==============================================================================
@@ -550,7 +586,7 @@ def get_rag_embedding_model() -> str:
550
586
  str: 嵌入模型的名称
551
587
  """
552
588
  config = _get_resolved_rag_config()
553
- return config.get("embedding_model", "BAAI/bge-m3")
589
+ return cast(str, config.get("embedding_model", "BAAI/bge-m3"))
554
590
 
555
591
 
556
592
  def get_rag_rerank_model() -> str:
@@ -561,7 +597,7 @@ def get_rag_rerank_model() -> str:
561
597
  str: rerank模型的名称
562
598
  """
563
599
  config = _get_resolved_rag_config()
564
- return config.get("rerank_model", "BAAI/bge-reranker-v2-m3")
600
+ return cast(str, config.get("rerank_model", "BAAI/bge-reranker-v2-m3"))
565
601
 
566
602
 
567
603
  def get_rag_embedding_cache_path() -> str:
@@ -671,7 +707,7 @@ def get_tool_use_list() -> List[str]:
671
707
  List[str]: 要使用的工具名称列表,空列表表示使用所有工具
672
708
  """
673
709
  config = _get_resolved_tool_config()
674
- return config.get("use", [])
710
+ return cast(List[str], config.get("use", []))
675
711
 
676
712
 
677
713
  def get_tool_dont_use_list() -> List[str]:
@@ -682,7 +718,7 @@ def get_tool_dont_use_list() -> List[str]:
682
718
  List[str]: 不使用的工具名称列表
683
719
  """
684
720
  config = _get_resolved_tool_config()
685
- return config.get("dont_use", [])
721
+ return cast(List[str], config.get("dont_use", []))
686
722
 
687
723
 
688
724
  def get_tool_filter_threshold() -> int:
@@ -694,28 +730,144 @@ def get_tool_filter_threshold() -> int:
694
730
  """
695
731
  return int(GLOBAL_CONFIG_DATA.get("JARVIS_TOOL_FILTER_THRESHOLD", 30))
696
732
 
733
+ def get_plan_max_depth() -> int:
734
+ """
735
+ 获取任务规划的最大层数。
736
+
737
+ 返回:
738
+ int: 最大规划层数,默认为2(可通过 GLOBAL_CONFIG_DATA['JARVIS_PLAN_MAX_DEPTH'] 配置)
739
+ """
740
+ try:
741
+ return int(GLOBAL_CONFIG_DATA.get("JARVIS_PLAN_MAX_DEPTH", 2))
742
+ except Exception:
743
+ return 2
744
+
745
+
746
+ def is_plan_enabled() -> bool:
747
+ """
748
+ 获取是否默认启用任务规划。
749
+
750
+ 返回:
751
+ bool: 如果启用任务规划则返回True,默认为True
752
+ """
753
+ return GLOBAL_CONFIG_DATA.get("JARVIS_PLAN_ENABLED", True) is True
754
+
755
+
756
+ def get_auto_summary_rounds() -> int:
757
+ """
758
+ 获取基于对话轮次的自动总结阈值。
759
+
760
+ 返回:
761
+ int: 轮次阈值,默认20
762
+ """
763
+ try:
764
+ return int(GLOBAL_CONFIG_DATA.get("JARVIS_AUTO_SUMMARY_ROUNDS", 50))
765
+ except Exception:
766
+ return 50
767
+
768
+
769
+
770
+
771
+
772
+ def get_script_execution_timeout() -> int:
773
+ """
774
+ 获取脚本执行的超时时间(秒)。
775
+
776
+ 返回:
777
+ int: 超时时间,默认为300秒(5分钟)
778
+ """
779
+ return int(GLOBAL_CONFIG_DATA.get("JARVIS_SCRIPT_EXECUTION_TIMEOUT", 300))
780
+
697
781
 
698
782
  def is_enable_git_repo_jca_switch() -> bool:
699
783
  """
700
784
  是否启用:在初始化环境前检测Git仓库并提示可切换到代码开发模式(jca)
701
- 默认关闭
785
+ 默认开启
702
786
  """
703
- return GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_GIT_JCA_SWITCH", False) is True
787
+ return GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_GIT_JCA_SWITCH", True) is True
704
788
 
705
789
 
706
790
  def is_enable_builtin_config_selector() -> bool:
707
791
  """
708
792
  是否启用:在进入默认通用代理前,列出可用配置(agent/multi_agent/roles)供选择
709
- 默认关闭
793
+ 默认开启
710
794
  """
711
795
  return (
712
- GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR", False) is True
796
+ GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR", True) is True
713
797
  )
714
798
 
715
799
 
800
+ def is_save_session_history() -> bool:
801
+ """
802
+ 是否保存会话记录。
803
+
804
+ 返回:
805
+ bool: 如果要保存会话记录则返回True, 默认为False
806
+ """
807
+ return GLOBAL_CONFIG_DATA.get("JARVIS_SAVE_SESSION_HISTORY", False) is True
808
+
809
+
716
810
  def is_immediate_abort() -> bool:
717
811
  """
718
812
  是否启用立即中断:当在对话过程中检测到用户中断信号时,立即停止输出并返回。
719
813
  默认关闭
720
814
  """
721
815
  return GLOBAL_CONFIG_DATA.get("JARVIS_IMMEDIATE_ABORT", False) is True
816
+
817
+
818
+ def is_non_interactive() -> bool:
819
+ """
820
+ 获取是否启用非交互模式。
821
+
822
+ 返回:
823
+ bool: 如果启用非交互模式则返回True,默认为False
824
+ """
825
+ # 优先读取环境变量,确保 CLI 标志生效且不被配置覆盖
826
+ try:
827
+ import os
828
+ v = os.getenv("JARVIS_NON_INTERACTIVE")
829
+ if v is not None:
830
+ val = str(v).strip().lower()
831
+ if val in ("1", "true", "yes", "on"):
832
+ return True
833
+ if val in ("0", "false", "no", "off"):
834
+ return False
835
+ except Exception:
836
+ # 忽略环境变量解析异常,回退到配置
837
+ pass
838
+ return GLOBAL_CONFIG_DATA.get("JARVIS_NON_INTERACTIVE", False) is True
839
+
840
+
841
+ def is_skip_predefined_tasks() -> bool:
842
+ """
843
+ 是否跳过预定义任务加载。
844
+
845
+ 返回:
846
+ bool: 如果跳过预定义任务加载则返回True,默认为False
847
+ """
848
+ return GLOBAL_CONFIG_DATA.get("JARVIS_SKIP_PREDEFINED_TASKS", False) is True
849
+
850
+
851
+ def get_addon_prompt_threshold() -> int:
852
+ """
853
+ 获取附加提示的触发阈值(字符数)。
854
+
855
+ 当消息长度超过此阈值时,会自动添加默认的附加提示。
856
+
857
+ 返回:
858
+ int: 触发阈值,默认为1024
859
+ """
860
+ try:
861
+ return int(GLOBAL_CONFIG_DATA.get("JARVIS_ADDON_PROMPT_THRESHOLD", 1024))
862
+ except Exception:
863
+ return 1024
864
+
865
+
866
+ def is_enable_intent_recognition() -> bool:
867
+ """
868
+ 获取是否启用意图识别功能。
869
+
870
+ 返回:
871
+ bool: 是否启用意图识别,默认为True(可通过 GLOBAL_CONFIG_DATA['JARVIS_ENABLE_INTENT_RECOGNITION'] 配置)
872
+ """
873
+ return GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_INTENT_RECOGNITION", True) is True
@@ -21,6 +21,9 @@ def get_context_token_count(text: str) -> int:
21
21
  返回:
22
22
  int: 文本中的token数量
23
23
  """
24
+ # 防御性检查:入参为 None 或空字符串时直接返回 0
25
+ if text is None or text == "":
26
+ return 0
24
27
  try:
25
28
  import tiktoken
26
29
 
@@ -14,7 +14,7 @@ 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
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
@@ -34,7 +34,13 @@ def find_git_root_and_cd(start_dir: str = ".") -> str:
34
34
  """
35
35
  os.chdir(start_dir)
36
36
  try:
37
- git_root = os.popen("git rev-parse --show-toplevel").read().strip()
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()
38
44
  if not git_root:
39
45
  subprocess.run(["git", "init"], check=True)
40
46
  git_root = os.path.abspath(".")
@@ -209,6 +215,126 @@ def revert_change() -> None:
209
215
  PrettyOutput.print(f"恢复更改失败: {str(e)}", OutputType.ERROR)
210
216
 
211
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
+
212
338
  def handle_commit_workflow() -> bool:
213
339
  """Handle the git commit workflow and return the commit details.
214
340
 
@@ -229,6 +355,10 @@ def handle_commit_workflow() -> bool:
229
355
  if not has_uncommitted_changes():
230
356
  return False
231
357
 
358
+ # 在提交前检查是否有大量代码删除
359
+ if not check_large_code_deletion():
360
+ return False
361
+
232
362
  # 获取当前分支的提交总数
233
363
  commit_result = subprocess.run(
234
364
  ["git", "rev-list", "--count", "HEAD"], capture_output=True, text=True
@@ -291,7 +421,13 @@ def get_modified_line_ranges() -> Dict[str, List[Tuple[int, int]]]:
291
421
  行号从1开始。
292
422
  """
293
423
  # 获取所有文件的Git差异
294
- diff_output = os.popen("git show").read()
424
+ # 仅用于解析修改行范围,减少上下文以降低输出体积和解析成本
425
+ proc = subprocess.run(
426
+ ["git", "show", "--no-color"],
427
+ capture_output=True,
428
+ text=True,
429
+ )
430
+ diff_output = proc.stdout
295
431
 
296
432
  # 解析差异以获取修改的文件及其行范围
297
433
  result: Dict[str, List[Tuple[int, int]]] = {}
@@ -329,7 +465,7 @@ def is_file_in_git_repo(filepath: str) -> bool:
329
465
 
330
466
  # 检查文件路径是否在仓库根目录下
331
467
  return os.path.abspath(filepath).startswith(os.path.abspath(repo_root))
332
- except:
468
+ except Exception:
333
469
  return False
334
470
 
335
471
 
@@ -610,7 +746,7 @@ def get_recent_commits_with_files() -> List[Dict[str, Any]]:
610
746
  if files_result.returncode == 0:
611
747
  file_lines = files_result.stdout.splitlines()
612
748
  unique_files: Set[str] = set(filter(None, file_lines))
613
- commit["files"] = list(unique_files)[:20] # type: ignore[list-item] # 限制最多20个文件
749
+ commit["files"] = list(unique_files)[:20] # 限制最多20个文件
614
750
 
615
751
  return commits
616
752
 
@@ -705,8 +841,47 @@ def confirm_add_new_files() -> None:
705
841
 
706
842
  if not user_confirm(
707
843
  "是否要添加这些变更(如果不需要请修改.gitignore文件以忽略不需要的文件)?",
708
- False,
844
+ True,
709
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
+
710
885
  continue
711
886
 
712
887
  break
@@ -19,9 +19,9 @@ MAX_HISTORY_SIZE = 50
19
19
  short_term_memories: List[Dict[str, Any]] = []
20
20
  MAX_SHORT_TERM_MEMORIES = 100
21
21
 
22
- import colorama
23
- from rich.console import Console
24
- from rich.theme import Theme
22
+ import colorama # noqa: E402
23
+ from rich.console import Console # noqa: E402
24
+ from rich.theme import Theme # noqa: E402
25
25
 
26
26
  # 初始化colorama以支持跨平台的彩色文本
27
27
  colorama.init()
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- import requests
3
+ import requests # type: ignore[import-untyped]
4
4
  from typing import Any, Dict, Optional, Generator
5
5
 
6
6