@pjmendonca/devflow 1.9.0
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.
- package/CHANGELOG.md +526 -0
- package/LICENSE +21 -0
- package/README.md +620 -0
- package/bin/devflow-checkpoint.js +10 -0
- package/bin/devflow-collab.js +10 -0
- package/bin/devflow-cost.js +10 -0
- package/bin/devflow-create-persona.js +10 -0
- package/bin/devflow-init.js +10 -0
- package/bin/devflow-memory.js +10 -0
- package/bin/devflow-new-doc.js +10 -0
- package/bin/devflow-personalize.js +10 -0
- package/bin/devflow-setup-checkpoint.js +10 -0
- package/bin/devflow-story.js +10 -0
- package/bin/devflow-tech-debt.js +10 -0
- package/bin/devflow-validate-overrides.js +10 -0
- package/bin/devflow-validate.js +10 -0
- package/bin/devflow-version.js +10 -0
- package/lib/constants.js +30 -0
- package/lib/exec-python.js +78 -0
- package/lib/python-check.js +178 -0
- package/package.json +64 -0
- package/tooling/.automation/agents/architect.md +135 -0
- package/tooling/.automation/agents/ba.md +70 -0
- package/tooling/.automation/agents/dev.md +79 -0
- package/tooling/.automation/agents/maintainer.md +97 -0
- package/tooling/.automation/agents/pm.md +116 -0
- package/tooling/.automation/agents/reviewer.md +141 -0
- package/tooling/.automation/agents/sm.md +61 -0
- package/tooling/.automation/agents/writer.md +193 -0
- package/tooling/.automation/config.ps1.template +61 -0
- package/tooling/.automation/config.sh.template +48 -0
- package/tooling/.automation/memory/.gitkeep +6 -0
- package/tooling/.automation/memory/knowledge/kg_integration-test.json +94 -0
- package/tooling/.automation/memory/knowledge/kg_test-story.json +300 -0
- package/tooling/.automation/memory/shared/shared_integration-test.json +30 -0
- package/tooling/.automation/memory/shared/shared_test-story.json +78 -0
- package/tooling/.automation/overrides/templates/README.md +113 -0
- package/tooling/.automation/overrides/templates/architect/README.md +27 -0
- package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +92 -0
- package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +85 -0
- package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +88 -0
- package/tooling/.automation/overrides/templates/ba/README.md +27 -0
- package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +86 -0
- package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +91 -0
- package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +89 -0
- package/tooling/.automation/overrides/templates/dev/README.md +32 -0
- package/tooling/.automation/overrides/templates/dev/junior-mentored.yaml +39 -0
- package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +52 -0
- package/tooling/.automation/overrides/templates/dev/security-focused.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/senior-fullstack.yaml +39 -0
- package/tooling/.automation/overrides/templates/maintainer/README.md +27 -0
- package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +113 -0
- package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +94 -0
- package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +94 -0
- package/tooling/.automation/overrides/templates/pm/README.md +27 -0
- package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +87 -0
- package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/reviewer/README.md +11 -0
- package/tooling/.automation/overrides/templates/reviewer/mentoring-reviewer.yaml +45 -0
- package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +50 -0
- package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +48 -0
- package/tooling/.automation/overrides/templates/sm/README.md +11 -0
- package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +52 -0
- package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +50 -0
- package/tooling/.automation/overrides/templates/sm/technical-lead.yaml +47 -0
- package/tooling/.automation/overrides/templates/user-profile.template.yaml +62 -0
- package/tooling/.automation/overrides/templates/writer/README.md +27 -0
- package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +99 -0
- package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +108 -0
- package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +100 -0
- package/tooling/completions/DevflowCompletion.ps1 +213 -0
- package/tooling/completions/_run-story +116 -0
- package/tooling/completions/run-story-completion.bash +136 -0
- package/tooling/docs/DOC-STANDARD.md +717 -0
- package/tooling/docs/sprint-status.yaml.template +24 -0
- package/tooling/docs/templates/bug-report.md +234 -0
- package/tooling/docs/templates/migration-spec.md +274 -0
- package/tooling/docs/templates/refactor-spec.md +86 -0
- package/tooling/docs/templates/tech-debt.md +86 -0
- package/tooling/scripts/context_checkpoint.py +556 -0
- package/tooling/scripts/cost_dashboard.py +617 -0
- package/tooling/scripts/create-persona.py +690 -0
- package/tooling/scripts/create-persona.sh +435 -0
- package/tooling/scripts/init-project-workflow.ps1 +651 -0
- package/tooling/scripts/init-project-workflow.py +70 -0
- package/tooling/scripts/init-project-workflow.sh +746 -0
- package/tooling/scripts/lib/__init__.py +35 -0
- package/tooling/scripts/lib/agent_handoff.py +526 -0
- package/tooling/scripts/lib/agent_router.py +698 -0
- package/tooling/scripts/lib/checkpoint-integration.ps1 +245 -0
- package/tooling/scripts/lib/checkpoint-integration.sh +191 -0
- package/tooling/scripts/lib/claude-cli.ps1 +952 -0
- package/tooling/scripts/lib/claude-cli.sh +1293 -0
- package/tooling/scripts/lib/cost_config.py +222 -0
- package/tooling/scripts/lib/cost_display.py +443 -0
- package/tooling/scripts/lib/cost_tracker.py +710 -0
- package/tooling/scripts/lib/currency_converter.py +328 -0
- package/tooling/scripts/lib/errors.py +438 -0
- package/tooling/scripts/lib/override-loader.sh +286 -0
- package/tooling/scripts/lib/pair_programming.py +589 -0
- package/tooling/scripts/lib/shared_memory.py +637 -0
- package/tooling/scripts/lib/swarm_orchestrator.py +689 -0
- package/tooling/scripts/memory_summarize.py +324 -0
- package/tooling/scripts/new-doc.ps1 +405 -0
- package/tooling/scripts/new-doc.py +93 -0
- package/tooling/scripts/new-doc.sh +534 -0
- package/tooling/scripts/personalize_agent.py +385 -0
- package/tooling/scripts/rollback-migration.sh +540 -0
- package/tooling/scripts/run-collab.ps1 +251 -0
- package/tooling/scripts/run-collab.py +605 -0
- package/tooling/scripts/run-collab.sh +110 -0
- package/tooling/scripts/run-story.ps1 +490 -0
- package/tooling/scripts/run-story.py +387 -0
- package/tooling/scripts/run-story.sh +467 -0
- package/tooling/scripts/setup-checkpoint-service.ps1 +219 -0
- package/tooling/scripts/setup-checkpoint-service.py +87 -0
- package/tooling/scripts/setup-checkpoint-service.sh +236 -0
- package/tooling/scripts/tech-debt-tracker.py +608 -0
- package/tooling/scripts/update_version.py +244 -0
- package/tooling/scripts/validate-overrides.py +511 -0
- package/tooling/scripts/validate-overrides.sh +432 -0
- package/tooling/scripts/validate_setup.py +539 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Collaborative Story Runner - Unified CLI for Agent Collaboration
|
|
4
|
+
|
|
5
|
+
Integrates all collaboration features:
|
|
6
|
+
- Agent routing (auto-select best agents)
|
|
7
|
+
- Swarm mode (multi-agent debate)
|
|
8
|
+
- Pair programming (DEV + REVIEWER interleaved)
|
|
9
|
+
- Shared memory and knowledge graph
|
|
10
|
+
- Automatic handoffs
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python run-collab.py <story-key> [mode] [options]
|
|
14
|
+
|
|
15
|
+
Modes:
|
|
16
|
+
--auto Auto-route to best agents (default)
|
|
17
|
+
--swarm Multi-agent debate/consensus
|
|
18
|
+
--pair DEV + REVIEWER pair programming
|
|
19
|
+
--sequential Traditional sequential pipeline
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
python run-collab.py 3-5 --auto
|
|
23
|
+
python run-collab.py 3-5 --swarm --agents ARCHITECT,DEV,REVIEWER
|
|
24
|
+
python run-collab.py 3-5 --pair
|
|
25
|
+
python run-collab.py "fix login bug" --auto
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import platform
|
|
32
|
+
import shutil
|
|
33
|
+
import sys
|
|
34
|
+
from datetime import datetime
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Optional
|
|
37
|
+
|
|
38
|
+
SCRIPT_DIR = Path(__file__).parent
|
|
39
|
+
sys.path.insert(0, str(SCRIPT_DIR / "lib"))
|
|
40
|
+
|
|
41
|
+
# Cross-platform detection
|
|
42
|
+
IS_WINDOWS = platform.system() == "Windows"
|
|
43
|
+
IS_MACOS = platform.system() == "Darwin"
|
|
44
|
+
IS_LINUX = platform.system() == "Linux"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def detect_claude_cli() -> Optional[str]:
|
|
48
|
+
"""Detect Claude CLI across platforms.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Path to Claude CLI or None if not found.
|
|
52
|
+
"""
|
|
53
|
+
# Check common CLI names
|
|
54
|
+
cli_names = ["claude", "claude-code"]
|
|
55
|
+
|
|
56
|
+
for cli_name in cli_names:
|
|
57
|
+
# Check if in PATH
|
|
58
|
+
cli_path = shutil.which(cli_name)
|
|
59
|
+
if cli_path:
|
|
60
|
+
return cli_path
|
|
61
|
+
|
|
62
|
+
# Platform-specific fallback locations
|
|
63
|
+
if IS_WINDOWS:
|
|
64
|
+
# Check common Windows install locations
|
|
65
|
+
possible_paths = [
|
|
66
|
+
Path(os.environ.get("LOCALAPPDATA", "")) / "Programs" / "claude" / "claude.exe",
|
|
67
|
+
Path(os.environ.get("PROGRAMFILES", "")) / "Claude" / "claude.exe",
|
|
68
|
+
Path.home() / ".claude" / "local" / "claude.exe",
|
|
69
|
+
Path.home() / "AppData" / "Local" / "Programs" / "claude" / "claude.exe",
|
|
70
|
+
]
|
|
71
|
+
else:
|
|
72
|
+
# macOS and Linux
|
|
73
|
+
possible_paths = [
|
|
74
|
+
Path.home() / ".claude" / "local" / "claude",
|
|
75
|
+
Path("/usr/local/bin/claude"),
|
|
76
|
+
Path("/opt/claude/bin/claude"),
|
|
77
|
+
Path.home() / ".local" / "bin" / "claude",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for path in possible_paths:
|
|
81
|
+
if path.exists() and path.is_file():
|
|
82
|
+
return str(path)
|
|
83
|
+
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_shell_quote(s: str) -> str:
|
|
88
|
+
"""Quote a string for shell use, cross-platform.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
s: String to quote.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Quoted string appropriate for current platform.
|
|
95
|
+
"""
|
|
96
|
+
if IS_WINDOWS:
|
|
97
|
+
# Windows: Use double quotes, escape internal quotes
|
|
98
|
+
if '"' in s:
|
|
99
|
+
s = s.replace('"', '""')
|
|
100
|
+
return f'"{s}"'
|
|
101
|
+
else:
|
|
102
|
+
# Unix: Use shlex.quote
|
|
103
|
+
import shlex
|
|
104
|
+
|
|
105
|
+
return shlex.quote(s)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def normalize_path(path: Path) -> Path:
|
|
109
|
+
"""Normalize path for cross-platform use.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
path: Path to normalize.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Normalized Path object.
|
|
116
|
+
"""
|
|
117
|
+
# Resolve to absolute path
|
|
118
|
+
path = path.resolve()
|
|
119
|
+
|
|
120
|
+
# On Windows, ensure we use the correct separators
|
|
121
|
+
if IS_WINDOWS:
|
|
122
|
+
return Path(str(path).replace("/", "\\"))
|
|
123
|
+
return path
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_config_dir() -> Path:
|
|
127
|
+
"""Get the configuration directory for storing data.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Path to configuration directory.
|
|
131
|
+
"""
|
|
132
|
+
if IS_WINDOWS:
|
|
133
|
+
base = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
|
|
134
|
+
return base / "devflow"
|
|
135
|
+
elif IS_MACOS:
|
|
136
|
+
return Path.home() / "Library" / "Application Support" / "devflow"
|
|
137
|
+
else:
|
|
138
|
+
# Linux/Unix: Use XDG
|
|
139
|
+
xdg_data = os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share")
|
|
140
|
+
return Path(xdg_data) / "devflow"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def get_cache_dir() -> Path:
|
|
144
|
+
"""Get the cache directory for temporary data.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Path to cache directory.
|
|
148
|
+
"""
|
|
149
|
+
if IS_WINDOWS:
|
|
150
|
+
base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
|
|
151
|
+
return base / "devflow" / "cache"
|
|
152
|
+
elif IS_MACOS:
|
|
153
|
+
return Path.home() / "Library" / "Caches" / "devflow"
|
|
154
|
+
else:
|
|
155
|
+
xdg_cache = os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache")
|
|
156
|
+
return Path(xdg_cache) / "devflow"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Import collaboration modules
|
|
160
|
+
from lib.agent_handoff import HandoffGenerator, create_handoff # noqa: E402
|
|
161
|
+
from lib.agent_router import AgentRouter, RoutingResult # noqa: E402
|
|
162
|
+
from lib.pair_programming import PairConfig, PairSession # noqa: E402
|
|
163
|
+
from lib.shared_memory import get_knowledge_graph, get_shared_memory, share_learning # noqa: E402
|
|
164
|
+
from lib.swarm_orchestrator import ConsensusType, SwarmConfig, SwarmOrchestrator # noqa: E402
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# Colors for terminal output (with Windows support)
|
|
168
|
+
class Colors:
|
|
169
|
+
"""ANSI color codes with Windows compatibility."""
|
|
170
|
+
|
|
171
|
+
# Enable ANSI colors on Windows
|
|
172
|
+
if IS_WINDOWS:
|
|
173
|
+
try:
|
|
174
|
+
import ctypes
|
|
175
|
+
|
|
176
|
+
kernel32 = ctypes.windll.kernel32
|
|
177
|
+
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
|
|
178
|
+
except Exception:
|
|
179
|
+
pass # Fall back to no colors
|
|
180
|
+
|
|
181
|
+
# Check if colors should be enabled
|
|
182
|
+
_use_colors = (
|
|
183
|
+
sys.stdout.isatty()
|
|
184
|
+
and os.environ.get("NO_COLOR") is None
|
|
185
|
+
and os.environ.get("TERM") != "dumb"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if _use_colors:
|
|
189
|
+
HEADER = "\033[95m"
|
|
190
|
+
BLUE = "\033[94m"
|
|
191
|
+
CYAN = "\033[96m"
|
|
192
|
+
GREEN = "\033[92m"
|
|
193
|
+
YELLOW = "\033[93m"
|
|
194
|
+
RED = "\033[91m"
|
|
195
|
+
BOLD = "\033[1m"
|
|
196
|
+
END = "\033[0m"
|
|
197
|
+
else:
|
|
198
|
+
HEADER = ""
|
|
199
|
+
BLUE = ""
|
|
200
|
+
CYAN = ""
|
|
201
|
+
GREEN = ""
|
|
202
|
+
YELLOW = ""
|
|
203
|
+
RED = ""
|
|
204
|
+
BOLD = ""
|
|
205
|
+
END = ""
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def print_banner():
|
|
209
|
+
"""Print the CLI banner."""
|
|
210
|
+
print(f"""
|
|
211
|
+
{Colors.CYAN}╔═══════════════════════════════════════════════════════════════╗
|
|
212
|
+
║ DEVFLOW COLLABORATIVE STORY RUNNER ║
|
|
213
|
+
╠═══════════════════════════════════════════════════════════════╣
|
|
214
|
+
║ Multi-agent collaboration with swarm, pair, and auto-routing ║
|
|
215
|
+
╚═══════════════════════════════════════════════════════════════╝{Colors.END}
|
|
216
|
+
""")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def print_routing_decision(result: RoutingResult, router: AgentRouter):
|
|
220
|
+
"""Print the routing decision."""
|
|
221
|
+
print(f"\n{Colors.BOLD}🎯 Routing Decision{Colors.END}")
|
|
222
|
+
print(router.explain_routing(result))
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def print_section(title: str, content: str = ""):
|
|
226
|
+
"""Print a section header."""
|
|
227
|
+
print(f"\n{Colors.BOLD}{Colors.BLUE}═══ {title} ═══{Colors.END}")
|
|
228
|
+
if content:
|
|
229
|
+
print(content)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def run_auto_mode(story_key: str, task: str, args: argparse.Namespace):
|
|
233
|
+
"""Run with automatic agent routing."""
|
|
234
|
+
print_section("Auto-Routing Mode")
|
|
235
|
+
|
|
236
|
+
router = AgentRouter()
|
|
237
|
+
result = router.route(task)
|
|
238
|
+
|
|
239
|
+
print_routing_decision(result, router)
|
|
240
|
+
|
|
241
|
+
# Decide execution mode based on routing
|
|
242
|
+
if result.workflow == "swarm":
|
|
243
|
+
print(f"\n{Colors.YELLOW}→ Using swarm mode for multi-agent collaboration{Colors.END}")
|
|
244
|
+
return run_swarm_mode(story_key, task, result.agents, args)
|
|
245
|
+
elif result.workflow == "pair":
|
|
246
|
+
print(f"\n{Colors.YELLOW}→ Using pair programming mode{Colors.END}")
|
|
247
|
+
return run_pair_mode(story_key, task, args)
|
|
248
|
+
else:
|
|
249
|
+
print(f"\n{Colors.YELLOW}→ Using sequential execution{Colors.END}")
|
|
250
|
+
return run_sequential_mode(story_key, task, result.agents, args)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def run_swarm_mode(story_key: str, task: str, agents: list[str], args: argparse.Namespace):
|
|
254
|
+
"""Run swarm mode with multi-agent debate."""
|
|
255
|
+
print_section("Swarm Mode", f"Agents: {', '.join(agents)}")
|
|
256
|
+
|
|
257
|
+
config = SwarmConfig(
|
|
258
|
+
max_iterations=args.max_iterations,
|
|
259
|
+
consensus_type=ConsensusType[args.consensus.upper()],
|
|
260
|
+
parallel_execution=args.parallel,
|
|
261
|
+
verbose=not args.quiet,
|
|
262
|
+
budget_limit_usd=args.budget,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
orchestrator = SwarmOrchestrator(story_key, config)
|
|
266
|
+
result = orchestrator.run_swarm(agents, task)
|
|
267
|
+
|
|
268
|
+
# Print result
|
|
269
|
+
print(f"\n{Colors.GREEN}{result.to_summary()}{Colors.END}")
|
|
270
|
+
|
|
271
|
+
# Save result
|
|
272
|
+
save_result(story_key, "swarm", result.to_dict())
|
|
273
|
+
|
|
274
|
+
return result
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def run_pair_mode(story_key: str, task: str, args: argparse.Namespace):
|
|
278
|
+
"""Run pair programming mode."""
|
|
279
|
+
print_section("Pair Programming Mode", "DEV + REVIEWER interleaved")
|
|
280
|
+
|
|
281
|
+
config = PairConfig(
|
|
282
|
+
max_revisions_per_chunk=args.max_revisions,
|
|
283
|
+
verbose=not args.quiet,
|
|
284
|
+
dev_model=args.model,
|
|
285
|
+
reviewer_model=args.model,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
session = PairSession(story_key, task, config)
|
|
289
|
+
result = session.run()
|
|
290
|
+
|
|
291
|
+
# Print result
|
|
292
|
+
print(f"\n{Colors.GREEN}{result.to_summary()}{Colors.END}")
|
|
293
|
+
|
|
294
|
+
# Save result
|
|
295
|
+
save_result(story_key, "pair", result.to_dict())
|
|
296
|
+
|
|
297
|
+
return result
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def run_sequential_mode(story_key: str, task: str, agents: list[str], args: argparse.Namespace):
|
|
301
|
+
"""Run sequential agent execution with handoffs."""
|
|
302
|
+
print_section("Sequential Mode", f"Pipeline: {' → '.join(agents)}")
|
|
303
|
+
|
|
304
|
+
get_shared_memory(story_key)
|
|
305
|
+
get_knowledge_graph(story_key)
|
|
306
|
+
handoff_gen = HandoffGenerator(story_key)
|
|
307
|
+
|
|
308
|
+
results = []
|
|
309
|
+
previous_output = ""
|
|
310
|
+
|
|
311
|
+
for i, agent in enumerate(agents):
|
|
312
|
+
print(f"\n{Colors.CYAN}▶ Running {agent}...{Colors.END}")
|
|
313
|
+
|
|
314
|
+
# Get context including handoffs
|
|
315
|
+
context = handoff_gen.generate_context_for_agent(agent)
|
|
316
|
+
|
|
317
|
+
# Build prompt
|
|
318
|
+
f"""You are the {agent} agent.
|
|
319
|
+
|
|
320
|
+
{context}
|
|
321
|
+
|
|
322
|
+
## Task
|
|
323
|
+
{task}
|
|
324
|
+
|
|
325
|
+
## Previous Work
|
|
326
|
+
{previous_output[:2000] if previous_output else "This is the first step."}
|
|
327
|
+
|
|
328
|
+
Complete your part of this task according to your role.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
# Invoke agent (simplified - in real use would call Claude CLI)
|
|
332
|
+
print(" → Generating response...")
|
|
333
|
+
|
|
334
|
+
# For demo, we'll note the handoff
|
|
335
|
+
if i > 0:
|
|
336
|
+
prev_agent = agents[i - 1]
|
|
337
|
+
handoff = create_handoff(
|
|
338
|
+
from_agent=prev_agent,
|
|
339
|
+
to_agent=agent,
|
|
340
|
+
story_key=story_key,
|
|
341
|
+
summary=f"Completed {prev_agent} phase, handing off to {agent}",
|
|
342
|
+
)
|
|
343
|
+
print(f" → Handoff from {prev_agent}: {handoff.id}")
|
|
344
|
+
|
|
345
|
+
# Record in shared memory
|
|
346
|
+
share_learning(
|
|
347
|
+
agent, f"Processed task: {task[:50]}...", story_key, tags=["sequential", agent.lower()]
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
results.append(
|
|
351
|
+
{"agent": agent, "status": "completed", "timestamp": datetime.now().isoformat()}
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
print(f"\n{Colors.GREEN}✅ Sequential pipeline complete!{Colors.END}")
|
|
355
|
+
|
|
356
|
+
# Save result
|
|
357
|
+
save_result(story_key, "sequential", {"agents": agents, "results": results})
|
|
358
|
+
|
|
359
|
+
return results
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def save_result(story_key: str, mode: str, result: dict):
|
|
363
|
+
"""Save result to file (cross-platform)."""
|
|
364
|
+
# Use cross-platform cache directory for results
|
|
365
|
+
results_dir = get_cache_dir() / "results"
|
|
366
|
+
results_dir.mkdir(parents=True, exist_ok=True)
|
|
367
|
+
|
|
368
|
+
# Sanitize story_key for filename (Windows compatibility)
|
|
369
|
+
safe_key = "".join(c if c.isalnum() or c in "-_" else "_" for c in story_key)
|
|
370
|
+
|
|
371
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
372
|
+
filename = f"{safe_key}_{mode}_{timestamp}.json"
|
|
373
|
+
|
|
374
|
+
filepath = normalize_path(results_dir / filename)
|
|
375
|
+
|
|
376
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
377
|
+
json.dump(
|
|
378
|
+
{
|
|
379
|
+
"story_key": story_key,
|
|
380
|
+
"mode": mode,
|
|
381
|
+
"timestamp": datetime.now().isoformat(),
|
|
382
|
+
"platform": platform.system(),
|
|
383
|
+
"result": result,
|
|
384
|
+
},
|
|
385
|
+
f,
|
|
386
|
+
indent=2,
|
|
387
|
+
default=str,
|
|
388
|
+
ensure_ascii=False,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
print(f"\n{Colors.CYAN}📁 Result saved to: {filepath}{Colors.END}")
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def show_memory(story_key: str):
|
|
395
|
+
"""Display shared memory and knowledge graph."""
|
|
396
|
+
print_section("Shared Memory & Knowledge Graph")
|
|
397
|
+
|
|
398
|
+
memory = get_shared_memory(story_key)
|
|
399
|
+
kg = get_knowledge_graph(story_key)
|
|
400
|
+
|
|
401
|
+
print(memory.to_context_string())
|
|
402
|
+
print()
|
|
403
|
+
print(kg.to_context_string())
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def query_knowledge(story_key: str, question: str):
|
|
407
|
+
"""Query the knowledge graph."""
|
|
408
|
+
print_section(f"Knowledge Query: {question}")
|
|
409
|
+
|
|
410
|
+
kg = get_knowledge_graph(story_key)
|
|
411
|
+
result = kg.query(question)
|
|
412
|
+
|
|
413
|
+
if result:
|
|
414
|
+
print(f"\n{Colors.GREEN}Answer:{Colors.END} {result['decision']}")
|
|
415
|
+
print(f"{Colors.CYAN}Source:{Colors.END} {result['agent']} ({result['timestamp'][:10]})")
|
|
416
|
+
print(f"{Colors.CYAN}Topic:{Colors.END} {result['topic']}")
|
|
417
|
+
else:
|
|
418
|
+
print(f"\n{Colors.YELLOW}No matching decision found.{Colors.END}")
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def parse_args():
|
|
422
|
+
"""Parse command line arguments."""
|
|
423
|
+
parser = argparse.ArgumentParser(
|
|
424
|
+
description="Collaborative Story Runner with multi-agent support",
|
|
425
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
426
|
+
epilog="""
|
|
427
|
+
Examples:
|
|
428
|
+
python run-collab.py 3-5 --auto
|
|
429
|
+
python run-collab.py 3-5 --swarm --agents ARCHITECT,DEV,REVIEWER
|
|
430
|
+
python run-collab.py 3-5 --pair --max-revisions 3
|
|
431
|
+
python run-collab.py "fix login bug" --auto
|
|
432
|
+
python run-collab.py 3-5 --memory # Show shared memory
|
|
433
|
+
python run-collab.py 3-5 --query "What did ARCHITECT decide about auth?"
|
|
434
|
+
""",
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
# Positional argument
|
|
438
|
+
parser.add_argument("story_key", nargs="?", default=None, help="Story key or task description")
|
|
439
|
+
|
|
440
|
+
# Mode selection
|
|
441
|
+
mode_group = parser.add_mutually_exclusive_group()
|
|
442
|
+
mode_group.add_argument(
|
|
443
|
+
"--auto", action="store_true", default=True, help="Auto-route to best agents (default)"
|
|
444
|
+
)
|
|
445
|
+
mode_group.add_argument("--swarm", action="store_true", help="Multi-agent swarm/debate mode")
|
|
446
|
+
mode_group.add_argument("--pair", action="store_true", help="DEV + REVIEWER pair programming")
|
|
447
|
+
mode_group.add_argument(
|
|
448
|
+
"--sequential", action="store_true", help="Traditional sequential pipeline"
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
# Utility modes
|
|
452
|
+
parser.add_argument(
|
|
453
|
+
"--memory", action="store_true", help="Show shared memory and knowledge graph"
|
|
454
|
+
)
|
|
455
|
+
parser.add_argument("--query", type=str, metavar="QUESTION", help="Query the knowledge graph")
|
|
456
|
+
parser.add_argument(
|
|
457
|
+
"--route-only", action="store_true", help="Just show routing decision, don't execute"
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# Agent selection
|
|
461
|
+
parser.add_argument(
|
|
462
|
+
"--agents", type=str, help="Comma-separated list of agents (for swarm/sequential)"
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
# Swarm options
|
|
466
|
+
parser.add_argument(
|
|
467
|
+
"--max-iterations", type=int, default=3, help="Maximum swarm iterations (default: 3)"
|
|
468
|
+
)
|
|
469
|
+
parser.add_argument(
|
|
470
|
+
"--consensus",
|
|
471
|
+
type=str,
|
|
472
|
+
default="reviewer_approval",
|
|
473
|
+
choices=["unanimous", "majority", "quorum", "reviewer_approval"],
|
|
474
|
+
help="Consensus type (default: reviewer_approval)",
|
|
475
|
+
)
|
|
476
|
+
parser.add_argument("--parallel", action="store_true", help="Enable parallel agent execution")
|
|
477
|
+
|
|
478
|
+
# Pair programming options
|
|
479
|
+
parser.add_argument(
|
|
480
|
+
"--max-revisions",
|
|
481
|
+
type=int,
|
|
482
|
+
default=3,
|
|
483
|
+
help="Max revisions per chunk in pair mode (default: 3)",
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
# General options
|
|
487
|
+
parser.add_argument(
|
|
488
|
+
"--model",
|
|
489
|
+
type=str,
|
|
490
|
+
default="opus",
|
|
491
|
+
choices=["opus", "sonnet", "haiku"],
|
|
492
|
+
help="Claude model to use (default: opus)",
|
|
493
|
+
)
|
|
494
|
+
parser.add_argument(
|
|
495
|
+
"--budget", type=float, default=20.0, help="Budget limit in USD (default: 20.0)"
|
|
496
|
+
)
|
|
497
|
+
parser.add_argument("--quiet", "-q", action="store_true", help="Reduce output verbosity")
|
|
498
|
+
parser.add_argument("--task", type=str, help="Override task description")
|
|
499
|
+
|
|
500
|
+
return parser.parse_args()
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def main():
|
|
504
|
+
args = parse_args()
|
|
505
|
+
|
|
506
|
+
# Handle utility modes first
|
|
507
|
+
if args.memory:
|
|
508
|
+
if not args.story_key:
|
|
509
|
+
print(f"{Colors.RED}Error: story_key required for --memory{Colors.END}")
|
|
510
|
+
return 1
|
|
511
|
+
show_memory(args.story_key)
|
|
512
|
+
return 0
|
|
513
|
+
|
|
514
|
+
if args.query:
|
|
515
|
+
if not args.story_key:
|
|
516
|
+
print(f"{Colors.RED}Error: story_key required for --query{Colors.END}")
|
|
517
|
+
return 1
|
|
518
|
+
query_knowledge(args.story_key, args.query)
|
|
519
|
+
return 0
|
|
520
|
+
|
|
521
|
+
# Validate story key
|
|
522
|
+
if not args.story_key:
|
|
523
|
+
print(f"{Colors.RED}Error: story_key or task description required{Colors.END}")
|
|
524
|
+
print("Use --help for usage information")
|
|
525
|
+
return 1
|
|
526
|
+
|
|
527
|
+
print_banner()
|
|
528
|
+
|
|
529
|
+
story_key = args.story_key
|
|
530
|
+
task = args.task or f"Implement story: {story_key}"
|
|
531
|
+
|
|
532
|
+
print(f"{Colors.BOLD}Story/Task:{Colors.END} {story_key}")
|
|
533
|
+
print(f"{Colors.BOLD}Mode:{Colors.END} ", end="")
|
|
534
|
+
|
|
535
|
+
# Route-only mode
|
|
536
|
+
if args.route_only:
|
|
537
|
+
print("Route Analysis Only")
|
|
538
|
+
router = AgentRouter()
|
|
539
|
+
result = router.route(task)
|
|
540
|
+
print_routing_decision(result, router)
|
|
541
|
+
return 0
|
|
542
|
+
|
|
543
|
+
# Parse and validate agents if provided
|
|
544
|
+
agents = None
|
|
545
|
+
if args.agents:
|
|
546
|
+
agents = [a.strip().upper() for a in args.agents.split(",")]
|
|
547
|
+
valid_agents = {
|
|
548
|
+
"SM",
|
|
549
|
+
"DEV",
|
|
550
|
+
"BA",
|
|
551
|
+
"ARCHITECT",
|
|
552
|
+
"PM",
|
|
553
|
+
"WRITER",
|
|
554
|
+
"MAINTAINER",
|
|
555
|
+
"REVIEWER",
|
|
556
|
+
"SECURITY",
|
|
557
|
+
}
|
|
558
|
+
invalid_agents = [a for a in agents if a not in valid_agents]
|
|
559
|
+
if invalid_agents:
|
|
560
|
+
print(
|
|
561
|
+
f"\n{Colors.RED}Error: Invalid agent name(s): {', '.join(invalid_agents)}{Colors.END}"
|
|
562
|
+
)
|
|
563
|
+
print(f"Valid agents are: {', '.join(sorted(valid_agents))}")
|
|
564
|
+
return 1
|
|
565
|
+
|
|
566
|
+
# Execute based on mode
|
|
567
|
+
try:
|
|
568
|
+
if args.swarm:
|
|
569
|
+
print("Swarm")
|
|
570
|
+
if not agents:
|
|
571
|
+
agents = ["ARCHITECT", "DEV", "REVIEWER"]
|
|
572
|
+
run_swarm_mode(story_key, task, agents, args)
|
|
573
|
+
|
|
574
|
+
elif args.pair:
|
|
575
|
+
print("Pair Programming")
|
|
576
|
+
run_pair_mode(story_key, task, args)
|
|
577
|
+
|
|
578
|
+
elif args.sequential:
|
|
579
|
+
print("Sequential")
|
|
580
|
+
if not agents:
|
|
581
|
+
agents = ["SM", "DEV", "REVIEWER"]
|
|
582
|
+
run_sequential_mode(story_key, task, agents, args)
|
|
583
|
+
|
|
584
|
+
else: # auto mode
|
|
585
|
+
print("Auto-Route")
|
|
586
|
+
run_auto_mode(story_key, task, args)
|
|
587
|
+
|
|
588
|
+
print(f"\n{Colors.GREEN}{'═' * 60}{Colors.END}")
|
|
589
|
+
print(f"{Colors.GREEN}✅ Collaboration complete!{Colors.END}")
|
|
590
|
+
return 0
|
|
591
|
+
|
|
592
|
+
except KeyboardInterrupt:
|
|
593
|
+
print(f"\n{Colors.YELLOW}⚠️ Interrupted by user{Colors.END}")
|
|
594
|
+
return 130
|
|
595
|
+
except Exception as e:
|
|
596
|
+
print(f"\n{Colors.RED}❌ Error: {e}{Colors.END}")
|
|
597
|
+
if not args.quiet:
|
|
598
|
+
import traceback
|
|
599
|
+
|
|
600
|
+
traceback.print_exc()
|
|
601
|
+
return 1
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
if __name__ == "__main__":
|
|
605
|
+
sys.exit(main())
|