@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,589 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Pair Programming Mode - DEV + REVIEWER Interleaved Collaboration
4
+
5
+ Real-time collaboration between DEV and REVIEWER where:
6
+ - DEV implements in small chunks
7
+ - REVIEWER provides immediate feedback
8
+ - DEV addresses feedback before continuing
9
+ - Results in higher quality, fewer iterations
10
+
11
+ Features:
12
+ - Chunk-based development
13
+ - Real-time feedback loops
14
+ - Incremental refinement
15
+ - Shared context maintenance
16
+ - Automatic issue tracking
17
+
18
+ Usage:
19
+ from lib.pair_programming import PairSession, start_pair_session
20
+
21
+ session = start_pair_session(story_key="3-5", task="Implement user login")
22
+ result = session.run()
23
+ """
24
+
25
+ import re
26
+ import subprocess
27
+ import sys
28
+ from dataclasses import dataclass, field
29
+ from datetime import datetime
30
+ from enum import Enum
31
+ from pathlib import Path
32
+ from typing import Optional
33
+
34
+ # Import dependencies
35
+ try:
36
+ from shared_memory import get_knowledge_graph, get_shared_memory
37
+ except ImportError:
38
+ from lib.shared_memory import get_knowledge_graph, get_shared_memory
39
+
40
+
41
+ PROJECT_ROOT = Path(__file__).parent.parent.parent.parent
42
+ CLAUDE_CLI = "claude.cmd" if sys.platform == "win32" else "claude"
43
+
44
+
45
+ class ChunkType(Enum):
46
+ """Types of code chunks."""
47
+
48
+ DESIGN = "design" # Architecture/design decision
49
+ IMPLEMENTATION = "implementation" # Core code
50
+ TEST = "test" # Test code
51
+ REFACTOR = "refactor" # Refactoring
52
+ FIX = "fix" # Bug fix
53
+ DOCUMENTATION = "documentation" # Docs/comments
54
+
55
+
56
+ class FeedbackType(Enum):
57
+ """Types of reviewer feedback."""
58
+
59
+ APPROVE = "approve" # Good to proceed
60
+ MINOR = "minor" # Minor issues, can proceed
61
+ MAJOR = "major" # Major issues, must fix
62
+ BLOCKING = "blocking" # Cannot proceed until fixed
63
+ QUESTION = "question" # Needs clarification
64
+
65
+
66
+ @dataclass
67
+ class CodeChunk:
68
+ """A chunk of code being developed."""
69
+
70
+ chunk_id: str
71
+ chunk_type: ChunkType
72
+ description: str
73
+ content: str
74
+ file_path: Optional[str] = None
75
+ line_range: Optional[tuple[int, int]] = None
76
+ timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
77
+
78
+ def to_dict(self) -> dict:
79
+ return {
80
+ "chunk_id": self.chunk_id,
81
+ "chunk_type": self.chunk_type.value,
82
+ "description": self.description,
83
+ "content": self.content[:500] + "..." if len(self.content) > 500 else self.content,
84
+ "file_path": self.file_path,
85
+ "line_range": self.line_range,
86
+ "timestamp": self.timestamp,
87
+ }
88
+
89
+
90
+ @dataclass
91
+ class ReviewFeedback:
92
+ """Feedback from reviewer on a chunk."""
93
+
94
+ chunk_id: str
95
+ feedback_type: FeedbackType
96
+ comments: list[str]
97
+ suggestions: list[str] = field(default_factory=list)
98
+ must_fix: list[str] = field(default_factory=list)
99
+ nice_to_have: list[str] = field(default_factory=list)
100
+ approved: bool = False
101
+ timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
102
+
103
+ def to_dict(self) -> dict:
104
+ return {
105
+ "chunk_id": self.chunk_id,
106
+ "feedback_type": self.feedback_type.value,
107
+ "comments": self.comments,
108
+ "suggestions": self.suggestions,
109
+ "must_fix": self.must_fix,
110
+ "nice_to_have": self.nice_to_have,
111
+ "approved": self.approved,
112
+ "timestamp": self.timestamp,
113
+ }
114
+
115
+ def has_blocking_issues(self) -> bool:
116
+ return self.feedback_type in [FeedbackType.MAJOR, FeedbackType.BLOCKING]
117
+
118
+
119
+ @dataclass
120
+ class PairExchange:
121
+ """A single exchange in the pair programming session."""
122
+
123
+ exchange_id: int
124
+ chunk: CodeChunk
125
+ feedback: Optional[ReviewFeedback] = None
126
+ revision: Optional[CodeChunk] = None
127
+ resolved: bool = False
128
+
129
+ def to_dict(self) -> dict:
130
+ return {
131
+ "exchange_id": self.exchange_id,
132
+ "chunk": self.chunk.to_dict(),
133
+ "feedback": self.feedback.to_dict() if self.feedback else None,
134
+ "revision": self.revision.to_dict() if self.revision else None,
135
+ "resolved": self.resolved,
136
+ }
137
+
138
+
139
+ @dataclass
140
+ class PairSessionResult:
141
+ """Result of a pair programming session."""
142
+
143
+ story_key: str
144
+ task: str
145
+ exchanges: list[PairExchange]
146
+ final_code: str
147
+ files_created: list[str]
148
+ files_modified: list[str]
149
+ total_chunks: int
150
+ total_revisions: int
151
+ approval_rate: float
152
+ start_time: str
153
+ end_time: str
154
+
155
+ def to_dict(self) -> dict:
156
+ return {
157
+ "story_key": self.story_key,
158
+ "task": self.task,
159
+ "exchanges": [e.to_dict() for e in self.exchanges],
160
+ "final_code": self.final_code[:1000]
161
+ if len(self.final_code) > 1000
162
+ else self.final_code,
163
+ "files_created": self.files_created,
164
+ "files_modified": self.files_modified,
165
+ "total_chunks": self.total_chunks,
166
+ "total_revisions": self.total_revisions,
167
+ "approval_rate": self.approval_rate,
168
+ "start_time": self.start_time,
169
+ "end_time": self.end_time,
170
+ }
171
+
172
+ def to_summary(self) -> str:
173
+ """Generate human-readable summary."""
174
+ lines = [
175
+ f"## Pair Programming Result: {self.story_key}",
176
+ "",
177
+ f"**Task**: {self.task}",
178
+ f"**Exchanges**: {len(self.exchanges)}",
179
+ f"**Chunks**: {self.total_chunks}",
180
+ f"**Revisions**: {self.total_revisions}",
181
+ f"**Approval Rate**: {self.approval_rate:.0%}",
182
+ "",
183
+ "### Files Created",
184
+ ]
185
+
186
+ for f in self.files_created:
187
+ lines.append(f"- `{f}`")
188
+
189
+ if self.files_modified:
190
+ lines.append("")
191
+ lines.append("### Files Modified")
192
+ for f in self.files_modified:
193
+ lines.append(f"- `{f}`")
194
+
195
+ return "\n".join(lines)
196
+
197
+
198
+ @dataclass
199
+ class PairConfig:
200
+ """Configuration for pair programming session."""
201
+
202
+ max_revisions_per_chunk: int = 3
203
+ timeout_seconds: int = 180
204
+ verbose: bool = True
205
+ auto_apply_fixes: bool = True
206
+ chunk_size_hint: str = "medium" # small, medium, large
207
+ reviewer_model: str = "opus"
208
+ dev_model: str = "opus"
209
+
210
+ def to_dict(self) -> dict:
211
+ return {
212
+ "max_revisions_per_chunk": self.max_revisions_per_chunk,
213
+ "timeout_seconds": self.timeout_seconds,
214
+ "auto_apply_fixes": self.auto_apply_fixes,
215
+ "chunk_size_hint": self.chunk_size_hint,
216
+ "reviewer_model": self.reviewer_model,
217
+ "dev_model": self.dev_model,
218
+ }
219
+
220
+
221
+ class PairSession:
222
+ """A pair programming session between DEV and REVIEWER."""
223
+
224
+ def __init__(self, story_key: str, task: str, config: Optional[PairConfig] = None):
225
+ self.story_key = story_key
226
+ self.task = task
227
+ self.config = config or PairConfig()
228
+ self.project_root = PROJECT_ROOT
229
+ self.shared_memory = get_shared_memory(story_key)
230
+ self.knowledge_graph = get_knowledge_graph(story_key)
231
+
232
+ self.exchanges: list[PairExchange] = []
233
+ self.files_created: list[str] = []
234
+ self.files_modified: list[str] = []
235
+ self.chunk_counter = 0
236
+ self.exchange_counter = 0
237
+
238
+ def _log(self, message: str, agent: str = "SYSTEM"):
239
+ """Log a message."""
240
+ if self.config.verbose:
241
+ timestamp = datetime.now().strftime("%H:%M:%S")
242
+ emoji = {"DEV": "💻", "REVIEWER": "👀", "SYSTEM": "⚙️"}.get(agent, "•")
243
+ print(f"[{timestamp}] {emoji} [{agent}] {message}")
244
+
245
+ def _invoke_agent(self, agent: str, prompt: str) -> str:
246
+ """Invoke an agent with Claude CLI."""
247
+ model = self.config.dev_model if agent == "DEV" else self.config.reviewer_model
248
+
249
+ try:
250
+ result = subprocess.run(
251
+ [CLAUDE_CLI, "--print", "--model", model, "-p", prompt],
252
+ capture_output=True,
253
+ text=True,
254
+ timeout=self.config.timeout_seconds,
255
+ cwd=str(self.project_root),
256
+ )
257
+ return result.stdout + result.stderr
258
+ except subprocess.TimeoutExpired:
259
+ return "[TIMEOUT: Agent did not respond in time]"
260
+ except Exception as e:
261
+ return f"[ERROR: {str(e)}]"
262
+
263
+ def _generate_chunk_id(self) -> str:
264
+ """Generate unique chunk ID."""
265
+ self.chunk_counter += 1
266
+ return f"chunk_{self.chunk_counter:03d}"
267
+
268
+ def _parse_dev_output(self, output: str) -> CodeChunk:
269
+ """Parse DEV output into a CodeChunk."""
270
+ # Try to extract file path
271
+ file_match = re.search(r'(?:file|path):\s*[`"]?([^\s`"]+)[`"]?', output, re.IGNORECASE)
272
+ file_path = file_match.group(1) if file_match else None
273
+
274
+ # Try to extract code blocks
275
+ code_blocks = re.findall(r"```[\w]*\n(.*?)```", output, re.DOTALL)
276
+ content = "\n\n".join(code_blocks) if code_blocks else output
277
+
278
+ # Determine chunk type
279
+ chunk_type = ChunkType.IMPLEMENTATION
280
+ output_lower = output.lower()
281
+ if "test" in output_lower:
282
+ chunk_type = ChunkType.TEST
283
+ elif "refactor" in output_lower:
284
+ chunk_type = ChunkType.REFACTOR
285
+ elif "design" in output_lower or "architecture" in output_lower:
286
+ chunk_type = ChunkType.DESIGN
287
+ elif "fix" in output_lower or "bug" in output_lower:
288
+ chunk_type = ChunkType.FIX
289
+
290
+ # Extract description
291
+ desc_match = re.search(r"^#+\s*(.+)$", output, re.MULTILINE)
292
+ description = desc_match.group(1) if desc_match else "Code implementation"
293
+
294
+ return CodeChunk(
295
+ chunk_id=self._generate_chunk_id(),
296
+ chunk_type=chunk_type,
297
+ description=description[:100],
298
+ content=content,
299
+ file_path=file_path,
300
+ )
301
+
302
+ def _parse_reviewer_output(self, output: str, chunk_id: str) -> ReviewFeedback:
303
+ """Parse REVIEWER output into ReviewFeedback."""
304
+ output_lower = output.lower()
305
+
306
+ # Determine feedback type
307
+ if any(word in output_lower for word in ["blocking", "cannot proceed", "critical"]):
308
+ feedback_type = FeedbackType.BLOCKING
309
+ elif any(word in output_lower for word in ["major", "significant", "must fix"]):
310
+ feedback_type = FeedbackType.MAJOR
311
+ elif any(word in output_lower for word in ["minor", "small", "nitpick"]):
312
+ feedback_type = FeedbackType.MINOR
313
+ elif any(word in output_lower for word in ["question", "clarify", "unclear"]):
314
+ feedback_type = FeedbackType.QUESTION
315
+ else:
316
+ feedback_type = FeedbackType.APPROVE
317
+
318
+ # Extract comments
319
+ comments = []
320
+ comment_patterns = [
321
+ r"[-•]\s*(.+)",
322
+ r"\d+\.\s*(.+)",
323
+ ]
324
+ for pattern in comment_patterns:
325
+ matches = re.findall(pattern, output)
326
+ comments.extend(matches[:10])
327
+
328
+ # Extract must-fix issues
329
+ must_fix = []
330
+ must_fix_section = re.search(
331
+ r"(?:must fix|blocking|required).*?:(.*?)(?:\n\n|\Z)", output, re.IGNORECASE | re.DOTALL
332
+ )
333
+ if must_fix_section:
334
+ issues = re.findall(r"[-•]\s*(.+)", must_fix_section.group(1))
335
+ must_fix.extend(issues)
336
+
337
+ # Extract suggestions
338
+ suggestions = []
339
+ suggestion_section = re.search(
340
+ r"(?:suggest|consider|recommend).*?:(.*?)(?:\n\n|\Z)", output, re.IGNORECASE | re.DOTALL
341
+ )
342
+ if suggestion_section:
343
+ sugs = re.findall(r"[-•]\s*(.+)", suggestion_section.group(1))
344
+ suggestions.extend(sugs)
345
+
346
+ # Check for approval
347
+ approved = feedback_type == FeedbackType.APPROVE or any(
348
+ word in output_lower for word in ["lgtm", "approved", "looks good", "ship it"]
349
+ )
350
+
351
+ return ReviewFeedback(
352
+ chunk_id=chunk_id,
353
+ feedback_type=feedback_type,
354
+ comments=comments[:10],
355
+ suggestions=suggestions[:5],
356
+ must_fix=must_fix[:5],
357
+ approved=approved,
358
+ )
359
+
360
+ def _build_dev_prompt(
361
+ self, task_part: str, context: str, previous_feedback: Optional[ReviewFeedback] = None
362
+ ) -> str:
363
+ """Build prompt for DEV agent."""
364
+
365
+ base_prompt = f"""You are in a PAIR PROGRAMMING session with a REVIEWER.
366
+ Work in small, focused chunks. After each chunk, wait for reviewer feedback.
367
+
368
+ ## Task
369
+ {task_part}
370
+
371
+ ## Context
372
+ {context}
373
+ """
374
+
375
+ if previous_feedback:
376
+ feedback_text = "\n".join(
377
+ [
378
+ "## Reviewer Feedback (address these)",
379
+ "",
380
+ "**Issues to Fix:**",
381
+ *[f"- ❌ {issue}" for issue in previous_feedback.must_fix],
382
+ "",
383
+ "**Suggestions:**",
384
+ *[f"- 💡 {sug}" for sug in previous_feedback.suggestions],
385
+ ]
386
+ )
387
+ base_prompt += f"\n\n{feedback_text}\n"
388
+
389
+ base_prompt += """
390
+ ## Instructions
391
+ 1. Implement ONE focused chunk of code
392
+ 2. Show the file path and code clearly
393
+ 3. Explain your approach briefly
394
+ 4. Keep the chunk small enough for quick review
395
+ """
396
+
397
+ return base_prompt
398
+
399
+ def _build_reviewer_prompt(self, chunk: CodeChunk, accumulated_code: str) -> str:
400
+ """Build prompt for REVIEWER agent."""
401
+
402
+ return f"""You are in a PAIR PROGRAMMING session reviewing DEV's work in real-time.
403
+
404
+ ## Current Chunk to Review
405
+ **Type**: {chunk.chunk_type.value}
406
+ **Description**: {chunk.description}
407
+ **File**: {chunk.file_path or "Not specified"}
408
+
409
+ ```
410
+ {chunk.content}
411
+ ```
412
+
413
+ ## Accumulated Code So Far
414
+ ```
415
+ {accumulated_code[-2000:] if len(accumulated_code) > 2000 else accumulated_code}
416
+ ```
417
+
418
+ ## Instructions
419
+ 1. Review this chunk for:
420
+ - Correctness
421
+ - Code quality
422
+ - Best practices
423
+ - Potential bugs
424
+ - Test coverage needs
425
+
426
+ 2. Categorize issues as:
427
+ - **BLOCKING**: Cannot proceed
428
+ - **MUST FIX**: Required before merge
429
+ - **SUGGESTION**: Nice to have
430
+
431
+ 3. If the code is good, say "LGTM" or "Approved"
432
+
433
+ 4. Be constructive and specific
434
+ """
435
+
436
+ def run(self) -> PairSessionResult:
437
+ """Run the pair programming session."""
438
+ start_time = datetime.now().isoformat()
439
+
440
+ self._log(f"Starting pair session for: {self.task[:50]}...")
441
+
442
+ # Break task into parts (for now, treat as single task)
443
+ task_parts = [self.task]
444
+
445
+ accumulated_code = ""
446
+ total_revisions = 0
447
+ approved_chunks = 0
448
+
449
+ for i, task_part in enumerate(task_parts):
450
+ self._log(f"Working on part {i + 1}/{len(task_parts)}")
451
+
452
+ # Get initial context
453
+ context = f"Story: {self.story_key}\n"
454
+ context += self.shared_memory.to_context_string(5)
455
+
456
+ # DEV creates initial chunk
457
+ self._log("Creating initial implementation...", "DEV")
458
+ dev_prompt = self._build_dev_prompt(task_part, context)
459
+ dev_output = self._invoke_agent("DEV", dev_prompt)
460
+
461
+ chunk = self._parse_dev_output(dev_output)
462
+ self._log(f"Created chunk: {chunk.description}", "DEV")
463
+
464
+ # Track file
465
+ if chunk.file_path:
466
+ if (
467
+ chunk.file_path not in self.files_created
468
+ and chunk.file_path not in self.files_modified
469
+ ):
470
+ self.files_created.append(chunk.file_path)
471
+
472
+ accumulated_code += f"\n\n// {chunk.description}\n{chunk.content}"
473
+
474
+ # REVIEWER reviews
475
+ self._log("Reviewing chunk...", "REVIEWER")
476
+ reviewer_prompt = self._build_reviewer_prompt(chunk, accumulated_code)
477
+ reviewer_output = self._invoke_agent("REVIEWER", reviewer_prompt)
478
+
479
+ feedback = self._parse_reviewer_output(reviewer_output, chunk.chunk_id)
480
+ self._log(f"Feedback: {feedback.feedback_type.value}", "REVIEWER")
481
+
482
+ exchange = PairExchange(
483
+ exchange_id=self.exchange_counter,
484
+ chunk=chunk,
485
+ feedback=feedback,
486
+ resolved=feedback.approved,
487
+ )
488
+ self.exchange_counter += 1
489
+
490
+ # Revision loop if needed
491
+ revision_count = 0
492
+ while (
493
+ feedback.has_blocking_issues()
494
+ and revision_count < self.config.max_revisions_per_chunk
495
+ ):
496
+ revision_count += 1
497
+ total_revisions += 1
498
+ self._log(f"Revision {revision_count} needed", "SYSTEM")
499
+
500
+ # DEV revises
501
+ self._log("Addressing feedback...", "DEV")
502
+ dev_prompt = self._build_dev_prompt(task_part, context, feedback)
503
+ dev_output = self._invoke_agent("DEV", dev_prompt)
504
+
505
+ revised_chunk = self._parse_dev_output(dev_output)
506
+ exchange.revision = revised_chunk
507
+
508
+ # Update accumulated code
509
+ accumulated_code += (
510
+ f"\n\n// Revision: {revised_chunk.description}\n{revised_chunk.content}"
511
+ )
512
+
513
+ # REVIEWER re-reviews
514
+ self._log("Re-reviewing...", "REVIEWER")
515
+ reviewer_prompt = self._build_reviewer_prompt(revised_chunk, accumulated_code)
516
+ reviewer_output = self._invoke_agent("REVIEWER", reviewer_prompt)
517
+
518
+ feedback = self._parse_reviewer_output(reviewer_output, revised_chunk.chunk_id)
519
+ exchange.feedback = feedback
520
+ exchange.resolved = feedback.approved
521
+
522
+ self._log(f"Feedback: {feedback.feedback_type.value}", "REVIEWER")
523
+
524
+ if exchange.resolved or feedback.approved:
525
+ approved_chunks += 1
526
+ self._log("✅ Chunk approved!", "SYSTEM")
527
+ else:
528
+ self._log("⚠️ Moving on with unresolved issues", "SYSTEM")
529
+
530
+ self.exchanges.append(exchange)
531
+
532
+ # Record in shared memory
533
+ self.shared_memory.add(
534
+ agent="PAIR",
535
+ content=f"Completed chunk: {chunk.description} ({feedback.feedback_type.value})",
536
+ tags=["pair-programming", "chunk"],
537
+ )
538
+
539
+ # Calculate approval rate
540
+ total_chunks = len(self.exchanges)
541
+ approval_rate = approved_chunks / total_chunks if total_chunks > 0 else 0
542
+
543
+ self._log(f"Session complete. Approval rate: {approval_rate:.0%}", "SYSTEM")
544
+
545
+ return PairSessionResult(
546
+ story_key=self.story_key,
547
+ task=self.task,
548
+ exchanges=self.exchanges,
549
+ final_code=accumulated_code,
550
+ files_created=self.files_created,
551
+ files_modified=self.files_modified,
552
+ total_chunks=total_chunks,
553
+ total_revisions=total_revisions,
554
+ approval_rate=approval_rate,
555
+ start_time=start_time,
556
+ end_time=datetime.now().isoformat(),
557
+ )
558
+
559
+
560
+ # Convenience functions
561
+ def start_pair_session(story_key: str, task: str, **config_kwargs) -> PairSession:
562
+ """Start a new pair programming session."""
563
+ config = PairConfig(**config_kwargs)
564
+ return PairSession(story_key, task, config)
565
+
566
+
567
+ def run_pair_session(story_key: str, task: str, **config_kwargs) -> PairSessionResult:
568
+ """Run a complete pair programming session."""
569
+ session = start_pair_session(story_key, task, **config_kwargs)
570
+ return session.run()
571
+
572
+
573
+ if __name__ == "__main__":
574
+ print("=== Pair Programming Mode Demo ===\n")
575
+ print("This module enables real-time DEV + REVIEWER collaboration.")
576
+ print("\nExample usage:")
577
+ print("""
578
+ from lib.pair_programming import run_pair_session
579
+
580
+ result = run_pair_session(
581
+ story_key="3-5",
582
+ task="Implement user authentication endpoint",
583
+ max_revisions_per_chunk=2,
584
+ verbose=True
585
+ )
586
+
587
+ print(result.to_summary())
588
+ print(f"Approval rate: {result.approval_rate:.0%}")
589
+ """)