devhive 0.1.18__tar.gz → 0.1.19__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.
- {devhive-0.1.18 → devhive-0.1.19}/PKG-INFO +1 -1
- devhive-0.1.19/devhive/agents/ceo.py +95 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/cli.py +1 -1
- {devhive-0.1.18 → devhive-0.1.19}/devhive/core/prompt_builder.py +145 -31
- {devhive-0.1.18 → devhive-0.1.19}/devhive/core/task_orchestrator.py +10 -2
- {devhive-0.1.18 → devhive-0.1.19}/devhive/server.py +7 -7
- {devhive-0.1.18 → devhive-0.1.19}/devhive/utils/validation.py +23 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive.egg-info/PKG-INFO +1 -1
- {devhive-0.1.18 → devhive-0.1.19}/pyproject.toml +1 -1
- {devhive-0.1.18 → devhive-0.1.19}/setup.py +1 -1
- devhive-0.1.18/devhive/agents/ceo.py +0 -190
- {devhive-0.1.18 → devhive-0.1.19}/LICENSE +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/README.md +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/__init__.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/agents/__init__.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/agents/architect.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/agents/archivist.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/agents/auditor.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/agents/base_agent.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/agents/developer.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/agents/explorer.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/agents/proposal.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/agents/qa.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/agents/task_agent.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/core/__init__.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/core/artifact_manager.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/core/context_optimizer.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/core/context_router.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/core/dependency_manager.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/core/llm.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/core/memory_store.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/core/project_state_manager.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/core/task_queue.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/skills/auditor/owasp_security_audit.md +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/skills/auditor/performance_bottlenecks.md +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/skills/developer/backend-architect.md +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/skills/developer/backend-security-coder.md +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/skills/explorer/edge_case_hunter.md +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/skills/explorer/nfr_checklist.md +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/skills/global/inclusive_language.md +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/skills/global/json_strictness.md +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/skills/global/security_mandates.md +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/utils/__init__.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive/utils/filesystem.py +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive.egg-info/SOURCES.txt +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive.egg-info/dependency_links.txt +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive.egg-info/entry_points.txt +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive.egg-info/requires.txt +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/devhive.egg-info/top_level.txt +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/setup.cfg +0 -0
- {devhive-0.1.18 → devhive-0.1.19}/tests/test_parallel_development.py +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
from mcp.server.fastmcp import Context
|
|
4
|
+
from devhive.agents.base_agent import BaseAgent
|
|
5
|
+
from devhive.core.project_state_manager import ProjectStateManager
|
|
6
|
+
|
|
7
|
+
class CEOAgent(BaseAgent):
|
|
8
|
+
role = "CEO"
|
|
9
|
+
|
|
10
|
+
def get_next_agent_deterministic(self) -> Dict[str, Any]:
|
|
11
|
+
"""
|
|
12
|
+
Determine next agent to run based on dynamic workflow_plan from the state.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Dict with 'agent' (role name) and 'reason' (explanation)
|
|
16
|
+
"""
|
|
17
|
+
state = self.state_manager.get_state()
|
|
18
|
+
artifacts = state.get("artifacts", {})
|
|
19
|
+
|
|
20
|
+
# If the CEO hasn't planned the workflow yet, run the CEO.
|
|
21
|
+
if artifacts.get("workflow_plan") is None:
|
|
22
|
+
return {
|
|
23
|
+
"agent": "CEO",
|
|
24
|
+
"reason": "Need initial workflow planning based on requirements"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Get the workflow plan created by the CEO
|
|
28
|
+
try:
|
|
29
|
+
plan_id = artifacts.get("workflow_plan")
|
|
30
|
+
plan_data = self.artifact_manager.load_artifact(plan_id)
|
|
31
|
+
workflow_plan = plan_data.get("workflow_plan", [])
|
|
32
|
+
# Fallback if workflow_plan is malformed
|
|
33
|
+
if not workflow_plan:
|
|
34
|
+
workflow_plan = ["Explorer", "Developer", "QA", "Archivist"]
|
|
35
|
+
except Exception:
|
|
36
|
+
# Fallback if something is broken
|
|
37
|
+
workflow_plan = ["Explorer", "Developer", "QA", "Archivist"]
|
|
38
|
+
|
|
39
|
+
# Map agent names to artifact keys
|
|
40
|
+
mapping = {
|
|
41
|
+
"Explorer": "exploration",
|
|
42
|
+
"Proposal": "proposal",
|
|
43
|
+
"Architect": "architecture",
|
|
44
|
+
"TaskPlanner": "tasks",
|
|
45
|
+
"Developer": "implementation",
|
|
46
|
+
"QA": "tests",
|
|
47
|
+
"Auditor": "verification",
|
|
48
|
+
"Archivist": "archive"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Iterate over the plan and find the first incomplete agent
|
|
52
|
+
for agent in workflow_plan:
|
|
53
|
+
artifact_key = mapping.get(agent)
|
|
54
|
+
|
|
55
|
+
if artifact_key == "implementation":
|
|
56
|
+
# Special iterative Developer logic
|
|
57
|
+
if artifacts.get("implementation") is None:
|
|
58
|
+
dev_progress = state.get("developer_progress", {})
|
|
59
|
+
pending_tasks = dev_progress.get("pending_tasks", [])
|
|
60
|
+
completed_tasks = dev_progress.get("completed_tasks", [])
|
|
61
|
+
|
|
62
|
+
if not dev_progress or pending_tasks:
|
|
63
|
+
task_count = len(pending_tasks) if pending_tasks else "tasks"
|
|
64
|
+
return {
|
|
65
|
+
"agent": "Developer",
|
|
66
|
+
"reason": f"Need to execute {task_count} pending tasks sequentially"
|
|
67
|
+
}
|
|
68
|
+
elif not pending_tasks and completed_tasks:
|
|
69
|
+
# Implementation is complete based on tasks
|
|
70
|
+
continue
|
|
71
|
+
else:
|
|
72
|
+
return {
|
|
73
|
+
"agent": "Developer",
|
|
74
|
+
"reason": "Ready for implementation"
|
|
75
|
+
}
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
if artifacts.get(artifact_key) is None:
|
|
79
|
+
return {
|
|
80
|
+
"agent": agent,
|
|
81
|
+
"reason": f"Next agent in workflow plan: {agent}"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# All done
|
|
85
|
+
return {
|
|
86
|
+
"agent": "Complete",
|
|
87
|
+
"reason": "All pipeline stages in the workflow plan have finished, project is complete"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async def execute(self, ctx: Context, **kwargs) -> Dict[str, Any]:
|
|
91
|
+
"""
|
|
92
|
+
CEO Agent execution logic is handled by TaskOrchestrator in the new task-based workflow.
|
|
93
|
+
This legacy method is not used.
|
|
94
|
+
"""
|
|
95
|
+
pass
|
|
@@ -95,7 +95,7 @@ def configure():
|
|
|
95
95
|
# 3. Configure OpenCode
|
|
96
96
|
if choice in ["opencode", "both"]:
|
|
97
97
|
dest_dir = Path.home() / ".config" / "opencode" / "agents"
|
|
98
|
-
dest_file = dest_dir / "
|
|
98
|
+
dest_file = dest_dir / "DevHive0119.md"
|
|
99
99
|
_copy_agent_file(source_to_use, dest_dir, dest_file, "OpenCode Agent")
|
|
100
100
|
_configure_devhive()
|
|
101
101
|
|
|
@@ -27,7 +27,9 @@ Note: You have access to the devhive_search_memory tool if you need to find spec
|
|
|
27
27
|
Dictionary with 'system_prompt' and 'user_prompt' keys
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
-
if agent_role == "
|
|
30
|
+
if agent_role == "CEO":
|
|
31
|
+
return PromptBuilder._build_ceo_prompts(context, **kwargs)
|
|
32
|
+
elif agent_role == "Explorer":
|
|
31
33
|
return PromptBuilder._build_explorer_prompts(context, **kwargs)
|
|
32
34
|
elif agent_role == "Proposal":
|
|
33
35
|
return PromptBuilder._build_proposal_prompts(context)
|
|
@@ -47,43 +49,155 @@ Note: You have access to the devhive_search_memory tool if you need to find spec
|
|
|
47
49
|
raise ValueError(f"Unknown agent role: {agent_role}")
|
|
48
50
|
|
|
49
51
|
@staticmethod
|
|
50
|
-
def
|
|
52
|
+
def _build_ceo_prompts(context: Dict[str, Any], **kwargs) -> Dict[str, str]:
|
|
51
53
|
requirements = kwargs.get("requirements", "No requirements provided")
|
|
52
54
|
|
|
53
|
-
system_prompt = "
|
|
55
|
+
system_prompt = """
|
|
56
|
+
You are an AI orchestration agent called "CEO".
|
|
57
|
+
|
|
58
|
+
Your job is to orchestrate an AI software development pipeline by selecting and ordering agents to fulfill a feature request.
|
|
59
|
+
|
|
60
|
+
Available agents:
|
|
61
|
+
- Explorer
|
|
62
|
+
- Proposal
|
|
63
|
+
- Architect
|
|
64
|
+
- TaskPlanner
|
|
65
|
+
- Developer
|
|
66
|
+
- QA
|
|
67
|
+
- Auditor
|
|
68
|
+
- Archivist
|
|
69
|
+
|
|
70
|
+
Agent roles:
|
|
71
|
+
|
|
72
|
+
Explorer
|
|
73
|
+
Analyzes requirements, constraints and dependencies.
|
|
74
|
+
|
|
75
|
+
Proposal
|
|
76
|
+
Creates a structured feature proposal and acceptance criteria.
|
|
77
|
+
|
|
78
|
+
Architect
|
|
79
|
+
Designs technical architecture and system structure.
|
|
80
|
+
|
|
81
|
+
TaskPlanner
|
|
82
|
+
Breaks the feature into development tasks.
|
|
83
|
+
|
|
84
|
+
Developer
|
|
85
|
+
Implements the feature.
|
|
86
|
+
|
|
87
|
+
QA
|
|
88
|
+
Creates tests for the implementation.
|
|
89
|
+
|
|
90
|
+
Auditor
|
|
91
|
+
Reviews architecture, consistency, and security.
|
|
92
|
+
|
|
93
|
+
Archivist
|
|
94
|
+
Archives the final project state.
|
|
95
|
+
|
|
96
|
+
Pipeline rules:
|
|
97
|
+
- Explorer should normally run first
|
|
98
|
+
- Developer must run before Archivist
|
|
99
|
+
- Archivist must always be the final step
|
|
100
|
+
- Agents must appear only once
|
|
101
|
+
- The workflow must be logically ordered
|
|
102
|
+
|
|
103
|
+
Agent selection guidelines:
|
|
104
|
+
|
|
105
|
+
Proposal
|
|
106
|
+
Use when the feature requires product clarification or acceptance criteria.
|
|
107
|
+
|
|
108
|
+
Architect
|
|
109
|
+
Use when backend systems, APIs, or architecture are involved.
|
|
110
|
+
|
|
111
|
+
TaskPlanner
|
|
112
|
+
Use when the implementation requires multiple tasks or moderate complexity.
|
|
113
|
+
|
|
114
|
+
QA
|
|
115
|
+
Use when code with logic is implemented.
|
|
116
|
+
|
|
117
|
+
Auditor
|
|
118
|
+
Use when architecture or security should be reviewed.
|
|
119
|
+
|
|
120
|
+
Simple features may skip Proposal, Architect, TaskPlanner, QA, and Auditor.
|
|
121
|
+
|
|
122
|
+
Output rules:
|
|
123
|
+
- Return valid JSON only
|
|
124
|
+
- Do not include explanations outside the JSON
|
|
125
|
+
- Follow the schema exactly
|
|
126
|
+
|
|
127
|
+
Schema:
|
|
128
|
+
{
|
|
129
|
+
"workflow_plan": string[],
|
|
130
|
+
"reasoning": string
|
|
131
|
+
}
|
|
132
|
+
"""
|
|
54
133
|
|
|
55
|
-
user_prompt = f"""
|
|
134
|
+
user_prompt = f"""
|
|
135
|
+
Feature request:
|
|
56
136
|
|
|
57
|
-
|
|
137
|
+
{requirements}
|
|
58
138
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
139
|
+
Determine the optimal sequence of agents to execute.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
"system_prompt": system_prompt,
|
|
144
|
+
"user_prompt": user_prompt
|
|
145
|
+
}
|
|
64
146
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
147
|
+
@staticmethod
|
|
148
|
+
def _build_explorer_prompts(context: Dict[str, Any], **kwargs) -> Dict[str, str]:
|
|
149
|
+
requirements = kwargs.get("requirements", "No requirements provided")
|
|
150
|
+
|
|
151
|
+
system_prompt = """
|
|
152
|
+
You are an AI software architecture analyst called "Analyst (Explorer)".
|
|
153
|
+
|
|
154
|
+
Your role is to analyze feature requests and produce structured technical analysis for downstream agents.
|
|
155
|
+
|
|
156
|
+
Responsibilities:
|
|
157
|
+
- Identify user needs
|
|
158
|
+
- Detect constraints
|
|
159
|
+
- List dependencies
|
|
160
|
+
- Identify risks
|
|
161
|
+
- Estimate complexity
|
|
162
|
+
- Suggest workflow
|
|
163
|
+
|
|
164
|
+
Context may include project information such as technology guidelines.
|
|
165
|
+
|
|
166
|
+
Guidelines handling:
|
|
167
|
+
- If project_guidelines says "Guidelines not found.", determine the likely technology stack.
|
|
168
|
+
- If the stack is clear, generate new guidelines.
|
|
169
|
+
- If the stack is unclear, ask a clarification question.
|
|
170
|
+
|
|
171
|
+
Complexity levels:
|
|
172
|
+
- low: simple changes, minimal architecture
|
|
173
|
+
- medium: moderate implementation work
|
|
174
|
+
- high: architectural changes or major system impact
|
|
175
|
+
|
|
176
|
+
Output rules:
|
|
177
|
+
- Output valid JSON only
|
|
178
|
+
- Do not include explanations outside JSON
|
|
179
|
+
- Follow the schema strictly
|
|
180
|
+
|
|
181
|
+
Schema:
|
|
182
|
+
{
|
|
183
|
+
"user_needs": string,
|
|
184
|
+
"constraints": string[],
|
|
185
|
+
"dependencies": string[],
|
|
186
|
+
"risks": string[],
|
|
187
|
+
"complexity": "low" | "medium" | "high",
|
|
188
|
+
"suggested_workflow": "fast_track" | "standard" | "comprehensive",
|
|
189
|
+
"new_guidelines_content": string | null,
|
|
190
|
+
"clarification_question": string | null
|
|
191
|
+
}
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
user_prompt = f"""
|
|
195
|
+
Analyze the following feature request: {requirements}
|
|
77
196
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"dependencies": ["CSV library", "File storage system"],
|
|
83
|
-
"risks": ["Large datasets may cause performance issues", "Security concerns with file downloads"],
|
|
84
|
-
"complexity": "medium",
|
|
85
|
-
"suggested_workflow": "standard"
|
|
86
|
-
}}"""
|
|
197
|
+
Context: {json.dumps(context, default=str)}
|
|
198
|
+
|
|
199
|
+
Perform the analysis and exploration and return the JSON response.
|
|
200
|
+
"""
|
|
87
201
|
|
|
88
202
|
return {
|
|
89
203
|
"system_prompt": system_prompt,
|
|
@@ -279,6 +279,7 @@ class TaskOrchestrator:
|
|
|
279
279
|
def _validate_response(self, agent_name: str, response: Dict[str, Any]) -> tuple[bool, str]:
|
|
280
280
|
"""Validate response using appropriate validator method."""
|
|
281
281
|
validators = {
|
|
282
|
+
"CEO": ResponseValidator.validate_ceo,
|
|
282
283
|
"Explorer": ResponseValidator.validate_explorer,
|
|
283
284
|
"Proposal": ResponseValidator.validate_proposal,
|
|
284
285
|
"Architect": ResponseValidator.validate_architect,
|
|
@@ -303,6 +304,7 @@ class TaskOrchestrator:
|
|
|
303
304
|
def _get_artifact_key(self, agent_name: str) -> str:
|
|
304
305
|
"""Map agent name to artifact key used in state."""
|
|
305
306
|
mapping = {
|
|
307
|
+
"CEO": "workflow_plan",
|
|
306
308
|
"Explorer": "exploration",
|
|
307
309
|
"Proposal": "proposal",
|
|
308
310
|
"Architect": "architecture",
|
|
@@ -313,7 +315,7 @@ class TaskOrchestrator:
|
|
|
313
315
|
"Archivist": "archive"
|
|
314
316
|
}
|
|
315
317
|
return mapping.get(agent_name, agent_name.lower())
|
|
316
|
-
|
|
318
|
+
|
|
317
319
|
def _generate_summary(self, agent_name: str, data: Dict[str, Any]) -> str:
|
|
318
320
|
"""
|
|
319
321
|
Generate 1-3 sentence executive summary of agent's work.
|
|
@@ -322,6 +324,7 @@ class TaskOrchestrator:
|
|
|
322
324
|
via their generate_summary() method for more specific summaries.
|
|
323
325
|
"""
|
|
324
326
|
summaries = {
|
|
327
|
+
"CEO": self._summarize_ceo,
|
|
325
328
|
"Explorer": self._summarize_explorer,
|
|
326
329
|
"Proposal": self._summarize_proposal,
|
|
327
330
|
"Architect": self._summarize_architect,
|
|
@@ -334,7 +337,12 @@ class TaskOrchestrator:
|
|
|
334
337
|
|
|
335
338
|
summarizer = summaries.get(agent_name, lambda x: f"{agent_name} completed successfully.")
|
|
336
339
|
return summarizer(data)
|
|
337
|
-
|
|
340
|
+
|
|
341
|
+
def _summarize_ceo(self, data: Dict[str, Any]) -> str:
|
|
342
|
+
"""Generate summary for CEO agent."""
|
|
343
|
+
plan = data.get("workflow_plan", [])
|
|
344
|
+
return f"Planned workflow with {len(plan)} agents: {' -> '.join(plan)}."
|
|
345
|
+
|
|
338
346
|
def _summarize_explorer(self, data: Dict[str, Any]) -> str:
|
|
339
347
|
"""Generate summary for Explorer agent."""
|
|
340
348
|
constraints_count = len(data.get("constraints", []))
|
|
@@ -75,7 +75,7 @@ def devhive_start_pipeline(project_name: str, requirements: str) -> str:
|
|
|
75
75
|
Returns:
|
|
76
76
|
JSON with Task launch instructions:
|
|
77
77
|
- status: "pending" (next task ready) or "complete" (pipeline done)
|
|
78
|
-
- agent: Next agent to run (Explorer, Proposal, etc.)
|
|
78
|
+
- agent: Next agent to run (CEO, Explorer, Proposal, etc.)
|
|
79
79
|
- reason: Why this agent needs to run
|
|
80
80
|
- system_prompt: System prompt for the Task
|
|
81
81
|
- user_prompt: User prompt for the Task
|
|
@@ -94,7 +94,7 @@ def devhive_start_pipeline(project_name: str, requirements: str) -> str:
|
|
|
94
94
|
# Initialize orchestrator (creates project if needed)
|
|
95
95
|
orchestrator = TaskOrchestrator(project_name)
|
|
96
96
|
|
|
97
|
-
# Get first task (
|
|
97
|
+
# Get first task (CEO)
|
|
98
98
|
task_info = orchestrator.get_next_task(requirements=requirements)
|
|
99
99
|
|
|
100
100
|
return json.dumps(task_info, indent=2)
|
|
@@ -116,7 +116,7 @@ def devhive_submit_result(project_name: str, agent_name: str, llm_response: str)
|
|
|
116
116
|
|
|
117
117
|
Args:
|
|
118
118
|
project_name: Project identifier
|
|
119
|
-
agent_name: Name of agent that produced result (Explorer, Proposal, Architect,
|
|
119
|
+
agent_name: Name of agent that produced result (CEO, Explorer, Proposal, Architect,
|
|
120
120
|
TaskPlanner, Developer, QA, Auditor, or Archivist)
|
|
121
121
|
llm_response: Raw LLM response (JSON string, can include markdown code blocks)
|
|
122
122
|
|
|
@@ -182,7 +182,7 @@ def get_next_step(project_name: str) -> str:
|
|
|
182
182
|
|
|
183
183
|
Returns:
|
|
184
184
|
JSON string with:
|
|
185
|
-
- agent: The agent role name (Explorer, Proposal, etc.)
|
|
185
|
+
- agent: The agent role name (CEO, Explorer, Proposal, etc.)
|
|
186
186
|
- reason: Why this agent needs to run
|
|
187
187
|
- system_prompt: System prompt to send to LLM
|
|
188
188
|
- user_prompt: User prompt to send to LLM
|
|
@@ -211,9 +211,9 @@ def get_next_step(project_name: str) -> str:
|
|
|
211
211
|
context = agent.get_context()
|
|
212
212
|
|
|
213
213
|
# Build prompts using PromptBuilder
|
|
214
|
-
# For Explorer, we need to check if requirements are in kwargs
|
|
215
|
-
if agent_role
|
|
216
|
-
# Requirements should be provided when executing
|
|
214
|
+
# For Explorer/CEO, we need to check if requirements are in kwargs
|
|
215
|
+
if agent_role in ["Explorer", "CEO"]:
|
|
216
|
+
# Requirements should be provided when executing
|
|
217
217
|
prompts = PromptBuilder.build_prompts(agent_role, context, requirements="[Requirements will be provided when executing]")
|
|
218
218
|
else:
|
|
219
219
|
prompts = PromptBuilder.build_prompts(agent_role, context)
|
|
@@ -7,6 +7,28 @@ class ResponseValidator:
|
|
|
7
7
|
Ensures responses contain required keys and valid data structures.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
@staticmethod
|
|
11
|
+
def validate_ceo(response: Dict[str, Any]) -> Tuple[bool, str]:
|
|
12
|
+
"""
|
|
13
|
+
Validate CEO agent response.
|
|
14
|
+
|
|
15
|
+
Expected keys: workflow_plan, reasoning
|
|
16
|
+
"""
|
|
17
|
+
required_keys = ["workflow_plan", "reasoning"]
|
|
18
|
+
is_valid, error = ResponseValidator._validate_keys(response, required_keys, "CEO")
|
|
19
|
+
if not is_valid:
|
|
20
|
+
return is_valid, error
|
|
21
|
+
|
|
22
|
+
if not isinstance(response.get("workflow_plan"), list):
|
|
23
|
+
return False, "CEO: 'workflow_plan' must be a list of agent names"
|
|
24
|
+
|
|
25
|
+
valid_agents = ["Explorer", "Proposal", "Architect", "TaskPlanner", "Developer", "QA", "Auditor", "Archivist"]
|
|
26
|
+
for agent in response["workflow_plan"]:
|
|
27
|
+
if agent not in valid_agents:
|
|
28
|
+
return False, f"CEO: Invalid agent name '{agent}' in workflow_plan. Valid agents are: {', '.join(valid_agents)}"
|
|
29
|
+
|
|
30
|
+
return True, ""
|
|
31
|
+
|
|
10
32
|
@staticmethod
|
|
11
33
|
def validate_explorer(response: Dict[str, Any]) -> Tuple[bool, str]:
|
|
12
34
|
"""
|
|
@@ -191,6 +213,7 @@ class ResponseValidator:
|
|
|
191
213
|
List of expected key names
|
|
192
214
|
"""
|
|
193
215
|
key_mapping = {
|
|
216
|
+
"CEO": ["workflow_plan", "reasoning"],
|
|
194
217
|
"Explorer": ["user_needs", "constraints", "dependencies", "risks", "complexity", "suggested_workflow", "new_guidelines_content", "clarification_question"],
|
|
195
218
|
"Proposal": ["feature_description", "user_value", "acceptance_criteria", "scope"],
|
|
196
219
|
"Architect": ["architecture_pattern", "components", "data_models", "apis"],
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import Dict, Any
|
|
3
|
-
from mcp.server.fastmcp import Context
|
|
4
|
-
from devhive.agents.base_agent import BaseAgent
|
|
5
|
-
from devhive.core.project_state_manager import ProjectStateManager
|
|
6
|
-
|
|
7
|
-
class CEOAgent(BaseAgent):
|
|
8
|
-
role = "CEO"
|
|
9
|
-
|
|
10
|
-
def get_next_agent_deterministic(self) -> Dict[str, Any]:
|
|
11
|
-
"""
|
|
12
|
-
Determine next agent to run based on current state (no LLM needed).
|
|
13
|
-
This is a rule-based decision making process following the strict pipeline sequence.
|
|
14
|
-
|
|
15
|
-
Pipeline order:
|
|
16
|
-
1. Explorer
|
|
17
|
-
2. Proposal
|
|
18
|
-
3. Architect
|
|
19
|
-
4. TaskPlanner
|
|
20
|
-
5. Developer (Iterative)
|
|
21
|
-
6. QA
|
|
22
|
-
7. Auditor
|
|
23
|
-
8. Archivist
|
|
24
|
-
|
|
25
|
-
Returns:
|
|
26
|
-
Dict with 'agent' (role name) and 'reason' (explanation)
|
|
27
|
-
"""
|
|
28
|
-
state = self.state_manager.get_state()
|
|
29
|
-
artifacts = state.get("artifacts", {})
|
|
30
|
-
|
|
31
|
-
# Check each stage in order
|
|
32
|
-
if artifacts.get("exploration") is None:
|
|
33
|
-
return {
|
|
34
|
-
"agent": "Explorer",
|
|
35
|
-
"reason": "Need initial feature analysis and exploration"
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
# Determine complexity from exploration artifact
|
|
39
|
-
complexity = "high" # Default to high safety
|
|
40
|
-
try:
|
|
41
|
-
expl_id = artifacts.get("exploration")
|
|
42
|
-
if expl_id:
|
|
43
|
-
expl_data = self.artifact_manager.load_artifact(expl_id)
|
|
44
|
-
complexity = expl_data.get("complexity", "high").lower()
|
|
45
|
-
except Exception:
|
|
46
|
-
pass # Fallback to high complexity on error
|
|
47
|
-
|
|
48
|
-
# Dynamic Routing based on Complexity
|
|
49
|
-
# Low: Explorer -> Developer -> QA -> Archivist
|
|
50
|
-
# Medium: Explorer -> Proposal -> Developer -> QA -> Archivist
|
|
51
|
-
# High: Full Suite
|
|
52
|
-
|
|
53
|
-
if artifacts.get("proposal") is None:
|
|
54
|
-
if complexity == "low":
|
|
55
|
-
pass # Skip Proposal
|
|
56
|
-
else:
|
|
57
|
-
return {
|
|
58
|
-
"agent": "Proposal",
|
|
59
|
-
"reason": "Exploration complete, need feature proposal"
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if artifacts.get("architecture") is None:
|
|
63
|
-
if complexity in ["low", "medium"]:
|
|
64
|
-
pass # Skip Architecture
|
|
65
|
-
else:
|
|
66
|
-
return {
|
|
67
|
-
"agent": "Architect",
|
|
68
|
-
"reason": "Proposal complete, need technical architecture design"
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if artifacts.get("tasks") is None:
|
|
72
|
-
if complexity in ["low", "medium"]:
|
|
73
|
-
pass # Skip Task Planning
|
|
74
|
-
else:
|
|
75
|
-
return {
|
|
76
|
-
"agent": "TaskPlanner",
|
|
77
|
-
"reason": "Architecture complete, need task breakdown"
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if artifacts.get("implementation") is None:
|
|
81
|
-
# Iterative Developer Workflow
|
|
82
|
-
dev_progress = state.get("developer_progress", {})
|
|
83
|
-
pending_tasks = dev_progress.get("pending_tasks", [])
|
|
84
|
-
completed_tasks = dev_progress.get("completed_tasks", [])
|
|
85
|
-
|
|
86
|
-
# If we haven't initialized developer progress yet, we need to route to developer
|
|
87
|
-
# Or if there are still pending tasks to complete
|
|
88
|
-
if not dev_progress or pending_tasks:
|
|
89
|
-
task_count = len(pending_tasks) if pending_tasks else "tasks"
|
|
90
|
-
return {
|
|
91
|
-
"agent": "Developer",
|
|
92
|
-
"reason": f"Need to execute {task_count} pending tasks sequentially"
|
|
93
|
-
}
|
|
94
|
-
elif not pending_tasks and completed_tasks:
|
|
95
|
-
# This state shouldn't normally happen (orchestrator should create implementation artifact)
|
|
96
|
-
# But just in case, if pending is empty but completed is not, we're done with implementation
|
|
97
|
-
pass
|
|
98
|
-
else:
|
|
99
|
-
# No tasks found at all? Fallback to single run
|
|
100
|
-
return {
|
|
101
|
-
"agent": "Developer",
|
|
102
|
-
"reason": "Ready for implementation"
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if artifacts.get("tests") is None:
|
|
106
|
-
return {
|
|
107
|
-
"agent": "QA",
|
|
108
|
-
"reason": "Implementation complete, need tests"
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if artifacts.get("verification") is None:
|
|
112
|
-
if complexity in ["low", "medium"]:
|
|
113
|
-
pass # Skip Auditor
|
|
114
|
-
else:
|
|
115
|
-
return {
|
|
116
|
-
"agent": "Auditor",
|
|
117
|
-
"reason": "Tests complete, need final verification"
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if artifacts.get("archive") is None:
|
|
121
|
-
return {
|
|
122
|
-
"agent": "Archivist",
|
|
123
|
-
"reason": "All stages complete, ready to archive project"
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
# All done
|
|
127
|
-
return {
|
|
128
|
-
"agent": "Complete",
|
|
129
|
-
"reason": "All pipeline stages finished, project is complete"
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async def execute(self, ctx: Context, **kwargs) -> Dict[str, Any]:
|
|
133
|
-
"""
|
|
134
|
-
Original LLM-based decision making (requires sampling support).
|
|
135
|
-
Currently not used in manual workflow.
|
|
136
|
-
"""
|
|
137
|
-
state_manager = ProjectStateManager(self.project_name)
|
|
138
|
-
state = state_manager.get_state()
|
|
139
|
-
|
|
140
|
-
context = {
|
|
141
|
-
"project_status": state.get('status'),
|
|
142
|
-
"artifacts_summary": state.get("artifacts", {})
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
sys_prompt = """
|
|
146
|
-
You are the CEO orchestrating an AI software development pipeline.
|
|
147
|
-
|
|
148
|
-
The pipeline is strictly sequential:
|
|
149
|
-
|
|
150
|
-
1 Explorer
|
|
151
|
-
2 Proposal
|
|
152
|
-
3 Architect
|
|
153
|
-
4 TaskPlanner
|
|
154
|
-
5 Developer
|
|
155
|
-
6 QA
|
|
156
|
-
7 Auditor
|
|
157
|
-
8 Archivist
|
|
158
|
-
|
|
159
|
-
Rules:
|
|
160
|
-
|
|
161
|
-
- NEVER return Wait unless the pipeline is finished
|
|
162
|
-
- ALWAYS select the next missing role
|
|
163
|
-
- Each role produces an artifact
|
|
164
|
-
|
|
165
|
-
Explorer → exploration
|
|
166
|
-
Proposal → proposal
|
|
167
|
-
Architect → architecture
|
|
168
|
-
TaskPlanner → tasks
|
|
169
|
-
Developer → implementation
|
|
170
|
-
QA → tests
|
|
171
|
-
Auditor → verification
|
|
172
|
-
Archivist → archived
|
|
173
|
-
|
|
174
|
-
Return JSON only:
|
|
175
|
-
{ "decision": "Run <Role>", "reason": "..." }
|
|
176
|
-
"""
|
|
177
|
-
user_prompt = f"""Decide next step.
|
|
178
|
-
|
|
179
|
-
Project: {context.get('project_name')}
|
|
180
|
-
Status: {context.get('project_status')}
|
|
181
|
-
Stage: {context.get('stage')}
|
|
182
|
-
Artifacts: {json.dumps(context.get('artifacts_summary', {}), indent=2)}
|
|
183
|
-
|
|
184
|
-
Available Roles: Explorer, Proposal, Architect, TaskPlanner, Developer, QA, Auditor, Archivist.
|
|
185
|
-
Return JSON: {{ "decision": "Run <Role>", "reason": "..." }}
|
|
186
|
-
If finished, "Run Archivist".
|
|
187
|
-
If stage is "initialization", "Run Explorer"
|
|
188
|
-
"""
|
|
189
|
-
resp = await self._call_llm(ctx, sys_prompt, user_prompt)
|
|
190
|
-
return self._parse_json(resp)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|