devsquad 3.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- devsquad-3.6.0.dist-info/METADATA +944 -0
- devsquad-3.6.0.dist-info/RECORD +95 -0
- devsquad-3.6.0.dist-info/WHEEL +5 -0
- devsquad-3.6.0.dist-info/entry_points.txt +2 -0
- devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
- devsquad-3.6.0.dist-info/top_level.txt +2 -0
- scripts/__init__.py +0 -0
- scripts/ai_semantic_matcher.py +512 -0
- scripts/alert_manager.py +505 -0
- scripts/api/__init__.py +43 -0
- scripts/api/models.py +386 -0
- scripts/api/routes/__init__.py +20 -0
- scripts/api/routes/dispatch.py +348 -0
- scripts/api/routes/lifecycle.py +330 -0
- scripts/api/routes/metrics_gates.py +347 -0
- scripts/api_server.py +318 -0
- scripts/auth.py +451 -0
- scripts/cli/__init__.py +1 -0
- scripts/cli/cli_visual.py +642 -0
- scripts/cli.py +1094 -0
- scripts/collaboration/__init__.py +212 -0
- scripts/collaboration/_version.py +1 -0
- scripts/collaboration/agent_briefing.py +656 -0
- scripts/collaboration/ai_semantic_matcher.py +260 -0
- scripts/collaboration/anchor_checker.py +281 -0
- scripts/collaboration/anti_rationalization.py +470 -0
- scripts/collaboration/async_integration_example.py +255 -0
- scripts/collaboration/batch_scheduler.py +149 -0
- scripts/collaboration/checkpoint_manager.py +561 -0
- scripts/collaboration/ci_feedback_adapter.py +351 -0
- scripts/collaboration/code_map_generator.py +247 -0
- scripts/collaboration/concern_pack_loader.py +352 -0
- scripts/collaboration/confidence_score.py +496 -0
- scripts/collaboration/config_loader.py +188 -0
- scripts/collaboration/consensus.py +244 -0
- scripts/collaboration/context_compressor.py +533 -0
- scripts/collaboration/coordinator.py +668 -0
- scripts/collaboration/dispatcher.py +1636 -0
- scripts/collaboration/dual_layer_context.py +128 -0
- scripts/collaboration/enhanced_worker.py +539 -0
- scripts/collaboration/feature_usage_tracker.py +206 -0
- scripts/collaboration/five_axis_consensus.py +334 -0
- scripts/collaboration/input_validator.py +401 -0
- scripts/collaboration/integration_example.py +287 -0
- scripts/collaboration/intent_workflow_mapper.py +350 -0
- scripts/collaboration/language_parsers.py +269 -0
- scripts/collaboration/lifecycle_protocol.py +1446 -0
- scripts/collaboration/llm_backend.py +453 -0
- scripts/collaboration/llm_cache.py +448 -0
- scripts/collaboration/llm_cache_async.py +347 -0
- scripts/collaboration/llm_retry.py +387 -0
- scripts/collaboration/llm_retry_async.py +389 -0
- scripts/collaboration/mce_adapter.py +597 -0
- scripts/collaboration/memory_bridge.py +1607 -0
- scripts/collaboration/models.py +537 -0
- scripts/collaboration/null_providers.py +297 -0
- scripts/collaboration/operation_classifier.py +289 -0
- scripts/collaboration/output_slicer.py +225 -0
- scripts/collaboration/performance_monitor.py +462 -0
- scripts/collaboration/permission_guard.py +865 -0
- scripts/collaboration/prompt_assembler.py +756 -0
- scripts/collaboration/prompt_variant_generator.py +483 -0
- scripts/collaboration/protocols.py +267 -0
- scripts/collaboration/report_formatter.py +352 -0
- scripts/collaboration/retrospective.py +279 -0
- scripts/collaboration/role_matcher.py +92 -0
- scripts/collaboration/role_template_market.py +352 -0
- scripts/collaboration/rule_collector.py +678 -0
- scripts/collaboration/scratchpad.py +346 -0
- scripts/collaboration/skill_registry.py +151 -0
- scripts/collaboration/skillifier.py +878 -0
- scripts/collaboration/standardized_role_template.py +317 -0
- scripts/collaboration/task_completion_checker.py +237 -0
- scripts/collaboration/test_quality_guard.py +695 -0
- scripts/collaboration/unified_gate_engine.py +598 -0
- scripts/collaboration/usage_tracker.py +309 -0
- scripts/collaboration/user_friendly_error.py +176 -0
- scripts/collaboration/verification_gate.py +312 -0
- scripts/collaboration/warmup_manager.py +635 -0
- scripts/collaboration/worker.py +513 -0
- scripts/collaboration/workflow_engine.py +684 -0
- scripts/dashboard.py +1088 -0
- scripts/generate_benchmark_report.py +786 -0
- scripts/history_manager.py +604 -0
- scripts/mcp_server.py +289 -0
- skills/__init__.py +32 -0
- skills/dispatch/handler.py +52 -0
- skills/intent/handler.py +59 -0
- skills/registry.py +67 -0
- skills/retrospective/__init__.py +0 -0
- skills/retrospective/handler.py +125 -0
- skills/review/handler.py +356 -0
- skills/security/handler.py +454 -0
- skills/test/__init__.py +0 -0
- skills/test/handler.py +78 -0
scripts/cli.py
ADDED
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
DevSquad CLI Entry Point — Cross-platform interface for any AI coding assistant.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python3 scripts/cli.py dispatch -t "design user auth system" -r architect coder tester
|
|
7
|
+
python3 scripts/cli.py dispatch -t "review code" -f json --mode consensus --backend openai
|
|
8
|
+
python3 scripts/cli.py status
|
|
9
|
+
python3 scripts/cli.py roles
|
|
10
|
+
python3 scripts/cli.py --version
|
|
11
|
+
"""
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
import os
|
|
16
|
+
import logging
|
|
17
|
+
|
|
18
|
+
if sys.version_info < (3, 9):
|
|
19
|
+
print("Error: DevSquad requires Python 3.9+. Current: " + sys.version, file=sys.stderr)
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
23
|
+
|
|
24
|
+
from scripts.collaboration.dispatcher import MultiAgentDispatcher
|
|
25
|
+
from scripts.collaboration.permission_guard import PermissionLevel
|
|
26
|
+
from scripts.collaboration.models import ROLE_REGISTRY, get_cli_role_list, resolve_role_id
|
|
27
|
+
from scripts.collaboration.input_validator import InputValidator
|
|
28
|
+
|
|
29
|
+
ROLES = get_cli_role_list()
|
|
30
|
+
ALL_ROLE_IDS = list(ROLE_REGISTRY.keys()) + ROLES
|
|
31
|
+
ALL_ROLE_IDS = sorted(set(ALL_ROLE_IDS))
|
|
32
|
+
MODES = ["auto", "parallel", "sequential", "consensus"]
|
|
33
|
+
FORMATS = ["markdown", "json", "compact", "structured", "detailed"]
|
|
34
|
+
BACKENDS = ["mock", "trae", "openai", "anthropic"]
|
|
35
|
+
LIFECYCLE_COMMANDS = ["spec", "plan", "build", "test", "review", "ship"]
|
|
36
|
+
from scripts.collaboration._version import __version__
|
|
37
|
+
VERSION = __version__
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
LIFECYCLE_PRESETS = {
|
|
41
|
+
"spec": {
|
|
42
|
+
"description": "Define and refine requirements before implementation",
|
|
43
|
+
"required_roles": ["architect", "product-manager"],
|
|
44
|
+
"mode": "sequential",
|
|
45
|
+
"gate": "spec_first",
|
|
46
|
+
"pre_dispatch_message": (
|
|
47
|
+
"📋 Generating specification before any code. "
|
|
48
|
+
"Output will include objectives, commands, structure, testing plan, and boundaries."
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
"plan": {
|
|
52
|
+
"description": "Break down work into small, verifiable tasks",
|
|
53
|
+
"required_roles": ["architect", "product-manager"],
|
|
54
|
+
"mode": "auto",
|
|
55
|
+
"gate": "task_breakdown_complete",
|
|
56
|
+
"pre_dispatch_message": (
|
|
57
|
+
"📝 Decomposing into atomic tasks with acceptance criteria and dependency ordering."
|
|
58
|
+
),
|
|
59
|
+
},
|
|
60
|
+
"build": {
|
|
61
|
+
"description": "Implement incrementally with TDD discipline",
|
|
62
|
+
"required_roles": ["architect", "solo-coder", "tester"],
|
|
63
|
+
"mode": "parallel",
|
|
64
|
+
"gate": "incremental_verification",
|
|
65
|
+
"pre_dispatch_message": (
|
|
66
|
+
"🔨 Building in thin vertical slices. Each slice: implement → test → verify → commit. "
|
|
67
|
+
"~100 lines per slice maximum."
|
|
68
|
+
),
|
|
69
|
+
},
|
|
70
|
+
"test": {
|
|
71
|
+
"description": "Run tests with mandatory evidence requirements",
|
|
72
|
+
"required_roles": ["tester", "solo-coder"],
|
|
73
|
+
"mode": "consensus",
|
|
74
|
+
"gate": "evidence_required",
|
|
75
|
+
"pre_dispatch_message": (
|
|
76
|
+
"🧪 Running tests with verification gate. Evidence required: test output, build status, diff summary. "
|
|
77
|
+
"'Seems right' is NOT sufficient."
|
|
78
|
+
),
|
|
79
|
+
},
|
|
80
|
+
"review": {
|
|
81
|
+
"description": "Five-axis code review (correctness/readability/arch/security/performance)",
|
|
82
|
+
"required_roles": ["solo-coder", "security", "tester", "architect"],
|
|
83
|
+
"mode": "consensus",
|
|
84
|
+
"gate": "change_size_limit",
|
|
85
|
+
"pre_dispatch_message": (
|
|
86
|
+
"🔍 Conducting multi-dimensional code review. Change size target: ~100 lines. "
|
|
87
|
+
"Severity labels: Critical (blocks merge) / Required / Nit (optional)."
|
|
88
|
+
),
|
|
89
|
+
},
|
|
90
|
+
"ship": {
|
|
91
|
+
"description": "Pre-launch verification and deployment preparation",
|
|
92
|
+
"required_roles": ["devops", "security", "architect"],
|
|
93
|
+
"mode": "sequential",
|
|
94
|
+
"gate": "pre_launch_checklist",
|
|
95
|
+
"pre_dispatch_message": (
|
|
96
|
+
"🚀 Running pre-launch checklist across 6 dimensions: Code Quality, Security, Performance, "
|
|
97
|
+
"Accessibility, Infrastructure, Documentation. Rollback plan required."
|
|
98
|
+
),
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _create_backend(backend_type: str,
|
|
104
|
+
base_url: str = None, model: str = None):
|
|
105
|
+
if backend_type == "mock" or backend_type is None:
|
|
106
|
+
return None
|
|
107
|
+
from scripts.collaboration.llm_backend import create_backend
|
|
108
|
+
kwargs = {}
|
|
109
|
+
if base_url:
|
|
110
|
+
kwargs["base_url"] = base_url
|
|
111
|
+
if model:
|
|
112
|
+
kwargs["model"] = model
|
|
113
|
+
if backend_type == "openai":
|
|
114
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
|
115
|
+
if not api_key:
|
|
116
|
+
print("Error: OPENAI_API_KEY environment variable not set.", file=sys.stderr)
|
|
117
|
+
print(" export OPENAI_API_KEY=\"sk-...\"", file=sys.stderr)
|
|
118
|
+
return None
|
|
119
|
+
kwargs["api_key"] = api_key
|
|
120
|
+
kwargs.setdefault("base_url", os.environ.get("OPENAI_BASE_URL"))
|
|
121
|
+
kwargs.setdefault("model", os.environ.get("OPENAI_MODEL", "gpt-4"))
|
|
122
|
+
elif backend_type == "anthropic":
|
|
123
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
|
124
|
+
if not api_key:
|
|
125
|
+
print("Error: ANTHROPIC_API_KEY environment variable not set.", file=sys.stderr)
|
|
126
|
+
print(" export ANTHROPIC_API_KEY=\"sk-ant-...\"", file=sys.stderr)
|
|
127
|
+
return None
|
|
128
|
+
kwargs["api_key"] = api_key
|
|
129
|
+
kwargs.setdefault("model", os.environ.get("ANTHROPIC_MODEL", "claude-sonnet-4-20250514"))
|
|
130
|
+
return create_backend(backend_type, **kwargs)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def cmd_demo(args):
|
|
134
|
+
"""
|
|
135
|
+
Demo command — show DevSquad capabilities in mock mode (no API key needed).
|
|
136
|
+
|
|
137
|
+
Scenarios:
|
|
138
|
+
intent - Intent detection & role auto-matching
|
|
139
|
+
security - Security scanning with permission checks
|
|
140
|
+
dispatch - Dispatch dry-run simulation
|
|
141
|
+
all - Run all scenarios (default)
|
|
142
|
+
"""
|
|
143
|
+
import time as _time
|
|
144
|
+
|
|
145
|
+
scenario = getattr(args, 'scenario', 'all')
|
|
146
|
+
|
|
147
|
+
print("\n" + "=" * 60)
|
|
148
|
+
print(" 🚀 DevSquad V3.6.0 Quick Demo")
|
|
149
|
+
print("=" * 60)
|
|
150
|
+
print(" Mode: Mock (no API key required)\n")
|
|
151
|
+
|
|
152
|
+
results = []
|
|
153
|
+
|
|
154
|
+
if scenario in ("all", "intent"):
|
|
155
|
+
print("▶️ Scenario 1: Intent Detection")
|
|
156
|
+
print("-" * 40)
|
|
157
|
+
start = _time.time()
|
|
158
|
+
try:
|
|
159
|
+
from scripts.collaboration.intent_workflow_mapper import IntentWorkflowMapper
|
|
160
|
+
|
|
161
|
+
mapper = IntentWorkflowMapper()
|
|
162
|
+
task = "修复用户登录模块中的认证失败问题,报错信息显示token验证异常"
|
|
163
|
+
result = mapper.detect_intent(task)
|
|
164
|
+
|
|
165
|
+
print(f" Task: {task}")
|
|
166
|
+
print(f" Intent: {result.intent_type}")
|
|
167
|
+
print(f" Confidence: {result.confidence:.1%}")
|
|
168
|
+
print(f" Required roles: {', '.join(result.required_roles)}")
|
|
169
|
+
if result.optional_roles:
|
|
170
|
+
print(f" Optional roles: {', '.join(result.optional_roles)}")
|
|
171
|
+
print(f" ✅ Completed in {_time.time() - start:.2f}s\n")
|
|
172
|
+
results.append({"scenario": "Intent Detection", "success": True, "duration": _time.time() - start})
|
|
173
|
+
except Exception as e:
|
|
174
|
+
print(f" ❌ Failed: {e}\n")
|
|
175
|
+
results.append({"scenario": "Intent Detection", "success": False, "duration": _time.time() - start})
|
|
176
|
+
|
|
177
|
+
if scenario in ("all", "security"):
|
|
178
|
+
print("▶️ Scenario 2: Security Scan")
|
|
179
|
+
print("-" * 40)
|
|
180
|
+
start = _time.time()
|
|
181
|
+
try:
|
|
182
|
+
from scripts.collaboration.input_validator import InputValidator
|
|
183
|
+
|
|
184
|
+
validator = InputValidator()
|
|
185
|
+
test_inputs = [
|
|
186
|
+
("DROP TABLE users;", "SQL Injection"),
|
|
187
|
+
("<script>alert('xss')</script>", "XSS"),
|
|
188
|
+
("rm -rf / && format C:", "Command Injection"),
|
|
189
|
+
("$(cat /etc/passwd)", "OS Command Injection"),
|
|
190
|
+
]
|
|
191
|
+
for inp, label in test_inputs:
|
|
192
|
+
result = validator.validate_task(inp)
|
|
193
|
+
status = "🚫 BLOCKED" if not result.valid else ("⚠️ WARNING" if result.sanitized_input != inp else "✅ OK")
|
|
194
|
+
print(f" [{status}] {label}: {inp[:40]}")
|
|
195
|
+
print(f" ✅ Completed in {_time.time() - start:.2f}s\n")
|
|
196
|
+
results.append({"scenario": "Security Scan", "success": True, "duration": _time.time() - start})
|
|
197
|
+
except Exception as e:
|
|
198
|
+
print(f" ❌ Failed: {e}\n")
|
|
199
|
+
results.append({"scenario": "Security Scan", "success": False, "duration": _time.time() - start})
|
|
200
|
+
|
|
201
|
+
if scenario in ("all", "dispatch"):
|
|
202
|
+
print("▶️ Scenario 3: Dispatch Dry-Run")
|
|
203
|
+
print("-" * 40)
|
|
204
|
+
start = _time.time()
|
|
205
|
+
try:
|
|
206
|
+
disp = MultiAgentDispatcher(enable_warmup=False)
|
|
207
|
+
result = disp.dispatch(
|
|
208
|
+
"设计一个微服务架构的用户认证系统",
|
|
209
|
+
dry_run=True,
|
|
210
|
+
)
|
|
211
|
+
print(f" Task: 设计一个微服务架构的用户认证系统")
|
|
212
|
+
print(f" Mode: Dry-run (simulation)")
|
|
213
|
+
if hasattr(result, 'matched_roles'):
|
|
214
|
+
print(f" Matched roles: {', '.join(result.matched_roles)}")
|
|
215
|
+
if hasattr(result, 'summary'):
|
|
216
|
+
print(f" Summary: {result.summary[:100]}...")
|
|
217
|
+
print(f" ✅ Completed in {_time.time() - start:.2f}s\n")
|
|
218
|
+
disp.shutdown()
|
|
219
|
+
results.append({"scenario": "Dispatch Dry-Run", "success": True, "duration": _time.time() - start})
|
|
220
|
+
except Exception as e:
|
|
221
|
+
print(f" ❌ Failed: {e}\n")
|
|
222
|
+
results.append({"scenario": "Dispatch Dry-Run", "success": False, "duration": _time.time() - start})
|
|
223
|
+
|
|
224
|
+
# Summary
|
|
225
|
+
print("=" * 60)
|
|
226
|
+
print(" 📊 Demo Summary")
|
|
227
|
+
print("=" * 60)
|
|
228
|
+
success_count = sum(1 for r in results if r['success'])
|
|
229
|
+
total_time = sum(r['duration'] for r in results)
|
|
230
|
+
print(f" Scenarios run: {len(results)}")
|
|
231
|
+
print(f" Successful: {success_count}/{len(results)}")
|
|
232
|
+
print(f" Total time: {total_time:.2f}s")
|
|
233
|
+
print()
|
|
234
|
+
print("💡 Next steps:")
|
|
235
|
+
print(" devsquad init # Interactive setup")
|
|
236
|
+
print(' devsquad dispatch -t "your task"')
|
|
237
|
+
print(" devsquad --help # All commands\n")
|
|
238
|
+
|
|
239
|
+
return 0 if all(r['success'] for r in results) else 1
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def cmd_init(args):
|
|
243
|
+
"""
|
|
244
|
+
Interactive initialization wizard for DevSquad.
|
|
245
|
+
|
|
246
|
+
Guides new users through setup:
|
|
247
|
+
- Project type selection
|
|
248
|
+
- LLM backend configuration
|
|
249
|
+
- Default role preferences
|
|
250
|
+
- Output language
|
|
251
|
+
- Config file generation
|
|
252
|
+
"""
|
|
253
|
+
import sys as _sys
|
|
254
|
+
|
|
255
|
+
# Quick init: non-interactive mode with sensible defaults
|
|
256
|
+
if getattr(args, 'quick', False) or getattr(args, 'non_interactive', False):
|
|
257
|
+
return _quick_init()
|
|
258
|
+
|
|
259
|
+
print("\n" + "=" * 60)
|
|
260
|
+
print("🚀 Welcome to DevSquad Setup Wizard!")
|
|
261
|
+
print("=" * 60)
|
|
262
|
+
print("\nThis wizard will help you configure DevSquad for your project.")
|
|
263
|
+
print("It should take about 1-2 minutes.\n")
|
|
264
|
+
|
|
265
|
+
config = {
|
|
266
|
+
"project_type": None,
|
|
267
|
+
"llm_backend": "mock",
|
|
268
|
+
"default_roles": ["auto"],
|
|
269
|
+
"language": "auto",
|
|
270
|
+
"features": {},
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
# Step 1: Project Type
|
|
274
|
+
print("📋 Step 1/5: Project Type")
|
|
275
|
+
print("-" * 40)
|
|
276
|
+
project_types = {
|
|
277
|
+
"1": {"id": "web-api", "name": "Web API / Backend Service", "desc": "REST API, GraphQL, microservices", "roles": ["architect", "solo-coder", "security", "tester"]},
|
|
278
|
+
"2": {"id": "web-fullstack", "name": "Full-Stack Web App", "desc": "Frontend + Backend + Database", "roles": ["architect", "ui-designer", "solo-coder", "tester"]},
|
|
279
|
+
"3": {"id": "cli-tool", "name": "CLI Tool / Utility", "desc": "Command-line application, DevOps tool", "roles": ["architect", "solo-coder", "tester"]},
|
|
280
|
+
"4": {"id": "ml-service", "name": "AI/ML Service", "desc": "Machine learning pipeline, data service", "roles": ["architect", "solo-coder", "tester", "devops"]},
|
|
281
|
+
"5": {"id": "library", "name": "Library / SDK", "desc": "Reusable package, API wrapper", "roles": ["architect", "solo-coder", "tester"]},
|
|
282
|
+
"6": {"id": "generic", "name": "Generic / Other", "desc": "Custom project or exploring DevSquad", "roles": ["auto"]},
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
for key, ptype in project_types.items():
|
|
286
|
+
print(f" {key}) {ptype['name']}")
|
|
287
|
+
print(f" {ptype['desc']}")
|
|
288
|
+
|
|
289
|
+
project_choice = _prompt_choice("Select your project type [1-6]", list(project_types.keys()), default="6")
|
|
290
|
+
selected_type = project_types[project_choice]
|
|
291
|
+
config["project_type"] = selected_type["id"]
|
|
292
|
+
config["default_roles"] = selected_type["roles"]
|
|
293
|
+
|
|
294
|
+
print(f"\n ✅ Selected: {selected_type['name']}")
|
|
295
|
+
print(f" 💡 Recommended roles: {', '.join(selected_type['roles'])}")
|
|
296
|
+
|
|
297
|
+
# Step 2: LLM Backend
|
|
298
|
+
print(f"\n\n🤖 Step 2/5: AI Backend Configuration")
|
|
299
|
+
print("-" * 40)
|
|
300
|
+
print("DevSquad can work with different AI backends:")
|
|
301
|
+
print()
|
|
302
|
+
print(" 1) Mock Mode (Recommended for beginners)")
|
|
303
|
+
print(" • No API key needed")
|
|
304
|
+
print(" • Fast response (< 1 second)")
|
|
305
|
+
print(" • Great for testing and learning")
|
|
306
|
+
print()
|
|
307
|
+
print(" 2) OpenAI (GPT-4, GPT-3.5)")
|
|
308
|
+
print(" • Requires OPENAI_API_KEY")
|
|
309
|
+
print(" • Real AI analysis and suggestions")
|
|
310
|
+
print(" • Best for production use")
|
|
311
|
+
print()
|
|
312
|
+
print(" 3) Anthropic (Claude)")
|
|
313
|
+
print(" • Requires ANTHROPIC_API_KEY")
|
|
314
|
+
print(" • Excellent at complex reasoning")
|
|
315
|
+
print(" • Good for architecture decisions")
|
|
316
|
+
print()
|
|
317
|
+
|
|
318
|
+
backend_options = {"1": "mock", "2": "openai", "3": "anthropic"}
|
|
319
|
+
backend_choice = _prompt_choice("Select AI backend [1-3]", list(backend_options.keys()), default="1")
|
|
320
|
+
config["llm_backend"] = backend_options[backend_choice]
|
|
321
|
+
|
|
322
|
+
if config["llm_backend"] != "mock":
|
|
323
|
+
env_var = "OPENAI_API_KEY" if config["llm_backend"] == "openai" else "ANTHROPIC_API_KEY"
|
|
324
|
+
if not os.environ.get(env_var):
|
|
325
|
+
print(f"\n ⚠️ Warning: {env_var} is not set!")
|
|
326
|
+
print(f" You'll need to set it before using real AI:")
|
|
327
|
+
print(f' export {env_var}="your-api-key-here"')
|
|
328
|
+
print(f"\n For now, we'll save the preference. You can configure the key later.")
|
|
329
|
+
|
|
330
|
+
print(f"\n ✅ Backend: {config['llm_backend'].upper()}")
|
|
331
|
+
|
|
332
|
+
# Step 3: Default Roles
|
|
333
|
+
print(f"\n\n👥 Step 3/5: Role Preferences")
|
|
334
|
+
print("-" * 40)
|
|
335
|
+
|
|
336
|
+
if "auto" in config["default_roles"]:
|
|
337
|
+
print(" With 'Generic' project type, roles will be auto-matched based on task content.")
|
|
338
|
+
print(" This is recommended for beginners!")
|
|
339
|
+
else:
|
|
340
|
+
print(f" Based on your project type, we recommend these roles:")
|
|
341
|
+
for role in config["default_roles"]:
|
|
342
|
+
role_def = ROLE_REGISTRY.get(role)
|
|
343
|
+
if role_def:
|
|
344
|
+
print(f" • {role_def.name} — {role_def.description}")
|
|
345
|
+
|
|
346
|
+
customize = _prompt_yes_no("Customize role selection?", default=False)
|
|
347
|
+
if customize:
|
|
348
|
+
print("\n Available roles:")
|
|
349
|
+
all_roles = []
|
|
350
|
+
for rid, rdef in ROLE_REGISTRY.items():
|
|
351
|
+
alias = rdef.aliases[0] if rdef.aliases else rid
|
|
352
|
+
status = "" if rdef.status == "active" else " [planned]"
|
|
353
|
+
print(f" {alias:<12} — {rdef.description}{status}")
|
|
354
|
+
all_roles.append(alias)
|
|
355
|
+
|
|
356
|
+
print()
|
|
357
|
+
roles_input = input(" Enter roles (comma-separated, e.g., arch sec test): ").strip()
|
|
358
|
+
if roles_input:
|
|
359
|
+
config["default_roles"] = [r.strip() for r in roles_input.split(",")]
|
|
360
|
+
|
|
361
|
+
print(f"\n ✅ Roles configured")
|
|
362
|
+
|
|
363
|
+
# Step 4: Language & Features
|
|
364
|
+
print(f"\n\n🌐 Step 4/5: Language & Features")
|
|
365
|
+
print("-" * 40)
|
|
366
|
+
|
|
367
|
+
lang_options = {
|
|
368
|
+
"1": ("auto", "Auto-detect from system locale"),
|
|
369
|
+
"2": ("zh", "中文 (Chinese)"),
|
|
370
|
+
"3": ("en", "English"),
|
|
371
|
+
"4": ("ja", "日本語 (Japanese)"),
|
|
372
|
+
}
|
|
373
|
+
print(" Output language:")
|
|
374
|
+
for key, (code, desc) in lang_options.items():
|
|
375
|
+
print(f" {key}) {desc}")
|
|
376
|
+
|
|
377
|
+
lang_choice = _prompt_choice("Select language [1-4]", list(lang_options.keys()), default="1")
|
|
378
|
+
config["language"] = lang_options[lang_choice][0]
|
|
379
|
+
|
|
380
|
+
print(f"\n Optional features (can be enabled later):")
|
|
381
|
+
features = {
|
|
382
|
+
"warmup": _prompt_yes_no(" Enable startup warmup? (faster subsequent runs)", default=True),
|
|
383
|
+
"compression": _prompt_yes_no(" Enable context compression? (for long tasks)", default=True),
|
|
384
|
+
"memory": _prompt_yes_no(" Enable memory bridge? (learn from history)", default=False),
|
|
385
|
+
"permission": _prompt_yes_no(" Enable permission guard? (security checks)", default=True),
|
|
386
|
+
}
|
|
387
|
+
config["features"] = features
|
|
388
|
+
|
|
389
|
+
print(f"\n ✅ Language: {config['language']}")
|
|
390
|
+
enabled_features = [k for k, v in features.items() if v]
|
|
391
|
+
if enabled_features:
|
|
392
|
+
print(f" ✅ Features: {', '.join(enabled_features)}")
|
|
393
|
+
|
|
394
|
+
# Step 5: Summary & Save
|
|
395
|
+
print(f"\n\n💾 Step 5/5: Configuration Summary")
|
|
396
|
+
print("-" * 60)
|
|
397
|
+
|
|
398
|
+
print(f"\n 📁 Project Type: {selected_type['name']}")
|
|
399
|
+
print(f" 🤖 AI Backend: {config['llm_backend'].upper()}")
|
|
400
|
+
print(f" 👥 Default Roles: {', '.join(config['default_roles'])}")
|
|
401
|
+
print(f" 🌐 Language: {config['language']}")
|
|
402
|
+
print(f" ⚙️ Features: {', '.join(enabled_features) if enabled_features else 'None'}")
|
|
403
|
+
|
|
404
|
+
confirm = _prompt_yes_no("\n Save this configuration?", default=True)
|
|
405
|
+
|
|
406
|
+
if not confirm:
|
|
407
|
+
print("\n ❌ Configuration cancelled. You can run 'devsquad init' again anytime.")
|
|
408
|
+
return 0
|
|
409
|
+
|
|
410
|
+
# Generate configuration file
|
|
411
|
+
config_path = os.path.expanduser("~/.devsquad.yaml")
|
|
412
|
+
saved = _save_config(config, config_path)
|
|
413
|
+
|
|
414
|
+
if saved:
|
|
415
|
+
print(f"\n ✅ Configuration saved to: {config_path}")
|
|
416
|
+
else:
|
|
417
|
+
print(f"\n ⚠️ Could not save to {config_path}. Using inline defaults.")
|
|
418
|
+
|
|
419
|
+
# Final success message
|
|
420
|
+
print("\n" + "=" * 60)
|
|
421
|
+
print("🎉 Setup Complete! DevSquad is ready to use.")
|
|
422
|
+
print("=" * 60)
|
|
423
|
+
|
|
424
|
+
print(f"\n🚀 Quick Start Commands:\n")
|
|
425
|
+
print(f" # Basic usage (auto-match roles)")
|
|
426
|
+
print(f' devsquad dispatch -t "your task description"')
|
|
427
|
+
print()
|
|
428
|
+
print(f" # With specific roles")
|
|
429
|
+
print(f' devsquad dispatch -t "design auth system" -r arch sec')
|
|
430
|
+
print()
|
|
431
|
+
print(f" # Use lifecycle commands")
|
|
432
|
+
print(f' devsquad spec -t "user authentication"')
|
|
433
|
+
print(f' devsquad build -t "implement login API"')
|
|
434
|
+
print()
|
|
435
|
+
|
|
436
|
+
if config["llm_backend"] != "mock":
|
|
437
|
+
print(f"⚡ To use real AI, set your API key:")
|
|
438
|
+
env_var = "OPENAI_API_KEY" if config["llm_backend"] == "openai" else "ANTHROPIC_API_KEY"
|
|
439
|
+
print(f' export {env_var}="your-key-here"')
|
|
440
|
+
print()
|
|
441
|
+
|
|
442
|
+
print(f"📚 Learn more:")
|
|
443
|
+
print(f" • docs: https://github.com/lulin70/DevSquad#readme")
|
|
444
|
+
print(f" • examples: python examples/quick_start.py")
|
|
445
|
+
print(f" • help: devsquad --help")
|
|
446
|
+
print(f" • roles: devsquad roles")
|
|
447
|
+
print()
|
|
448
|
+
|
|
449
|
+
print("Happy coding! 🎯\n")
|
|
450
|
+
|
|
451
|
+
return 0
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _prompt_choice(prompt: str, valid_choices: list, default: str = None) -> str:
|
|
455
|
+
"""Prompt user for choice with validation."""
|
|
456
|
+
while True:
|
|
457
|
+
try:
|
|
458
|
+
user_input = input(f" {prompt}: ").strip()
|
|
459
|
+
if not user_input and default:
|
|
460
|
+
return default
|
|
461
|
+
if user_input in valid_choices:
|
|
462
|
+
return user_input
|
|
463
|
+
print(f" ❌ Invalid choice. Please enter: {', '.join(valid_choices)}")
|
|
464
|
+
except EOFError:
|
|
465
|
+
if default:
|
|
466
|
+
return default
|
|
467
|
+
print(" Non-interactive mode detected. Using default.")
|
|
468
|
+
return default
|
|
469
|
+
except KeyboardInterrupt:
|
|
470
|
+
print("\n\n❌ Setup cancelled by user.")
|
|
471
|
+
sys.exit(1)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _prompt_yes_no(prompt: str, default: bool = True) -> bool:
|
|
475
|
+
"""Prompt user for yes/no confirmation."""
|
|
476
|
+
default_str = "Y/n" if default else "y/N"
|
|
477
|
+
while True:
|
|
478
|
+
try:
|
|
479
|
+
user_input = input(f"{prompt} [{default_str}]: ").strip().lower()
|
|
480
|
+
if not user_input:
|
|
481
|
+
return default
|
|
482
|
+
if user_input in ("y", "yes", "1", "true"):
|
|
483
|
+
return True
|
|
484
|
+
if user_input in ("n", "no", "0", "false"):
|
|
485
|
+
return False
|
|
486
|
+
print(" Please enter y/n or yes/no")
|
|
487
|
+
except EOFError:
|
|
488
|
+
return default
|
|
489
|
+
except KeyboardInterrupt:
|
|
490
|
+
print("\n\n❌ Setup cancelled by user.")
|
|
491
|
+
sys.exit(1)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _save_config(config: dict, config_path: str) -> bool:
|
|
495
|
+
"""Save configuration to YAML file."""
|
|
496
|
+
config_path = os.path.expanduser(config_path)
|
|
497
|
+
|
|
498
|
+
if os.path.islink(config_path):
|
|
499
|
+
print(f"\n ⚠️ {config_path} is a symbolic link. Skipping for security.")
|
|
500
|
+
return False
|
|
501
|
+
|
|
502
|
+
try:
|
|
503
|
+
import yaml
|
|
504
|
+
|
|
505
|
+
yaml_config = {
|
|
506
|
+
"version": VERSION,
|
|
507
|
+
"project_type": config["project_type"],
|
|
508
|
+
"llm_backend": config["llm_backend"],
|
|
509
|
+
"default_language": config["language"],
|
|
510
|
+
"default_roles": config["default_roles"],
|
|
511
|
+
"features": {
|
|
512
|
+
"warmup": config["features"].get("warmup", True),
|
|
513
|
+
"compression": config["features"].get("compression", True),
|
|
514
|
+
"memory_bridge": config["features"].get("memory", False),
|
|
515
|
+
"permission_guard": config["features"].get("permission", True),
|
|
516
|
+
},
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
with open(config_path, "w") as f:
|
|
520
|
+
yaml.dump(yaml_config, f, default_flow_style=False, allow_unicode=True)
|
|
521
|
+
|
|
522
|
+
return True
|
|
523
|
+
|
|
524
|
+
except ImportError:
|
|
525
|
+
# YAML not available, create simple format
|
|
526
|
+
try:
|
|
527
|
+
with open(config_path, "w") as f:
|
|
528
|
+
f.write(f"# DevSquad Configuration (generated by init wizard)\n")
|
|
529
|
+
f.write(f"# Created: {__import__('datetime').datetime.now().isoformat()}\n\n")
|
|
530
|
+
f.write(f"project_type: {config['project_type']}\n")
|
|
531
|
+
f.write(f"llm_backend: {config['llm_backend']}\n")
|
|
532
|
+
f.write(f"default_language: {config['language']}\n")
|
|
533
|
+
f.write(f"default_roles: {', '.join(config['default_roles'])}\n")
|
|
534
|
+
return True
|
|
535
|
+
except Exception as e:
|
|
536
|
+
logger.warning("Failed to save config: %s", e)
|
|
537
|
+
return False
|
|
538
|
+
except Exception as e:
|
|
539
|
+
logger.warning("Failed to save config: %s", e)
|
|
540
|
+
return False
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _quick_init():
|
|
544
|
+
"""
|
|
545
|
+
Quick non-interactive initialization with sensible defaults.
|
|
546
|
+
|
|
547
|
+
Generates:
|
|
548
|
+
- ~/.devsquad.yaml (main config)
|
|
549
|
+
- ~/.devsquad/.env (environment template, copied from .env.example if exists)
|
|
550
|
+
"""
|
|
551
|
+
import shutil as _shutil
|
|
552
|
+
|
|
553
|
+
print("\n⚡ DevSquad Quick Setup (non-interactive)")
|
|
554
|
+
|
|
555
|
+
config = {
|
|
556
|
+
"project_type": "generic",
|
|
557
|
+
"llm_backend": "mock",
|
|
558
|
+
"default_roles": ["auto"],
|
|
559
|
+
"language": "auto",
|
|
560
|
+
"features": {
|
|
561
|
+
"warmup": True,
|
|
562
|
+
"compression": True,
|
|
563
|
+
"memory": False,
|
|
564
|
+
"permission": True,
|
|
565
|
+
},
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
# Save main config
|
|
569
|
+
config_path = os.path.expanduser("~/.devsquad.yaml")
|
|
570
|
+
saved = _save_config(config, config_path)
|
|
571
|
+
|
|
572
|
+
if saved:
|
|
573
|
+
print(f" ✅ Config saved: {config_path}")
|
|
574
|
+
else:
|
|
575
|
+
print(f" ⚠️ Could not save config to {config_path}")
|
|
576
|
+
|
|
577
|
+
# Copy .env.example to ~/.devsquad/.env (if not exists)
|
|
578
|
+
devsquad_dir = os.path.expanduser("~/.devsquad")
|
|
579
|
+
env_path = os.path.join(devsquad_dir, ".env")
|
|
580
|
+
|
|
581
|
+
try:
|
|
582
|
+
os.makedirs(devsquad_dir, exist_ok=True)
|
|
583
|
+
|
|
584
|
+
if not os.path.exists(env_path):
|
|
585
|
+
# Try to copy from project root .env.example
|
|
586
|
+
project_env_example = os.path.join(
|
|
587
|
+
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
588
|
+
".env.example"
|
|
589
|
+
)
|
|
590
|
+
if os.path.exists(project_env_example):
|
|
591
|
+
_shutil.copy2(project_env_example, env_path)
|
|
592
|
+
print(f" ✅ Env template: {env_path}")
|
|
593
|
+
print(" Edit this file to add your API keys")
|
|
594
|
+
else:
|
|
595
|
+
# Create minimal .env
|
|
596
|
+
with open(env_path, 'w') as f:
|
|
597
|
+
f.write("# DevSquad Environment Configuration\n")
|
|
598
|
+
f.write("# Generated by quick init\n\n")
|
|
599
|
+
f.write("DEVSQUAD_LLM_BACKEND=mock\n")
|
|
600
|
+
f.write("# Uncomment and fill in your API keys:\n")
|
|
601
|
+
f.write("# OPENAI_API_KEY=\n")
|
|
602
|
+
f.write("# ANTHROPIC_API_KEY=\n")
|
|
603
|
+
print(f" ✅ Env template: {env_path} (minimal)")
|
|
604
|
+
else:
|
|
605
|
+
print(f" ℹ️ Env file already exists: {env_path}")
|
|
606
|
+
|
|
607
|
+
except Exception as e:
|
|
608
|
+
logger.warning("Failed to create env template: %s", e)
|
|
609
|
+
print(f" ⚠️ Could not create env template: {e}")
|
|
610
|
+
|
|
611
|
+
# Summary
|
|
612
|
+
print("\n🎉 Quick setup complete!")
|
|
613
|
+
print("\n📋 Next steps:")
|
|
614
|
+
print(' 1. Run demo: devsquad demo')
|
|
615
|
+
print(' 2. Run task: devsquad dispatch -t "your task"')
|
|
616
|
+
print(" 3. Set keys: edit ~/.devsquad/.env (optional, for real AI)")
|
|
617
|
+
print()
|
|
618
|
+
|
|
619
|
+
return 0
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def cmd_dispatch(args):
|
|
623
|
+
task_text = args.task if args.task is not None else args.task_positional
|
|
624
|
+
if not task_text:
|
|
625
|
+
print("Error: Task description required. Usage: devsquad dispatch \"your task\" or devsquad dispatch -t \"your task\"", file=sys.stderr)
|
|
626
|
+
return 1
|
|
627
|
+
|
|
628
|
+
validator = InputValidator()
|
|
629
|
+
|
|
630
|
+
task_result = validator.validate_task(task_text)
|
|
631
|
+
if not task_result.valid:
|
|
632
|
+
print(f"Error: Invalid task - {task_result.reason}", file=sys.stderr)
|
|
633
|
+
return 1
|
|
634
|
+
|
|
635
|
+
task = task_result.sanitized_input or task_text
|
|
636
|
+
|
|
637
|
+
# 验证角色列表(如果提供)
|
|
638
|
+
if args.roles:
|
|
639
|
+
args.roles = [resolve_role_id(r) for r in args.roles]
|
|
640
|
+
roles_result = validator.validate_roles(args.roles)
|
|
641
|
+
if not roles_result.valid:
|
|
642
|
+
print(f"Error: Invalid roles - {roles_result.reason}", file=sys.stderr)
|
|
643
|
+
return 1
|
|
644
|
+
|
|
645
|
+
# 检查可疑模式(警告但不阻止)
|
|
646
|
+
warnings = validator.check_suspicious_patterns(task)
|
|
647
|
+
if warnings:
|
|
648
|
+
print(f"Warning: Suspicious patterns detected: {', '.join(warnings)}", file=sys.stderr)
|
|
649
|
+
print("Proceeding anyway...", file=sys.stderr)
|
|
650
|
+
|
|
651
|
+
kwargs = {
|
|
652
|
+
"persist_dir": args.persist_dir,
|
|
653
|
+
"enable_warmup": not args.no_warmup,
|
|
654
|
+
"enable_compression": not args.no_compression,
|
|
655
|
+
"enable_permission": not args.skip_permission,
|
|
656
|
+
"enable_memory": not args.no_memory,
|
|
657
|
+
"enable_skillify": not args.no_skillify,
|
|
658
|
+
"stream": getattr(args, 'stream', False),
|
|
659
|
+
"lang": getattr(args, 'lang', 'auto'),
|
|
660
|
+
}
|
|
661
|
+
if args.permission_level:
|
|
662
|
+
kwargs["permission_level"] = PermissionLevel(args.permission_level.upper())
|
|
663
|
+
|
|
664
|
+
backend = _create_backend(args.backend, args.base_url, args.model)
|
|
665
|
+
if backend is None and args.backend not in ("mock", None):
|
|
666
|
+
print(f"\nError: Failed to create '{args.backend}' backend.", file=sys.stderr)
|
|
667
|
+
print("Falling back to mock mode is NOT allowed when a backend is explicitly specified.", file=sys.stderr)
|
|
668
|
+
print("Please check your API key and configuration.", file=sys.stderr)
|
|
669
|
+
return 1
|
|
670
|
+
if backend is not None:
|
|
671
|
+
kwargs["llm_backend"] = backend
|
|
672
|
+
|
|
673
|
+
disp = MultiAgentDispatcher(**kwargs)
|
|
674
|
+
|
|
675
|
+
try:
|
|
676
|
+
if args.quick:
|
|
677
|
+
result = disp.quick_dispatch(
|
|
678
|
+
task, # 使用验证后的任务
|
|
679
|
+
output_format=args.format if args.format in ("structured", "compact", "detailed") else "structured",
|
|
680
|
+
include_action_items=args.action_items,
|
|
681
|
+
include_timing=args.timing,
|
|
682
|
+
)
|
|
683
|
+
else:
|
|
684
|
+
result = disp.dispatch(
|
|
685
|
+
task, # 使用验证后的任务
|
|
686
|
+
roles=args.roles,
|
|
687
|
+
mode=args.mode,
|
|
688
|
+
dry_run=args.dry_run,
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
if args.format == "json":
|
|
692
|
+
output = {
|
|
693
|
+
"success": result.success,
|
|
694
|
+
"matched_roles": getattr(result, 'matched_roles', None),
|
|
695
|
+
"summary": result.summary,
|
|
696
|
+
"report": result.to_markdown(),
|
|
697
|
+
"timing": getattr(result, 'timing', None),
|
|
698
|
+
}
|
|
699
|
+
print(json.dumps(output, ensure_ascii=False, indent=2))
|
|
700
|
+
elif args.format == "compact":
|
|
701
|
+
print(result.summary)
|
|
702
|
+
else:
|
|
703
|
+
print(result.to_markdown())
|
|
704
|
+
|
|
705
|
+
return 0 if result.success else 1
|
|
706
|
+
finally:
|
|
707
|
+
disp.shutdown()
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def cmd_status(args):
|
|
711
|
+
disp = MultiAgentDispatcher(enable_warmup=False)
|
|
712
|
+
try:
|
|
713
|
+
stats = disp.get_status() if hasattr(disp, 'get_status') else {}
|
|
714
|
+
status = {
|
|
715
|
+
"name": "DevSquad",
|
|
716
|
+
"version": VERSION,
|
|
717
|
+
"status": "ready",
|
|
718
|
+
"available_roles": ROLES,
|
|
719
|
+
"available_modes": MODES,
|
|
720
|
+
"modules_loaded": list(stats.keys()) if stats else "unknown",
|
|
721
|
+
}
|
|
722
|
+
print(json.dumps(status, ensure_ascii=False, indent=2))
|
|
723
|
+
return 0
|
|
724
|
+
finally:
|
|
725
|
+
disp.shutdown()
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def cmd_roles(args):
|
|
729
|
+
role_descriptions = {}
|
|
730
|
+
for rid, rdef in ROLE_REGISTRY.items():
|
|
731
|
+
display_id = rdef.aliases[0] if rdef.aliases else rid
|
|
732
|
+
status_tag = " [planned]" if rdef.status == "planned" else ""
|
|
733
|
+
role_descriptions[display_id] = f"{rdef.description}{status_tag}"
|
|
734
|
+
if args.format == "json":
|
|
735
|
+
print(json.dumps(role_descriptions, ensure_ascii=False, indent=2))
|
|
736
|
+
else:
|
|
737
|
+
for role, desc in role_descriptions.items():
|
|
738
|
+
print(f" {role:<12} — {desc}")
|
|
739
|
+
return 0
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def cmd_lifecycle(args):
|
|
743
|
+
"""Handle lifecycle commands (spec/plan/build/test/review/ship) as View Layer over 11-phase lifecycle."""
|
|
744
|
+
command = args.lifecycle_command
|
|
745
|
+
preset = LIFECYCLE_PRESETS.get(command)
|
|
746
|
+
|
|
747
|
+
if not preset:
|
|
748
|
+
print(f"Error: Unknown lifecycle command '{command}'", file=sys.stderr)
|
|
749
|
+
print(f"Available: {', '.join(LIFECYCLE_COMMANDS)}", file=sys.stderr)
|
|
750
|
+
return 1
|
|
751
|
+
|
|
752
|
+
task_text = args.task if args.task is not None else args.task_positional
|
|
753
|
+
if not task_text:
|
|
754
|
+
print(f"Error: Task description required for '{command}' command.", file=sys.stderr)
|
|
755
|
+
print(f"Usage: devsquad {command} \"your task\"", file=sys.stderr)
|
|
756
|
+
return 1
|
|
757
|
+
|
|
758
|
+
validator = InputValidator()
|
|
759
|
+
task_result = validator.validate_task(task_text)
|
|
760
|
+
if not task_result.valid:
|
|
761
|
+
print(f"Error: Invalid task - {task_result.reason}", file=sys.stderr)
|
|
762
|
+
return 1
|
|
763
|
+
|
|
764
|
+
task = task_result.sanitized_input or task_text
|
|
765
|
+
|
|
766
|
+
# Check for visual mode
|
|
767
|
+
use_visual = getattr(args, 'visual', False)
|
|
768
|
+
use_verbose = getattr(args, 'verbose', False)
|
|
769
|
+
|
|
770
|
+
# Show view layer mapping information (Plan C: CLI as View Layer)
|
|
771
|
+
try:
|
|
772
|
+
from scripts.collaboration.lifecycle_protocol import VIEW_MAPPINGS, get_shared_protocol
|
|
773
|
+
mapping = VIEW_MAPPINGS.get(command)
|
|
774
|
+
|
|
775
|
+
if use_visual:
|
|
776
|
+
# Use enhanced visual output
|
|
777
|
+
import sys as _sys
|
|
778
|
+
import os as _os
|
|
779
|
+
_sys.path.insert(0, _os.path.join(_os.path.dirname(__file__), 'cli'))
|
|
780
|
+
|
|
781
|
+
try:
|
|
782
|
+
from cli_visual import VisualFormatter, get_visual_formatter, Colors, Icons
|
|
783
|
+
|
|
784
|
+
vf = get_visual_formatter(use_color=True)
|
|
785
|
+
|
|
786
|
+
vf.print_lifecycle_header(command, mapping, preset)
|
|
787
|
+
|
|
788
|
+
# Show resolved phases with details
|
|
789
|
+
protocol = get_shared_protocol()
|
|
790
|
+
if mapping:
|
|
791
|
+
phases = protocol.resolve_command_to_phases(command)
|
|
792
|
+
if phases:
|
|
793
|
+
vf.print_phase_list(phases)
|
|
794
|
+
|
|
795
|
+
# Show progress overview
|
|
796
|
+
completed_count = len([p for p in phases if p.phase_id in (protocol._completed_phases or [])])
|
|
797
|
+
vf.print_progress_overview(
|
|
798
|
+
completed_count,
|
|
799
|
+
len(phases),
|
|
800
|
+
f"Command '{command}' Coverage"
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
# Show gate status
|
|
804
|
+
gate_name = preset.get('gate', 'Unknown')
|
|
805
|
+
vf.print_gate_status(None, gate_name)
|
|
806
|
+
|
|
807
|
+
# Verbose mode: show additional info
|
|
808
|
+
if use_verbose:
|
|
809
|
+
status = protocol.get_status()
|
|
810
|
+
vf.print_status_summary(status)
|
|
811
|
+
|
|
812
|
+
# Show all available phases info
|
|
813
|
+
all_phases = protocol.get_all_phases()
|
|
814
|
+
vf.print_info_box(
|
|
815
|
+
"All Available Phases",
|
|
816
|
+
[f"{p.phase_id}: {p.name} ({p.role_id})" for p in all_phases[:8]],
|
|
817
|
+
icon="📋",
|
|
818
|
+
color=Colors.BLUE,
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
# Print action prompt
|
|
822
|
+
vf.print_info_box(
|
|
823
|
+
"Ready to Execute",
|
|
824
|
+
[
|
|
825
|
+
f"Task: {task[:60]}{'...' if len(task) > 60 else ''}",
|
|
826
|
+
f"Command: {command.upper()}",
|
|
827
|
+
f"Next step: Run dispatch or view examples",
|
|
828
|
+
],
|
|
829
|
+
icon=Icons.ROCKET,
|
|
830
|
+
color=Colors.GREEN,
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
vf.print_footer()
|
|
834
|
+
|
|
835
|
+
except ImportError as ve:
|
|
836
|
+
print(f"\n⚠️ Visual module not available: {ve}")
|
|
837
|
+
print("Falling back to standard output...\n")
|
|
838
|
+
use_visual = False # Fall back to standard output
|
|
839
|
+
|
|
840
|
+
elif use_verbose:
|
|
841
|
+
# Verbose text output (no colors but detailed)
|
|
842
|
+
print(f"\n{'='*60}")
|
|
843
|
+
print(f"🔄 DevSquad Lifecycle [Verbose Mode]")
|
|
844
|
+
print(f"{'='*60}")
|
|
845
|
+
print(f"📌 Command: {command.upper()}")
|
|
846
|
+
if mapping:
|
|
847
|
+
print(f"📋 Maps to Phases: {', '.join(mapping.phases)}")
|
|
848
|
+
print(f"🎯 Mode: SHORTCUT (simplified view of 11-phase lifecycle)")
|
|
849
|
+
|
|
850
|
+
# Show phase details
|
|
851
|
+
protocol = get_shared_protocol()
|
|
852
|
+
phases = protocol.resolve_command_to_phases(command)
|
|
853
|
+
if phases:
|
|
854
|
+
print(f"\n📝 Phase Details:")
|
|
855
|
+
for p in phases:
|
|
856
|
+
print(f" • {p.phase_id}: {p.name}")
|
|
857
|
+
print(f" Role: {p.role_id}")
|
|
858
|
+
if p.dependencies:
|
|
859
|
+
print(f" Dependencies: {', '.join(p.dependencies)}")
|
|
860
|
+
|
|
861
|
+
print(f"\n📝 Description: {preset['description']}")
|
|
862
|
+
print(f"👥 Roles: {', '.join(preset['required_roles'])}")
|
|
863
|
+
print(f"⚙️ Mode: {preset['mode']}")
|
|
864
|
+
print(f"🚧 Gate: {preset['gate']}")
|
|
865
|
+
print(f"\n{preset['pre_dispatch_message']}\n")
|
|
866
|
+
print(f"{'='*60}\n")
|
|
867
|
+
|
|
868
|
+
else:
|
|
869
|
+
# Original simple output (backward compatible)
|
|
870
|
+
print(f"\n{'='*60}")
|
|
871
|
+
print(f"🔄 DevSquad Lifecycle [View Layer Mode]")
|
|
872
|
+
print(f"{'='*60}")
|
|
873
|
+
print(f"📌 Command: {command.upper()}")
|
|
874
|
+
if mapping:
|
|
875
|
+
print(f"📋 Maps to Phases: {', '.join(mapping.phases)}")
|
|
876
|
+
print(f"🎯 Mode: SHORTCUT (simplified view of 11-phase lifecycle)")
|
|
877
|
+
print(f"📝 Description: {preset['description']}")
|
|
878
|
+
print(f"👥 Roles: {', '.join(preset['required_roles'])}")
|
|
879
|
+
print(f"🚧 Gate: {preset['gate']}")
|
|
880
|
+
print(f"\n{preset['pre_dispatch_message']}\n")
|
|
881
|
+
print(f"💡 Tip: Use --visual for enhanced output, --verbose for details\n")
|
|
882
|
+
|
|
883
|
+
except Exception as e:
|
|
884
|
+
print(f"\n{'='*60}")
|
|
885
|
+
print(f"🔄 DevSquad Lifecycle: {command.upper()}")
|
|
886
|
+
print(f"{'='*60}")
|
|
887
|
+
print(f"📌 Description: {preset['description']}")
|
|
888
|
+
print(f"👥 Roles: {', '.join(preset['required_roles'])}")
|
|
889
|
+
print(f"(View mapping info unavailable: {e})\n")
|
|
890
|
+
|
|
891
|
+
kwargs = {
|
|
892
|
+
"persist_dir": args.persist_dir,
|
|
893
|
+
"enable_warmup": not args.no_warmup,
|
|
894
|
+
"enable_compression": not args.no_compression,
|
|
895
|
+
"enable_permission": not args.skip_permission,
|
|
896
|
+
"enable_memory": not args.no_memory,
|
|
897
|
+
"enable_skillify": not args.no_skillify,
|
|
898
|
+
"stream": getattr(args, 'stream', False),
|
|
899
|
+
"lang": getattr(args, 'lang', 'auto'),
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
backend = _create_backend(args.backend, args.base_url, args.model)
|
|
903
|
+
if backend is not None:
|
|
904
|
+
kwargs["llm_backend"] = backend
|
|
905
|
+
|
|
906
|
+
disp = MultiAgentDispatcher(**kwargs)
|
|
907
|
+
|
|
908
|
+
try:
|
|
909
|
+
result = disp.dispatch(
|
|
910
|
+
task,
|
|
911
|
+
roles=preset["required_roles"],
|
|
912
|
+
mode=preset["mode"],
|
|
913
|
+
dry_run=args.dry_run,
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
if args.format == "json":
|
|
917
|
+
output = {
|
|
918
|
+
"lifecycle_command": command,
|
|
919
|
+
"gate": preset["gate"],
|
|
920
|
+
"success": result.success,
|
|
921
|
+
"matched_roles": getattr(result, 'matched_roles', None),
|
|
922
|
+
"summary": result.summary,
|
|
923
|
+
"report": result.to_markdown(),
|
|
924
|
+
}
|
|
925
|
+
print(json.dumps(output, ensure_ascii=False, indent=2))
|
|
926
|
+
elif args.format == "compact":
|
|
927
|
+
print(result.summary)
|
|
928
|
+
else:
|
|
929
|
+
print(result.to_markdown())
|
|
930
|
+
|
|
931
|
+
return 0 if result.success else 1
|
|
932
|
+
finally:
|
|
933
|
+
disp.shutdown()
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
def main():
|
|
937
|
+
parser = argparse.ArgumentParser(
|
|
938
|
+
description="DevSquad V3.6 — Multi-Agent Orchestration Engine for Software Development",
|
|
939
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
940
|
+
epilog="""
|
|
941
|
+
Examples:
|
|
942
|
+
%(prog)s init # Interactive setup wizard (recommended for new users)
|
|
943
|
+
%(prog)s dispatch -t "Design user auth system" -r architect pm tester
|
|
944
|
+
%(prog)s dispatch -t "Review codebase" --mode consensus --format json
|
|
945
|
+
%(prog)s dispatch -t "Analyze API" --quick --format compact
|
|
946
|
+
%(prog)s dispatch -t "Security audit" -r security --backend openai
|
|
947
|
+
%(prog)s spec -t "User authentication system"
|
|
948
|
+
%(prog)s build -t "Implement login API"
|
|
949
|
+
%(prog)s test -t "Run all unit tests"
|
|
950
|
+
%(prog)s review -t "Check PR #123"
|
|
951
|
+
%(prog)s ship -t "Deploy v2.0 to production"
|
|
952
|
+
%(prog)s roles
|
|
953
|
+
%(prog)s status
|
|
954
|
+
%(prog)s demo # Quick demo (no API key needed)
|
|
955
|
+
%(prog)s demo --scenario intent # Run only intent detection scenario
|
|
956
|
+
%(prog)s --version
|
|
957
|
+
|
|
958
|
+
Getting Started (New Users):
|
|
959
|
+
1. Run: %(prog)s init # Launches interactive setup wizard
|
|
960
|
+
2. Choose your project type and AI backend
|
|
961
|
+
3. Start collaborating: %(prog)s dispatch -t "your task"
|
|
962
|
+
|
|
963
|
+
Lifecycle Commands (P0-4 Agent Skills Integration):
|
|
964
|
+
spec Define/refine requirements into specification (architect + pm)
|
|
965
|
+
plan Break down spec into atomic tasks (architect + pm)
|
|
966
|
+
build Implement with TDD discipline (architect + coder + tester)
|
|
967
|
+
test Run tests with evidence requirements (tester + coder)
|
|
968
|
+
review Five-axis code review (coder + security + tester + architect)
|
|
969
|
+
ship Pre-launch checklist + deployment prep (devops + security + architect)
|
|
970
|
+
|
|
971
|
+
Environment Variables (API keys are read from env vars only, never command line):
|
|
972
|
+
DEVSQUAD_LLM_BACKEND Default LLM backend (mock/openai/anthropic)
|
|
973
|
+
OPENAI_API_KEY OpenAI API key (required for --backend openai)
|
|
974
|
+
OPENAI_BASE_URL Custom API endpoint (for OpenAI-compatible APIs)
|
|
975
|
+
OPENAI_MODEL Model name (default: gpt-4)
|
|
976
|
+
ANTHROPIC_API_KEY Anthropic API key (required for --backend anthropic)
|
|
977
|
+
ANTHROPIC_MODEL Model name (default: claude-sonnet-4-20250514)
|
|
978
|
+
""",
|
|
979
|
+
)
|
|
980
|
+
parser.add_argument("--version", action="version", version=f"DevSquad {VERSION}")
|
|
981
|
+
|
|
982
|
+
subparsers = parser.add_subparsers(dest="command", required=True, help="Available commands")
|
|
983
|
+
|
|
984
|
+
# Init command (interactive setup wizard)
|
|
985
|
+
p_init = subparsers.add_parser("init", aliases=["setup", "i"],
|
|
986
|
+
help="Interactive setup wizard for new users")
|
|
987
|
+
p_init.add_argument("--non-interactive", action="store_true",
|
|
988
|
+
help="Run in non-interactive mode (use defaults)")
|
|
989
|
+
p_init.add_argument("--quick", "-q", action="store_true",
|
|
990
|
+
help="Quick non-interactive setup with sensible defaults")
|
|
991
|
+
|
|
992
|
+
# Demo command (quick demo, no API key needed)
|
|
993
|
+
p_demo = subparsers.add_parser("demo", aliases=["play", "try"],
|
|
994
|
+
help="Quick demo showing DevSquad capabilities (mock mode)")
|
|
995
|
+
p_demo.add_argument("--scenario", "-s",
|
|
996
|
+
choices=["all", "intent", "security", "dispatch"],
|
|
997
|
+
default="all",
|
|
998
|
+
help="Which scenario to run (default: all)")
|
|
999
|
+
|
|
1000
|
+
p_dispatch = subparsers.add_parser("dispatch", aliases=["run", "d"], help="Execute a multi-agent task")
|
|
1001
|
+
p_dispatch.add_argument("task_positional", nargs="?", default=None, help="Task description (positional, no -t needed)")
|
|
1002
|
+
p_dispatch.add_argument("--task", "-t", help="Task description (alternative to positional)")
|
|
1003
|
+
p_dispatch.add_argument("--roles", "-r", nargs="+", choices=ALL_ROLE_IDS, help="Roles to involve (default: auto-match)")
|
|
1004
|
+
p_dispatch.add_argument("--mode", "-m", choices=MODES, default="auto", help="Execution mode (default: auto)")
|
|
1005
|
+
p_dispatch.add_argument("--format", "-f", choices=FORMATS, default="markdown", help="Output format")
|
|
1006
|
+
p_dispatch.add_argument("--backend", "-b", choices=BACKENDS, default=os.environ.get("DEVSQUAD_LLM_BACKEND", "mock"),
|
|
1007
|
+
help="LLM backend (default: mock, or DEVSQUAD_LLM_BACKEND env)")
|
|
1008
|
+
p_dispatch.add_argument("--base-url", help="Custom API base URL (or use OPENAI_BASE_URL env)")
|
|
1009
|
+
p_dispatch.add_argument("--model", help="Model name (or use OPENAI_MODEL/ANTHROPIC_MODEL env)")
|
|
1010
|
+
p_dispatch.add_argument("--dry-run", action="store_true", help="Simulate without execution")
|
|
1011
|
+
p_dispatch.add_argument("--quick", "-q", action="store_true", help="Use quick_dispatch (3 formats)")
|
|
1012
|
+
p_dispatch.add_argument("--action-items", action="store_true", help="Include H/M/L action items")
|
|
1013
|
+
p_dispatch.add_argument("--timing", action="store_true", help="Include timing info")
|
|
1014
|
+
p_dispatch.add_argument("--persist-dir", help="Custom scratchpad directory")
|
|
1015
|
+
p_dispatch.add_argument("--no-warmup", action="store_true", help="Disable startup warmup")
|
|
1016
|
+
p_dispatch.add_argument("--no-compression", action="store_true", help="Disable context compression")
|
|
1017
|
+
p_dispatch.add_argument("--stream", action="store_true", help="Stream LLM output in real-time (requires --backend)")
|
|
1018
|
+
p_dispatch.add_argument("--lang", choices=["auto", "en", "zh", "ja"], default="auto", help="Output language (default: auto-detect)")
|
|
1019
|
+
p_dispatch.add_argument("--skip-permission", action="store_true", help="Skip permission checks")
|
|
1020
|
+
p_dispatch.add_argument("--no-memory", action="store_true", help="Disable memory bridge")
|
|
1021
|
+
p_dispatch.add_argument("--no-skillify", action="store_true", help="Disable skill learning")
|
|
1022
|
+
p_dispatch.add_argument("--permission-level", choices=["PLAN", "DEFAULT", "AUTO", "BYPASS"], help="Permission level")
|
|
1023
|
+
|
|
1024
|
+
subparsers.add_parser("status", aliases=["s"], help="Show system status")
|
|
1025
|
+
|
|
1026
|
+
p_roles = subparsers.add_parser("roles", aliases=["ls"], help="List available roles")
|
|
1027
|
+
p_roles.add_argument("--format", "-f", choices=["text", "json"], default="text", help="Output format")
|
|
1028
|
+
|
|
1029
|
+
p_lifecycle = subparsers.add_parser("lifecycle", aliases=["lc"], help="Execute lifecycle workflow command")
|
|
1030
|
+
p_lifecycle.add_argument("lifecycle_command", choices=LIFECYCLE_COMMANDS, help="Lifecycle command to execute")
|
|
1031
|
+
p_lifecycle.add_argument("task_positional", nargs="?", default=None, help="Task description (positional)")
|
|
1032
|
+
p_lifecycle.add_argument("--task", "-t", help="Task description (alternative to positional)")
|
|
1033
|
+
p_lifecycle.add_argument("--format", "-f", choices=FORMATS, default="markdown", help="Output format")
|
|
1034
|
+
p_lifecycle.add_argument("--backend", "-b", choices=BACKENDS, default=os.environ.get("DEVSQUAD_LLM_BACKEND", "mock"),
|
|
1035
|
+
help="LLM backend (default: mock, or DEVSQUAD_LLM_BACKEND env)")
|
|
1036
|
+
p_lifecycle.add_argument("--base-url", help="Custom API base URL (or use OPENAI_BASE_URL env)")
|
|
1037
|
+
p_lifecycle.add_argument("--model", help="Model name (or use OPENAI_MODEL/ANTHROPIC_MODEL env)")
|
|
1038
|
+
p_lifecycle.add_argument("--dry-run", action="store_true", help="Simulate without execution")
|
|
1039
|
+
p_lifecycle.add_argument("--persist-dir", help="Custom scratchpad directory")
|
|
1040
|
+
p_lifecycle.add_argument("--no-warmup", action="store_true", help="Disable startup warmup")
|
|
1041
|
+
p_lifecycle.add_argument("--no-compression", action="store_true", help="Disable context compression")
|
|
1042
|
+
p_lifecycle.add_argument("--stream", action="store_true", help="Stream LLM output in real-time (requires --backend)")
|
|
1043
|
+
p_lifecycle.add_argument("--lang", choices=["auto", "en", "zh", "ja"], default="auto", help="Output language (default: auto-detect)")
|
|
1044
|
+
p_lifecycle.add_argument("--skip-permission", action="store_true", help="Skip permission checks")
|
|
1045
|
+
p_lifecycle.add_argument("--no-memory", action="store_true", help="Disable memory bridge")
|
|
1046
|
+
p_lifecycle.add_argument("--no-skillify", action="store_true", help="Disable skill learning")
|
|
1047
|
+
p_lifecycle.add_argument("--visual", "-v", action="store_true",
|
|
1048
|
+
help="Enable enhanced visual output (colored progress, icons, formatted tables)")
|
|
1049
|
+
p_lifecycle.add_argument("--verbose", action="store_true",
|
|
1050
|
+
help="Show detailed phase information and gate status")
|
|
1051
|
+
|
|
1052
|
+
for cmd_name in LIFECYCLE_COMMANDS:
|
|
1053
|
+
cmd_help = LIFECYCLE_PRESETS[cmd_name]["description"]
|
|
1054
|
+
p_cmd = subparsers.add_parser(cmd_name, help=cmd_help)
|
|
1055
|
+
p_cmd.add_argument("task_positional", nargs="?", default=None, help="Task description (positional)")
|
|
1056
|
+
p_cmd.add_argument("--task", "-t", help="Task description (alternative to positional)")
|
|
1057
|
+
p_cmd.add_argument("--format", "-f", choices=FORMATS, default="markdown", help="Output format")
|
|
1058
|
+
p_cmd.add_argument("--backend", "-b", choices=BACKENDS, default=os.environ.get("DEVSQUAD_LLM_BACKEND", "mock"),
|
|
1059
|
+
help="LLM backend (default: mock, or DEVSQUAD_LLM_BACKEND env)")
|
|
1060
|
+
p_cmd.add_argument("--base-url", help="Custom API base URL (or use OPENAI_BASE_URL env)")
|
|
1061
|
+
p_cmd.add_argument("--model", help="Model name (or use OPENAI_MODEL/ANTHROPIC_MODEL env)")
|
|
1062
|
+
p_cmd.add_argument("--dry-run", action="store_true", help="Simulate without execution")
|
|
1063
|
+
p_cmd.add_argument("--persist-dir", help="Custom scratchpad directory")
|
|
1064
|
+
p_cmd.add_argument("--no-warmup", action="store_true", help="Disable startup warmup")
|
|
1065
|
+
p_cmd.add_argument("--no-compression", action="store_true", help="Disable context compression")
|
|
1066
|
+
p_cmd.add_argument("--stream", action="store_true", help="Stream LLM output in real-time (requires --backend)")
|
|
1067
|
+
p_cmd.add_argument("--lang", choices=["auto", "en", "zh", "ja"], default="auto", help="Output language (default: auto-detect)")
|
|
1068
|
+
p_cmd.add_argument("--skip-permission", action="store_true", help="Skip permission checks")
|
|
1069
|
+
p_cmd.add_argument("--no-memory", action="store_true", help="Disable memory bridge")
|
|
1070
|
+
p_cmd.add_argument("--no-skillify", action="store_true", help="Disable skill learning")
|
|
1071
|
+
|
|
1072
|
+
args = parser.parse_args()
|
|
1073
|
+
|
|
1074
|
+
if args.command in ("init", "setup", "i"):
|
|
1075
|
+
return cmd_init(args)
|
|
1076
|
+
elif args.command in ("demo", "play", "try"):
|
|
1077
|
+
return cmd_demo(args)
|
|
1078
|
+
elif args.command in ("dispatch", "run", "d"):
|
|
1079
|
+
return cmd_dispatch(args)
|
|
1080
|
+
elif args.command in ("status", "s"):
|
|
1081
|
+
return cmd_status(args)
|
|
1082
|
+
elif args.command in ("roles", "ls"):
|
|
1083
|
+
return cmd_roles(args)
|
|
1084
|
+
elif args.command in ("lifecycle", "lc") or args.command in LIFECYCLE_COMMANDS:
|
|
1085
|
+
if args.command in LIFECYCLE_COMMANDS:
|
|
1086
|
+
args.lifecycle_command = args.command
|
|
1087
|
+
return cmd_lifecycle(args)
|
|
1088
|
+
else:
|
|
1089
|
+
parser.print_help()
|
|
1090
|
+
return 0
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
if __name__ == "__main__":
|
|
1094
|
+
sys.exit(main())
|