@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,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Devflow Library - Core modules for the Devflow automation system.
|
|
3
|
+
|
|
4
|
+
This package provides:
|
|
5
|
+
- cost_tracker: Token usage and cost tracking
|
|
6
|
+
- cost_display: Terminal-based cost monitoring display
|
|
7
|
+
- cost_config: Configuration management for costs
|
|
8
|
+
- currency_converter: Multi-currency support
|
|
9
|
+
- errors: Enhanced error handling
|
|
10
|
+
- agent_router: Dynamic agent selection
|
|
11
|
+
- agent_handoff: Structured agent transitions
|
|
12
|
+
- shared_memory: Cross-agent knowledge sharing
|
|
13
|
+
- swarm_orchestrator: Multi-agent collaboration
|
|
14
|
+
- pair_programming: DEV + REVIEWER collaboration
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
from lib.cost_tracker import CostTracker
|
|
18
|
+
from lib.agent_router import AgentRouter
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
__version__ = "1.9.0"
|
|
22
|
+
|
|
23
|
+
# Lazy imports to avoid circular dependencies
|
|
24
|
+
__all__ = [
|
|
25
|
+
"cost_tracker",
|
|
26
|
+
"cost_display",
|
|
27
|
+
"cost_config",
|
|
28
|
+
"currency_converter",
|
|
29
|
+
"errors",
|
|
30
|
+
"agent_router",
|
|
31
|
+
"agent_handoff",
|
|
32
|
+
"shared_memory",
|
|
33
|
+
"swarm_orchestrator",
|
|
34
|
+
"pair_programming",
|
|
35
|
+
]
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Agent Handoff System - Structured Handoffs Between Agents
|
|
4
|
+
|
|
5
|
+
Creates explicit handoff summaries when transitioning work between agents.
|
|
6
|
+
Ensures context is preserved and the next agent has all necessary information.
|
|
7
|
+
|
|
8
|
+
Features:
|
|
9
|
+
- Automatic context extraction
|
|
10
|
+
- Git diff analysis for file changes
|
|
11
|
+
- Decision tracking
|
|
12
|
+
- Warning/blocker identification
|
|
13
|
+
- Structured handoff documents
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
from lib.agent_handoff import HandoffGenerator, create_handoff
|
|
17
|
+
|
|
18
|
+
generator = HandoffGenerator(story_key="3-5")
|
|
19
|
+
handoff = generator.generate(
|
|
20
|
+
from_agent="SM",
|
|
21
|
+
to_agent="DEV",
|
|
22
|
+
work_summary="Created story context with all acceptance criteria"
|
|
23
|
+
)
|
|
24
|
+
print(handoff.to_markdown())
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import re
|
|
28
|
+
import subprocess
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from datetime import datetime
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Any, Optional
|
|
33
|
+
|
|
34
|
+
# Import shared memory for integration
|
|
35
|
+
try:
|
|
36
|
+
from shared_memory import (
|
|
37
|
+
HandoffSummary,
|
|
38
|
+
get_knowledge_graph,
|
|
39
|
+
get_shared_memory,
|
|
40
|
+
)
|
|
41
|
+
except ImportError:
|
|
42
|
+
from lib.shared_memory import (
|
|
43
|
+
HandoffSummary,
|
|
44
|
+
get_knowledge_graph,
|
|
45
|
+
get_shared_memory,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class FileChange:
|
|
54
|
+
"""Represents a changed file."""
|
|
55
|
+
|
|
56
|
+
path: str
|
|
57
|
+
status: str # added, modified, deleted
|
|
58
|
+
additions: int = 0
|
|
59
|
+
deletions: int = 0
|
|
60
|
+
|
|
61
|
+
def to_dict(self) -> dict:
|
|
62
|
+
return {
|
|
63
|
+
"path": self.path,
|
|
64
|
+
"status": self.status,
|
|
65
|
+
"additions": self.additions,
|
|
66
|
+
"deletions": self.deletions,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class AgentWorkSummary:
|
|
72
|
+
"""Summary of work done by an agent."""
|
|
73
|
+
|
|
74
|
+
agent: str
|
|
75
|
+
story_key: str
|
|
76
|
+
start_time: str
|
|
77
|
+
end_time: str
|
|
78
|
+
description: str
|
|
79
|
+
files_changed: list[FileChange] = field(default_factory=list)
|
|
80
|
+
decisions_made: list[str] = field(default_factory=list)
|
|
81
|
+
blockers_encountered: list[str] = field(default_factory=list)
|
|
82
|
+
blockers_resolved: list[str] = field(default_factory=list)
|
|
83
|
+
warnings: list[str] = field(default_factory=list)
|
|
84
|
+
questions_for_next: list[str] = field(default_factory=list)
|
|
85
|
+
artifacts_created: list[str] = field(default_factory=list)
|
|
86
|
+
|
|
87
|
+
def to_dict(self) -> dict:
|
|
88
|
+
return {
|
|
89
|
+
"agent": self.agent,
|
|
90
|
+
"story_key": self.story_key,
|
|
91
|
+
"start_time": self.start_time,
|
|
92
|
+
"end_time": self.end_time,
|
|
93
|
+
"description": self.description,
|
|
94
|
+
"files_changed": [f.to_dict() for f in self.files_changed],
|
|
95
|
+
"decisions_made": self.decisions_made,
|
|
96
|
+
"blockers_encountered": self.blockers_encountered,
|
|
97
|
+
"blockers_resolved": self.blockers_resolved,
|
|
98
|
+
"warnings": self.warnings,
|
|
99
|
+
"questions_for_next": self.questions_for_next,
|
|
100
|
+
"artifacts_created": self.artifacts_created,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# Standard handoff templates for each agent transition
|
|
105
|
+
HANDOFF_TEMPLATES = {
|
|
106
|
+
("SM", "DEV"): {
|
|
107
|
+
"focus_areas": [
|
|
108
|
+
"Story acceptance criteria",
|
|
109
|
+
"Technical context and constraints",
|
|
110
|
+
"Related files and patterns to follow",
|
|
111
|
+
"Known blockers or dependencies",
|
|
112
|
+
],
|
|
113
|
+
"expected_questions": [
|
|
114
|
+
"What patterns should I follow?",
|
|
115
|
+
"Are there existing similar implementations?",
|
|
116
|
+
"What tests are expected?",
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
("SM", "ARCHITECT"): {
|
|
120
|
+
"focus_areas": [
|
|
121
|
+
"High-level requirements",
|
|
122
|
+
"System constraints",
|
|
123
|
+
"Integration points",
|
|
124
|
+
"Scale requirements",
|
|
125
|
+
],
|
|
126
|
+
"expected_questions": [
|
|
127
|
+
"What are the non-functional requirements?",
|
|
128
|
+
"What existing systems need integration?",
|
|
129
|
+
"What are the performance targets?",
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
("ARCHITECT", "DEV"): {
|
|
133
|
+
"focus_areas": [
|
|
134
|
+
"Architecture decisions",
|
|
135
|
+
"Design patterns to use",
|
|
136
|
+
"Component structure",
|
|
137
|
+
"Interface definitions",
|
|
138
|
+
],
|
|
139
|
+
"expected_questions": [
|
|
140
|
+
"What patterns should I implement?",
|
|
141
|
+
"How should components communicate?",
|
|
142
|
+
"What are the boundaries?",
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
("DEV", "REVIEWER"): {
|
|
146
|
+
"focus_areas": [
|
|
147
|
+
"Implementation approach",
|
|
148
|
+
"Key decisions made",
|
|
149
|
+
"Areas of uncertainty",
|
|
150
|
+
"Test coverage",
|
|
151
|
+
],
|
|
152
|
+
"expected_questions": [
|
|
153
|
+
"Why was this approach chosen?",
|
|
154
|
+
"What alternatives were considered?",
|
|
155
|
+
"What edge cases were handled?",
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
("REVIEWER", "DEV"): {
|
|
159
|
+
"focus_areas": [
|
|
160
|
+
"Issues found",
|
|
161
|
+
"Required changes",
|
|
162
|
+
"Suggestions (optional)",
|
|
163
|
+
"Approval status",
|
|
164
|
+
],
|
|
165
|
+
"expected_questions": [
|
|
166
|
+
"Which issues are blocking?",
|
|
167
|
+
"Are there acceptable alternatives?",
|
|
168
|
+
"What's the priority order?",
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
("BA", "DEV"): {
|
|
172
|
+
"focus_areas": [
|
|
173
|
+
"Refined requirements",
|
|
174
|
+
"Acceptance criteria",
|
|
175
|
+
"User stories",
|
|
176
|
+
"Edge cases",
|
|
177
|
+
],
|
|
178
|
+
"expected_questions": [
|
|
179
|
+
"What are the exact acceptance criteria?",
|
|
180
|
+
"What user flows need support?",
|
|
181
|
+
"What error scenarios exist?",
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class HandoffGenerator:
|
|
188
|
+
"""Generates structured handoffs between agents."""
|
|
189
|
+
|
|
190
|
+
def __init__(self, story_key: str, project_root: Optional[Path] = None):
|
|
191
|
+
self.story_key = story_key
|
|
192
|
+
self.project_root = project_root or PROJECT_ROOT
|
|
193
|
+
self.knowledge_graph = get_knowledge_graph(story_key)
|
|
194
|
+
self.shared_memory = get_shared_memory(story_key)
|
|
195
|
+
|
|
196
|
+
def get_git_changes(self, since_commit: Optional[str] = None) -> list[FileChange]:
|
|
197
|
+
"""Get list of changed files from git."""
|
|
198
|
+
try:
|
|
199
|
+
if since_commit:
|
|
200
|
+
cmd = ["git", "diff", "--numstat", since_commit]
|
|
201
|
+
else:
|
|
202
|
+
cmd = ["git", "diff", "--numstat", "HEAD~1"]
|
|
203
|
+
|
|
204
|
+
result = subprocess.run(cmd, capture_output=True, text=True, cwd=str(self.project_root))
|
|
205
|
+
|
|
206
|
+
changes = []
|
|
207
|
+
for line in result.stdout.strip().split("\n"):
|
|
208
|
+
if not line:
|
|
209
|
+
continue
|
|
210
|
+
parts = line.split("\t")
|
|
211
|
+
if len(parts) >= 3:
|
|
212
|
+
additions = int(parts[0]) if parts[0] != "-" else 0
|
|
213
|
+
deletions = int(parts[1]) if parts[1] != "-" else 0
|
|
214
|
+
path = parts[2]
|
|
215
|
+
|
|
216
|
+
status = "modified"
|
|
217
|
+
if additions > 0 and deletions == 0:
|
|
218
|
+
status = "added"
|
|
219
|
+
elif additions == 0 and deletions > 0:
|
|
220
|
+
status = "deleted"
|
|
221
|
+
|
|
222
|
+
changes.append(
|
|
223
|
+
FileChange(
|
|
224
|
+
path=path, status=status, additions=additions, deletions=deletions
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return changes
|
|
229
|
+
except Exception:
|
|
230
|
+
return []
|
|
231
|
+
|
|
232
|
+
def get_staged_changes(self) -> list[FileChange]:
|
|
233
|
+
"""Get staged changes from git."""
|
|
234
|
+
try:
|
|
235
|
+
result = subprocess.run(
|
|
236
|
+
["git", "diff", "--cached", "--numstat"],
|
|
237
|
+
capture_output=True,
|
|
238
|
+
text=True,
|
|
239
|
+
cwd=str(self.project_root),
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
changes = []
|
|
243
|
+
for line in result.stdout.strip().split("\n"):
|
|
244
|
+
if not line:
|
|
245
|
+
continue
|
|
246
|
+
parts = line.split("\t")
|
|
247
|
+
if len(parts) >= 3:
|
|
248
|
+
changes.append(
|
|
249
|
+
FileChange(
|
|
250
|
+
path=parts[2],
|
|
251
|
+
status="staged",
|
|
252
|
+
additions=int(parts[0]) if parts[0] != "-" else 0,
|
|
253
|
+
deletions=int(parts[1]) if parts[1] != "-" else 0,
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return changes
|
|
258
|
+
except Exception:
|
|
259
|
+
return []
|
|
260
|
+
|
|
261
|
+
def extract_decisions_from_memory(self, agent: str) -> list[str]:
|
|
262
|
+
"""Extract decisions made by an agent from shared memory."""
|
|
263
|
+
decisions = self.knowledge_graph.get_decisions_by_agent(agent)
|
|
264
|
+
return [f"{d.topic}: {d.decision}" for d in decisions]
|
|
265
|
+
|
|
266
|
+
def extract_warnings_from_log(self, log_content: str) -> list[str]:
|
|
267
|
+
"""Extract warnings from agent log output."""
|
|
268
|
+
warnings = []
|
|
269
|
+
warning_patterns = [
|
|
270
|
+
r"⚠️\s*(.+)",
|
|
271
|
+
r"WARNING:\s*(.+)",
|
|
272
|
+
r"WARN:\s*(.+)",
|
|
273
|
+
r"Watch out.*?:\s*(.+)",
|
|
274
|
+
r"Note:\s*(.+)",
|
|
275
|
+
]
|
|
276
|
+
|
|
277
|
+
for pattern in warning_patterns:
|
|
278
|
+
matches = re.findall(pattern, log_content, re.IGNORECASE)
|
|
279
|
+
warnings.extend(matches)
|
|
280
|
+
|
|
281
|
+
return list(set(warnings))[:5] # Dedupe and limit
|
|
282
|
+
|
|
283
|
+
def generate(
|
|
284
|
+
self,
|
|
285
|
+
from_agent: str,
|
|
286
|
+
to_agent: str,
|
|
287
|
+
work_summary: str,
|
|
288
|
+
decisions_made: Optional[list[str]] = None,
|
|
289
|
+
blockers_resolved: Optional[list[str]] = None,
|
|
290
|
+
warnings: Optional[list[str]] = None,
|
|
291
|
+
files_changed: Optional[list[FileChange]] = None,
|
|
292
|
+
next_steps: Optional[list[str]] = None,
|
|
293
|
+
log_content: Optional[str] = None,
|
|
294
|
+
) -> HandoffSummary:
|
|
295
|
+
"""Generate a handoff from one agent to another."""
|
|
296
|
+
|
|
297
|
+
# Auto-detect file changes if not provided
|
|
298
|
+
if files_changed is None:
|
|
299
|
+
files_changed = self.get_git_changes()
|
|
300
|
+
|
|
301
|
+
# Auto-extract decisions from memory
|
|
302
|
+
if decisions_made is None:
|
|
303
|
+
decisions_made = self.extract_decisions_from_memory(from_agent)
|
|
304
|
+
|
|
305
|
+
# Auto-extract warnings from log
|
|
306
|
+
if warnings is None and log_content:
|
|
307
|
+
warnings = self.extract_warnings_from_log(log_content)
|
|
308
|
+
|
|
309
|
+
# Get template for this transition
|
|
310
|
+
template = HANDOFF_TEMPLATES.get((from_agent, to_agent), {})
|
|
311
|
+
|
|
312
|
+
# Generate next steps if not provided
|
|
313
|
+
if next_steps is None:
|
|
314
|
+
next_steps = self._generate_next_steps(from_agent, to_agent, files_changed, template)
|
|
315
|
+
|
|
316
|
+
# Create handoff
|
|
317
|
+
handoff = self.knowledge_graph.add_handoff(
|
|
318
|
+
from_agent=from_agent,
|
|
319
|
+
to_agent=to_agent,
|
|
320
|
+
story_key=self.story_key,
|
|
321
|
+
summary=work_summary,
|
|
322
|
+
key_decisions=decisions_made or [],
|
|
323
|
+
blockers_resolved=blockers_resolved or [],
|
|
324
|
+
watch_out_for=warnings or [],
|
|
325
|
+
files_touched=[f.path for f in files_changed] if files_changed else [],
|
|
326
|
+
next_steps=next_steps or [],
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Also record in shared memory
|
|
330
|
+
self.shared_memory.add(
|
|
331
|
+
agent=from_agent,
|
|
332
|
+
content=f"Handed off to {to_agent}: {work_summary}",
|
|
333
|
+
tags=["handoff", to_agent.lower()],
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
return handoff
|
|
337
|
+
|
|
338
|
+
def _generate_next_steps(
|
|
339
|
+
self, from_agent: str, to_agent: str, files: list[FileChange], template: dict
|
|
340
|
+
) -> list[str]:
|
|
341
|
+
"""Generate suggested next steps for the receiving agent."""
|
|
342
|
+
steps = []
|
|
343
|
+
|
|
344
|
+
# Generic steps based on agent transition
|
|
345
|
+
if to_agent == "DEV":
|
|
346
|
+
steps.append("Review the acceptance criteria in the story context")
|
|
347
|
+
if files:
|
|
348
|
+
steps.append(f"Examine the {len(files)} files that have context")
|
|
349
|
+
steps.append("Implement the required functionality")
|
|
350
|
+
steps.append("Write tests for the implementation")
|
|
351
|
+
|
|
352
|
+
elif to_agent == "REVIEWER":
|
|
353
|
+
if files:
|
|
354
|
+
steps.append(f"Review the {len(files)} changed files")
|
|
355
|
+
steps.append("Check for code quality and best practices")
|
|
356
|
+
steps.append("Verify test coverage")
|
|
357
|
+
steps.append("Provide actionable feedback")
|
|
358
|
+
|
|
359
|
+
elif to_agent == "ARCHITECT":
|
|
360
|
+
steps.append("Analyze the requirements for design implications")
|
|
361
|
+
steps.append("Document key architectural decisions")
|
|
362
|
+
steps.append("Define component interfaces")
|
|
363
|
+
|
|
364
|
+
elif to_agent == "SM":
|
|
365
|
+
steps.append("Review the completed work")
|
|
366
|
+
steps.append("Update story status")
|
|
367
|
+
steps.append("Prepare for next phase or story")
|
|
368
|
+
|
|
369
|
+
return steps
|
|
370
|
+
|
|
371
|
+
def get_latest_handoff_for(self, agent: str) -> Optional[HandoffSummary]:
|
|
372
|
+
"""Get the most recent handoff for an agent."""
|
|
373
|
+
return self.knowledge_graph.get_latest_handoff(agent)
|
|
374
|
+
|
|
375
|
+
def generate_context_for_agent(self, agent: str) -> str:
|
|
376
|
+
"""Generate full context string for an agent including handoffs."""
|
|
377
|
+
lines = [f"## Context for {agent}", ""]
|
|
378
|
+
|
|
379
|
+
# Get latest handoff
|
|
380
|
+
handoff = self.get_latest_handoff_for(agent)
|
|
381
|
+
if handoff:
|
|
382
|
+
lines.append("### Handoff from Previous Agent")
|
|
383
|
+
lines.append(handoff.to_markdown())
|
|
384
|
+
lines.append("")
|
|
385
|
+
|
|
386
|
+
# Get relevant shared memory
|
|
387
|
+
recent_memory = self.shared_memory.get_recent(5)
|
|
388
|
+
if recent_memory:
|
|
389
|
+
lines.append("### Recent Team Activity")
|
|
390
|
+
for entry in recent_memory:
|
|
391
|
+
lines.append(f"- **{entry.agent}**: {entry.content}")
|
|
392
|
+
lines.append("")
|
|
393
|
+
|
|
394
|
+
# Get relevant decisions
|
|
395
|
+
decisions = list(self.knowledge_graph.decisions.values())
|
|
396
|
+
active_decisions = [d for d in decisions if d.status == "active"]
|
|
397
|
+
if active_decisions:
|
|
398
|
+
lines.append("### Active Decisions")
|
|
399
|
+
for dec in active_decisions[-5:]:
|
|
400
|
+
lines.append(f"- **{dec.topic}** ({dec.agent}): {dec.decision}")
|
|
401
|
+
lines.append("")
|
|
402
|
+
|
|
403
|
+
return "\n".join(lines)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class WorkTracker:
|
|
407
|
+
"""Tracks work in progress for handoff generation."""
|
|
408
|
+
|
|
409
|
+
def __init__(self, story_key: str, agent: str):
|
|
410
|
+
self.story_key = story_key
|
|
411
|
+
self.agent = agent
|
|
412
|
+
self.start_time = datetime.now().isoformat()
|
|
413
|
+
self.decisions: list[str] = []
|
|
414
|
+
self.blockers: list[str] = []
|
|
415
|
+
self.warnings: list[str] = []
|
|
416
|
+
self.notes: list[str] = []
|
|
417
|
+
self.files_touched: list[str] = []
|
|
418
|
+
self.shared_memory = get_shared_memory(story_key)
|
|
419
|
+
self.knowledge_graph = get_knowledge_graph(story_key)
|
|
420
|
+
|
|
421
|
+
def record_decision(self, topic: str, decision: str, context: Optional[dict[str, Any]] = None):
|
|
422
|
+
"""Record a decision made during work."""
|
|
423
|
+
self.decisions.append(f"{topic}: {decision}")
|
|
424
|
+
self.knowledge_graph.add_decision(
|
|
425
|
+
agent=self.agent, topic=topic, decision=decision, context=context or {}
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
def record_blocker(self, blocker: str, resolved: bool = False):
|
|
429
|
+
"""Record a blocker encountered."""
|
|
430
|
+
if resolved:
|
|
431
|
+
self.blockers.append(f"✅ {blocker} (resolved)")
|
|
432
|
+
else:
|
|
433
|
+
self.blockers.append(f"❌ {blocker}")
|
|
434
|
+
|
|
435
|
+
def record_warning(self, warning: str):
|
|
436
|
+
"""Record a warning for the next agent."""
|
|
437
|
+
self.warnings.append(warning)
|
|
438
|
+
|
|
439
|
+
def record_note(self, note: str):
|
|
440
|
+
"""Record a general note."""
|
|
441
|
+
self.notes.append(note)
|
|
442
|
+
self.shared_memory.add(agent=self.agent, content=note, tags=["note"])
|
|
443
|
+
|
|
444
|
+
def record_file(self, file_path: str):
|
|
445
|
+
"""Record a file that was touched."""
|
|
446
|
+
if file_path not in self.files_touched:
|
|
447
|
+
self.files_touched.append(file_path)
|
|
448
|
+
|
|
449
|
+
def generate_handoff(self, to_agent: str, summary: str) -> HandoffSummary:
|
|
450
|
+
"""Generate a handoff based on tracked work."""
|
|
451
|
+
generator = HandoffGenerator(self.story_key)
|
|
452
|
+
|
|
453
|
+
# Separate resolved and unresolved blockers
|
|
454
|
+
resolved = [
|
|
455
|
+
b.replace("✅ ", "").replace(" (resolved)", "")
|
|
456
|
+
for b in self.blockers
|
|
457
|
+
if b.startswith("✅")
|
|
458
|
+
]
|
|
459
|
+
|
|
460
|
+
return generator.generate(
|
|
461
|
+
from_agent=self.agent,
|
|
462
|
+
to_agent=to_agent,
|
|
463
|
+
work_summary=summary,
|
|
464
|
+
decisions_made=self.decisions,
|
|
465
|
+
blockers_resolved=resolved,
|
|
466
|
+
warnings=self.warnings,
|
|
467
|
+
next_steps=self.notes if self.notes else None,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
# Convenience functions
|
|
472
|
+
def create_handoff(
|
|
473
|
+
from_agent: str, to_agent: str, story_key: str, summary: str, **kwargs
|
|
474
|
+
) -> HandoffSummary:
|
|
475
|
+
"""Quick function to create a handoff."""
|
|
476
|
+
generator = HandoffGenerator(story_key)
|
|
477
|
+
return generator.generate(from_agent, to_agent, summary, **kwargs)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def get_agent_context(agent: str, story_key: str) -> str:
|
|
481
|
+
"""Get full context for an agent including handoffs."""
|
|
482
|
+
generator = HandoffGenerator(story_key)
|
|
483
|
+
return generator.generate_context_for_agent(agent)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def start_work_tracking(story_key: str, agent: str) -> WorkTracker:
|
|
487
|
+
"""Start tracking work for handoff generation."""
|
|
488
|
+
return WorkTracker(story_key, agent)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
if __name__ == "__main__":
|
|
492
|
+
# Demo usage
|
|
493
|
+
print("=== Handoff System Demo ===\n")
|
|
494
|
+
|
|
495
|
+
# Simulate a workflow
|
|
496
|
+
story_key = "demo-handoff"
|
|
497
|
+
|
|
498
|
+
# SM starts work
|
|
499
|
+
tracker = start_work_tracking(story_key, "SM")
|
|
500
|
+
tracker.record_decision(
|
|
501
|
+
"implementation-approach",
|
|
502
|
+
"Use existing UserService class",
|
|
503
|
+
{"reason": "Follows established patterns"},
|
|
504
|
+
)
|
|
505
|
+
tracker.record_decision(
|
|
506
|
+
"testing-strategy",
|
|
507
|
+
"Unit tests for service, integration for API",
|
|
508
|
+
{"coverage_target": "80%"},
|
|
509
|
+
)
|
|
510
|
+
tracker.record_warning("Rate limiting on profile image uploads")
|
|
511
|
+
tracker.record_note("Existing user.py has the patterns to follow")
|
|
512
|
+
|
|
513
|
+
# Generate handoff to DEV
|
|
514
|
+
handoff = tracker.generate_handoff(
|
|
515
|
+
to_agent="DEV",
|
|
516
|
+
summary="Created story context for user profile feature. Requirements are clear, patterns are established.",
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
print(handoff.to_markdown())
|
|
520
|
+
|
|
521
|
+
print("\n" + "=" * 60 + "\n")
|
|
522
|
+
|
|
523
|
+
# DEV receives context
|
|
524
|
+
generator = HandoffGenerator(story_key)
|
|
525
|
+
context = generator.generate_context_for_agent("DEV")
|
|
526
|
+
print(context)
|