sycommon-python-lib 0.2.1b9__py3-none-any.whl → 0.2.2__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 (52) hide show
  1. command/cli.py +24 -3
  2. command/core/models.py +15 -0
  3. command/core/project.py +18 -7
  4. command/templates/cli/README.md.tpl +54 -0
  5. command/templates/cli/__init__.py +1 -0
  6. command/templates/cli/app.py.tpl +45 -0
  7. command/templates/cli/commands/__init__.py.tpl +1 -0
  8. command/templates/cli/commands/hello.py.tpl +6 -0
  9. command/templates/cli/pyproject.toml.tpl +14 -0
  10. command/templates/cli/tools/__init__.py.tpl +3 -0
  11. nexus/__init__.py +40 -0
  12. sycli/agents/prompts.py +88 -0
  13. sycli/commands/resume_cmd.py +107 -0
  14. sycli/mode/create.py +4 -4
  15. sycli/models/config_models.py +8 -0
  16. sycli/rl/baseline_tests.py +245 -0
  17. sycli/rl/checkpoint.py +497 -0
  18. sycli/rl/convergence.py +83 -12
  19. sycli/rl/diagnosis.py +14 -7
  20. sycli/rl/engine.py +407 -31
  21. sycli/rl/environment.py +252 -0
  22. sycli/rl/experience.py +27 -0
  23. sycli/rl/optimization_logger.py +264 -0
  24. sycli/rl/reward.py +39 -13
  25. sycli/rl/strategy_bandit.py +80 -7
  26. sycli/rl/strategy_generator.py +17 -0
  27. sycli/rl/strategy_prompts.py +4 -0
  28. sycommon/agent/deep_agent.py +11 -2
  29. sycommon/agent/multi_agent_team.py +8 -3
  30. sycommon/agent/sandbox/__init__.py +4 -0
  31. sycommon/agent/sandbox/file_ops.py +57 -91
  32. sycommon/agent/sandbox/http_sandbox_backend.py +373 -222
  33. sycommon/agent/sandbox/minio_sync.py +500 -0
  34. sycommon/agent/summarization_utils.py +68 -0
  35. sycommon/database/async_database_service.py +4 -2
  36. sycommon/database/token_usage_db_service.py +3 -2
  37. sycommon/llm/get_llm.py +11 -3
  38. sycommon/llm/struct_token.py +4 -6
  39. sycommon/llm/token_usage_mysql_service.py +44 -47
  40. sycommon/middleware/sandbox.py +674 -359
  41. sycommon/middleware/tool_result_truncation.py +136 -0
  42. sycommon/middleware/traceid.py +20 -2
  43. sycommon/models/sandbox.py +93 -2
  44. sycommon/notice/__init__.py +5 -0
  45. sycommon/notice/wecom_message.py +328 -0
  46. sycommon/services.py +18 -5
  47. {sycommon_python_lib-0.2.1b9.dist-info → sycommon_python_lib-0.2.2.dist-info}/METADATA +13 -11
  48. {sycommon_python_lib-0.2.1b9.dist-info → sycommon_python_lib-0.2.2.dist-info}/RECORD +51 -37
  49. {sycommon_python_lib-0.2.1b9.dist-info → sycommon_python_lib-0.2.2.dist-info}/entry_points.txt +1 -0
  50. {sycommon_python_lib-0.2.1b9.dist-info → sycommon_python_lib-0.2.2.dist-info}/top_level.txt +1 -0
  51. command/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  52. {sycommon_python_lib-0.2.1b9.dist-info → sycommon_python_lib-0.2.2.dist-info}/WHEEL +0 -0
command/cli.py CHANGED
@@ -49,6 +49,12 @@ def create_init_parser(subparsers) -> None:
49
49
  web - 标准 FastAPI Web 服务,适合 RESTful API 开发
50
50
  agent - AI Agent 服务,基于 langgraph 实现状态图
51
51
 
52
+ CLI 项目结构:
53
+ commands/ - 子命令目录
54
+ model/ - 数据模型
55
+ client/ - 外部服务客户端
56
+ app.py - CLI 入口 (argparse)
57
+
52
58
  Agent 项目结构:
53
59
  agent/ - Agent 核心 (main_agent, nodes, states, enums)
54
60
  api/sse/ - SSE 流式接口
@@ -79,7 +85,7 @@ Web 项目结构:
79
85
  # 位置参数
80
86
  init_parser.add_argument(
81
87
  "project_type",
82
- choices=["web", "agent"],
88
+ choices=["web", "agent", "cli"],
83
89
  help="项目类型:web / agent"
84
90
  )
85
91
  init_parser.add_argument(
@@ -221,6 +227,8 @@ def _detect_project_type(project_path) -> str | None:
221
227
  if (project_path / "agent" / "main_agent.py").exists() or \
222
228
  (project_path / "agent" / "enums").is_dir():
223
229
  return "agent"
230
+ if (project_path / "commands").is_dir() and (project_path / "app.py").exists():
231
+ return "cli"
224
232
  if (project_path / "api").is_dir():
225
233
  return "web"
226
234
  return None
@@ -247,13 +255,19 @@ def handle_doctor_command(args) -> bool:
247
255
  warnings.append("无法识别项目类型 (非 web / agent 标准结构)")
248
256
 
249
257
  # 1. 检查必要文件
250
- required_files = ["app.py", "requirements.txt"]
258
+ if project_type == "cli":
259
+ required_files = ["app.py", "pyproject.toml"]
260
+ else:
261
+ required_files = ["app.py", "requirements.txt"]
251
262
  for file in required_files:
252
263
  if not (project_path / file).exists():
253
264
  issues.append(f"缺少必要文件: {file}")
254
265
 
255
266
  # 2. 检查配置文件
256
- config_files = ["app.yaml", ".env"]
267
+ if project_type == "cli":
268
+ config_files = [".env"]
269
+ else:
270
+ config_files = ["app.yaml", ".env"]
257
271
  for file in config_files:
258
272
  if not (project_path / file).exists():
259
273
  warnings.append(f"缺少配置文件: {file}")
@@ -276,6 +290,8 @@ def handle_doctor_command(args) -> bool:
276
290
  issues.append(f"缺少 Agent 核心文件: {file}")
277
291
  elif project_type == "web":
278
292
  required_dirs = ["api", "model", "tools", "client"]
293
+ elif project_type == "cli":
294
+ required_dirs = ["commands", "model", "tools"]
279
295
  else:
280
296
  required_dirs = ["api", "model", "tools"]
281
297
 
@@ -351,6 +367,11 @@ def handle_list_command(args) -> bool:
351
367
  "api/sse/", "api/query.py", "tools/", "model/", "db/"],
352
368
  "features": "nacos, kafka_log, llm, sentry (默认启用)",
353
369
  },
370
+ "cli": {
371
+ "desc": "Python CLI 命令行工具 (argparse)",
372
+ "dirs": ["commands/", "model/", "client/"],
373
+ "features": "无默认依赖 (精简模板)",
374
+ },
354
375
  }
355
376
 
356
377
  for name, info in templates.items():
command/core/models.py CHANGED
@@ -13,6 +13,19 @@ AGENT_DEFAULTS = {
13
13
  "llm": True,
14
14
  }
15
15
 
16
+ # CLI 项目默认关闭所有服务依赖
17
+ CLI_DEFAULTS = {
18
+ "database": False,
19
+ "rabbitmq": False,
20
+ "nacos": False,
21
+ "kafka_log": False,
22
+ "sse": False,
23
+ "llm": False,
24
+ "redis": False,
25
+ "elasticsearch": False,
26
+ "sentry": False,
27
+ }
28
+
16
29
 
17
30
  @dataclass
18
31
  class FeatureOptions:
@@ -32,6 +45,8 @@ class FeatureOptions:
32
45
  """根据项目类型返回推荐默认配置"""
33
46
  if project_type == "agent":
34
47
  return cls(**AGENT_DEFAULTS)
48
+ if project_type == "cli":
49
+ return cls(**CLI_DEFAULTS)
35
50
  return cls()
36
51
 
37
52
  def to_template_vars(self) -> dict:
command/core/project.py CHANGED
@@ -153,13 +153,19 @@ def init_project(
153
153
  'db/', 'db/service.py', 'db/model/', 'db/model/entity.py',
154
154
  ],
155
155
  }
156
+ # CLI 项目不需要的服务端文件
157
+ cli_skip_files = {'app.yaml', 'app.yaml.tpl', 'requirements.txt', 'requirements.txt.tpl', 'Dockerfile', 'Dockerfile.tpl'}
156
158
  disabled_features = {
157
159
  key for key, flag_val in features.to_template_vars().items()
158
160
  if flag_val == 'false' and key in feature_map
159
161
  }
160
162
 
161
163
  def _should_skip(rel_path: str) -> bool:
162
- """检查文件是否因功能关闭而应跳过"""
164
+ """检查文件是否因功能关闭或项目类型而应跳过"""
165
+ # CLI 项目跳过不需要的服务端文件
166
+ if project_type == "cli":
167
+ if rel_path in cli_skip_files or rel_path.replace('.tpl', '') in cli_skip_files:
168
+ return True
163
169
  for feat_key in disabled_features:
164
170
  for pattern in feature_map[feat_key]:
165
171
  if pattern in rel_path:
@@ -191,7 +197,7 @@ def init_project(
191
197
  copied_files = _render_files(file_mappings, context, project_path, verbose)
192
198
 
193
199
  if copied_files > 0:
194
- _print_success_message(project_path, project_name, copied_files)
200
+ _print_success_message(project_path, project_name, copied_files, project_type)
195
201
  return True
196
202
  else:
197
203
  print_warning("未创建任何文件,可能是缺少模板文件或模板路径配置错误")
@@ -284,7 +290,8 @@ def _render_files(
284
290
  def _print_success_message(
285
291
  project_path: Path,
286
292
  project_name: str,
287
- copied_files: int
293
+ copied_files: int,
294
+ project_type: str = "web",
288
295
  ) -> None:
289
296
  """打印成功信息"""
290
297
  print_success(f"模板工程 {project_name} 创建完成!")
@@ -293,7 +300,11 @@ def _print_success_message(
293
300
  print()
294
301
  print_info("下一步操作:")
295
302
  print(f" cd {project_name}")
296
- print(f" # 建议先创建虚拟环境")
297
- print(f" python -m venv .venv && source .venv/bin/activate")
298
- print(f" pip install -r requirements.txt")
299
- print(f" python app.py")
303
+ if project_type == "cli":
304
+ print(f" uv sync")
305
+ print(f" uv run python app.py")
306
+ else:
307
+ print(f" # 建议先创建虚拟环境")
308
+ print(f" python -m venv .venv && source .venv/bin/activate")
309
+ print(f" pip install -r requirements.txt")
310
+ print(f" python app.py")
@@ -0,0 +1,54 @@
1
+ # {{ project_name }}
2
+
3
+ {{ project_name }} - Python CLI 命令行工具
4
+
5
+ ## 项目结构
6
+
7
+ ```
8
+ {{ project_name }}/
9
+ ├── commands/ # 子命令目录
10
+ │ ├── __init__.py
11
+ │ └── hello.py # 示例子命令
12
+ ├── tools/ # 工具函数
13
+ ├── model/ # 数据模型
14
+ ├── client/ # 外部服务客户端
15
+ ├── app.py # CLI 入口
16
+ ├── pyproject.toml # uv 项目配置
17
+ └── README.md
18
+ ```
19
+
20
+ ## 快速开始
21
+
22
+ ### 安装依赖
23
+ ```bash
24
+ uv sync
25
+ ```
26
+
27
+ ### 使用
28
+ ```bash
29
+ # 查看帮助
30
+ uv run python app.py -h
31
+
32
+ # hello 命令
33
+ uv run python app.py hello --name "World"
34
+
35
+ # 查看版本
36
+ uv run python app.py version
37
+ ```
38
+
39
+ ### 添加新子命令
40
+
41
+ 1. 在 `commands/` 目录下创建新模块,例如 `commands/xxx.py`
42
+ 2. 实现 `handle_xxx(args)` 函数
43
+ 3. 在 `app.py` 中注册子命令:
44
+
45
+ ```python
46
+ from commands.xxx import handle_xxx
47
+
48
+ # 在 create_parser() 中添加:
49
+ xxx_parser = subparsers.add_parser("xxx", help="xxx 说明")
50
+
51
+ # 在 main() 中添加:
52
+ elif args.command == "xxx":
53
+ handle_xxx(args)
54
+ ```
@@ -0,0 +1 @@
1
+ """CLI 项目模板"""
@@ -0,0 +1,45 @@
1
+ """
2
+ {{ project_name }} - CLI 工具入口
3
+ 创建时间: {{ create_time }}
4
+ 作者: {{ author }}
5
+ """
6
+ import argparse
7
+ import sys
8
+
9
+ from commands.hello import handle_hello
10
+
11
+
12
+ def create_parser() -> argparse.ArgumentParser:
13
+ """创建命令行解析器"""
14
+ parser = argparse.ArgumentParser(
15
+ prog="{{ project_name }}",
16
+ description="{{ project_name }} - CLI 命令行工具",
17
+ )
18
+
19
+ subparsers = parser.add_subparsers(dest="command", help="子命令")
20
+
21
+ # hello 子命令
22
+ hello_parser = subparsers.add_parser("hello", help="打招呼")
23
+ hello_parser.add_argument("--name", default="World", help="名字")
24
+
25
+ # version 子命令
26
+ subparsers.add_parser("version", help="显示版本")
27
+
28
+ return parser
29
+
30
+
31
+ def main() -> None:
32
+ """CLI 主入口"""
33
+ parser = create_parser()
34
+ args = parser.parse_args()
35
+
36
+ if args.command == "hello":
37
+ handle_hello(args)
38
+ elif args.command == "version":
39
+ print("{{ project_name }} 1.0.0")
40
+ else:
41
+ parser.print_help()
42
+
43
+
44
+ if __name__ == "__main__":
45
+ main()
@@ -0,0 +1 @@
1
+ """子命令模块"""
@@ -0,0 +1,6 @@
1
+ """hello 子命令"""
2
+
3
+
4
+ def handle_hello(args) -> None:
5
+ """处理 hello 命令"""
6
+ print(f"Hello, {args.name}!")
@@ -0,0 +1,14 @@
1
+ [project]
2
+ name = "{{ project_name }}"
3
+ version = "0.1.0"
4
+ description = "{{ project_name }} - CLI 命令行工具"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = []
8
+
9
+ [project.scripts]
10
+ {{ short_project_name }} = "app:main"
11
+
12
+ [build-system]
13
+ requires = ["hatchling"]
14
+ build-backend = "hatchling.build"
@@ -0,0 +1,3 @@
1
+ """
2
+ 工具函数模块
3
+ """
nexus/__init__.py ADDED
@@ -0,0 +1,40 @@
1
+ import subprocess
2
+ import sys
3
+ import os
4
+ import glob
5
+ import argparse
6
+
7
+
8
+ NEXUS_URL = "http://192.168.2.174:8081"
9
+ USERNAME = "Osulcode.xiao"
10
+ PASSWORD = "Osulcode.xiao@123"
11
+ REPOSITORY = "shengye"
12
+
13
+
14
+ def main():
15
+ parser = argparse.ArgumentParser(prog="sypublish", description="上传 whl 到 Nexus")
16
+ parser.add_argument("file", nargs="?", help="whl 文件路径 (默认: dist/*.whl 中最新的)")
17
+ parser.add_argument("--url", default=NEXUS_URL, help=f"Nexus 地址 (默认: {NEXUS_URL})")
18
+ parser.add_argument("-r", "--repository", default=REPOSITORY, help=f"仓库名称 (默认: {REPOSITORY})")
19
+ args = parser.parse_args()
20
+
21
+ if args.file:
22
+ file_path = args.file
23
+ else:
24
+ dist_files = sorted(glob.glob("dist/*.whl"))
25
+ if not dist_files:
26
+ print("No .whl file found in dist/")
27
+ sys.exit(1)
28
+ file_path = dist_files[-1]
29
+
30
+ if not os.path.exists(file_path):
31
+ print(f"File not found: {file_path}")
32
+ sys.exit(1)
33
+
34
+ repo_url = f"{args.url}/repository/{args.repository}/"
35
+ cmd = ["twine", "upload", "--repository-url", repo_url, file_path, "-u", USERNAME, "-p", PASSWORD]
36
+ sys.exit(subprocess.call(cmd))
37
+
38
+
39
+ if __name__ == "__main__":
40
+ main()
sycli/agents/prompts.py CHANGED
@@ -379,3 +379,91 @@ SERVICE_REPORT:
379
379
  notes: {any relevant notes about the service state}
380
380
  ```
381
381
  """
382
+
383
+ # ── Direction Correction (E2) ────────────────────────────────────
384
+
385
+ DIRECTION_CORRECTION_PROMPT = """\
386
+ 你是一个架构纠偏专家。RL 优化 agent 已经在错误方向上持续了多轮。
387
+
388
+ ## 失败轨迹
389
+ {failure_trajectory}
390
+
391
+ ## 当前架构信息
392
+ - 框架: {framework}
393
+ - 架构模式: {architecture_pattern}
394
+ - 启动方式: {startup_method}
395
+ - 关键模块: {key_modules}
396
+
397
+ ## 分析要求
398
+ 1. 指出当前方向的**根本问题**(不是表面症状)
399
+ 2. 分析为什么 LLM 一直在错误方向上尝试(可能的认知偏差)
400
+ 3. 建议一个**完全不同**的架构/实现方向
401
+ 4. 列出需要**避免**的策略类型
402
+ 5. 列出**应该尝试**的策略类型
403
+
404
+ ## 输出格式(严格遵守)
405
+
406
+ CORRECTION_ANALYSIS:
407
+ root_cause: |
408
+ (根本原因分析)
409
+ wrong_directions:
410
+ - (错误方向1)
411
+ - (错误方向2)
412
+ suggested_direction: |
413
+ (建议的新方向)
414
+ constraints:
415
+ - (必须遵守的约束1)
416
+ - (必须遵守的约束2)
417
+ avoid_strategies:
418
+ - (应避免的策略类型1)
419
+ - (应避免的策略类型2)
420
+ recommended_strategies:
421
+ - name: (策略名称)
422
+ approach: (具体方法)
423
+ focus: (聚焦领域)
424
+ """
425
+
426
+ # ── Pre-Analysis (E5) ────────────────────────────────────────────
427
+
428
+ PRE_ANALYSIS_PROMPT = """\
429
+ 你是一个项目分析专家。在开始 RL 优化前,请对项目进行深度预分析。
430
+
431
+ ## 项目环境信息
432
+ {environment_info}
433
+
434
+ ## 预分析目标
435
+ 1. **启动方式分析**: 项目如何启动?有哪些可选的启动方式?
436
+ 2. **架构模式识别**: 项目采用了什么架构模式?各模块职责是什么?
437
+ 3. **方案优劣评估**: 当前架构方案的优势和不足是什么?
438
+ 4. **风险识别**: 优化过程中可能遇到的主要风险
439
+ 5. **优化建议**: 建议的优化方向和优先级
440
+
441
+ ## 输出格式(严格遵守)
442
+
443
+ PRE_ANALYSIS:
444
+ startup_analysis:
445
+ primary_method: (主要启动方式)
446
+ alternative_methods: (其他可选方式)
447
+ configuration: (关键配置)
448
+ architecture_analysis:
449
+ pattern: (架构模式名称)
450
+ description: (模式描述)
451
+ modules:
452
+ - name: (模块名)
453
+ role: (职责描述)
454
+ dependencies: (依赖的其他模块)
455
+ data_flow: (数据流向描述)
456
+ strengths:
457
+ - (优势1)
458
+ - (优势2)
459
+ weaknesses:
460
+ - (不足1)
461
+ - (不足2)
462
+ risks:
463
+ - risk: (风险描述)
464
+ mitigation: (缓解措施)
465
+ optimization_priorities:
466
+ - priority: HIGH|MEDIUM|LOW
467
+ area: (优化领域)
468
+ reason: (原因)
469
+ """
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import json
7
+ import subprocess
7
8
  from pathlib import Path
8
9
 
9
10
  from sycli.core.backend import create_backend
@@ -14,6 +15,99 @@ from sycli.mode.create import run_create
14
15
  from sycli.mode.optimize import run_optimize
15
16
 
16
17
 
18
+ def _show_resume_summary(sycli_dir: Path, state: dict) -> None:
19
+ """显示恢复前的进展摘要。"""
20
+ # 尝试从 optimization_log.jsonl 读取最近事件
21
+ log_file = sycli_dir / "optimization_log.jsonl"
22
+ if log_file.exists():
23
+ try:
24
+ recent_events: list[dict] = []
25
+ with open(log_file, encoding="utf-8") as f:
26
+ for line in f:
27
+ line = line.strip()
28
+ if line:
29
+ recent_events.append(json.loads(line))
30
+ # 只保留 round_end 事件
31
+ round_ends = [e for e in recent_events if e.get("event") == "round_end"]
32
+ if round_ends:
33
+ info("最近轮次:")
34
+ for ev in round_ends[-5:]:
35
+ data = ev.get("data", {})
36
+ r = ev.get("round", "?")
37
+ score = data.get("score", 0)
38
+ decision = data.get("decision", "?")
39
+ strategy = data.get("strategy", "")
40
+ info(f" R{r}: score={score:.3f}, decision={decision}, strategy={strategy}")
41
+ except Exception:
42
+ pass
43
+
44
+ # 从 rounds.json 读取最佳得分
45
+ rounds_file = sycli_dir / "rl" / "rounds.json"
46
+ if rounds_file.exists():
47
+ try:
48
+ with open(rounds_file) as f:
49
+ rounds = json.load(f)
50
+ if rounds:
51
+ best = max(rounds, key=lambda r: r.get("score", 0))
52
+ info(f"最佳轮次: R{best.get('round_number', '?')}, score={best.get('score', 0):.3f}")
53
+ total_rounds = len(rounds)
54
+ info(f"已完成轮次: {total_rounds}")
55
+ except Exception:
56
+ pass
57
+
58
+
59
+ def _verify_git_state(project_root: Path, state: dict) -> tuple[bool, str]:
60
+ """验证 git 状态与记录一致。
61
+
62
+ Returns:
63
+ (is_consistent, message)
64
+ """
65
+ last_commit = state.get("last_git_commit", "")
66
+ if not last_commit:
67
+ return True, "无 git commit 记录(跳过校验)"
68
+
69
+ try:
70
+ result = subprocess.run(
71
+ ["git", "rev-parse", "HEAD"],
72
+ capture_output=True, text=True, cwd=str(project_root), timeout=10,
73
+ )
74
+ if result.returncode != 0:
75
+ return True, "非 git 仓库(跳过校验)"
76
+
77
+ current = result.stdout.strip()
78
+ if current == last_commit:
79
+ return True, f"git HEAD 一致 ({current[:8]})"
80
+
81
+ # 检查是否有未提交的修改
82
+ status_result = subprocess.run(
83
+ ["git", "status", "--porcelain"],
84
+ capture_output=True, text=True, cwd=str(project_root), timeout=10,
85
+ )
86
+ dirty = bool(status_result.stdout.strip())
87
+
88
+ msg = f"git HEAD 不一致: 记录={last_commit[:8]}, 当前={current[:8]}"
89
+ if dirty:
90
+ msg += "(且有未提交修改)"
91
+ return False, msg
92
+
93
+ except (FileNotFoundError, subprocess.TimeoutExpired):
94
+ return True, "git 不可用(跳过校验)"
95
+
96
+
97
+ def _get_git_head(project_root: Path) -> str:
98
+ """获取当前 git HEAD commit hash。"""
99
+ try:
100
+ result = subprocess.run(
101
+ ["git", "rev-parse", "HEAD"],
102
+ capture_output=True, text=True, cwd=str(project_root), timeout=10,
103
+ )
104
+ if result.returncode == 0:
105
+ return result.stdout.strip()
106
+ except Exception:
107
+ pass
108
+ return ""
109
+
110
+
17
111
  def handle_resume(args) -> int:
18
112
  """Handle `sycli resume` command - resume an interrupted optimization."""
19
113
  project_root = Path(args.project_root or ".").resolve()
@@ -63,6 +157,19 @@ def handle_resume(args) -> int:
63
157
  info(f"Status was: {status}")
64
158
  info(f"Task: {task}")
65
159
 
160
+ # ── E4: 进展摘要 ──
161
+ info("")
162
+ info("── 进展摘要 ──")
163
+ _show_resume_summary(sycli_dir, state)
164
+
165
+ # ── E4: Git 状态校验 ──
166
+ info("")
167
+ info("── Git 状态校验 ──")
168
+ is_consistent, git_msg = _verify_git_state(project_root, state)
169
+ info(f" {git_msg}")
170
+ if not is_consistent:
171
+ warning("代码可能已被外部修改,优化结果可能不可预测。")
172
+
66
173
  # Initialize components
67
174
  try:
68
175
  llm = create_llm(config.llm, streaming=True)
sycli/mode/create.py CHANGED
@@ -12,7 +12,7 @@ from typing import Any
12
12
  from deepagents import create_deep_agent
13
13
  from deepagents.backends.local_shell import LocalShellBackend
14
14
  from langchain_core.language_models import BaseChatModel
15
- from langgraph.checkpoint.memory import MemorySaver
15
+ from sycli.rl.checkpoint import FileCheckpointer
16
16
 
17
17
  from sycli.agents.factory import build_subagents, _memory_middleware, _inject_project_root
18
18
  from sycli.agents.prompts import ARCHITECT_PROMPT, DATA_ANALYST_PROMPT, ORCHESTRATOR_PROMPT
@@ -119,7 +119,7 @@ async def run_create(
119
119
  header("Phase 3: Build & Iterate")
120
120
 
121
121
  subagents = build_subagents(config, llm, backend, project_name, mode="create", project_root=str(sycli_dir.parent))
122
- checkpointer = MemorySaver()
122
+ checkpointer = FileCheckpointer(sycli_dir / "checkpoints")
123
123
  thread_id = f"sycli-create-{project_name}-{uuid.uuid4().hex[:8]}"
124
124
 
125
125
  agent = create_deep_agent(
@@ -355,7 +355,7 @@ async def _run_architect_phase(
355
355
  system_prompt=_inject_project_root(ARCHITECT_PROMPT, str(backend.cwd)),
356
356
  subagents=[researcher_subagent],
357
357
  memory=[],
358
- checkpointer=MemorySaver(),
358
+ checkpointer=FileCheckpointer(sycli_dir / "checkpoints"),
359
359
  debug=False,
360
360
  name="sycli-architect",
361
361
  )
@@ -422,7 +422,7 @@ async def _run_data_analyst_phase(
422
422
  system_prompt=_inject_project_root(DATA_ANALYST_PROMPT, str(backend.cwd)),
423
423
  subagents=[researcher_subagent],
424
424
  memory=[],
425
- checkpointer=MemorySaver(),
425
+ checkpointer=FileCheckpointer(sycli_dir / "checkpoints"),
426
426
  debug=False,
427
427
  name="sycli-data-analyst",
428
428
  )
@@ -76,6 +76,7 @@ class RLSettings(BaseModel):
76
76
  convergence_strategy: str = "window" # window | ema | patience
77
77
  convergence_threshold: float = 0.05
78
78
  convergence_window_size: int = 10
79
+ convergence_near_target_margin: float = 0.02 # 接近目标时的容差(#8)
79
80
  regression_penalty_weight: float = 0.3
80
81
  auto_fix_compilation: bool = True
81
82
  auto_fix_max_retries: int = 5
@@ -84,6 +85,13 @@ class RLSettings(BaseModel):
84
85
  exploration_epsilon_start: float = 0.3
85
86
  exploration_epsilon_end: float = 0.05
86
87
  exploration_epsilon_decay: float = 0.95
88
+ accept_threshold: float = 0.8 # ACCEPT 决策的准确率阈值,默认 0.8(#4)
89
+ experience_keep_rounds: int = 30 # 经验存储保留的最近轮数(#15)
90
+ perturbation_restart: bool = True # 停滞但未达标时是否扰动重启(#10)
91
+ max_bandit_arms: int = 20 # Bandit 最大臂数量,超过时淘汰低效臂(#6)
92
+ direction_correction_enabled: bool = True # 是否启用方向纠偏(E2)
93
+ direction_correction_rollback_threshold: int = 3 # 连续 ROLLBACK 触发数
94
+ direction_correction_decline_rounds: int = 5 # 持续下降触发轮数
87
95
 
88
96
 
89
97
  class MemorySettings(BaseModel):