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.
@@ -0,0 +1,564 @@
1
+ """State 结构验证器。
2
+
3
+ 功能:
4
+ 1. 验证 project_state.yaml 的结构是否符合 Schema
5
+ 2. 检测 State 文件格式与代码期望是否兼容
6
+ 3. 提供详细的验证错误信息
7
+ """
8
+ from typing import Dict, List, Any, Optional, Union
9
+ from enum import Enum
10
+ from dataclasses import dataclass, field
11
+ import logging
12
+
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class ValidationLevel(Enum):
18
+ """验证级别。"""
19
+ ERROR = "error"
20
+ WARNING = "warning"
21
+ INFO = "info"
22
+
23
+
24
+ class ValidationResult:
25
+ """验证结果。"""
26
+
27
+ def __init__(
28
+ self,
29
+ level: ValidationLevel,
30
+ field: str,
31
+ message: str,
32
+ suggestion: str = ""
33
+ ):
34
+ self.level = level
35
+ self.field = field
36
+ self.message = message
37
+ self.suggestion = suggestion
38
+
39
+ def __str__(self):
40
+ result = f"[{self.level.value.upper()}] {self.field}: {self.message}"
41
+ if self.suggestion:
42
+ result += f"\n 💡 {self.suggestion}"
43
+ return result
44
+
45
+ def to_dict(self) -> Dict:
46
+ """转换为字典。"""
47
+ return {
48
+ "level": self.level.value,
49
+ "field": self.field,
50
+ "message": self.message,
51
+ "suggestion": self.suggestion
52
+ }
53
+
54
+
55
+ class StateValidator:
56
+ """State 结构验证器。"""
57
+
58
+ # Schema 定义
59
+ SCHEMA = {
60
+ "version": {
61
+ "type": str,
62
+ "required": True,
63
+ "pattern": r"^\d+\.\d+\.\d+$",
64
+ "error_message": "version 格式不正确,期望格式: X.Y.Z"
65
+ },
66
+ "project": {
67
+ "type": dict,
68
+ "required": True,
69
+ "fields": {
70
+ "name": {
71
+ "type": str,
72
+ "required": True,
73
+ "error_message": "project.name 缺失"
74
+ },
75
+ "type": {
76
+ "type": str,
77
+ "required": True,
78
+ "error_message": "project.type 缺失"
79
+ },
80
+ "phase": {
81
+ "type": str,
82
+ "required": True,
83
+ "enum": ["unknown", "requirements", "design",
84
+ "development", "testing", "deployment",
85
+ "completed"],
86
+ "error_message": "project.phase 值无效"
87
+ }
88
+ }
89
+ },
90
+ "requirements": {
91
+ "type": [dict, list],
92
+ "required": True,
93
+ "error_message": "requirements 字段格式不正确"
94
+ },
95
+ "design": {
96
+ "type": [dict, list],
97
+ "required": True,
98
+ "error_message": "design 字段格式不正确"
99
+ },
100
+ "test": {
101
+ "type": dict,
102
+ "required": True,
103
+ "fields": {
104
+ "status": {
105
+ "type": str,
106
+ "enum": ["pending", "in_progress", "passed", "failed"],
107
+ "error_message": "test.status 值无效"
108
+ }
109
+ }
110
+ },
111
+ "development": {
112
+ "type": dict,
113
+ "required": True,
114
+ "fields": {
115
+ "status": {
116
+ "type": str,
117
+ "enum": ["pending", "in_progress", "completed"],
118
+ "error_message": "development.status 值无效"
119
+ }
120
+ }
121
+ },
122
+ "deployment": {
123
+ "type": dict,
124
+ "required": True,
125
+ "fields": {
126
+ "status": {
127
+ "type": str,
128
+ "enum": ["pending", "in_progress", "released"],
129
+ "error_message": "deployment.status 值无效"
130
+ }
131
+ }
132
+ },
133
+ "iteration": {
134
+ "type": dict,
135
+ "required": False,
136
+ "fields": {
137
+ "current": {"type": str, "required": False},
138
+ "status": {"type": str, "required": False}
139
+ }
140
+ },
141
+ "iterations": {
142
+ "type": dict,
143
+ "required": False,
144
+ "error_message": "iterations 应该是字典格式"
145
+ }
146
+ }
147
+
148
+ def __init__(self, strict_mode: bool = False):
149
+ """初始化验证器。
150
+
151
+ Args:
152
+ strict_mode: 严格模式,更多验证
153
+ """
154
+ self.strict_mode = strict_mode
155
+ self.results: List[ValidationResult] = []
156
+ self._validated = False
157
+
158
+ def validate(self, state: Dict[str, Any]) -> List[ValidationResult]:
159
+ """验证 State 结构。
160
+
161
+ Args:
162
+ state: State 字典
163
+
164
+ Returns:
165
+ 验证结果列表
166
+ """
167
+ self.results = []
168
+ self._validated = True
169
+
170
+ if not isinstance(state, dict):
171
+ self.results.append(ValidationResult(
172
+ level=ValidationLevel.ERROR,
173
+ field="root",
174
+ message="State 应该是字典格式",
175
+ suggestion="检查 project_state.yaml 文件格式是否正确"
176
+ ))
177
+ return self.results
178
+
179
+ # 验证 version
180
+ self._validate_version(state)
181
+
182
+ # 验证 project
183
+ self._validate_project(state)
184
+
185
+ # 验证 requirements
186
+ self._validate_field("requirements", state)
187
+
188
+ # 验证 design
189
+ self._validate_design(state)
190
+
191
+ # 验证 test
192
+ self._validate_test(state)
193
+
194
+ # 验证 development
195
+ self._validate_development(state)
196
+
197
+ # 验证 deployment
198
+ self._validate_deployment(state)
199
+
200
+ # 验证 iteration
201
+ if "iteration" in state:
202
+ self._validate_iteration(state)
203
+
204
+ return self.results
205
+
206
+ def _validate_version(self, state: Dict[str, Any]):
207
+ """验证 version 字段。"""
208
+ if "version" not in state:
209
+ self.results.append(ValidationResult(
210
+ level=ValidationLevel.ERROR,
211
+ field="version",
212
+ message="version 字段缺失",
213
+ suggestion="在 state 文件开头添加 version: X.Y.Z"
214
+ ))
215
+ return
216
+
217
+ version = state["version"]
218
+
219
+ if not isinstance(version, str):
220
+ self.results.append(ValidationResult(
221
+ level=ValidationLevel.ERROR,
222
+ field="version",
223
+ message=f"version 应该是字符串,实际: {type(version)}"
224
+ ))
225
+ return
226
+
227
+ import re
228
+ if not re.match(r"^\d+\.\d+\.\d+$", version):
229
+ self.results.append(ValidationResult(
230
+ level=ValidationLevel.ERROR,
231
+ field="version",
232
+ message=f"version 格式不正确: {version}",
233
+ suggestion="期望格式: X.Y.Z (例如: 2.0.0 或 2.1.0)"
234
+ ))
235
+
236
+ def _validate_project(self, state: Dict[str, Any]):
237
+ """验证 project 字段。"""
238
+ if "project" not in state:
239
+ self.results.append(ValidationResult(
240
+ level=ValidationLevel.ERROR,
241
+ field="project",
242
+ message="project 字段缺失",
243
+ suggestion="添加 project 字段,包含 name、type、phase"
244
+ ))
245
+ return
246
+
247
+ project = state["project"]
248
+
249
+ if not isinstance(project, dict):
250
+ self.results.append(ValidationResult(
251
+ level=ValidationLevel.ERROR,
252
+ field="project",
253
+ message=f"project 应该是字典格式,实际: {type(project)}"
254
+ ))
255
+ return
256
+
257
+ # 验证 project.name
258
+ if "name" not in project:
259
+ self.results.append(ValidationResult(
260
+ level=ValidationLevel.WARNING,
261
+ field="project.name",
262
+ message="project.name 缺失",
263
+ suggestion="添加项目名称"
264
+ ))
265
+
266
+ # 验证 project.phase
267
+ if "phase" not in project:
268
+ # 检查是否在根级
269
+ if "phase" in state:
270
+ self.results.append(ValidationResult(
271
+ level=ValidationLevel.WARNING,
272
+ field="project.phase",
273
+ message="phase 在根级而非 project.phase",
274
+ suggestion="考虑迁移 phase 到 project.phase"
275
+ ))
276
+ else:
277
+ self.results.append(ValidationResult(
278
+ level=ValidationLevel.ERROR,
279
+ field="project.phase",
280
+ message="project.phase 缺失"
281
+ ))
282
+ else:
283
+ valid_phases = ["unknown", "requirements", "design",
284
+ "development", "testing", "deployment", "completed"]
285
+ if project["phase"] not in valid_phases:
286
+ self.results.append(ValidationResult(
287
+ level=ValidationLevel.ERROR,
288
+ field="project.phase",
289
+ message=f"无效的 phase 值: {project['phase']}",
290
+ suggestion=f"有效值: {valid_phases}"
291
+ ))
292
+
293
+ def _validate_field(self, field_name: str, state: Dict[str, Any]):
294
+ """验证通用字段。"""
295
+ schema = self.SCHEMA.get(field_name, {})
296
+
297
+ if field_name not in state:
298
+ if schema.get("required", False):
299
+ self.results.append(ValidationResult(
300
+ level=ValidationLevel.ERROR,
301
+ field=field_name,
302
+ message=f"{field_name} 字段缺失"
303
+ ))
304
+ return
305
+
306
+ value = state[field_name]
307
+ expected_types = schema.get("type", dict)
308
+
309
+ if isinstance(expected_types, list):
310
+ if not any(isinstance(value, t) for t in expected_types):
311
+ self.results.append(ValidationResult(
312
+ level=ValidationLevel.ERROR,
313
+ field=field_name,
314
+ message=f"{field_name} 类型错误,期望: {expected_types},实际: {type(value)}"
315
+ ))
316
+ elif not isinstance(value, expected_types):
317
+ self.results.append(ValidationResult(
318
+ level=ValidationLevel.ERROR,
319
+ field=field_name,
320
+ message=f"{field_name} 类型错误,期望: {expected_types},实际: {type(value)}"
321
+ ))
322
+
323
+ def _validate_design(self, state: Dict[str, Any]):
324
+ """验证 design 字段(处理列表和字典两种格式)。"""
325
+ if "design" not in state:
326
+ self.results.append(ValidationResult(
327
+ level=ValidationLevel.ERROR,
328
+ field="design",
329
+ message="design 字段缺失"
330
+ ))
331
+ return
332
+
333
+ design = state["design"]
334
+
335
+ # 列表格式
336
+ if isinstance(design, list):
337
+ for i, item in enumerate(design):
338
+ if isinstance(item, dict):
339
+ if not item.get("version"):
340
+ self.results.append(ValidationResult(
341
+ level=ValidationLevel.WARNING,
342
+ field=f"design[{i}].version",
343
+ message="设计文档缺少 version 字段"
344
+ ))
345
+ # 字典格式
346
+ elif isinstance(design, dict):
347
+ self.results.append(ValidationResult(
348
+ level=ValidationLevel.WARNING,
349
+ field="design",
350
+ message="design 字段是字典格式,建议迁移到列表格式",
351
+ suggestion="使用 StateMigrator 迁移到列表格式"
352
+ ))
353
+
354
+ def _validate_test(self, state: Dict[str, Any]):
355
+ """验证 test 字段。"""
356
+ if "test" not in state:
357
+ self.results.append(ValidationResult(
358
+ level=ValidationLevel.ERROR,
359
+ field="test",
360
+ message="test 字段缺失"
361
+ ))
362
+ return
363
+
364
+ test = state["test"]
365
+
366
+ if not isinstance(test, dict):
367
+ self.results.append(ValidationResult(
368
+ level=ValidationLevel.ERROR,
369
+ field="test",
370
+ message=f"test 应该是字典格式,实际: {type(test)}"
371
+ ))
372
+ return
373
+
374
+ # 验证 status
375
+ if "status" in test:
376
+ valid_statuses = ["pending", "in_progress", "passed", "failed"]
377
+ if test["status"] not in valid_statuses:
378
+ self.results.append(ValidationResult(
379
+ level=ValidationLevel.ERROR,
380
+ field="test.status",
381
+ message=f"无效的 test.status: {test['status']}",
382
+ suggestion=f"有效值: {valid_statuses}"
383
+ ))
384
+
385
+ def _validate_development(self, state: Dict[str, Any]):
386
+ """验证 development 字段。"""
387
+ if "development" not in state:
388
+ self.results.append(ValidationResult(
389
+ level=ValidationLevel.ERROR,
390
+ field="development",
391
+ message="development 字段缺失"
392
+ ))
393
+ return
394
+
395
+ dev = state["development"]
396
+
397
+ if not isinstance(dev, dict):
398
+ self.results.append(ValidationResult(
399
+ level=ValidationLevel.ERROR,
400
+ field="development",
401
+ message=f"development 应该是字典格式,实际: {type(dev)}"
402
+ ))
403
+ return
404
+
405
+ def _validate_deployment(self, state: Dict[str, Any]):
406
+ """验证 deployment 字段。"""
407
+ if "deployment" not in state:
408
+ self.results.append(ValidationResult(
409
+ level=ValidationLevel.ERROR,
410
+ field="deployment",
411
+ message="deployment 字段缺失"
412
+ ))
413
+ return
414
+
415
+ deploy = state["deployment"]
416
+
417
+ if not isinstance(deploy, dict):
418
+ self.results.append(ValidationResult(
419
+ level=ValidationLevel.ERROR,
420
+ field="deployment",
421
+ message=f"deployment 应该是字典格式,实际: {type(deploy)}"
422
+ ))
423
+ return
424
+
425
+ def _validate_iteration(self, state: Dict[str, Any]):
426
+ """验证 iteration 字段。"""
427
+ iteration = state["iteration"]
428
+
429
+ if not isinstance(iteration, dict):
430
+ self.results.append(ValidationResult(
431
+ level=ValidationLevel.ERROR,
432
+ field="iteration",
433
+ message=f"iteration 应该是字典格式,实际: {type(iteration)}"
434
+ ))
435
+ return
436
+
437
+ if "current" not in iteration:
438
+ self.results.append(ValidationResult(
439
+ level=ValidationLevel.WARNING,
440
+ field="iteration.current",
441
+ message="iteration.current 缺失"
442
+ ))
443
+
444
+ def check_compatibility(self, state: Dict[str, Any]) -> List[ValidationResult]:
445
+ """检测 State 文件格式与代码期望是否兼容。
446
+
447
+ Args:
448
+ state: State 字典
449
+
450
+ Returns:
451
+ 兼容性问题列表
452
+ """
453
+ issues = []
454
+
455
+ # 检测 design 字段类型
456
+ if "design" in state:
457
+ design = state["design"]
458
+ if isinstance(design, list):
459
+ issues.append(ValidationResult(
460
+ level=ValidationLevel.INFO,
461
+ field="design",
462
+ message="design 字段是列表格式",
463
+ suggestion="代码已兼容列表格式,无需修改"
464
+ ))
465
+ elif isinstance(design, dict):
466
+ issues.append(ValidationResult(
467
+ level=ValidationLevel.WARNING,
468
+ field="design",
469
+ message="design 字段是字典格式,建议迁移到列表格式",
470
+ suggestion="使用 StateMigrator.migrate_v1_to_v2() 迁移"
471
+ ))
472
+
473
+ # 检测 phase 位置
474
+ if "phase" in state:
475
+ issues.append(ValidationResult(
476
+ level=ValidationLevel.WARNING,
477
+ field="phase",
478
+ message="phase 在根级,建议迁移到 project.phase",
479
+ suggestion="使用 StateMigrator.migrate_v1_to_v2() 迁移"
480
+ ))
481
+
482
+ return issues
483
+
484
+ def is_valid(self) -> bool:
485
+ """检查是否有错误级别的验证结果。"""
486
+ if not self._validated:
487
+ return False
488
+ return not any(r.level == ValidationLevel.ERROR for r in self.results)
489
+
490
+ def get_errors(self) -> List[ValidationResult]:
491
+ """获取所有错误级别的验证结果。"""
492
+ return [r for r in self.results if r.level == ValidationLevel.ERROR]
493
+
494
+ def get_warnings(self) -> List[ValidationResult]:
495
+ """获取所有警告级别的验证结果。"""
496
+ return [r for r in self.results if r.level == ValidationLevel.WARNING]
497
+
498
+
499
+ def validate_state_file(state_path: str) -> bool:
500
+ """验证 State 文件。
501
+
502
+ Args:
503
+ state_path: State 文件路径
504
+
505
+ Returns:
506
+ 验证是否通过
507
+ """
508
+ import yaml
509
+
510
+ with open(state_path, 'r') as f:
511
+ state = yaml.safe_load(f)
512
+
513
+ validator = StateValidator()
514
+ results = validator.validate(state)
515
+
516
+ # 输出验证结果
517
+ print("\n" + "=" * 50)
518
+ print("🔍 State 结构验证结果")
519
+ print("=" * 50)
520
+
521
+ if results:
522
+ for result in results:
523
+ print(f"\n{result}")
524
+
525
+ print("\n" + "-" * 50)
526
+ if validator.is_valid():
527
+ print("✅ 验证通过")
528
+ else:
529
+ print(f"❌ 验证失败,发现 {len(validator.get_errors())} 个错误")
530
+ else:
531
+ print("✅ State 结构有效")
532
+
533
+ print("=" * 50 + "\n")
534
+
535
+ return validator.is_valid()
536
+
537
+
538
+ if __name__ == "__main__":
539
+ import sys
540
+
541
+ if len(sys.argv) > 1:
542
+ validate_state_file(sys.argv[1])
543
+ else:
544
+ # 测试验证器
545
+ test_state = {
546
+ "version": "2.0.0",
547
+ "project": {
548
+ "name": "Test Project",
549
+ "type": "PYTHON",
550
+ "phase": "development"
551
+ },
552
+ "design": [{"version": "v1", "status": "completed"}],
553
+ "test": {"status": "pending"},
554
+ "development": {"status": "in_progress"},
555
+ "deployment": {"status": "pending"}
556
+ }
557
+
558
+ validator = StateValidator()
559
+ results = validator.validate(test_state)
560
+
561
+ print("测试 State 验证:")
562
+ for r in results:
563
+ print(f" {r}")
564
+ print(f"\n是否有效: {validator.is_valid()}")