opencode-collaboration 2.2.0.post3__py3-none-any.whl → 2.2.0.post4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-collaboration
3
- Version: 2.2.0.post3
3
+ Version: 2.2.0.post4
4
4
  Summary: 双Agent协作框架 - 产品经理与开发的分离式协作工具
5
5
  Author-email: liuzhen <dev@opencode.ai>
6
6
  License: MIT
@@ -2,7 +2,7 @@ src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  src/main.py,sha256=W_G68r7XFyioa8wHlSsdwBK1eIyWDaIQWciyZxhZHgg,15
3
3
  src/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  src/cli/agent.py,sha256=o8-3UpfcnUMePltpBdVBuBFMECMiKnOA7airyvi1m54,9874
5
- src/cli/main.py,sha256=e3lgFfCSB9qHx0GOcG3f00VSQ_e_4AfMSWg-gSGAzeY,47300
5
+ src/cli/main.py,sha256=nzC2NCkDgqKMSv2WZPJqMk-050V-APfe_FbirOddXQM,50007
6
6
  src/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  src/core/agent_manager.py,sha256=Mxe4G_IfEaQ1lERdGNfMvK0tIS6DuE3WJUNOxgtAD8o,16535
8
8
  src/core/auto_doc_git.py,sha256=_yphH8tLAMq3R-yMHpc2bcmmap2p7b2So4qVmXUtCh8,1396
@@ -10,7 +10,8 @@ src/core/auto_docs.py,sha256=rQfJrrKGe8fiQK4eSq4gAp1rfFAu_o-sDV_ZloaFQVQ,11142
10
10
  src/core/auto_engine.py,sha256=bV9VXa0naPpxKuE7p7xHAZKZJe9DIGfzrFwHhiaDHKA,16031
11
11
  src/core/auto_git_sync.py,sha256=jklR_NOYBXYgRSdG0rMRgvQM3jvRBpvm-qSjc-vDK28,2077
12
12
  src/core/auto_retry.py,sha256=qG4UY2d312PoECJKKuIq3qspe6qP1mTMEQ4M2Y9J1oo,7571
13
- src/core/brain_engine.py,sha256=OJliBiMIxMwTCyEMJW0Os9SRpB5mCf3ujCnvjFVbLcg,14893
13
+ src/core/brain_engine.py,sha256=b6PtFde-zkFfCtDihd1es-BEROA_6wQXBJV9S_fL9WE,15032
14
+ src/core/change_compliance.py,sha256=twFvW5gwEpzJHtPubkYJqV2xnzpHRPmczkRvGDVuRSE,3574
14
15
  src/core/config_reloader.py,sha256=Fne2FJ-AgPNnqV7Ody-nRpLOSptde65ZXqyGoFl-PbE,8726
15
16
  src/core/daemon.py,sha256=pu776YVEC-dw4mKFh7JZg4q7R6XGPe52wiQtwnsQxb4,6470
16
17
  src/core/design_review_notifier.py,sha256=XlWyPXUZ7V4CeWdvgf92QBqbrSvO_VzBW7VhUWmlRqk,9826
@@ -28,22 +29,25 @@ src/core/phase_advance.py,sha256=76UN8TI7RpJgRfjgV4bvs7uWlvNt-h_dz6gTDgK2zrY,112
28
29
  src/core/project_manager.py,sha256=qayc8NHy3go26LWZgw17nL57p8LXKjpprAS1_iKZHDg,16637
29
30
  src/core/resource_lock.py,sha256=1G2yOdbqZnWqVdcIt6IKbhRLhSg23kLgJhdwiFt7jdY,13889
30
31
  src/core/session_manager.py,sha256=2Hr36C71kIPia4UYKXx8Ym86T7_9TTQxC8ZrXm65IZU,6825
31
- src/core/signoff.py,sha256=fG6KFNz3AYh6hKYMPfognU_SBs5amCW2q4bne4huFf0,6643
32
+ src/core/signoff.py,sha256=Zl5rV7C6X3tg1xwZiePH5kbetmOGReJIN4ydhQIjtxY,8037
33
+ src/core/signoff_record_manager.py,sha256=66ZqKdVzYR2-oGprbvfu-QXEMHH0yj3Byzetsq3wPv8,3051
32
34
  src/core/state_machine.py,sha256=L1gfdsYC6mlWcHP5RwQQen8vCTwV2spIP-Ktwz4gux8,14919
33
35
  src/core/state_manager.py,sha256=jkgci5q71DRjmKe8igeFhOTxHJ2xqNjAHJo6THFtw_M,16672
34
36
  src/core/state_migrator.py,sha256=OYXtwxA_ePFAS_XqOMhKR2fyU1werLePbqCyNZg8eXQ,13746
35
37
  src/core/state_validator.py,sha256=Q86jbEO0fNdzDi3zIPB_G_ibQ0QWyXFYV_rPj6Eakjw,18827
36
38
  src/core/story_manager.py,sha256=8b0Kelw6zdvZYr9sb0Rnlf7_XeXXOSdVar4q2vQjdbI,21867
37
39
  src/core/supervisor.py,sha256=pT_5CkimpFgB_gyqzsUL-25l3MsRGP1TWFEnHdGJtwo,7290
38
- src/core/task_executor.py,sha256=xcM9sNu8MyAVqlNvtc2GL4eiYeTMmeduTrrA3j292aM,23720
40
+ src/core/task_executor.py,sha256=l3FeVe5yWZ-z8mwfucCsp5FwXhc2Rvpw5zJEu7u2Kso,24881
39
41
  src/core/workflow.py,sha256=LpH9g6xbtCmYOhhCSxpcstRR7TptN8e6b0mEag3UcW4,5634
42
+ src/core/workflow_inference.py,sha256=XU8Z32H4z4MGj5AUS2x9gmS88nD9UL7xWQueXJRlvmA,6093
40
43
  src/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
44
  src/utils/date.py,sha256=iWS0hTaoDE2iC0jJb3lTIB5yK5xxRbrC1C98Fgb8LFc,577
45
+ src/utils/environment.py,sha256=zowgHHBIZ8fgVAZ_fP_I4mX7S9zSn3mvPvnFUYIethE,1509
42
46
  src/utils/file.py,sha256=5IFKkT2m1emJUHDzIiLsa4YG9GCqOhhmiLvc6aVY9-Y,1301
43
47
  src/utils/lock.py,sha256=soxYFsBKJHUzN-_QXkorVfgnmt0D5p1SZtqwPNqcWPI,2880
44
48
  src/utils/yaml.py,sha256=zcbh0OP7NOqxTexEAR3akQkllUh8xeKt42O2CHIImyg,777
45
- opencode_collaboration-2.2.0.post3.dist-info/METADATA,sha256=bwam0apvaWCByCngne2gy9fW3X1pjX_Who0Gy1LmZgc,3127
46
- opencode_collaboration-2.2.0.post3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
47
- opencode_collaboration-2.2.0.post3.dist-info/entry_points.txt,sha256=fYyHWa_NefMp527B7fHl-29SwZQCElRdtxm_7LoUK-Y,48
48
- opencode_collaboration-2.2.0.post3.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
49
- opencode_collaboration-2.2.0.post3.dist-info/RECORD,,
49
+ opencode_collaboration-2.2.0.post4.dist-info/METADATA,sha256=9kWRm0S8WLlmBDyc6OY9aQM74-aY5LxJTGxLGNR9HiI,3127
50
+ opencode_collaboration-2.2.0.post4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
51
+ opencode_collaboration-2.2.0.post4.dist-info/entry_points.txt,sha256=fYyHWa_NefMp527B7fHl-29SwZQCElRdtxm_7LoUK-Y,48
52
+ opencode_collaboration-2.2.0.post4.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
53
+ opencode_collaboration-2.2.0.post4.dist-info/RECORD,,
src/cli/main.py CHANGED
@@ -175,63 +175,15 @@ def switch_command(agent_id: int, welcome: bool):
175
175
  @main.command("review")
176
176
  @click.argument("stage", type=click.Choice(["requirements", "design", "test"]))
177
177
  @click.option("--file", "-f", help="指定评审文件路径")
178
- @click.option("--checklist", "-c", is_flag=True, default=False, help="显示动态检查清单")
179
178
  @click.option("--new", is_flag=True, default=False)
180
179
  @click.option("--list", "-l", is_flag=True, default=False)
181
- def review_command(stage: str, file: str, checklist: bool, new: bool, list: bool):
180
+ def review_command(stage: str, file: str, new: bool, list: bool):
182
181
  """管理评审流程。"""
183
182
  try:
184
183
  project_path = get_project_path()
185
184
  state_manager = StateManager(project_path)
186
185
  workflow_engine = WorkflowEngine(state_manager)
187
186
 
188
- if checklist:
189
- from ..core.checklist_generator import ChecklistGenerator, CheckStatus
190
-
191
- generator = ChecklistGenerator(project_path)
192
-
193
- if stage == "requirements":
194
- if file:
195
- doc_path = file
196
- else:
197
- req_dir = Path(project_path) / "docs" / "01-requirements"
198
- doc_path = str(sorted(req_dir.glob("*.md"))[-1] if req_dir.exists() else "")
199
-
200
- if doc_path and Path(doc_path).exists():
201
- checklist_items = generator.generate_requirements_checklist(doc_path)
202
- rendered = generator.render_checklist(checklist_items, f"{stage.upper()} 评审 Checklist")
203
- console.print(Panel(rendered, title="动态检查清单", style="green"))
204
- else:
205
- click.echo("错误: 未找到需求文档")
206
-
207
- elif stage == "design":
208
- if file:
209
- doc_path = file
210
- else:
211
- design_dir = Path(project_path) / "docs" / "02-design"
212
- doc_path = str(sorted(design_dir.glob("*.md"))[-1] if design_dir.exists() else "")
213
-
214
- if doc_path and Path(doc_path).exists():
215
- checklist_items = generator.generate_design_checklist(doc_path)
216
- rendered = generator.render_checklist(checklist_items, f"{stage.upper()} 评审 Checklist")
217
- console.print(Panel(rendered, title="动态检查清单", style="green"))
218
- else:
219
- click.echo("错误: 未找到设计文档")
220
-
221
- elif stage == "test":
222
- if file:
223
- doc_path = file
224
- else:
225
- test_dir = Path(project_path) / "docs" / "03-test"
226
- doc_path = str(sorted(test_dir.glob("*.md"))[-1] if test_dir.exists() else "")
227
-
228
- if doc_path and Path(doc_path).exists():
229
- checklist_items = generator.generate_test_checklist(doc_path)
230
- rendered = generator.render_checklist(checklist_items, f"{stage.upper()} 评审 Checklist")
231
- console.print(Panel(rendered, title="动态检查清单", style="green"))
232
- else:
233
- click.echo("错误: 未找到测试文档")
234
-
235
187
  if new:
236
188
  workflow_engine.start_review(stage)
237
189
  click.echo(f"已发起 {stage} 评审")
@@ -252,26 +204,74 @@ def review_command(stage: str, file: str, checklist: bool, new: bool, list: bool
252
204
  @click.argument("stage", type=click.Choice(["requirements", "design", "test"]))
253
205
  @click.option("--comment", "-m", default="")
254
206
  @click.option("--reject", "-r", default=None)
255
- def signoff_command(stage: str, comment: str, reject: str):
207
+ @click.option("--sync", "-s", is_flag=True, default=False, help="签署后自动同步到远程")
208
+ def signoff_command(stage: str, comment: str, reject: str, sync: bool):
256
209
  """签署确认。"""
257
210
  try:
258
211
  project_path = get_project_path()
259
212
  state_manager = StateManager(project_path)
260
213
  workflow_engine = WorkflowEngine(state_manager)
261
214
  signoff_engine = SignoffEngine(state_manager, workflow_engine)
262
-
215
+
263
216
  agent_id = state_manager.get_active_agent()
264
-
217
+
265
218
  if reject:
266
219
  result = signoff_engine.reject(stage, agent_id, reject)
267
220
  click.echo(f"已拒签 {stage} 阶段")
268
221
  else:
269
- result = signoff_engine.sign(stage, agent_id, comment)
270
- click.echo(f"已签署 {stage} 阶段")
271
-
222
+ if sync:
223
+ from ..core.git import GitHelper
224
+ git_helper = GitHelper(project_path)
225
+ result = signoff_engine.signoff_with_sync(stage, agent_id, comment, git_helper)
226
+ click.echo(result.message)
227
+ else:
228
+ result = signoff_engine.sign(stage, agent_id, comment)
229
+ click.echo(f"已签署 {stage} 阶段")
230
+
272
231
  if state_manager.can_proceed_to_next_phase():
273
232
  click.echo("双方已签署,可以推进到下一阶段")
274
-
233
+
234
+ except Exception as e:
235
+ click.echo(f"错误: {e}")
236
+ sys.exit(1)
237
+
238
+
239
+ @main.command("signoffs")
240
+ @click.option("--list", "-l", is_flag=True, default=False, help="列出所有签署记录")
241
+ @click.option("--id", "-i", help="查看特定签署记录ID")
242
+ def signoffs_command(list: bool, id: str):
243
+ """查看签署记录。"""
244
+ try:
245
+ from ..core.signoff_record_manager import SignoffRecordManager
246
+
247
+ project_path = get_project_path()
248
+ manager = SignoffRecordManager(project_path)
249
+
250
+ if id:
251
+ record = manager.get_signoff(id)
252
+ if record:
253
+ console.print(f"\n[bold]签署记录: {id}[/bold]")
254
+ console.print(f"里程碑: {record.get('milestone')}")
255
+ console.print(f"阶段: {record.get('phase')}")
256
+ console.print(f"状态: {record.get('status')}")
257
+ console.print("\n签署者:")
258
+ for signer in record.get("signers", []):
259
+ status_icon = "✓" if signer.get("status") == "approved" else "○"
260
+ console.print(f" {status_icon} {signer.get('role')} ({signer.get('agent')}) - {signer.get('timestamp')}")
261
+ else:
262
+ click.echo(f"未找到签署记录: {id}")
263
+ elif list:
264
+ signoffs = manager.list_signoffs()
265
+ if signoffs:
266
+ console.print("\n[bold]签署记录列表[/bold]")
267
+ for record in signoffs:
268
+ status_icon = "✓" if record.get("status") == "APPROVED" else "○"
269
+ console.print(f"{status_icon} {record.get('signoff_id')} - {record.get('milestone')} - {record.get('phase')}")
270
+ else:
271
+ click.echo("暂无签署记录")
272
+ else:
273
+ click.echo("请使用 --list 列出所有记录,或 --id 查看特定记录")
274
+
275
275
  except Exception as e:
276
276
  click.echo(f"错误: {e}")
277
277
  sys.exit(1)
@@ -1168,5 +1168,87 @@ def advance_command(phase: str, force: bool, check: bool):
1168
1168
  sys.exit(1)
1169
1169
 
1170
1170
 
1171
+ @main.command("workflow")
1172
+ @click.option("--check", "-c", is_flag=True, help="显示合规检查结果")
1173
+ @click.option("--suggest", "-s", is_flag=True, help="显示下一步建议")
1174
+ def workflow_command(check: bool, suggest: bool):
1175
+ """查看当前工作流状态和推理。"""
1176
+ try:
1177
+ from ..core.workflow_inference import WorkflowInferenceEngine
1178
+
1179
+ project_path = get_project_path()
1180
+ state_manager = StateManager(project_path)
1181
+ engine = WorkflowInferenceEngine(project_path, state_manager)
1182
+
1183
+ result = engine.infer_next_action()
1184
+
1185
+ if result.unknown:
1186
+ click.echo("⚠️ 无法确定当前流程状态")
1187
+ click.echo("请先初始化项目: oc-collab project init")
1188
+ else:
1189
+ console.print(Panel(
1190
+ f"当前阶段: {result.current_phase}\n\n{result.suggestion}",
1191
+ title="工作流状态",
1192
+ style="blue"
1193
+ ))
1194
+
1195
+ if check or suggest:
1196
+ compliance = result.compliance_check
1197
+ if compliance.valid:
1198
+ click.echo("\n✅ 流程合规")
1199
+ else:
1200
+ click.echo("\n⚠️ 流程不合规:")
1201
+ for violation in compliance.violations:
1202
+ click.echo(f" - {violation}")
1203
+ for suggestion in compliance.suggestions:
1204
+ click.echo(f" 💡 {suggestion}")
1205
+
1206
+ except Exception as e:
1207
+ click.echo(f"错误: {e}")
1208
+ sys.exit(1)
1209
+
1210
+
1211
+ @main.command("compliance")
1212
+ @click.option("--prd", "-p", help="检查 PRD 文件")
1213
+ @click.option("--rfc", "-r", help="检查 RFC 文件")
1214
+ @click.option("--detect", "-d", is_flag=True, help="检测冲突")
1215
+ def compliance_command(prd: str, rfc: str, detect: bool):
1216
+ """检查变更合规性。"""
1217
+ try:
1218
+ from ..core.change_compliance import ChangeComplianceChecker
1219
+
1220
+ project_path = get_project_path()
1221
+ checker = ChangeComplianceChecker(project_path)
1222
+
1223
+ if prd:
1224
+ result = checker.check_prd_compliance(prd)
1225
+ if result.valid:
1226
+ click.echo("✅ PRD 合规")
1227
+ else:
1228
+ for v in result.violations:
1229
+ click.echo(f"❌ {v}")
1230
+
1231
+ if rfc:
1232
+ result = checker.check_rfc_compliance(rfc, prd)
1233
+ if result.valid:
1234
+ click.echo("✅ RFC 合规")
1235
+ else:
1236
+ for v in result.violations:
1237
+ click.echo(f"❌ {v}")
1238
+
1239
+ if detect and prd and rfc:
1240
+ conflicts = checker.detect_conflicts(prd, rfc)
1241
+ if conflicts:
1242
+ click.echo(f"⚠️ 发现 {len(conflicts)} 个冲突:")
1243
+ for c in conflicts:
1244
+ click.echo(f" - {c.type}: {c.description}")
1245
+ else:
1246
+ click.echo("✅ 未检测到冲突")
1247
+
1248
+ except Exception as e:
1249
+ click.echo(f"错误: {e}")
1250
+ sys.exit(1)
1251
+
1252
+
1171
1253
  if __name__ == "__main__":
1172
1254
  main()
src/core/brain_engine.py CHANGED
@@ -141,11 +141,11 @@ class BrainEngine:
141
141
  agent_type=AgentType.AGENT_1,
142
142
  conditions=[
143
143
  Condition("phase_equals", {"value": "requirements_review"}),
144
- Condition("signoff_complete", {"stage": "requirements"})
144
+ Condition("custom", {"function": lambda ctx: not ctx.get("signoff", {}).get("requirements", {}).get("pm_signoff", False)})
145
145
  ],
146
146
  action=ActionType.SIGNOFF_REQUIREMENTS,
147
147
  priority=90,
148
- description="当阶段为requirements_review且双方签署后,签署需求"
148
+ description="当阶段为requirements_review且Agent1未签署时,签署需求"
149
149
  ),
150
150
  Rule(
151
151
  id="agent1-review-design",
@@ -191,11 +191,11 @@ class BrainEngine:
191
191
  agent_type=AgentType.AGENT_2,
192
192
  conditions=[
193
193
  Condition("phase_equals", {"value": "requirements_review"}),
194
- Condition("signoff_complete", {"stage": "requirements"})
194
+ Condition("custom", {"function": lambda ctx: not ctx.get("signoff", {}).get("requirements", {}).get("dev_signoff", False)})
195
195
  ],
196
196
  action=ActionType.SIGNOFF_REQUIREMENTS,
197
197
  priority=95,
198
- description="当阶段为requirements_review且双方签署后,签署需求"
198
+ description="当阶段为requirements_review且Agent2未签署时,签署需求"
199
199
  ),
200
200
  Rule(
201
201
  id="agent2-create-design",
@@ -0,0 +1,112 @@
1
+ """变更合规检查器模块。"""
2
+ from pathlib import Path
3
+ from typing import List, Dict, Any, Optional, Tuple
4
+ from dataclasses import dataclass, field
5
+ import re
6
+ import logging
7
+
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ @dataclass
13
+ class ComplianceResult:
14
+ """合规检查结果"""
15
+ valid: bool
16
+ violations: List[str] = field(default_factory=list)
17
+ warnings: List[str] = field(default_factory=list)
18
+ suggestions: List[str] = field(default_factory=list)
19
+
20
+
21
+ @dataclass
22
+ class Conflict:
23
+ """冲突信息"""
24
+ type: str
25
+ file: str
26
+ description: str
27
+ details: str
28
+
29
+
30
+ class ChangeComplianceChecker:
31
+ """变更合规检查器"""
32
+
33
+ PRD_PATTERN = r'(?:FR|UR|NFR|BUG)-[\w]+(?:[-][\w]+)*'
34
+ RFC_PATTERN = r'RFC-[\d]+'
35
+
36
+ def __init__(self, project_path: str):
37
+ self.project_path = Path(project_path)
38
+
39
+ def check_prd_compliance(self, prd_file: str) -> ComplianceResult:
40
+ """检查 PRD 合规性"""
41
+ result = ComplianceResult(valid=True)
42
+
43
+ if not Path(prd_file).exists():
44
+ result.valid = False
45
+ result.violations.append(f"PRD 文件不存在: {prd_file}")
46
+ return result
47
+
48
+ with open(prd_file, 'r') as f:
49
+ content = f.read()
50
+
51
+ if "## 签署确认" not in content:
52
+ result.violations.append("PRD 缺少签署确认章节")
53
+ result.suggestions.append("请添加签署确认表格")
54
+
55
+ prd_ids = re.findall(self.PRD_PATTERN, content)
56
+ if prd_ids:
57
+ result.warnings.append(f"发现 {len(set(prd_ids))} 个需求 ID,建议检查追溯性")
58
+
59
+ return result
60
+
61
+ def check_rfc_compliance(self, rfc_file: str, prd_file: str = None) -> ComplianceResult:
62
+ """检查 RFC 合规性"""
63
+ result = ComplianceResult(valid=True)
64
+
65
+ if not Path(rfc_file).exists():
66
+ result.valid = False
67
+ result.violations.append(f"RFC 文件不存在: {rfc_file}")
68
+ return result
69
+
70
+ with open(rfc_file, 'r') as f:
71
+ content = f.read()
72
+
73
+ rfc_ids = re.findall(self.RFC_PATTERN, content)
74
+ if rfc_ids:
75
+ result.suggestions.append(f"发现 {len(set(rfc_ids))} 个 RFC 引用")
76
+
77
+ if prd_file and Path(prd_file).exists():
78
+ with open(prd_file, 'r') as f:
79
+ prd_content = f.read()
80
+
81
+ if content.strip()[-100:] in prd_content[-200:]:
82
+ result.warnings.append("RFC 内容可能已包含在 PRD 中,可作为记录无需单独评审")
83
+
84
+ return result
85
+
86
+ def detect_conflicts(self, prd_file: str, rfc_file: str) -> List[Conflict]:
87
+ """检测 PRD 与 RFC 之间的冲突"""
88
+ conflicts = []
89
+
90
+ if not Path(prd_file).exists() or not Path(rfc_file).exists():
91
+ return conflicts
92
+
93
+ with open(prd_file, 'r') as f:
94
+ prd_content = f.read()
95
+
96
+ with open(rfc_file, 'r') as f:
97
+ rfc_content = f.read()
98
+
99
+ prd_ids = set(re.findall(self.PRD_PATTERN, prd_content))
100
+ rfc_refs = set(re.findall(self.RFC_PATTERN, rfc_content))
101
+
102
+ for prd_id in prd_ids:
103
+ for rfc_ref in rfc_refs:
104
+ if prd_id in rfc_content and prd_id not in rfc_ref:
105
+ conflicts.append(Conflict(
106
+ type="内容冲突",
107
+ file=f"PRD vs RFC",
108
+ description=f"需求 {prd_id} 在 RFC 中被引用,但 RFC 编号不一致",
109
+ details=f"PRD 引用: {prd_id}, RFC 内容: 包含 {prd_id}"
110
+ ))
111
+
112
+ return conflicts
src/core/signoff.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """签署引擎模块。"""
2
- from typing import Tuple
2
+ from typing import Tuple, Optional
3
+ from dataclasses import dataclass
3
4
 
4
5
 
5
6
  class SignoffError(Exception):
@@ -27,6 +28,22 @@ class RejectionError(SignoffError):
27
28
  pass
28
29
 
29
30
 
31
+ @dataclass
32
+ class SignoffResult:
33
+ """签署结果"""
34
+ success: bool
35
+ message: str
36
+ synced: bool = False
37
+ sync_error: Optional[str] = None
38
+
39
+
40
+ @dataclass
41
+ class SyncResult:
42
+ """同步结果"""
43
+ success: bool
44
+ message: str
45
+
46
+
30
47
  class SignoffEngine:
31
48
  """签署引擎。"""
32
49
 
@@ -194,3 +211,36 @@ class SignoffEngine:
194
211
  return False
195
212
 
196
213
  return True
214
+
215
+ def signoff_with_sync(self, stage: str, agent: str, comment: str = "", git_helper=None) -> SignoffResult:
216
+ """执行签署并同步到远程"""
217
+ try:
218
+ sign_result = self.sign(stage, agent, comment)
219
+
220
+ synced = False
221
+ sync_error = None
222
+
223
+ if git_helper:
224
+ try:
225
+ git_helper.push()
226
+ synced = True
227
+ except Exception as e:
228
+ sync_error = str(e)
229
+
230
+ message = f"签署成功: {stage} 阶段"
231
+ if synced:
232
+ message += "\n✓ 已同步到远程仓库"
233
+ elif sync_error:
234
+ message += f"\n⚠ 同步失败: {sync_error}"
235
+
236
+ return SignoffResult(
237
+ success=True,
238
+ message=message,
239
+ synced=synced,
240
+ sync_error=sync_error
241
+ )
242
+ except SignoffError as e:
243
+ return SignoffResult(
244
+ success=False,
245
+ message=f"签署失败: {e}"
246
+ )
@@ -0,0 +1,94 @@
1
+ """签署记录管理器模块。"""
2
+ from pathlib import Path
3
+ from typing import List, Optional, Dict, Any
4
+ import yaml
5
+ from datetime import datetime
6
+ from dataclasses import dataclass, asdict
7
+
8
+
9
+ @dataclass
10
+ class SignoffRecord:
11
+ """签署记录"""
12
+ signoff_id: str
13
+ milestone: str
14
+ phase: str
15
+ signers: List[Dict[str, Any]]
16
+ status: str
17
+ created_at: str
18
+
19
+ def to_dict(self) -> Dict[str, Any]:
20
+ return asdict(self)
21
+
22
+
23
+ class SignoffRecordManager:
24
+ """签署记录管理器"""
25
+
26
+ def __init__(self, project_path: str):
27
+ self.project_path = Path(project_path)
28
+ self.signoffs_dir = self.project_path / "state" / "signoffs"
29
+ self.signoffs_dir.mkdir(parents=True, exist_ok=True)
30
+
31
+ def _generate_signoff_id(self, milestone: str) -> str:
32
+ """生成签署记录ID"""
33
+ timestamp = datetime.now().strftime("%Y%m%d")
34
+ return f"SIG-{milestone}-{timestamp}"
35
+
36
+ def save_signoff(self, milestone: str, phase: str, signers: List[Dict[str, Any]], status: str = "PENDING") -> str:
37
+ """保存签署记录"""
38
+ signoff_id = self._generate_signoff_id(milestone)
39
+
40
+ record = SignoffRecord(
41
+ signoff_id=signoff_id,
42
+ milestone=milestone,
43
+ phase=phase,
44
+ signers=signers,
45
+ status=status,
46
+ created_at=datetime.now().isoformat()
47
+ )
48
+
49
+ file_path = self.signoffs_dir / f"{signoff_id}.yaml"
50
+ with open(file_path, 'w') as f:
51
+ yaml.dump(record.to_dict(), f)
52
+
53
+ return signoff_id
54
+
55
+ def get_signoff(self, signoff_id: str) -> Optional[Dict[str, Any]]:
56
+ """获取签署记录"""
57
+ file_path = self.signoffs_dir / f"{signoff_id}.yaml"
58
+
59
+ if file_path.exists():
60
+ with open(file_path) as f:
61
+ return yaml.safe_load(f)
62
+
63
+ return None
64
+
65
+ def list_signoffs(self) -> List[Dict[str, Any]]:
66
+ """列出所有签署记录"""
67
+ signoffs = []
68
+ for file_path in self.signoffs_dir.glob("*.yaml"):
69
+ with open(file_path) as f:
70
+ signoffs.append(yaml.safe_load(f))
71
+ return signoffs
72
+
73
+ def update_signoff_status(self, signoff_id: str, status: str, signer: Dict[str, Any] = None):
74
+ """更新签署记录状态"""
75
+ record = self.get_signoff(signoff_id)
76
+ if record:
77
+ record["status"] = status
78
+
79
+ if signer:
80
+ for i, s in enumerate(record["signers"]):
81
+ if s.get("agent") == signer.get("agent"):
82
+ record["signers"][i] = signer
83
+ break
84
+
85
+ file_path = self.signoffs_dir / f"{signoff_id}.yaml"
86
+ with open(file_path, 'w') as f:
87
+ yaml.dump(record, f)
88
+
89
+ def check_all_signed(self, signoff_id: str) -> bool:
90
+ """检查签署记录中所有签署者是否都已签署"""
91
+ record = self.get_signoff(signoff_id)
92
+ if record:
93
+ return all(s.get("status") == "approved" for s in record.get("signers", []))
94
+ return False
src/core/task_executor.py CHANGED
@@ -442,7 +442,7 @@ class ExecuteBlackboxTestStrategy(TaskStrategy):
442
442
  test_result_path = f"docs/03-test/test_report_{project_name}_v1.md"
443
443
 
444
444
  result = subprocess.run(
445
- ["python", "-m", "pytest", "-v", "--tb=short"],
445
+ ["python3", "-m", "pytest", "-v", "--tb=short"],
446
446
  capture_output=True,
447
447
  text=True,
448
448
  timeout=300
@@ -646,13 +646,20 @@ class TaskExecutor:
646
646
  FixBugsStrategy()
647
647
  ]
648
648
 
649
- def __init__(self, max_retries: int = 3, default_timeout: int = 300):
650
- """初始化任务执行器。"""
649
+ def __init__(self, max_retries: int = 3, default_timeout: int = 300, mock_mode: bool = False):
650
+ """初始化任务执行器。
651
+
652
+ Args:
653
+ max_retries: 最大重试次数
654
+ default_timeout: 默认超时时间(秒)
655
+ mock_mode: 是否使用 Mock 模式(E2E 测试时使用)
656
+ """
651
657
  self.strategies: Dict[str, TaskStrategy] = {}
652
658
  self.task_history: List[Task] = []
653
659
  self.max_retries = max_retries
654
660
  self.default_timeout = default_timeout
655
-
661
+ self.mock_mode = mock_mode
662
+
656
663
  for strategy in self.DEFAULT_STRATEGIES:
657
664
  self.register_strategy(strategy)
658
665
 
@@ -742,31 +749,61 @@ class TaskExecutor:
742
749
  return result
743
750
 
744
751
  def execute_action(self, action_type: str, context: Dict[str, Any]) -> TaskResult:
745
- """根据动作类型执行任务。"""
752
+ """根据动作类型执行任务。
753
+
754
+ Args:
755
+ action_type: 动作类型
756
+ context: 上下文信息
757
+
758
+ Returns:
759
+ 任务执行结果
760
+ """
746
761
  action_to_task_type = {
747
762
  "create_requirements": "create_requirements",
748
763
  "review_requirements": "review_requirements",
749
764
  "signoff_requirements": "signoff_requirements",
750
765
  "create_design": "create_design",
751
766
  "review_design": "review_design",
767
+ "implement_code": "implement_code",
768
+ "fix_bugs": "fix_bugs",
752
769
  "execute_blackbox_test": "execute_blackbox_test",
753
- "execute_deployment": "execute_deployment",
754
- "implement_code": "implement_code"
770
+ "execute_deployment": "execute_deployment"
755
771
  }
756
-
772
+
757
773
  task_type = action_to_task_type.get(action_type)
758
774
  if not task_type:
759
775
  return TaskResult(
760
776
  success=False,
761
777
  message=f"未知的动作类型: {action_type}"
762
778
  )
763
-
779
+
780
+ if self.mock_mode and action_type == "execute_blackbox_test":
781
+ return TaskResult(
782
+ success=True,
783
+ message="黑盒测试通过(Mock 模式)",
784
+ files_created=[],
785
+ files_modified=[],
786
+ duration=0.0,
787
+ quality_score=1.0
788
+ )
789
+
790
+ if self.mock_mode and action_type == "fix_bugs":
791
+ pending_issues = context.get("pending_issues", 0)
792
+ return TaskResult(
793
+ success=True,
794
+ message=f"已修复 {pending_issues} 个 Bug(Mock 模式)",
795
+ files_created=[],
796
+ files_modified=[],
797
+ duration=0.0,
798
+ quality_score=1.0
799
+ )
800
+
764
801
  task = self.create_task(
765
802
  name=f"执行{action_type}",
766
803
  task_type=task_type,
767
804
  params=context
768
805
  )
769
-
806
+
770
807
  return self.execute_task(task, context)
771
808
 
772
809
  def get_pending_tasks(self) -> List[Task]:
@@ -0,0 +1,169 @@
1
+ """主动流程推理引擎模块。"""
2
+ from typing import List, Dict, Any, Optional
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum
5
+ import logging
6
+
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class ComplianceLevel(Enum):
12
+ """合规级别"""
13
+ VALID = "valid"
14
+ WARNING = "warning"
15
+ VIOLATION = "violation"
16
+
17
+
18
+ @dataclass
19
+ class ComplianceResult:
20
+ """合规检查结果"""
21
+ valid: bool
22
+ level: ComplianceLevel
23
+ violations: List[str] = field(default_factory=list)
24
+ warnings: List[str] = field(default_factory=list)
25
+ suggestions: List[str] = field(default_factory=list)
26
+
27
+
28
+ @dataclass
29
+ class InferenceResult:
30
+ """流程推理结果"""
31
+ current_phase: str
32
+ next_phases: List[str]
33
+ suggestion: str
34
+ compliance_check: ComplianceResult
35
+ unknown: bool = False
36
+
37
+
38
+ class WorkflowInferenceEngine:
39
+ """主动流程推理引擎"""
40
+
41
+ PHASE_SEQUENCE = {
42
+ "requirements": ["design", "development", "testing"],
43
+ "design": ["development", "testing"],
44
+ "development": ["testing"],
45
+ "testing": ["deployment"],
46
+ }
47
+
48
+ PHASE_NEXT = {
49
+ "requirements": "设计阶段",
50
+ "design": "开发阶段",
51
+ "development": "测试阶段",
52
+ "testing": "部署阶段",
53
+ }
54
+
55
+ def __init__(self, project_path: str, state_manager):
56
+ """初始化流程推理引擎"""
57
+ self.project_path = project_path
58
+ self.state_manager = state_manager
59
+
60
+ def infer_next_action(self) -> InferenceResult:
61
+ """根据当前状态推理下一步应该执行的操作"""
62
+ try:
63
+ current_state = self.state_manager.load_state()
64
+ phase = current_state.get("phase", current_state.get("project", {}).get("phase", "unknown"))
65
+
66
+ if phase == "unknown":
67
+ return InferenceResult(
68
+ current_phase="unknown",
69
+ next_phases=[],
70
+ suggestion="请先初始化项目: oc-collab project init",
71
+ compliance_check=ComplianceResult(
72
+ valid=False,
73
+ level=ComplianceLevel.VIOLATION,
74
+ violations=["无法确定当前流程状态"],
75
+ suggestions=["请先初始化项目"]
76
+ ),
77
+ unknown=True
78
+ )
79
+
80
+ next_phases = self.PHASE_SEQUENCE.get(phase, [])
81
+
82
+ compliance = self._run_compliance_check(phase, current_state)
83
+
84
+ if next_phases:
85
+ suggestion = f"当前阶段: {phase}\n建议: 进入 {self.PHASE_NEXT.get(phase, phase)}"
86
+ else:
87
+ suggestion = f"当前阶段: {phase}\n可选: 无(流程已完成)"
88
+
89
+ return InferenceResult(
90
+ current_phase=phase,
91
+ next_phases=next_phases,
92
+ suggestion=suggestion,
93
+ compliance_check=compliance
94
+ )
95
+
96
+ except Exception as e:
97
+ logger.error(f"流程推理失败: {e}")
98
+ return InferenceResult(
99
+ current_phase="unknown",
100
+ next_phases=[],
101
+ suggestion=f"推理失败: {e}",
102
+ compliance_check=ComplianceResult(
103
+ valid=False,
104
+ level=ComplianceLevel.VIOLATION,
105
+ violations=[str(e)],
106
+ suggestions=["请检查项目状态"]
107
+ ),
108
+ unknown=True
109
+ )
110
+
111
+ def _run_compliance_check(self, phase: str, current_state: Dict[str, Any]) -> ComplianceResult:
112
+ """运行合规检查"""
113
+ violations = []
114
+ warnings = []
115
+ suggestions = []
116
+
117
+ if phase == "unknown":
118
+ violations.append("无法确定当前流程状态")
119
+ suggestions.append("请先初始化项目")
120
+
121
+ if phase == "requirements":
122
+ requirements_status = current_state.get("requirements", {})
123
+ if isinstance(requirements_status, dict):
124
+ if not requirements_status.get("pm_signoff") or not requirements_status.get("dev_signoff"):
125
+ violations.append("需求阶段尚未完成签署")
126
+ suggestions.append("请先完成需求评审和签署")
127
+
128
+ elif phase == "design":
129
+ design_status = current_state.get("design", {})
130
+ if isinstance(design_status, dict):
131
+ if not design_status.get("pm_signoff") or not design_status.get("dev_signoff"):
132
+ violations.append("设计阶段尚未完成签署")
133
+ suggestions.append("请先完成设计评审和签署")
134
+ elif isinstance(design_status, list):
135
+ if not design_status:
136
+ violations.append("没有设计文档")
137
+ suggestions.append("请先创建设计文档")
138
+
139
+ return ComplianceResult(
140
+ valid=len(violations) == 0,
141
+ level=ComplianceLevel.VIOLATION if violations else ComplianceLevel.VALID,
142
+ violations=violations,
143
+ warnings=warnings,
144
+ suggestions=suggestions
145
+ )
146
+
147
+ def validate_phase_transition(self, from_phase: str, to_phase: str) -> ComplianceResult:
148
+ """验证阶段转换是否合规"""
149
+ valid_transitions = self.PHASE_SEQUENCE.get(from_phase, [])
150
+
151
+ if to_phase not in valid_transitions:
152
+ return ComplianceResult(
153
+ valid=False,
154
+ level=ComplianceLevel.VIOLATION,
155
+ violations=[f"阶段顺序不正确: {from_phase} → {to_phase}"],
156
+ suggestions=[f"从 {from_phase} 阶段只能转换到: {', '.join(valid_transitions)}"]
157
+ )
158
+
159
+ return ComplianceResult(
160
+ valid=True,
161
+ level=ComplianceLevel.VALID,
162
+ violations=[],
163
+ suggestions=[f"✓ {from_phase} → {to_phase} 符合流程规范"]
164
+ )
165
+
166
+ def get_workflow_status(self) -> str:
167
+ """获取工作流状态"""
168
+ result = self.infer_next_action()
169
+ return f"[{result.current_phase.upper()}] - {result.suggestion}"
@@ -0,0 +1,64 @@
1
+ """环境检测工具模块。"""
2
+ import sys
3
+ import shutil
4
+ from typing import Optional
5
+
6
+
7
+ class EnvironmentError(Exception):
8
+ """环境异常基类。"""
9
+ pass
10
+
11
+
12
+ class PythonNotFoundError(EnvironmentError):
13
+ """Python 解释器未找到异常。"""
14
+ pass
15
+
16
+
17
+ def get_python_command() -> str:
18
+ """检测并返回可用的 Python 命令。
19
+
20
+ Returns:
21
+ 可用的 Python 命令(优先使用 python3)
22
+
23
+ Raises:
24
+ PythonNotFoundError: 未找到 Python 解释器
25
+ """
26
+ if shutil.which("python3"):
27
+ return "python3"
28
+ elif shutil.which("python"):
29
+ return "python"
30
+ else:
31
+ raise PythonNotFoundError("未找到 Python 解释器(python 或 python3)")
32
+
33
+
34
+ def get_python_executable() -> str:
35
+ """返回当前使用的 Python 解释器路径。
36
+
37
+ Returns:
38
+ Python 解释器的绝对路径
39
+ """
40
+ return sys.executable
41
+
42
+
43
+ def is_test_environment() -> bool:
44
+ """检测是否在测试环境中运行。
45
+
46
+ Returns:
47
+ 如果在测试环境中返回 True,否则返回 False
48
+ """
49
+ return "pytest" in sys.modules or "test" in sys.argv[0].lower()
50
+
51
+
52
+ def get_environment_info() -> dict:
53
+ """获取当前环境信息。
54
+
55
+ Returns:
56
+ 包含环境信息的字典
57
+ """
58
+ return {
59
+ "python_command": get_python_command(),
60
+ "python_executable": get_python_executable(),
61
+ "is_test_environment": is_test_environment(),
62
+ "python_version": sys.version,
63
+ "platform": sys.platform
64
+ }