@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.
Files changed (124) hide show
  1. package/CHANGELOG.md +526 -0
  2. package/LICENSE +21 -0
  3. package/README.md +620 -0
  4. package/bin/devflow-checkpoint.js +10 -0
  5. package/bin/devflow-collab.js +10 -0
  6. package/bin/devflow-cost.js +10 -0
  7. package/bin/devflow-create-persona.js +10 -0
  8. package/bin/devflow-init.js +10 -0
  9. package/bin/devflow-memory.js +10 -0
  10. package/bin/devflow-new-doc.js +10 -0
  11. package/bin/devflow-personalize.js +10 -0
  12. package/bin/devflow-setup-checkpoint.js +10 -0
  13. package/bin/devflow-story.js +10 -0
  14. package/bin/devflow-tech-debt.js +10 -0
  15. package/bin/devflow-validate-overrides.js +10 -0
  16. package/bin/devflow-validate.js +10 -0
  17. package/bin/devflow-version.js +10 -0
  18. package/lib/constants.js +30 -0
  19. package/lib/exec-python.js +78 -0
  20. package/lib/python-check.js +178 -0
  21. package/package.json +64 -0
  22. package/tooling/.automation/agents/architect.md +135 -0
  23. package/tooling/.automation/agents/ba.md +70 -0
  24. package/tooling/.automation/agents/dev.md +79 -0
  25. package/tooling/.automation/agents/maintainer.md +97 -0
  26. package/tooling/.automation/agents/pm.md +116 -0
  27. package/tooling/.automation/agents/reviewer.md +141 -0
  28. package/tooling/.automation/agents/sm.md +61 -0
  29. package/tooling/.automation/agents/writer.md +193 -0
  30. package/tooling/.automation/config.ps1.template +61 -0
  31. package/tooling/.automation/config.sh.template +48 -0
  32. package/tooling/.automation/memory/.gitkeep +6 -0
  33. package/tooling/.automation/memory/knowledge/kg_integration-test.json +94 -0
  34. package/tooling/.automation/memory/knowledge/kg_test-story.json +300 -0
  35. package/tooling/.automation/memory/shared/shared_integration-test.json +30 -0
  36. package/tooling/.automation/memory/shared/shared_test-story.json +78 -0
  37. package/tooling/.automation/overrides/templates/README.md +113 -0
  38. package/tooling/.automation/overrides/templates/architect/README.md +27 -0
  39. package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +92 -0
  40. package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +85 -0
  41. package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +88 -0
  42. package/tooling/.automation/overrides/templates/ba/README.md +27 -0
  43. package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +86 -0
  44. package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +91 -0
  45. package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +89 -0
  46. package/tooling/.automation/overrides/templates/dev/README.md +32 -0
  47. package/tooling/.automation/overrides/templates/dev/junior-mentored.yaml +39 -0
  48. package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +43 -0
  49. package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +52 -0
  50. package/tooling/.automation/overrides/templates/dev/security-focused.yaml +43 -0
  51. package/tooling/.automation/overrides/templates/dev/senior-fullstack.yaml +39 -0
  52. package/tooling/.automation/overrides/templates/maintainer/README.md +27 -0
  53. package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +113 -0
  54. package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +94 -0
  55. package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +94 -0
  56. package/tooling/.automation/overrides/templates/pm/README.md +27 -0
  57. package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +91 -0
  58. package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +87 -0
  59. package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +91 -0
  60. package/tooling/.automation/overrides/templates/reviewer/README.md +11 -0
  61. package/tooling/.automation/overrides/templates/reviewer/mentoring-reviewer.yaml +45 -0
  62. package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +50 -0
  63. package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +48 -0
  64. package/tooling/.automation/overrides/templates/sm/README.md +11 -0
  65. package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +52 -0
  66. package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +50 -0
  67. package/tooling/.automation/overrides/templates/sm/technical-lead.yaml +47 -0
  68. package/tooling/.automation/overrides/templates/user-profile.template.yaml +62 -0
  69. package/tooling/.automation/overrides/templates/writer/README.md +27 -0
  70. package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +99 -0
  71. package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +108 -0
  72. package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +100 -0
  73. package/tooling/completions/DevflowCompletion.ps1 +213 -0
  74. package/tooling/completions/_run-story +116 -0
  75. package/tooling/completions/run-story-completion.bash +136 -0
  76. package/tooling/docs/DOC-STANDARD.md +717 -0
  77. package/tooling/docs/sprint-status.yaml.template +24 -0
  78. package/tooling/docs/templates/bug-report.md +234 -0
  79. package/tooling/docs/templates/migration-spec.md +274 -0
  80. package/tooling/docs/templates/refactor-spec.md +86 -0
  81. package/tooling/docs/templates/tech-debt.md +86 -0
  82. package/tooling/scripts/context_checkpoint.py +556 -0
  83. package/tooling/scripts/cost_dashboard.py +617 -0
  84. package/tooling/scripts/create-persona.py +690 -0
  85. package/tooling/scripts/create-persona.sh +435 -0
  86. package/tooling/scripts/init-project-workflow.ps1 +651 -0
  87. package/tooling/scripts/init-project-workflow.py +70 -0
  88. package/tooling/scripts/init-project-workflow.sh +746 -0
  89. package/tooling/scripts/lib/__init__.py +35 -0
  90. package/tooling/scripts/lib/agent_handoff.py +526 -0
  91. package/tooling/scripts/lib/agent_router.py +698 -0
  92. package/tooling/scripts/lib/checkpoint-integration.ps1 +245 -0
  93. package/tooling/scripts/lib/checkpoint-integration.sh +191 -0
  94. package/tooling/scripts/lib/claude-cli.ps1 +952 -0
  95. package/tooling/scripts/lib/claude-cli.sh +1293 -0
  96. package/tooling/scripts/lib/cost_config.py +222 -0
  97. package/tooling/scripts/lib/cost_display.py +443 -0
  98. package/tooling/scripts/lib/cost_tracker.py +710 -0
  99. package/tooling/scripts/lib/currency_converter.py +328 -0
  100. package/tooling/scripts/lib/errors.py +438 -0
  101. package/tooling/scripts/lib/override-loader.sh +286 -0
  102. package/tooling/scripts/lib/pair_programming.py +589 -0
  103. package/tooling/scripts/lib/shared_memory.py +637 -0
  104. package/tooling/scripts/lib/swarm_orchestrator.py +689 -0
  105. package/tooling/scripts/memory_summarize.py +324 -0
  106. package/tooling/scripts/new-doc.ps1 +405 -0
  107. package/tooling/scripts/new-doc.py +93 -0
  108. package/tooling/scripts/new-doc.sh +534 -0
  109. package/tooling/scripts/personalize_agent.py +385 -0
  110. package/tooling/scripts/rollback-migration.sh +540 -0
  111. package/tooling/scripts/run-collab.ps1 +251 -0
  112. package/tooling/scripts/run-collab.py +605 -0
  113. package/tooling/scripts/run-collab.sh +110 -0
  114. package/tooling/scripts/run-story.ps1 +490 -0
  115. package/tooling/scripts/run-story.py +387 -0
  116. package/tooling/scripts/run-story.sh +467 -0
  117. package/tooling/scripts/setup-checkpoint-service.ps1 +219 -0
  118. package/tooling/scripts/setup-checkpoint-service.py +87 -0
  119. package/tooling/scripts/setup-checkpoint-service.sh +236 -0
  120. package/tooling/scripts/tech-debt-tracker.py +608 -0
  121. package/tooling/scripts/update_version.py +244 -0
  122. package/tooling/scripts/validate-overrides.py +511 -0
  123. package/tooling/scripts/validate-overrides.sh +432 -0
  124. 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())