opencode-collaboration 2.2.0.post2__tar.gz → 2.2.0.post4__tar.gz

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 (102) hide show
  1. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/PKG-INFO +1 -1
  2. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/opencode_collaboration.egg-info/PKG-INFO +1 -1
  3. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/opencode_collaboration.egg-info/SOURCES.txt +7 -0
  4. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/pyproject.toml +1 -1
  5. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/cli/main.py +164 -18
  6. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/brain_engine.py +4 -4
  7. opencode_collaboration-2.2.0.post4/src/core/change_compliance.py +112 -0
  8. opencode_collaboration-2.2.0.post4/src/core/session_manager.py +213 -0
  9. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/signoff.py +51 -1
  10. opencode_collaboration-2.2.0.post4/src/core/signoff_record_manager.py +94 -0
  11. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/task_executor.py +47 -10
  12. opencode_collaboration-2.2.0.post4/src/core/workflow_inference.py +169 -0
  13. opencode_collaboration-2.2.0.post4/src/utils/environment.py +64 -0
  14. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_agent_behavior.py +22 -22
  15. opencode_collaboration-2.2.0.post4/tests/test_cli_integrity.py +174 -0
  16. opencode_collaboration-2.2.0.post4/tests/test_session_manager.py +239 -0
  17. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/README.md +0 -0
  18. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/opencode_collaboration.egg-info/dependency_links.txt +0 -0
  19. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/opencode_collaboration.egg-info/entry_points.txt +0 -0
  20. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/opencode_collaboration.egg-info/requires.txt +0 -0
  21. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/opencode_collaboration.egg-info/top_level.txt +0 -0
  22. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/setup.cfg +0 -0
  23. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/__init__.py +0 -0
  24. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/cli/__init__.py +0 -0
  25. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/cli/agent.py +0 -0
  26. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/__init__.py +0 -0
  27. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/agent_manager.py +0 -0
  28. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/auto_doc_git.py +0 -0
  29. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/auto_docs.py +0 -0
  30. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/auto_engine.py +0 -0
  31. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/auto_git_sync.py +0 -0
  32. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/auto_retry.py +0 -0
  33. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/config_reloader.py +0 -0
  34. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/daemon.py +0 -0
  35. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/design_review_notifier.py +0 -0
  36. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/detector.py +0 -0
  37. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/doc_generator.py +0 -0
  38. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/error_templates.py +0 -0
  39. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/exception_handler.py +0 -0
  40. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/git.py +0 -0
  41. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/git_monitor.py +0 -0
  42. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/git_workflow_enforcer.py +0 -0
  43. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/iteration_status_manager.py +0 -0
  44. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/meeting_manager.py +0 -0
  45. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/monitor.py +0 -0
  46. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/phase_advance.py +0 -0
  47. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/project_manager.py +0 -0
  48. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/resource_lock.py +0 -0
  49. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/state_machine.py +0 -0
  50. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/state_manager.py +0 -0
  51. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/state_migrator.py +0 -0
  52. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/state_validator.py +0 -0
  53. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/story_manager.py +0 -0
  54. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/supervisor.py +0 -0
  55. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/core/workflow.py +0 -0
  56. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/main.py +0 -0
  57. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/utils/__init__.py +0 -0
  58. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/utils/date.py +0 -0
  59. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/utils/file.py +0 -0
  60. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/utils/lock.py +0 -0
  61. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/src/utils/yaml.py +0 -0
  62. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_agent_daemon.py +0 -0
  63. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_agent_daemon_complete.py +0 -0
  64. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_agent_daemon_long_running.py +0 -0
  65. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_agent_daemon_true_long_running.py +0 -0
  66. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_agent_manager.py +0 -0
  67. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_auto_git_sync.py +0 -0
  68. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_auto_retry.py +0 -0
  69. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_blackbox_v210.py +0 -0
  70. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_brain_engine.py +0 -0
  71. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_config_reloader.py +0 -0
  72. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_daemon.py +0 -0
  73. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_daemon_integration.py +0 -0
  74. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_design_review_notifier.py +0 -0
  75. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_detector.py +0 -0
  76. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_doc_generator.py +0 -0
  77. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_e2e.py +0 -0
  78. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_e2e_v2_2_0.py +0 -0
  79. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_error_templates.py +0 -0
  80. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_exception_handler.py +0 -0
  81. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_git.py +0 -0
  82. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_git_monitor.py +0 -0
  83. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_git_workflow_enforcer.py +0 -0
  84. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_iteration_status_manager.py +0 -0
  85. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_m4_enhancements.py +0 -0
  86. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_meeting_manager.py +0 -0
  87. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_monitor.py +0 -0
  88. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_package_completeness.py +0 -0
  89. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_phase_advance.py +0 -0
  90. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_project_manager.py +0 -0
  91. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_resource_lock.py +0 -0
  92. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_signoff.py +0 -0
  93. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_state_machine.py +0 -0
  94. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_state_manager.py +0 -0
  95. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_state_manager_v2.py +0 -0
  96. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_state_migration.py +0 -0
  97. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_state_structure_compatibility.py +0 -0
  98. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_state_validator.py +0 -0
  99. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_story_manager.py +0 -0
  100. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_supervisor.py +0 -0
  101. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_task_executor.py +0 -0
  102. {opencode_collaboration-2.2.0.post2 → opencode_collaboration-2.2.0.post4}/tests/test_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-collaboration
3
- Version: 2.2.0.post2
3
+ Version: 2.2.0.post4
4
4
  Summary: 双Agent协作框架 - 产品经理与开发的分离式协作工具
5
5
  Author-email: liuzhen <dev@opencode.ai>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-collaboration
3
- Version: 2.2.0.post2
3
+ Version: 2.2.0.post4
4
4
  Summary: 双Agent协作框架 - 产品经理与开发的分离式协作工具
5
5
  Author-email: liuzhen <dev@opencode.ai>
6
6
  License: MIT
@@ -19,6 +19,7 @@ src/core/auto_engine.py
19
19
  src/core/auto_git_sync.py
20
20
  src/core/auto_retry.py
21
21
  src/core/brain_engine.py
22
+ src/core/change_compliance.py
22
23
  src/core/config_reloader.py
23
24
  src/core/daemon.py
24
25
  src/core/design_review_notifier.py
@@ -35,7 +36,9 @@ src/core/monitor.py
35
36
  src/core/phase_advance.py
36
37
  src/core/project_manager.py
37
38
  src/core/resource_lock.py
39
+ src/core/session_manager.py
38
40
  src/core/signoff.py
41
+ src/core/signoff_record_manager.py
39
42
  src/core/state_machine.py
40
43
  src/core/state_manager.py
41
44
  src/core/state_migrator.py
@@ -44,8 +47,10 @@ src/core/story_manager.py
44
47
  src/core/supervisor.py
45
48
  src/core/task_executor.py
46
49
  src/core/workflow.py
50
+ src/core/workflow_inference.py
47
51
  src/utils/__init__.py
48
52
  src/utils/date.py
53
+ src/utils/environment.py
49
54
  src/utils/file.py
50
55
  src/utils/lock.py
51
56
  src/utils/yaml.py
@@ -59,6 +64,7 @@ tests/test_auto_git_sync.py
59
64
  tests/test_auto_retry.py
60
65
  tests/test_blackbox_v210.py
61
66
  tests/test_brain_engine.py
67
+ tests/test_cli_integrity.py
62
68
  tests/test_config_reloader.py
63
69
  tests/test_daemon.py
64
70
  tests/test_daemon_integration.py
@@ -80,6 +86,7 @@ tests/test_package_completeness.py
80
86
  tests/test_phase_advance.py
81
87
  tests/test_project_manager.py
82
88
  tests/test_resource_lock.py
89
+ tests/test_session_manager.py
83
90
  tests/test_signoff.py
84
91
  tests/test_state_machine.py
85
92
  tests/test_state_manager.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opencode-collaboration"
7
- version = "2.2.0.post2"
7
+ version = "2.2.0.post4"
8
8
  description = "双Agent协作框架 - 产品经理与开发的分离式协作工具"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -17,6 +17,7 @@ from ..core.auto_engine import AutoCollaborationEngine, TodoCommandExecutor, Wor
17
17
  from ..core.auto_retry import AutoRetry, AutoRetryConfig
18
18
  from ..core.auto_docs import AutoDocs, AutoDocsConfig
19
19
  from ..core.phase_advance import PhaseAdvanceEngine
20
+ from ..core.session_manager import SessionManager
20
21
  from ..utils.lock import LockExistsError
21
22
 
22
23
 
@@ -123,6 +124,9 @@ def status_command():
123
124
  req_status = state_manager.get_signoff_status("requirements")
124
125
  console.print(f"需求签署 - 产品经理: {'✓' if req_status['pm_signoff'] else '✗'}, 开发: {'✓' if req_status['dev_signoff'] else '✗'}")
125
126
 
127
+ session_manager = SessionManager(project_path)
128
+ session_manager.show_welcome(active_agent)
129
+
126
130
  except StateFileNotFoundError:
127
131
  click.echo("错误: 未找到项目状态文件,请先初始化项目")
128
132
  sys.exit(1)
@@ -136,22 +140,33 @@ def status_command():
136
140
 
137
141
  @main.command("switch")
138
142
  @click.argument("agent_id", type=click.IntRange(1, 2))
139
- def switch_command(agent_id: int):
143
+ @click.option("--welcome/--no-welcome", "-w", default=True, help="显示欢迎信息")
144
+ def switch_command(agent_id: int, welcome: bool):
140
145
  """切换Agent角色。"""
141
146
  try:
142
147
  project_path = get_project_path()
143
148
  state_manager = StateManager(project_path)
144
-
149
+
145
150
  current_agent = state_manager.get_active_agent()
146
151
  if current_agent == f"agent{agent_id}":
147
152
  click.echo(f"已经是 Agent {agent_id}")
148
153
  return
149
-
154
+
150
155
  state_manager.set_active_agent(f"agent{agent_id}")
151
-
152
- agent_info = state_manager.load_state()["agents"][f"agent{agent_id}"]
153
- click.echo(f"已切换到 Agent {agent_id} ({agent_info['role']})")
154
-
156
+
157
+ if welcome:
158
+ session_manager = SessionManager(project_path)
159
+ session_manager.show_welcome(f"agent{agent_id}")
160
+ else:
161
+ try:
162
+ state = state_manager.load_state()
163
+ agents = state.get("agents", {})
164
+ agent_info = agents.get(f"agent{agent_id}", {})
165
+ role = agent_info.get("role", "未知")
166
+ except Exception:
167
+ role = "未知"
168
+ click.echo(f"已切换到 Agent {agent_id} ({role})")
169
+
155
170
  except Exception as e:
156
171
  click.echo(f"错误: {e}")
157
172
  sys.exit(1)
@@ -159,26 +174,27 @@ def switch_command(agent_id: int):
159
174
 
160
175
  @main.command("review")
161
176
  @click.argument("stage", type=click.Choice(["requirements", "design", "test"]))
177
+ @click.option("--file", "-f", help="指定评审文件路径")
162
178
  @click.option("--new", is_flag=True, default=False)
163
179
  @click.option("--list", "-l", is_flag=True, default=False)
164
- def review_command(stage: str, new: bool, list: bool):
180
+ def review_command(stage: str, file: str, new: bool, list: bool):
165
181
  """管理评审流程。"""
166
182
  try:
167
183
  project_path = get_project_path()
168
184
  state_manager = StateManager(project_path)
169
185
  workflow_engine = WorkflowEngine(state_manager)
170
-
186
+
171
187
  if new:
172
188
  workflow_engine.start_review(stage)
173
189
  click.echo(f"已发起 {stage} 评审")
174
-
190
+
175
191
  if list:
176
192
  history = state_manager.get_history()
177
193
  console.print(f"\n[bold]{stage.upper()} 评审历史[/bold]")
178
194
  for item in history[:10]:
179
195
  if "review" in item["action"] or "signoff" in item["action"]:
180
196
  console.print(f"- {item['timestamp']}: Agent {item['agent']} - {item['details']}")
181
-
197
+
182
198
  except Exception as e:
183
199
  click.echo(f"错误: {e}")
184
200
  sys.exit(1)
@@ -188,26 +204,74 @@ def review_command(stage: str, new: bool, list: bool):
188
204
  @click.argument("stage", type=click.Choice(["requirements", "design", "test"]))
189
205
  @click.option("--comment", "-m", default="")
190
206
  @click.option("--reject", "-r", default=None)
191
- 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):
192
209
  """签署确认。"""
193
210
  try:
194
211
  project_path = get_project_path()
195
212
  state_manager = StateManager(project_path)
196
213
  workflow_engine = WorkflowEngine(state_manager)
197
214
  signoff_engine = SignoffEngine(state_manager, workflow_engine)
198
-
215
+
199
216
  agent_id = state_manager.get_active_agent()
200
-
217
+
201
218
  if reject:
202
219
  result = signoff_engine.reject(stage, agent_id, reject)
203
220
  click.echo(f"已拒签 {stage} 阶段")
204
221
  else:
205
- result = signoff_engine.sign(stage, agent_id, comment)
206
- click.echo(f"已签署 {stage} 阶段")
207
-
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
+
208
231
  if state_manager.can_proceed_to_next_phase():
209
232
  click.echo("双方已签署,可以推进到下一阶段")
210
-
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
+
211
275
  except Exception as e:
212
276
  click.echo(f"错误: {e}")
213
277
  sys.exit(1)
@@ -1104,5 +1168,87 @@ def advance_command(phase: str, force: bool, check: bool):
1104
1168
  sys.exit(1)
1105
1169
 
1106
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
+
1107
1253
  if __name__ == "__main__":
1108
1254
  main()
@@ -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
@@ -0,0 +1,213 @@
1
+ from pathlib import Path
2
+ from typing import Optional, List
3
+ from rich.console import Console
4
+ from rich.panel import Panel
5
+ from rich.text import Text
6
+
7
+ from .state_manager import StateManager
8
+
9
+ console = Console()
10
+
11
+ AGENT_ROLES = {
12
+ "agent1": {
13
+ "role": "产品经理",
14
+ "responsibilities": [
15
+ "编写和评审需求文档",
16
+ "定义验收标准",
17
+ "签署需求确认",
18
+ "评审设计文档",
19
+ "评审测试报告"
20
+ ]
21
+ },
22
+ "agent2": {
23
+ "role": "开发负责人",
24
+ "responsibilities": [
25
+ "评审需求文档",
26
+ "编写详细设计",
27
+ "代码实现",
28
+ "编写单元测试",
29
+ "签署技术确认"
30
+ ]
31
+ }
32
+ }
33
+
34
+ COMMON_COMMANDS = [
35
+ ("oc-collab status", "查看项目状态"),
36
+ ("oc-collab todo", "查看待办事项"),
37
+ ("oc-collab review", "评审文档"),
38
+ ("oc-collab signoff", "签署确认"),
39
+ ("oc-collab history", "查看协作历史"),
40
+ ("oc-collab switch <1|2>", "切换Agent角色")
41
+ ]
42
+
43
+
44
+ class SessionConfig:
45
+ def __init__(self, project_path: str):
46
+ self.project_path = project_path
47
+ self.enabled = True
48
+ self.show_responsibilities = True
49
+ self.show_todo = True
50
+ self.show_pending = True
51
+ self.load_config()
52
+
53
+ def load_config(self):
54
+ state_file = Path(self.project_path) / "state" / "project_state.yaml"
55
+ if state_file.exists():
56
+ try:
57
+ import yaml
58
+ with open(state_file) as f:
59
+ state = yaml.safe_load(f) or {}
60
+ session_config = state.get("session_start", {})
61
+ self.enabled = session_config.get("enabled", True)
62
+ self.show_responsibilities = session_config.get("show_responsibilities", True)
63
+ self.show_todo = session_config.get("show_todo", True)
64
+ self.show_pending = session_config.get("show_pending", True)
65
+ except Exception:
66
+ pass
67
+
68
+
69
+ class SessionManager:
70
+ def __init__(self, project_path: str):
71
+ self.project_path = project_path
72
+ self.state_manager = StateManager(project_path)
73
+ self.config = SessionConfig(project_path)
74
+
75
+ def get_project_info(self) -> dict:
76
+ try:
77
+ state = self.state_manager.load_state()
78
+ metadata = state.get("metadata", {})
79
+ project_info = state.get("project", {})
80
+
81
+ return {
82
+ "name": metadata.get("project_name") or project_info.get("name", "未配置"),
83
+ "phase": project_info.get("phase") or state.get("phase", "未知"),
84
+ "milestone": state.get("current_milestone", "待定义")
85
+ }
86
+ except Exception:
87
+ return {"name": "未配置", "phase": "未知", "milestone": "待定义"}
88
+
89
+ def get_agent_info(self, agent_id: str) -> dict:
90
+ agent_config = AGENT_ROLES.get(agent_id, {
91
+ "role": "未知",
92
+ "responsibilities": []
93
+ })
94
+
95
+ try:
96
+ state = self.state_manager.load_state()
97
+ agents = state.get("agents", {})
98
+ agent_state = agents.get(agent_id, {})
99
+ except KeyError:
100
+ agent_state = {}
101
+ except Exception:
102
+ agent_state = {}
103
+
104
+ return {
105
+ "id": agent_id,
106
+ "role": agent_config["role"],
107
+ "current_task": agent_state.get("current_task", ""),
108
+ "responsibilities": agent_config["responsibilities"]
109
+ }
110
+
111
+ def get_responsibilities_text(self, agent_id: str) -> str:
112
+ agent = self.get_agent_info(agent_id)
113
+ if not self.config.show_responsibilities:
114
+ return ""
115
+
116
+ lines = ["你的职责:"]
117
+ for resp in agent["responsibilities"]:
118
+ lines.append(f" - {resp}")
119
+ return "\n".join(lines)
120
+
121
+ def get_todo_items(self) -> str:
122
+ if not self.config.show_todo:
123
+ return ""
124
+
125
+ try:
126
+ from .auto_engine import TodoCommandExecutor
127
+ executor = TodoCommandExecutor(self.project_path)
128
+ todo_list = executor.get_todo_list()
129
+
130
+ if not todo_list:
131
+ return "待办事项:\n 暂无待办事项"
132
+
133
+ lines = ["待办事项:"]
134
+ for item in todo_list[:5]:
135
+ task = item.get("task", "")
136
+ lines.append(f" [ ] {task}")
137
+ return "\n".join(lines)
138
+ except Exception:
139
+ return "待办事项:\n 暂无待办事项"
140
+
141
+ def get_pending_issues(self) -> str:
142
+ if not self.config.show_pending:
143
+ return ""
144
+
145
+ pending_file = Path(self.project_path) / "state" / "memory" / "pending.yaml"
146
+ if not pending_file.exists():
147
+ return "上次会话遗留:\n 无遗留问题"
148
+
149
+ try:
150
+ import yaml
151
+ with open(pending_file) as f:
152
+ pending = yaml.safe_load(f) or []
153
+
154
+ if not pending:
155
+ return "上次会话遗留:\n 无遗留问题"
156
+
157
+ lines = ["上次会话遗留:"]
158
+ for item in pending[:5]:
159
+ desc = item.get("description", item)
160
+ lines.append(f" - {desc}")
161
+ return "\n".join(lines)
162
+ except Exception:
163
+ return "上次会话遗留:\n 无遗留问题"
164
+
165
+ def get_common_commands(self) -> str:
166
+ lines = ["常用命令:"]
167
+ for cmd, desc in COMMON_COMMANDS:
168
+ lines.append(f" - {cmd}: {desc}")
169
+ return "\n".join(lines)
170
+
171
+ def get_welcome_message(self, agent_id: str) -> str:
172
+ if not self.config.enabled:
173
+ return ""
174
+
175
+ project = self.get_project_info()
176
+ agent = self.get_agent_info(agent_id)
177
+
178
+ parts = [
179
+ f"=== Agent {agent_id.replace('agent', '')} ({agent['role']}) ===",
180
+ "",
181
+ f"当前项目: {project['name']}",
182
+ f"当前阶段: {project['phase']}",
183
+ f"当前里程碑: {project['milestone']}",
184
+ ""
185
+ ]
186
+
187
+ resp_text = self.get_responsibilities_text(agent_id)
188
+ if resp_text:
189
+ parts.append(resp_text)
190
+ parts.append("")
191
+
192
+ todo_text = self.get_todo_items()
193
+ if todo_text:
194
+ parts.append(todo_text)
195
+ parts.append("")
196
+
197
+ pending_text = self.get_pending_issues()
198
+ if pending_text:
199
+ parts.append(pending_text)
200
+ parts.append("")
201
+
202
+ parts.append(self.get_common_commands())
203
+
204
+ return "\n".join(parts)
205
+
206
+ def show_welcome(self, agent_id: str):
207
+ message = self.get_welcome_message(agent_id)
208
+ if message:
209
+ console.print(Panel(
210
+ Text(message, justify="left"),
211
+ title="会话引导",
212
+ style="blue"
213
+ ))