opencode-collaboration 2.0.0__py3-none-any.whl → 2.1.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.
src/core/monitor.py ADDED
@@ -0,0 +1,268 @@
1
+ """监控告警模块。
2
+
3
+ 功能:
4
+ 1. 资源监控 - CPU、内存、磁盘使用率
5
+ 2. 告警机制 - 基于阈值的告警通知
6
+ 3. 状态报告 - 生成系统状态报告
7
+ """
8
+ import time
9
+ import logging
10
+ import threading
11
+ from typing import Dict, List, Optional, Callable
12
+ from dataclasses import dataclass, field
13
+ from datetime import datetime, timedelta
14
+ from enum import Enum
15
+ import psutil
16
+
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class AlertLevel(Enum):
22
+ """告警级别。"""
23
+ INFO = "INFO"
24
+ WARNING = "WARNING"
25
+ ERROR = "ERROR"
26
+ CRITICAL = "CRITICAL"
27
+
28
+
29
+ @dataclass
30
+ class Alert:
31
+ """告警信息。"""
32
+ level: AlertLevel
33
+ message: str
34
+ timestamp: datetime = field(default_factory=datetime.now)
35
+ metric: str = ""
36
+ value: float = 0.0
37
+ threshold: float = 0.0
38
+
39
+ def __str__(self):
40
+ return f"[{self.level.value}] {self.timestamp.strftime('%Y-%m-%d %H:%M:%S')} - {self.message}"
41
+
42
+
43
+ class ResourceMonitor:
44
+ """资源监控器。"""
45
+
46
+ def __init__(
47
+ self,
48
+ cpu_threshold: float = 80.0,
49
+ memory_threshold: float = 85.0,
50
+ disk_threshold: float = 90.0,
51
+ sample_interval: int = 10
52
+ ):
53
+ """初始化。
54
+
55
+ Args:
56
+ cpu_threshold: CPU 使用率阈值 (%)
57
+ memory_threshold: 内存使用率阈值 (%)
58
+ disk_threshold: 磁盘使用率阈值 (%)
59
+ sample_interval: 采样间隔 (秒)
60
+ """
61
+ self.cpu_threshold = cpu_threshold
62
+ self.memory_threshold = memory_threshold
63
+ self.disk_threshold = disk_threshold
64
+ self.sample_interval = sample_interval
65
+
66
+ self.alerts: List[Alert] = []
67
+ self.alert_callbacks: List[Callable[[Alert], None]] = []
68
+
69
+ self.stats = {
70
+ "cpu_samples": [],
71
+ "memory_samples": [],
72
+ "disk_samples": [],
73
+ "restart_count": 0,
74
+ "git_operations": 0,
75
+ "exception_count": 0
76
+ }
77
+
78
+ self._stop_event = threading.Event()
79
+ self._monitor_thread: Optional[threading.Thread] = None
80
+ self._cpu_percent = psutil.cpu_percent(interval=None)
81
+
82
+ def get_current_stats(self) -> Dict:
83
+ """获取当前资源使用情况。"""
84
+ return {
85
+ "cpu_percent": psutil.cpu_percent(interval=1),
86
+ "memory_percent": psutil.virtual_memory().percent,
87
+ "disk_percent": psutil.disk_usage('/').percent,
88
+ "cpu_count": psutil.cpu_count(),
89
+ "memory_used_gb": psutil.virtual_memory().used / (1024**3),
90
+ "memory_total_gb": psutil.virtual_memory().total / (1024**3),
91
+ "boot_time": psutil.boot_time()
92
+ }
93
+
94
+ def check_thresholds(self) -> List[Alert]:
95
+ """检查阈值,返回告警列表。"""
96
+ alerts = []
97
+ stats = self.get_current_stats()
98
+
99
+ if stats["cpu_percent"] > self.cpu_threshold:
100
+ alert = Alert(
101
+ level=AlertLevel.WARNING,
102
+ message=f"CPU 使用率 {stats['cpu_percent']:.1f}% (阈值: {self.cpu_threshold}%)",
103
+ metric="cpu_percent",
104
+ value=stats["cpu_percent"],
105
+ threshold=self.cpu_threshold
106
+ )
107
+ alerts.append(alert)
108
+
109
+ if stats["memory_percent"] > self.memory_threshold:
110
+ alert = Alert(
111
+ level=AlertLevel.WARNING,
112
+ message=f"内存使用率 {stats['memory_percent']:.1f}% (阈值: {self.memory_threshold}%)",
113
+ metric="memory_percent",
114
+ value=stats["memory_percent"],
115
+ threshold=self.memory_threshold
116
+ )
117
+ alerts.append(alert)
118
+
119
+ if stats["disk_percent"] > self.disk_threshold:
120
+ alert = Alert(
121
+ level=AlertLevel.WARNING,
122
+ message=f"磁盘使用率 {stats['disk_percent']:.1f}% (阈值: {self.disk_threshold}%)",
123
+ metric="disk_percent",
124
+ value=stats["disk_percent"],
125
+ threshold=self.disk_threshold
126
+ )
127
+ alerts.append(alert)
128
+
129
+ self.alerts.extend(alerts)
130
+
131
+ for alert in alerts:
132
+ self._notify_alert(alert)
133
+
134
+ return alerts
135
+
136
+ def register_alert_callback(self, callback: Callable[[Alert], None]):
137
+ """注册告警回调。"""
138
+ self.alert_callbacks.append(callback)
139
+
140
+ def _notify_alert(self, alert: Alert):
141
+ """发送告警通知。"""
142
+ for callback in self.alert_callbacks:
143
+ try:
144
+ callback(alert)
145
+ except Exception as e:
146
+ logger.error(f"告警回调执行失败: {e}")
147
+
148
+ def get_recent_alerts(self, count: int = 10) -> List[Alert]:
149
+ """获取最近告警。"""
150
+ return self.alerts[-count:]
151
+
152
+ def clear_old_alerts(self, hours: int = 24) -> int:
153
+ """清理旧告警。"""
154
+ cutoff = datetime.now() - timedelta(hours=hours)
155
+ old_count = len(self.alerts)
156
+ self.alerts = [a for a in self.alerts if a.timestamp > cutoff]
157
+ return old_count - len(self.alerts)
158
+
159
+ def get_status_report(self) -> Dict:
160
+ """生成状态报告。"""
161
+ current = self.get_current_stats()
162
+
163
+ status = "healthy"
164
+ if current["cpu_percent"] > self.cpu_threshold:
165
+ status = "warning"
166
+ if current["memory_percent"] > self.memory_threshold:
167
+ status = "warning"
168
+ if current["disk_percent"] > self.disk_threshold:
169
+ status = "critical"
170
+
171
+ return {
172
+ "status": status,
173
+ "resources": {
174
+ "cpu": f"{current['cpu_percent']:.1f}%",
175
+ "memory": f"{current['memory_percent']:.1f}%",
176
+ "disk": f"{current['disk_percent']:.1f}%"
177
+ },
178
+ "stats": {
179
+ "restart_count": self.stats["restart_count"],
180
+ "git_operations": self.stats["git_operations"],
181
+ "exception_count": self.stats["exception_count"]
182
+ },
183
+ "recent_alerts": len(self.alerts[-10:]),
184
+ "alerts": [str(a) for a in self.alerts[-5:]]
185
+ }
186
+
187
+ def increment_restart_count(self):
188
+ """增加重启计数。"""
189
+ self.stats["restart_count"] += 1
190
+
191
+ def increment_git_operations(self):
192
+ """增加 Git 操作计数。"""
193
+ self.stats["git_operations"] += 1
194
+
195
+ def increment_exception_count(self):
196
+ """增加异常计数。"""
197
+ self.stats["exception_count"] += 1
198
+
199
+ def start_monitoring(self):
200
+ """开始监控循环。"""
201
+ if self._monitor_thread is not None and self._monitor_thread.is_alive():
202
+ return
203
+
204
+ self._stop_event.clear()
205
+ self._monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
206
+ self._monitor_thread.start()
207
+ logger.info("资源监控已启动")
208
+
209
+ def stop_monitoring(self):
210
+ """停止监控循环。"""
211
+ self._stop_event.set()
212
+ if self._monitor_thread is not None:
213
+ self._monitor_thread.join(timeout=5)
214
+ self._monitor_thread = None
215
+ logger.info("资源监控已停止")
216
+
217
+ def _monitor_loop(self):
218
+ """监控循环。"""
219
+ while not self._stop_event.is_set():
220
+ try:
221
+ self.check_thresholds()
222
+ except Exception as e:
223
+ logger.error(f"监控检查失败: {e}")
224
+
225
+ self._stop_event.wait(timeout=self.sample_interval)
226
+
227
+ def get_cpu_percent(self) -> float:
228
+ """获取 CPU 使用率(非阻塞)。"""
229
+ return psutil.cpu_percent(interval=None)
230
+
231
+
232
+ def get_system_info() -> Dict:
233
+ """获取系统信息。"""
234
+ return {
235
+ "cpu_count": psutil.cpu_count(),
236
+ "cpu_freq": psutil.cpu_freq()._asdict() if psutil.cpu_freq() else {},
237
+ "memory": {
238
+ "total_gb": psutil.virtual_memory().total / (1024**3),
239
+ "available_gb": psutil.virtual_memory().available / (1024**3),
240
+ "percent": psutil.virtual_memory().percent
241
+ },
242
+ "disk": {
243
+ "total_gb": psutil.disk_usage('/').total / (1024**3),
244
+ "free_gb": psutil.disk_usage('/').free / (1024**3),
245
+ "percent": psutil.disk_usage('/').percent
246
+ },
247
+ "boot_time": datetime.fromtimestamp(psutil.boot_time()).isoformat()
248
+ }
249
+
250
+
251
+ if __name__ == "__main__":
252
+ logging.basicConfig(level=logging.INFO)
253
+
254
+ monitor = ResourceMonitor(cpu_threshold=80.0, memory_threshold=85.0, disk_threshold=90.0)
255
+
256
+ print("系统信息:")
257
+ print(get_system_info())
258
+
259
+ print("\n当前状态:")
260
+ print(monitor.get_current_stats())
261
+
262
+ print("\n状态报告:")
263
+ print(monitor.get_status_report())
264
+
265
+ print("\n阈值检查:")
266
+ alerts = monitor.check_thresholds()
267
+ for alert in alerts:
268
+ print(f" {alert}")
@@ -0,0 +1,404 @@
1
+ """State 版本迁移器。
2
+
3
+ 功能:
4
+ 1. 将旧版本的 state 文件迁移到新版本
5
+ 2. 支持 v1.0 → v2.0 → v2.1 的迁移
6
+ 3. 迁移前自动备份
7
+ 4. 验证迁移完整性
8
+ """
9
+ import shutil
10
+ import logging
11
+ from pathlib import Path
12
+ from typing import Dict, Any, Optional, Tuple
13
+ from datetime import datetime
14
+ import yaml
15
+
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class StateMigrator:
21
+ """State 版本迁移器。"""
22
+
23
+ # 支持的迁移路径
24
+ MIGRATION_PATHS = {
25
+ "1.0": ["2.0"],
26
+ "1.1": ["2.0"],
27
+ "2.0": ["2.1"],
28
+ }
29
+
30
+ CURRENT_VERSION = "2.1.0"
31
+
32
+ def __init__(
33
+ self,
34
+ state_path: str,
35
+ backup_dir: Optional[str] = None,
36
+ dry_run: bool = False
37
+ ):
38
+ """初始化迁移器。
39
+
40
+ Args:
41
+ state_path: State 文件路径
42
+ backup_dir: 备份目录 (默认: state/backups)
43
+ dry_run: 演练模式,不实际修改文件
44
+ """
45
+ self.state_path = Path(state_path)
46
+ self.backup_dir = Path(backup_dir) if backup_dir else self.state_path.parent / "backups"
47
+ self.dry_run = dry_run
48
+
49
+ if not self.dry_run:
50
+ self.backup_dir.mkdir(parents=True, exist_ok=True)
51
+
52
+ def get_current_version(self, state: Dict[str, Any]) -> str:
53
+ """获取当前版本号。"""
54
+ return state.get("version", "1.0")
55
+
56
+ def needs_migration(self, state: Dict[str, Any]) -> bool:
57
+ """检查是否需要迁移。"""
58
+ current_version = self.get_current_version(state)
59
+ return current_version != self.CURRENT_VERSION
60
+
61
+ def migrate(self, state: Dict[str, Any]) -> Tuple[bool, Dict[str, Any]]:
62
+ """执行迁移。
63
+
64
+ Args:
65
+ state: State 字典
66
+
67
+ Returns:
68
+ (是否成功, 迁移后的状态或错误信息)
69
+ """
70
+ if not self.needs_migration(state):
71
+ return True, state
72
+
73
+ current_version = self.get_current_version(state)
74
+
75
+ logger.info(f"开始迁移: {current_version} → {self.CURRENT_VERSION}")
76
+
77
+ # 备份原始状态
78
+ if not self.dry_run:
79
+ self._create_backup(state)
80
+
81
+ migrated_state = state.copy()
82
+
83
+ # 执行迁移
84
+ # v1.x → v2.0
85
+ if current_version in ["1.0", "1.1"]:
86
+ migrated_state = self._migrate_v1_to_v2(migrated_state)
87
+ current_version = "2.0"
88
+
89
+ # v2.0 → v2.1
90
+ if current_version == "2.0":
91
+ migrated_state = self._migrate_v2_to_v2_1(migrated_state)
92
+ current_version = "2.1"
93
+
94
+ # 更新版本号
95
+ migrated_state["version"] = self.CURRENT_VERSION
96
+
97
+ # 验证迁移结果
98
+ is_valid, error = self._validate_migration(state, migrated_state)
99
+ if not is_valid:
100
+ logger.error(f"迁移验证失败: {error}")
101
+ if not self.dry_run:
102
+ self._rollback(state)
103
+ return False, {"error": error}
104
+
105
+ logger.info(f"迁移成功: {self.CURRENT_VERSION}")
106
+
107
+ return True, migrated_state
108
+
109
+ def migrate_file(self, output_path: Optional[str] = None) -> Tuple[bool, str]:
110
+ """迁移 State 文件。
111
+
112
+ Args:
113
+ output_path: 输出路径 (默认: 覆盖原文件)
114
+
115
+ Returns:
116
+ (是否成功, 消息)
117
+ """
118
+ # 读取原始状态
119
+ if not self.state_path.exists():
120
+ return False, f"文件不存在: {self.state_path}"
121
+
122
+ with open(self.state_path, 'r') as f:
123
+ state = yaml.safe_load(f)
124
+
125
+ # 执行迁移
126
+ success, result = self.migrate(state)
127
+
128
+ if not success:
129
+ return False, result.get("error", "迁移失败")
130
+
131
+ # 保存迁移结果
132
+ output = output_path or str(self.state_path)
133
+
134
+ if not self.dry_run:
135
+ with open(output, 'w') as f:
136
+ yaml.dump(result, f, allow_unicode=True, sort_keys=False)
137
+
138
+ logger.info(f"已保存迁移结果: {output}")
139
+
140
+ return True, f"迁移成功: {result['version']}"
141
+
142
+ def _migrate_v1_to_v2(self, state: Dict[str, Any]) -> Dict[str, Any]:
143
+ """v1.x → v2.0 迁移。"""
144
+ logger.info("执行迁移: v1.x → v2.0")
145
+
146
+ migrated = state.copy()
147
+
148
+ # 1. phase 从根级迁移到 project.phase
149
+ if "phase" in migrated:
150
+ if "project" not in migrated:
151
+ migrated["project"] = {}
152
+
153
+ # 只有 project.phase 为空时才迁移
154
+ if not migrated["project"].get("phase"):
155
+ migrated["project"]["phase"] = migrated.pop("phase")
156
+ logger.info(" ✓ phase 迁移到 project.phase")
157
+
158
+ # 2. requirements 从字典转为列表
159
+ if "requirements" in migrated:
160
+ old_req = migrated["requirements"]
161
+ if isinstance(old_req, dict):
162
+ migrated["requirements"] = [{
163
+ "version": old_req.get("version", ""),
164
+ "status": old_req.get("status", "pending"),
165
+ "pm_signoff": old_req.get("pm_signoff", False),
166
+ "dev_signoff": old_req.get("dev_signoff", False)
167
+ }]
168
+ logger.info(" ✓ requirements 转换为列表格式")
169
+
170
+ # 3. design 从字典转为列表
171
+ if "design" in migrated:
172
+ old_design = migrated["design"]
173
+ if isinstance(old_design, dict):
174
+ migrated["design"] = [{
175
+ "version": old_design.get("version", "v1"),
176
+ "status": old_design.get("status", "pending"),
177
+ "pm_signoff": old_design.get("pm_signoff", False),
178
+ "dev_signoff": old_design.get("dev_signoff", False),
179
+ "document": old_design.get("document", ""),
180
+ "review_document": old_design.get("review_document", "")
181
+ }]
182
+ logger.info(" ✓ design 转换为列表格式")
183
+
184
+ # 4. 添加 history 字段(如果不存在)
185
+ if "history" not in migrated:
186
+ migrated["history"] = [{
187
+ "id": "migration_v1_to_v2",
188
+ "timestamp": datetime.now().isoformat(),
189
+ "action": "migration",
190
+ "agent_id": "system",
191
+ "details": "从 v1.x 迁移到 v2.0"
192
+ }]
193
+
194
+ migrated["version"] = "2.0"
195
+
196
+ return migrated
197
+
198
+ def _migrate_v2_to_v2_1(self, state: Dict[str, Any]) -> Dict[str, Any]:
199
+ """v2.0 → v2.1 迁移。"""
200
+ logger.info("执行迁移: v2.0 → v2.1")
201
+
202
+ migrated = state.copy()
203
+
204
+ # 1. 添加 agent_constraints 字段
205
+ if "agent_constraints" not in migrated:
206
+ migrated["agent_constraints"] = {
207
+ "version": "1.0",
208
+ "last_updated": datetime.now().isoformat(),
209
+ "agent1": {
210
+ "role": "产品经理",
211
+ "allowed_actions": [
212
+ "CREATE_REQUIREMENTS",
213
+ "REVIEW_DESIGN",
214
+ "EXECUTE_BLACKBOX_TEST",
215
+ "CONFIRM_DEPLOYMENT",
216
+ "SIGN_OFF"
217
+ ],
218
+ "forbidden_actions": [
219
+ "CREATE_DESIGN",
220
+ "WRITE_CODE",
221
+ "EXECUTE_WHITEBOX_TEST",
222
+ "UPLOAD_PYPI"
223
+ ]
224
+ },
225
+ "agent2": {
226
+ "role": "开发",
227
+ "allowed_actions": [
228
+ "REVIEW_REQUIREMENTS",
229
+ "CREATE_DESIGN",
230
+ "WRITE_CODE",
231
+ "EXECUTE_WHITEBOX_TEST",
232
+ "UPLOAD_PYPI",
233
+ "SUPLEMENT_REQUIREMENTS"
234
+ ],
235
+ "forbidden_actions": [
236
+ "CREATE_REQUIREMENTS",
237
+ "SIGN_OFF_REQUIREMENTS",
238
+ "CONFIRM_DEPLOYMENT"
239
+ ]
240
+ }
241
+ }
242
+ logger.info(" ✓ 添加 agent_constraints 字段")
243
+
244
+ # 2. 添加 iteration 字段(如果不存在且 project.phase 存在)
245
+ if "iteration" not in migrated and "project" in migrated:
246
+ phase = migrated["project"].get("phase", "unknown")
247
+ migrated["iteration"] = {
248
+ "current": "v2.1.0",
249
+ "start_date": datetime.now().isoformat(),
250
+ "status": phase
251
+ }
252
+ logger.info(" ✓ 添加 iteration 字段")
253
+
254
+ # 3. 添加 history 记录
255
+ if "history" not in migrated:
256
+ migrated["history"] = []
257
+
258
+ migrated["history"].insert(0, {
259
+ "id": "migration_v2_to_v2_1",
260
+ "timestamp": datetime.now().isoformat(),
261
+ "action": "migration",
262
+ "agent_id": "system",
263
+ "details": "从 v2.0 迁移到 v2.1"
264
+ })
265
+
266
+ migrated["version"] = "2.1"
267
+
268
+ return migrated
269
+
270
+ def _create_backup(self, state: Dict[str, Any]):
271
+ """创建备份。"""
272
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
273
+ backup_file = self.backup_dir / f"state_{timestamp}.yaml"
274
+
275
+ with open(backup_file, 'w') as f:
276
+ yaml.dump(state, f, allow_unicode=True, sort_keys=False)
277
+
278
+ logger.info(f"已创建备份: {backup_file}")
279
+
280
+ # 清理旧备份(保留最近 10 个)
281
+ backups = sorted(self.backup_dir.glob("state_*.yaml"))
282
+ for old_backup in backups[:-10]:
283
+ old_backup.unlink()
284
+ logger.info(f" 清理旧备份: {old_backup}")
285
+
286
+ def _rollback(self, original_state: Dict[str, Any]):
287
+ """回滚到原始状态。"""
288
+ backup_files = sorted(self.backup_dir.glob("state_*.yaml"))
289
+
290
+ if backup_files:
291
+ latest_backup = backup_files[-1]
292
+
293
+ with open(latest_backup, 'r') as f:
294
+ backup_state = yaml.safe_load(f)
295
+
296
+ with open(self.state_path, 'w') as f:
297
+ yaml.dump(backup_state, f, allow_unicode=True, sort_keys=False)
298
+
299
+ logger.info(f"已回滚到备份: {latest_backup}")
300
+
301
+ def _validate_migration(
302
+ self,
303
+ original: Dict[str, Any],
304
+ migrated: Dict[str, Any]
305
+ ) -> Tuple[bool, Optional[str]]:
306
+ """验证迁移结果。
307
+
308
+ Args:
309
+ original: 原始状态
310
+ migrated: 迁移后的状态
311
+
312
+ Returns:
313
+ (是否有效, 错误消息)
314
+ """
315
+ # 检查版本号
316
+ if migrated.get("version") != self.CURRENT_VERSION:
317
+ return False, f"版本号错误: 期望 {self.CURRENT_VERSION}, 实际 {migrated.get('version')}"
318
+
319
+ # 检查必填字段
320
+ required_fields = ["project", "requirements", "design", "test", "development", "deployment"]
321
+ for field in required_fields:
322
+ if field not in migrated:
323
+ return False, f"字段缺失: {field}"
324
+
325
+ # 检查 project.phase
326
+ if "phase" in migrated:
327
+ return False, "phase 应该在 project 下,不应该在根级"
328
+
329
+ logger.info("迁移验证通过")
330
+
331
+ return True, None
332
+
333
+
334
+ def migrate_state_file(
335
+ state_path: str,
336
+ backup: bool = True,
337
+ dry_run: bool = False
338
+ ) -> bool:
339
+ """迁移 State 文件的命令行工具。
340
+
341
+ Args:
342
+ state_path: State 文件路径
343
+ backup: 是否备份
344
+ dry_run: 演练模式
345
+
346
+ Returns:
347
+ 是否成功
348
+ """
349
+ import sys
350
+
351
+ migrator = StateMigrator(
352
+ state_path=state_path,
353
+ dry_run=dry_run
354
+ )
355
+
356
+ # 读取当前状态
357
+ with open(state_path, 'r') as f:
358
+ state = yaml.safe_load(f)
359
+
360
+ current_version = state.get("version", "unknown")
361
+
362
+ print("\n" + "=" * 50)
363
+ print("🔄 State 迁移工具")
364
+ print("=" * 50)
365
+ print(f"当前版本: {current_version}")
366
+ print(f"目标版本: {migrator.CURRENT_VERSION}")
367
+ print(f"备份: {'是' if backup else '否'}")
368
+ print(f"演练模式: {'是' if dry_run else '否'}")
369
+ print("=" * 50)
370
+
371
+ if migrator.needs_migration(state):
372
+ if dry_run:
373
+ print("\n演练模式:以下是将要执行的迁移操作:")
374
+ # 显示迁移步骤
375
+ if current_version in ["1.0", "1.1"]:
376
+ print(" - phase 迁移到 project.phase")
377
+ print(" - requirements 转换为列表格式")
378
+ print(" - design 转换为列表格式")
379
+ if current_version in ["1.0", "1.1", "2.0"]:
380
+ print(" - 添加 agent_constraints 字段")
381
+ print(" - 添加 iteration 字段")
382
+ else:
383
+ success, message = migrator.migrate_file()
384
+
385
+ if success:
386
+ print(f"\n✅ {message}")
387
+ else:
388
+ print(f"\n❌ 迁移失败: {message}")
389
+ return False
390
+ else:
391
+ print(f"\n✅ 已是最新版本 ({current_version}),无需迁移")
392
+
393
+ print()
394
+ return True
395
+
396
+
397
+ if __name__ == "__main__":
398
+ import sys
399
+
400
+ state_path = sys.argv[1] if len(sys.argv) > 1 else "state/project_state.yaml"
401
+ backup = "--no-backup" not in sys.argv
402
+ dry_run = "--dry-run" in sys.argv
403
+
404
+ migrate_state_file(state_path, backup, dry_run)