opencode-collaboration 2.1.0__py3-none-any.whl → 2.2.0.post1__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.
- opencode_collaboration-2.2.0.post1.dist-info/METADATA +136 -0
- {opencode_collaboration-2.1.0.dist-info → opencode_collaboration-2.2.0.post1.dist-info}/RECORD +10 -5
- src/core/agent_manager.py +553 -0
- src/core/meeting_manager.py +502 -0
- src/core/project_manager.py +549 -0
- src/core/resource_lock.py +468 -0
- src/core/story_manager.py +712 -0
- opencode_collaboration-2.1.0.dist-info/METADATA +0 -99
- {opencode_collaboration-2.1.0.dist-info → opencode_collaboration-2.2.0.post1.dist-info}/WHEEL +0 -0
- {opencode_collaboration-2.1.0.dist-info → opencode_collaboration-2.2.0.post1.dist-info}/entry_points.txt +0 -0
- {opencode_collaboration-2.1.0.dist-info → opencode_collaboration-2.2.0.post1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
"""项目管理模块 - v2.2.0 M2 项目管理
|
|
2
|
+
|
|
3
|
+
提供任务分配、依赖管理、进度可视化等功能。
|
|
4
|
+
"""
|
|
5
|
+
import uuid
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, List, Optional, Set
|
|
10
|
+
import yaml
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TaskStatus(Enum):
|
|
20
|
+
"""任务状态枚举。"""
|
|
21
|
+
PENDING = "pending"
|
|
22
|
+
IN_PROGRESS = "in_progress"
|
|
23
|
+
COMPLETED = "completed"
|
|
24
|
+
BLOCKED = "blocked"
|
|
25
|
+
CANCELLED = "cancelled"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DependencyType(Enum):
|
|
29
|
+
"""依赖类型枚举。"""
|
|
30
|
+
PRECEDING = "preceding" # 前置依赖
|
|
31
|
+
PARALLEL = "parallel" # 并行依赖
|
|
32
|
+
REFERENCE = "reference" # 参考依赖
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class TaskDependency:
|
|
37
|
+
"""任务依赖。"""
|
|
38
|
+
task_id: str
|
|
39
|
+
dependency_type: DependencyType
|
|
40
|
+
description: str = ""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class ProjectTask:
|
|
45
|
+
"""任务。"""
|
|
46
|
+
task_id: str
|
|
47
|
+
feature_id: str
|
|
48
|
+
title: str
|
|
49
|
+
description: str = ""
|
|
50
|
+
assignee: Optional[str] = None
|
|
51
|
+
status: TaskStatus = TaskStatus.PENDING
|
|
52
|
+
dependencies: List[str] = field(default_factory=list)
|
|
53
|
+
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
54
|
+
updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
55
|
+
completed_at: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
58
|
+
return {
|
|
59
|
+
"task_id": self.task_id,
|
|
60
|
+
"feature_id": self.feature_id,
|
|
61
|
+
"title": self.title,
|
|
62
|
+
"description": self.description,
|
|
63
|
+
"assignee": self.assignee,
|
|
64
|
+
"status": self.status.value,
|
|
65
|
+
"dependencies": self.dependencies,
|
|
66
|
+
"created_at": self.created_at,
|
|
67
|
+
"updated_at": self.updated_at,
|
|
68
|
+
"completed_at": self.completed_at
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ProjectTask":
|
|
73
|
+
return cls(
|
|
74
|
+
task_id=data["task_id"],
|
|
75
|
+
feature_id=data["feature_id"],
|
|
76
|
+
title=data["title"],
|
|
77
|
+
description=data.get("description", ""),
|
|
78
|
+
assignee=data.get("assignee"),
|
|
79
|
+
status=TaskStatus(data.get("status", "pending")),
|
|
80
|
+
dependencies=data.get("dependencies", []),
|
|
81
|
+
created_at=data.get("created_at", datetime.now().isoformat()),
|
|
82
|
+
updated_at=data.get("updated_at", datetime.now().isoformat()),
|
|
83
|
+
completed_at=data.get("completed_at")
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class Feature:
|
|
89
|
+
"""功能模块。"""
|
|
90
|
+
feature_id: str
|
|
91
|
+
title: str
|
|
92
|
+
description: str = ""
|
|
93
|
+
default_agent: Optional[str] = None
|
|
94
|
+
status: str = "pending"
|
|
95
|
+
tasks: List[ProjectTask] = field(default_factory=list)
|
|
96
|
+
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
97
|
+
updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
98
|
+
|
|
99
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
100
|
+
return {
|
|
101
|
+
"feature_id": self.feature_id,
|
|
102
|
+
"title": self.title,
|
|
103
|
+
"description": self.description,
|
|
104
|
+
"default_agent": self.default_agent,
|
|
105
|
+
"status": self.status,
|
|
106
|
+
"tasks": [t.to_dict() for t in self.tasks],
|
|
107
|
+
"created_at": self.created_at,
|
|
108
|
+
"updated_at": self.updated_at
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Feature":
|
|
113
|
+
return cls(
|
|
114
|
+
feature_id=data["feature_id"],
|
|
115
|
+
title=data["title"],
|
|
116
|
+
description=data.get("description", ""),
|
|
117
|
+
default_agent=data.get("default_agent"),
|
|
118
|
+
status=data.get("status", "pending"),
|
|
119
|
+
tasks=[ProjectTask.from_dict(t) for t in data.get("tasks", [])],
|
|
120
|
+
created_at=data.get("created_at", datetime.now().isoformat()),
|
|
121
|
+
updated_at=data.get("updated_at", datetime.now().isoformat())
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class ProjectManagerError(Exception):
|
|
126
|
+
"""项目管理异常基类。"""
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class FeatureNotFoundError(ProjectManagerError):
|
|
131
|
+
"""Feature 未找到异常。"""
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TaskNotFoundError(ProjectManagerError):
|
|
136
|
+
"""任务未找到异常。"""
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class CircularDependencyError(ProjectManagerError):
|
|
141
|
+
"""循环依赖异常。"""
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ProjectManager:
|
|
146
|
+
"""项目管理器。"""
|
|
147
|
+
|
|
148
|
+
DEFAULT_PROJECT_FILE = "project_data.yaml"
|
|
149
|
+
|
|
150
|
+
def __init__(self, project_path: str, project_file: Optional[str] = None):
|
|
151
|
+
"""初始化项目管理器。
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
project_path: 项目路径
|
|
155
|
+
project_file: 项目数据文件
|
|
156
|
+
"""
|
|
157
|
+
self.project_path = Path(project_path)
|
|
158
|
+
self.project_file = self.project_path / (project_file or self.DEFAULT_PROJECT_FILE)
|
|
159
|
+
self.features: Dict[str, Feature] = {}
|
|
160
|
+
self._load_project()
|
|
161
|
+
|
|
162
|
+
def _load_project(self) -> None:
|
|
163
|
+
"""加载项目数据。"""
|
|
164
|
+
if self.project_file.exists():
|
|
165
|
+
try:
|
|
166
|
+
with open(self.project_file, 'r', encoding='utf-8') as f:
|
|
167
|
+
data = yaml.safe_load(f)
|
|
168
|
+
if data and "features" in data:
|
|
169
|
+
for feature_data in data.get("features", []):
|
|
170
|
+
feature = Feature.from_dict(feature_data)
|
|
171
|
+
self.features[feature.feature_id] = feature
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logger.warning(f"加载项目数据失败: {e}")
|
|
174
|
+
|
|
175
|
+
def _save_project(self) -> None:
|
|
176
|
+
"""保存项目数据。"""
|
|
177
|
+
data = {
|
|
178
|
+
"features": [f.to_dict() for f in self.features.values()],
|
|
179
|
+
"updated_at": datetime.now().isoformat()
|
|
180
|
+
}
|
|
181
|
+
with open(self.project_file, 'w', encoding='utf-8') as f:
|
|
182
|
+
yaml.dump(data, f, allow_unicode=True)
|
|
183
|
+
|
|
184
|
+
def create_feature(
|
|
185
|
+
self,
|
|
186
|
+
title: str,
|
|
187
|
+
description: str = "",
|
|
188
|
+
default_agent: Optional[str] = None
|
|
189
|
+
) -> Feature:
|
|
190
|
+
"""创建 Feature。
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
title: Feature 标题
|
|
194
|
+
description: 描述
|
|
195
|
+
default_agent: 默认负责 Agent
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
创建的 Feature
|
|
199
|
+
"""
|
|
200
|
+
feature_id = f"FEATURE-{len(self.features) + 1:03d}"
|
|
201
|
+
feature = Feature(
|
|
202
|
+
feature_id=feature_id,
|
|
203
|
+
title=title,
|
|
204
|
+
description=description,
|
|
205
|
+
default_agent=default_agent
|
|
206
|
+
)
|
|
207
|
+
self.features[feature_id] = feature
|
|
208
|
+
self._save_project()
|
|
209
|
+
logger.info(f"创建 Feature: {feature_id} - {title}")
|
|
210
|
+
return feature
|
|
211
|
+
|
|
212
|
+
def get_feature(self, feature_id: str) -> Feature:
|
|
213
|
+
"""获取 Feature。
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
feature_id: Feature ID
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Feature
|
|
220
|
+
|
|
221
|
+
Raises:
|
|
222
|
+
FeatureNotFoundError: Feature 未找到
|
|
223
|
+
"""
|
|
224
|
+
if feature_id not in self.features:
|
|
225
|
+
raise FeatureNotFoundError(f"Feature 未找到: {feature_id}")
|
|
226
|
+
return self.features[feature_id]
|
|
227
|
+
|
|
228
|
+
def list_features(self, status: Optional[str] = None) -> List[Feature]:
|
|
229
|
+
"""列出 Feature。
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
status: 状态过滤
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Feature 列表
|
|
236
|
+
"""
|
|
237
|
+
features = list(self.features.values())
|
|
238
|
+
if status:
|
|
239
|
+
features = [f for f in features if f.status == status]
|
|
240
|
+
return features
|
|
241
|
+
|
|
242
|
+
def create_task(
|
|
243
|
+
self,
|
|
244
|
+
feature_id: str,
|
|
245
|
+
title: str,
|
|
246
|
+
description: str = "",
|
|
247
|
+
assignee: Optional[str] = None,
|
|
248
|
+
dependencies: Optional[List[str]] = None
|
|
249
|
+
) -> ProjectTask:
|
|
250
|
+
"""创建任务。
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
feature_id: 所属 Feature ID
|
|
254
|
+
title: 任务标题
|
|
255
|
+
description: 描述
|
|
256
|
+
assignee: 执行者
|
|
257
|
+
dependencies: 依赖任务 ID 列表
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
创建的任务
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
FeatureNotFoundError: Feature 未找到
|
|
264
|
+
"""
|
|
265
|
+
feature = self.get_feature(feature_id)
|
|
266
|
+
|
|
267
|
+
task_id = f"TASK-{len(feature.tasks) + 1:03d}"
|
|
268
|
+
task = ProjectTask(
|
|
269
|
+
task_id=task_id,
|
|
270
|
+
feature_id=feature_id,
|
|
271
|
+
title=title,
|
|
272
|
+
description=description,
|
|
273
|
+
assignee=assignee,
|
|
274
|
+
dependencies=dependencies or []
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
feature.tasks.append(task)
|
|
278
|
+
feature.updated_at = datetime.now().isoformat()
|
|
279
|
+
self._save_project()
|
|
280
|
+
logger.info(f"创建任务: {task_id} - {title}")
|
|
281
|
+
return task
|
|
282
|
+
|
|
283
|
+
def get_task(self, feature_id: str, task_id: str) -> ProjectTask:
|
|
284
|
+
"""获取任务。
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
feature_id: Feature ID
|
|
288
|
+
task_id: 任务 ID
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
任务
|
|
292
|
+
|
|
293
|
+
Raises:
|
|
294
|
+
TaskNotFoundError: 任务未找到
|
|
295
|
+
"""
|
|
296
|
+
feature = self.get_feature(feature_id)
|
|
297
|
+
for task in feature.tasks:
|
|
298
|
+
if task.task_id == task_id:
|
|
299
|
+
return task
|
|
300
|
+
raise TaskNotFoundError(f"任务未找到: {task_id}")
|
|
301
|
+
|
|
302
|
+
def list_tasks(
|
|
303
|
+
self,
|
|
304
|
+
feature_id: Optional[str] = None,
|
|
305
|
+
assignee: Optional[str] = None,
|
|
306
|
+
status: Optional[TaskStatus] = None
|
|
307
|
+
) -> List[ProjectTask]:
|
|
308
|
+
"""列出任务。
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
feature_id: Feature 过滤
|
|
312
|
+
assignee: 执行者过滤
|
|
313
|
+
status: 状态过滤
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
任务列表
|
|
317
|
+
"""
|
|
318
|
+
tasks = []
|
|
319
|
+
for feature in self.features.values():
|
|
320
|
+
if feature_id and feature.feature_id != feature_id:
|
|
321
|
+
continue
|
|
322
|
+
for task in feature.tasks:
|
|
323
|
+
if assignee and task.assignee != assignee:
|
|
324
|
+
continue
|
|
325
|
+
if status and task.status != status:
|
|
326
|
+
continue
|
|
327
|
+
tasks.append(task)
|
|
328
|
+
return tasks
|
|
329
|
+
|
|
330
|
+
def update_task_status(
|
|
331
|
+
self,
|
|
332
|
+
feature_id: str,
|
|
333
|
+
task_id: str,
|
|
334
|
+
status: TaskStatus
|
|
335
|
+
) -> ProjectTask:
|
|
336
|
+
"""更新任务状态。
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
feature_id: Feature ID
|
|
340
|
+
task_id: 任务 ID
|
|
341
|
+
status: 新状态
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
更新后的任务
|
|
345
|
+
|
|
346
|
+
Raises:
|
|
347
|
+
TaskNotFoundError: 任务未找到
|
|
348
|
+
"""
|
|
349
|
+
task = self.get_task(feature_id, task_id)
|
|
350
|
+
old_status = task.status
|
|
351
|
+
task.status = status
|
|
352
|
+
task.updated_at = datetime.now().isoformat()
|
|
353
|
+
|
|
354
|
+
if status == TaskStatus.COMPLETED:
|
|
355
|
+
task.completed_at = datetime.now().isoformat()
|
|
356
|
+
|
|
357
|
+
self._save_project()
|
|
358
|
+
logger.info(f"任务状态更新: {task_id} {old_status.value} -> {status.value}")
|
|
359
|
+
return task
|
|
360
|
+
|
|
361
|
+
def assign_task(
|
|
362
|
+
self,
|
|
363
|
+
feature_id: str,
|
|
364
|
+
task_id: str,
|
|
365
|
+
assignee: str
|
|
366
|
+
) -> ProjectTask:
|
|
367
|
+
"""分配任务。
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
feature_id: Feature ID
|
|
371
|
+
task_id: 任务 ID
|
|
372
|
+
assignee: 新执行者
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
更新后的任务
|
|
376
|
+
"""
|
|
377
|
+
task = self.get_task(feature_id, task_id)
|
|
378
|
+
task.assignee = assignee
|
|
379
|
+
task.updated_at = datetime.now().isoformat()
|
|
380
|
+
self._save_project()
|
|
381
|
+
logger.info(f"任务分配: {task_id} -> {assignee}")
|
|
382
|
+
return task
|
|
383
|
+
|
|
384
|
+
def detect_circular_dependencies(self) -> List[List[str]]:
|
|
385
|
+
"""检测循环依赖。
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
循环依赖链列表,每个链是任务 ID 列表
|
|
389
|
+
"""
|
|
390
|
+
cycles = []
|
|
391
|
+
visited = set()
|
|
392
|
+
recursion_stack = set()
|
|
393
|
+
|
|
394
|
+
def dfs(task_id: str, path: List[str]) -> None:
|
|
395
|
+
if task_id in recursion_stack:
|
|
396
|
+
cycle_start = path.index(task_id)
|
|
397
|
+
cycles.append(path[cycle_start:] + [task_id])
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
if task_id in visited:
|
|
401
|
+
return
|
|
402
|
+
|
|
403
|
+
visited.add(task_id)
|
|
404
|
+
recursion_stack.add(task_id)
|
|
405
|
+
path.append(task_id)
|
|
406
|
+
|
|
407
|
+
for feature in self.features.values():
|
|
408
|
+
for task in feature.tasks:
|
|
409
|
+
if task.task_id != task_id:
|
|
410
|
+
continue
|
|
411
|
+
for dep_id in task.dependencies:
|
|
412
|
+
if dep_id not in visited:
|
|
413
|
+
dfs(dep_id, path.copy())
|
|
414
|
+
|
|
415
|
+
recursion_stack.remove(task_id)
|
|
416
|
+
path.pop()
|
|
417
|
+
|
|
418
|
+
for feature in self.features.values():
|
|
419
|
+
for task in feature.tasks:
|
|
420
|
+
if task.task_id not in visited:
|
|
421
|
+
dfs(task.task_id, [])
|
|
422
|
+
|
|
423
|
+
return cycles
|
|
424
|
+
|
|
425
|
+
def check_dependencies_resolved(
|
|
426
|
+
self,
|
|
427
|
+
feature_id: str,
|
|
428
|
+
task_id: str
|
|
429
|
+
) -> tuple[bool, List[str]]:
|
|
430
|
+
"""检查任务依赖是否已解决。
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
feature_id: Feature ID
|
|
434
|
+
task_id: 任务 ID
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
(是否可开始, 未解决的依赖列表)
|
|
438
|
+
"""
|
|
439
|
+
task = self.get_task(feature_id, task_id)
|
|
440
|
+
unresolved = []
|
|
441
|
+
|
|
442
|
+
for dep_id in task.dependencies:
|
|
443
|
+
dep_task = None
|
|
444
|
+
for feature in self.features.values():
|
|
445
|
+
for t in feature.tasks:
|
|
446
|
+
if t.task_id == dep_id:
|
|
447
|
+
dep_task = t
|
|
448
|
+
break
|
|
449
|
+
if dep_task:
|
|
450
|
+
break
|
|
451
|
+
|
|
452
|
+
if not dep_task or dep_task.status != TaskStatus.COMPLETED:
|
|
453
|
+
unresolved.append(dep_id)
|
|
454
|
+
|
|
455
|
+
return len(unresolved) == 0, unresolved
|
|
456
|
+
|
|
457
|
+
def get_progress_summary(self) -> Dict[str, Any]:
|
|
458
|
+
"""获取进度摘要。
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
进度摘要
|
|
462
|
+
"""
|
|
463
|
+
total_tasks = 0
|
|
464
|
+
completed_tasks = 0
|
|
465
|
+
in_progress_tasks = 0
|
|
466
|
+
blocked_tasks = 0
|
|
467
|
+
by_assignee = {}
|
|
468
|
+
|
|
469
|
+
for feature in self.features.values():
|
|
470
|
+
for task in feature.tasks:
|
|
471
|
+
total_tasks += 1
|
|
472
|
+
if task.status == TaskStatus.COMPLETED:
|
|
473
|
+
completed_tasks += 1
|
|
474
|
+
elif task.status == TaskStatus.IN_PROGRESS:
|
|
475
|
+
in_progress_tasks += 1
|
|
476
|
+
elif task.status == TaskStatus.BLOCKED:
|
|
477
|
+
blocked_tasks += 1
|
|
478
|
+
|
|
479
|
+
if task.assignee:
|
|
480
|
+
if task.assignee not in by_assignee:
|
|
481
|
+
by_assignee[task.assignee] = {"total": 0, "completed": 0, "in_progress": 0}
|
|
482
|
+
by_assignee[task.assignee]["total"] += 1
|
|
483
|
+
if task.status == TaskStatus.COMPLETED:
|
|
484
|
+
by_assignee[task.assignee]["completed"] += 1
|
|
485
|
+
elif task.status == TaskStatus.IN_PROGRESS:
|
|
486
|
+
by_assignee[task.assignee]["in_progress"] += 1
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
"total_features": len(self.features),
|
|
490
|
+
"total_tasks": total_tasks,
|
|
491
|
+
"completed_tasks": completed_tasks,
|
|
492
|
+
"in_progress_tasks": in_progress_tasks,
|
|
493
|
+
"blocked_tasks": blocked_tasks,
|
|
494
|
+
"completion_rate": round(completed_tasks / total_tasks * 100, 2) if total_tasks > 0 else 0,
|
|
495
|
+
"by_assignee": by_assignee
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
def export_project(self, output_path: Optional[str] = None) -> Dict[str, Any]:
|
|
499
|
+
"""导出项目数据。
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
output_path: 输出路径(可选)
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
项目数据字典
|
|
506
|
+
"""
|
|
507
|
+
data = {
|
|
508
|
+
"features": [f.to_dict() for f in self.features.values()],
|
|
509
|
+
"progress": self.get_progress_summary(),
|
|
510
|
+
"exported_at": datetime.now().isoformat()
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if output_path:
|
|
514
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
515
|
+
yaml.dump(data, f, allow_unicode=True)
|
|
516
|
+
|
|
517
|
+
return data
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
if __name__ == "__main__":
|
|
521
|
+
import tempfile
|
|
522
|
+
|
|
523
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
524
|
+
pm = ProjectManager(tmpdir)
|
|
525
|
+
|
|
526
|
+
feature = pm.create_feature(
|
|
527
|
+
title="用户认证模块",
|
|
528
|
+
description="用户登录、注册、登出功能",
|
|
529
|
+
default_agent="agent_backend_go"
|
|
530
|
+
)
|
|
531
|
+
print(f"创建 Feature: {feature.feature_id}")
|
|
532
|
+
|
|
533
|
+
task1 = pm.create_task(
|
|
534
|
+
feature_id=feature.feature_id,
|
|
535
|
+
title="设计用户认证 API",
|
|
536
|
+
assignee="agent_backend_go"
|
|
537
|
+
)
|
|
538
|
+
print(f"创建任务: {task1.task_id}")
|
|
539
|
+
|
|
540
|
+
task2 = pm.create_task(
|
|
541
|
+
feature_id=feature.feature_id,
|
|
542
|
+
title="实现 JWT 认证",
|
|
543
|
+
assignee="agent_backend_go",
|
|
544
|
+
dependencies=[task1.task_id]
|
|
545
|
+
)
|
|
546
|
+
print(f"创建任务: {task2.task_id}")
|
|
547
|
+
|
|
548
|
+
progress = pm.get_progress_summary()
|
|
549
|
+
print(f"进度: {progress}")
|