anvil-dev-framework 0.1.6

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 (190) hide show
  1. package/README.md +719 -0
  2. package/VERSION +1 -0
  3. package/docs/ANVIL-REPO-IMPLEMENTATION-PLAN.md +441 -0
  4. package/docs/FIRST-SKILL-TUTORIAL.md +408 -0
  5. package/docs/INSTALLATION-RETRO-NOTES.md +458 -0
  6. package/docs/INSTALLATION.md +984 -0
  7. package/docs/anvil-hud.md +469 -0
  8. package/docs/anvil-init.md +255 -0
  9. package/docs/anvil-state.md +210 -0
  10. package/docs/boris-cherny-ralph-wiggum-insights.md +608 -0
  11. package/docs/command-reference.md +2022 -0
  12. package/docs/hooks-tts.md +368 -0
  13. package/docs/implementation-guide.md +810 -0
  14. package/docs/linear-github-integration.md +247 -0
  15. package/docs/local-issues.md +677 -0
  16. package/docs/patterns/README.md +419 -0
  17. package/docs/planning-responsibilities.md +139 -0
  18. package/docs/session-workflow.md +573 -0
  19. package/docs/simplification-plan-template.md +297 -0
  20. package/docs/simplification-principles.md +129 -0
  21. package/docs/specifications/CCS-RALPH-INTEGRATION-DESIGN.md +633 -0
  22. package/docs/specifications/CCS-RESEARCH-REPORT.md +169 -0
  23. package/docs/specifications/PLAN-ANV-verification-ralph-wiggum.md +403 -0
  24. package/docs/specifications/PLAN-parallel-tracks-anvil-memory-ccs.md +494 -0
  25. package/docs/specifications/SPEC-ANV-VRW/component-01-verify.md +208 -0
  26. package/docs/specifications/SPEC-ANV-VRW/component-02-stop-gate.md +226 -0
  27. package/docs/specifications/SPEC-ANV-VRW/component-03-posttooluse.md +209 -0
  28. package/docs/specifications/SPEC-ANV-VRW/component-04-ralph-wiggum.md +604 -0
  29. package/docs/specifications/SPEC-ANV-VRW/component-05-atomic-actions.md +311 -0
  30. package/docs/specifications/SPEC-ANV-VRW/component-06-verify-subagent.md +264 -0
  31. package/docs/specifications/SPEC-ANV-VRW/component-07-claude-md.md +363 -0
  32. package/docs/specifications/SPEC-ANV-VRW/index.md +182 -0
  33. package/docs/specifications/SPEC-ANV-anvil-memory.md +573 -0
  34. package/docs/specifications/SPEC-ANV-context-checkpoints.md +781 -0
  35. package/docs/specifications/SPEC-ANV-verification-ralph-wiggum.md +789 -0
  36. package/docs/sync.md +122 -0
  37. package/global/CLAUDE.md +140 -0
  38. package/global/agents/verify-app.md +164 -0
  39. package/global/commands/anvil-settings.md +527 -0
  40. package/global/commands/anvil-sync.md +121 -0
  41. package/global/commands/change.md +197 -0
  42. package/global/commands/clarify.md +252 -0
  43. package/global/commands/cleanup.md +292 -0
  44. package/global/commands/commit-push-pr.md +207 -0
  45. package/global/commands/decay-review.md +127 -0
  46. package/global/commands/discover.md +158 -0
  47. package/global/commands/doc-coverage.md +122 -0
  48. package/global/commands/evidence.md +307 -0
  49. package/global/commands/explore.md +121 -0
  50. package/global/commands/force-exit.md +135 -0
  51. package/global/commands/handoff.md +191 -0
  52. package/global/commands/healthcheck.md +302 -0
  53. package/global/commands/hud.md +84 -0
  54. package/global/commands/insights.md +319 -0
  55. package/global/commands/linear-setup.md +184 -0
  56. package/global/commands/lint-fix.md +198 -0
  57. package/global/commands/orient.md +510 -0
  58. package/global/commands/plan.md +228 -0
  59. package/global/commands/ralph.md +346 -0
  60. package/global/commands/ready.md +182 -0
  61. package/global/commands/release.md +305 -0
  62. package/global/commands/retro.md +96 -0
  63. package/global/commands/shard.md +166 -0
  64. package/global/commands/spec.md +227 -0
  65. package/global/commands/sprint.md +184 -0
  66. package/global/commands/tasks.md +228 -0
  67. package/global/commands/test-and-commit.md +151 -0
  68. package/global/commands/validate.md +132 -0
  69. package/global/commands/verify.md +251 -0
  70. package/global/commands/weekly-review.md +156 -0
  71. package/global/hooks/__pycache__/ralph_context_monitor.cpython-314.pyc +0 -0
  72. package/global/hooks/__pycache__/statusline_agent_sync.cpython-314.pyc +0 -0
  73. package/global/hooks/anvil_memory_observe.ts +322 -0
  74. package/global/hooks/anvil_memory_session.ts +166 -0
  75. package/global/hooks/anvil_memory_stop.ts +187 -0
  76. package/global/hooks/parse_transcript.py +116 -0
  77. package/global/hooks/post_merge_cleanup.sh +132 -0
  78. package/global/hooks/post_tool_format.sh +215 -0
  79. package/global/hooks/ralph_context_monitor.py +240 -0
  80. package/global/hooks/ralph_stop.sh +502 -0
  81. package/global/hooks/statusline.sh +1110 -0
  82. package/global/hooks/statusline_agent_sync.py +224 -0
  83. package/global/hooks/stop_gate.sh +250 -0
  84. package/global/lib/.claude/anvil-state.json +21 -0
  85. package/global/lib/__pycache__/agent_registry.cpython-314.pyc +0 -0
  86. package/global/lib/__pycache__/claim_service.cpython-314.pyc +0 -0
  87. package/global/lib/__pycache__/coderabbit_service.cpython-314.pyc +0 -0
  88. package/global/lib/__pycache__/config_service.cpython-314.pyc +0 -0
  89. package/global/lib/__pycache__/coordination_service.cpython-314.pyc +0 -0
  90. package/global/lib/__pycache__/doc_coverage_service.cpython-314.pyc +0 -0
  91. package/global/lib/__pycache__/gate_logger.cpython-314.pyc +0 -0
  92. package/global/lib/__pycache__/github_service.cpython-314.pyc +0 -0
  93. package/global/lib/__pycache__/hygiene_service.cpython-314.pyc +0 -0
  94. package/global/lib/__pycache__/issue_models.cpython-314.pyc +0 -0
  95. package/global/lib/__pycache__/issue_provider.cpython-314.pyc +0 -0
  96. package/global/lib/__pycache__/linear_data_service.cpython-314.pyc +0 -0
  97. package/global/lib/__pycache__/linear_provider.cpython-314.pyc +0 -0
  98. package/global/lib/__pycache__/local_provider.cpython-314.pyc +0 -0
  99. package/global/lib/__pycache__/quality_service.cpython-314.pyc +0 -0
  100. package/global/lib/__pycache__/ralph_state.cpython-314.pyc +0 -0
  101. package/global/lib/__pycache__/state_manager.cpython-314.pyc +0 -0
  102. package/global/lib/__pycache__/transcript_parser.cpython-314.pyc +0 -0
  103. package/global/lib/__pycache__/verification_runner.cpython-314.pyc +0 -0
  104. package/global/lib/__pycache__/verify_iteration.cpython-314.pyc +0 -0
  105. package/global/lib/__pycache__/verify_subagent.cpython-314.pyc +0 -0
  106. package/global/lib/agent_registry.py +995 -0
  107. package/global/lib/anvil-state.sh +435 -0
  108. package/global/lib/claim_service.py +515 -0
  109. package/global/lib/coderabbit_service.py +314 -0
  110. package/global/lib/config_service.py +423 -0
  111. package/global/lib/coordination_service.py +331 -0
  112. package/global/lib/doc_coverage_service.py +1305 -0
  113. package/global/lib/gate_logger.py +316 -0
  114. package/global/lib/github_service.py +310 -0
  115. package/global/lib/handoff_generator.py +775 -0
  116. package/global/lib/hygiene_service.py +712 -0
  117. package/global/lib/issue_models.py +257 -0
  118. package/global/lib/issue_provider.py +339 -0
  119. package/global/lib/linear_data_service.py +210 -0
  120. package/global/lib/linear_provider.py +987 -0
  121. package/global/lib/linear_provider.py.backup +671 -0
  122. package/global/lib/local_provider.py +486 -0
  123. package/global/lib/orient_fast.py +457 -0
  124. package/global/lib/quality_service.py +470 -0
  125. package/global/lib/ralph_prompt_generator.py +563 -0
  126. package/global/lib/ralph_state.py +1202 -0
  127. package/global/lib/state_manager.py +417 -0
  128. package/global/lib/transcript_parser.py +597 -0
  129. package/global/lib/verification_runner.py +557 -0
  130. package/global/lib/verify_iteration.py +490 -0
  131. package/global/lib/verify_subagent.py +250 -0
  132. package/global/skills/README.md +155 -0
  133. package/global/skills/quality-gates/SKILL.md +252 -0
  134. package/global/skills/skill-template/SKILL.md +109 -0
  135. package/global/skills/testing-strategies/SKILL.md +337 -0
  136. package/global/templates/CHANGE-template.md +105 -0
  137. package/global/templates/HANDOFF-template.md +63 -0
  138. package/global/templates/PLAN-template.md +111 -0
  139. package/global/templates/SPEC-template.md +93 -0
  140. package/global/templates/ralph/PROMPT.md.template +89 -0
  141. package/global/templates/ralph/fix_plan.md.template +31 -0
  142. package/global/templates/ralph/progress.txt.template +23 -0
  143. package/global/tests/__pycache__/test_doc_coverage.cpython-314.pyc +0 -0
  144. package/global/tests/test_doc_coverage.py +520 -0
  145. package/global/tests/test_issue_models.py +299 -0
  146. package/global/tests/test_local_provider.py +323 -0
  147. package/global/tools/README.md +178 -0
  148. package/global/tools/__pycache__/anvil-hud.cpython-314.pyc +0 -0
  149. package/global/tools/anvil-hud.py +3622 -0
  150. package/global/tools/anvil-hud.py.bak +3318 -0
  151. package/global/tools/anvil-issue.py +432 -0
  152. package/global/tools/anvil-memory/CLAUDE.md +49 -0
  153. package/global/tools/anvil-memory/README.md +42 -0
  154. package/global/tools/anvil-memory/bun.lock +25 -0
  155. package/global/tools/anvil-memory/bunfig.toml +9 -0
  156. package/global/tools/anvil-memory/package.json +23 -0
  157. package/global/tools/anvil-memory/src/__tests__/ccs/context-monitor.test.ts +535 -0
  158. package/global/tools/anvil-memory/src/__tests__/ccs/edge-cases.test.ts +645 -0
  159. package/global/tools/anvil-memory/src/__tests__/ccs/fixtures.ts +363 -0
  160. package/global/tools/anvil-memory/src/__tests__/ccs/index.ts +8 -0
  161. package/global/tools/anvil-memory/src/__tests__/ccs/integration.test.ts +417 -0
  162. package/global/tools/anvil-memory/src/__tests__/ccs/prompt-generator.test.ts +571 -0
  163. package/global/tools/anvil-memory/src/__tests__/ccs/ralph-stop.test.ts +440 -0
  164. package/global/tools/anvil-memory/src/__tests__/ccs/test-utils.ts +252 -0
  165. package/global/tools/anvil-memory/src/__tests__/commands.test.ts +657 -0
  166. package/global/tools/anvil-memory/src/__tests__/db.test.ts +641 -0
  167. package/global/tools/anvil-memory/src/__tests__/hooks.test.ts +272 -0
  168. package/global/tools/anvil-memory/src/__tests__/performance.test.ts +427 -0
  169. package/global/tools/anvil-memory/src/__tests__/test-utils.ts +113 -0
  170. package/global/tools/anvil-memory/src/commands/checkpoint.ts +197 -0
  171. package/global/tools/anvil-memory/src/commands/get.ts +115 -0
  172. package/global/tools/anvil-memory/src/commands/init.ts +94 -0
  173. package/global/tools/anvil-memory/src/commands/observe.ts +163 -0
  174. package/global/tools/anvil-memory/src/commands/search.ts +112 -0
  175. package/global/tools/anvil-memory/src/db.ts +638 -0
  176. package/global/tools/anvil-memory/src/index.ts +205 -0
  177. package/global/tools/anvil-memory/src/types.ts +122 -0
  178. package/global/tools/anvil-memory/tsconfig.json +29 -0
  179. package/global/tools/ralph-loop.sh +359 -0
  180. package/package.json +45 -0
  181. package/scripts/anvil +822 -0
  182. package/scripts/extract_patterns.py +222 -0
  183. package/scripts/init-project.sh +541 -0
  184. package/scripts/install.sh +229 -0
  185. package/scripts/postinstall.js +41 -0
  186. package/scripts/rollback.sh +188 -0
  187. package/scripts/sync.sh +623 -0
  188. package/scripts/test-statusline.sh +248 -0
  189. package/scripts/update_claude_md.py +224 -0
  190. package/scripts/verify.sh +255 -0
@@ -0,0 +1,331 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ coordination_service.py - Agent coordination and conflict detection for HUD (ANV-106-110)
4
+
5
+ Tracks file locks between agents, detects conflicts when multiple agents edit
6
+ the same file, and provides branch age information for trunk-based development.
7
+
8
+ Usage:
9
+ from coordination_service import CoordinationService
10
+
11
+ service = CoordinationService()
12
+ service.update_from_registry(agents)
13
+ conflicts = service.get_conflicts()
14
+ """
15
+
16
+ import subprocess
17
+ import time
18
+ from datetime import datetime, timezone
19
+ from pathlib import Path
20
+ from typing import Any, Dict, List, Optional, Set, TypedDict
21
+
22
+
23
+ class FileLock(TypedDict, total=False):
24
+ """Lock on a file by an agent."""
25
+ agent_id: str # Agent holding the lock
26
+ agent_display: str # Short display name for agent
27
+ file_path: str # Full path to file
28
+ pattern: str # Directory pattern (e.g., "src/auth/*")
29
+ since: str # ISO timestamp when lock acquired
30
+ operation: str # Type of operation (Edit, Write, etc.)
31
+ duration_seconds: int # How long the lock has been held
32
+
33
+
34
+ class FileConflict(TypedDict, total=False):
35
+ """Conflict when multiple agents edit the same file."""
36
+ file_path: str # Full path to conflicting file
37
+ pattern: str # Directory pattern
38
+ agents: List[str] # Agent IDs involved in conflict
39
+ detected: str # ISO timestamp when detected
40
+ suggestion: str # Suggested action
41
+
42
+
43
+ class BranchInfo(TypedDict, total=False):
44
+ """Branch age information for an agent."""
45
+ agent_id: str
46
+ branch: str # Branch name
47
+ age_seconds: int # Age of branch in seconds
48
+ age_display: str # Human-readable age (e.g., "2h 15m")
49
+ is_stale: bool # True if branch age exceeds threshold
50
+
51
+
52
+ class CoordinationData(TypedDict, total=False):
53
+ """Aggregated coordination data."""
54
+ file_locks: Dict[str, FileLock] # pattern -> lock
55
+ conflicts: List[FileConflict] # List of conflicts
56
+ branches: Dict[str, BranchInfo] # agent_id -> branch info
57
+ last_update: float # Timestamp
58
+
59
+
60
+ # Thresholds
61
+ STALE_BRANCH_THRESHOLD = 4 * 60 * 60 # 4 hours in seconds
62
+
63
+
64
+ class CoordinationService:
65
+ """Service for tracking agent coordination and conflicts (ANV-106-110)."""
66
+
67
+ def __init__(self):
68
+ """Initialize the coordination service."""
69
+ self._locks: Dict[str, FileLock] = {} # pattern -> lock
70
+ self._lock_timestamps: Dict[str, float] = {} # pattern -> timestamp
71
+ self._conflicts: List[FileConflict] = []
72
+ self._branches: Dict[str, BranchInfo] = {} # agent_id -> branch
73
+ self._last_update = 0.0
74
+
75
+ def update_from_registry(self, agents: Dict[str, Any]) -> CoordinationData:
76
+ """Update coordination data from agent registry.
77
+
78
+ Args:
79
+ agents: Dictionary of agent_id -> agent data
80
+
81
+ Returns:
82
+ CoordinationData with all coordination information
83
+ """
84
+ now = time.time()
85
+ new_locks: Dict[str, FileLock] = {}
86
+ agent_files: Dict[str, Set[str]] = {} # agent_id -> set of file patterns
87
+
88
+ for agent_id, agent in agents.items():
89
+ project = agent.get("project", "")
90
+ display_id = agent_id[:12] if len(agent_id) > 12 else agent_id
91
+
92
+ # Track files from recent tool calls
93
+ recent_tools = agent.get("recentTools", [])
94
+ if not recent_tools:
95
+ # Try to get from detailed state
96
+ detailed = agent.get("detailed", {})
97
+ recent_tools = detailed.get("recentTools", [])
98
+
99
+ files_touched: Set[str] = set()
100
+ for tool in recent_tools:
101
+ tool_name = tool.get("name", "")
102
+ # Only track write operations
103
+ if tool_name in ("Edit", "Write", "MultiEdit"):
104
+ file_path = tool.get("args", {}).get("file_path", "")
105
+ if file_path:
106
+ # Get directory pattern
107
+ pattern = self._get_pattern(file_path, project)
108
+ files_touched.add(pattern)
109
+
110
+ # Create or update lock
111
+ if pattern not in new_locks:
112
+ # Check if we had this lock before
113
+ since = self._lock_timestamps.get(pattern, now)
114
+ self._lock_timestamps[pattern] = since
115
+
116
+ new_locks[pattern] = {
117
+ "agent_id": agent_id,
118
+ "agent_display": display_id,
119
+ "file_path": file_path,
120
+ "pattern": pattern,
121
+ "since": datetime.fromtimestamp(since, timezone.utc).isoformat(),
122
+ "operation": tool_name,
123
+ "duration_seconds": int(now - since),
124
+ }
125
+
126
+ agent_files[agent_id] = files_touched
127
+
128
+ # Get branch info
129
+ branch_info = self._get_branch_info(agent_id, project)
130
+ if branch_info:
131
+ self._branches[agent_id] = branch_info
132
+
133
+ # Update locks
134
+ self._locks = new_locks
135
+
136
+ # Detect conflicts (same pattern touched by multiple agents)
137
+ self._conflicts = []
138
+ pattern_agents: Dict[str, List[str]] = {} # pattern -> [agent_ids]
139
+
140
+ for agent_id, patterns in agent_files.items():
141
+ for pattern in patterns:
142
+ if pattern not in pattern_agents:
143
+ pattern_agents[pattern] = []
144
+ pattern_agents[pattern].append(agent_id)
145
+
146
+ # Find patterns with multiple agents
147
+ for pattern, agent_ids in pattern_agents.items():
148
+ if len(agent_ids) > 1:
149
+ self._conflicts.append({
150
+ "file_path": pattern,
151
+ "pattern": pattern,
152
+ "agents": agent_ids,
153
+ "detected": datetime.now(timezone.utc).isoformat(),
154
+ "suggestion": "Coordinate via /handoff or stagger work",
155
+ })
156
+
157
+ self._last_update = now
158
+
159
+ return self.get_data()
160
+
161
+ def get_data(self) -> CoordinationData:
162
+ """Get current coordination data."""
163
+ return {
164
+ "file_locks": self._locks,
165
+ "conflicts": self._conflicts,
166
+ "branches": self._branches,
167
+ "last_update": self._last_update,
168
+ }
169
+
170
+ def get_locks(self) -> Dict[str, FileLock]:
171
+ """Get current file locks."""
172
+ return self._locks
173
+
174
+ def get_conflicts(self) -> List[FileConflict]:
175
+ """Get current conflicts."""
176
+ return self._conflicts
177
+
178
+ def get_branches(self) -> Dict[str, BranchInfo]:
179
+ """Get branch information."""
180
+ return self._branches
181
+
182
+ def _get_pattern(self, file_path: str, project: str) -> str:
183
+ """Get directory pattern from file path.
184
+
185
+ Converts full path to a pattern like "src/auth/*"
186
+ """
187
+ try:
188
+ path = Path(file_path)
189
+ if project:
190
+ try:
191
+ path = path.relative_to(project)
192
+ except ValueError:
193
+ pass
194
+
195
+ # Get parent directory
196
+ parent = path.parent
197
+ return str(parent) + "/*" if str(parent) != "." else str(path)
198
+ except Exception:
199
+ return file_path
200
+
201
+ def _get_branch_info(self, agent_id: str, project: str) -> Optional[BranchInfo]:
202
+ """Get branch age information for an agent's project."""
203
+ if not project or not Path(project).exists():
204
+ return None
205
+
206
+ try:
207
+ # Get current branch
208
+ result = subprocess.run(
209
+ ["git", "branch", "--show-current"],
210
+ cwd=project,
211
+ capture_output=True,
212
+ text=True,
213
+ timeout=5,
214
+ )
215
+ branch = result.stdout.strip()
216
+ if not branch:
217
+ return None
218
+
219
+ # Get branch creation/diverge time (commits since main)
220
+ result = subprocess.run(
221
+ ["git", "log", "main.." + branch, "--format=%ci", "--reverse"],
222
+ cwd=project,
223
+ capture_output=True,
224
+ text=True,
225
+ timeout=5,
226
+ )
227
+
228
+ age_seconds = 0
229
+ if result.stdout.strip():
230
+ # Get first commit time
231
+ first_commit = result.stdout.strip().split("\n")[0]
232
+ try:
233
+ # Parse git date format: "2024-01-15 10:30:00 +0000"
234
+ # Split off timezone, replace first space with T
235
+ parts = first_commit.rsplit(" ", 1) # Split off timezone
236
+ if len(parts) == 2:
237
+ dt_str, tz_str = parts
238
+ # dt_str is "2024-01-15 10:30:00", tz_str is "+0000"
239
+ iso_str = dt_str.replace(" ", "T") + tz_str.replace(":", "")
240
+ commit_time = datetime.fromisoformat(iso_str)
241
+ age_seconds = int((datetime.now(timezone.utc) - commit_time.astimezone(timezone.utc)).total_seconds())
242
+ except Exception:
243
+ pass
244
+
245
+ # If no divergence from main, use branch checkout time instead
246
+ if age_seconds == 0:
247
+ # Fallback: use reflog
248
+ result = subprocess.run(
249
+ ["git", "reflog", "show", "--format=%ci", "-1", branch],
250
+ cwd=project,
251
+ capture_output=True,
252
+ text=True,
253
+ timeout=5,
254
+ )
255
+ if result.stdout.strip():
256
+ try:
257
+ # Parse git date format: "2024-01-15 10:30:00 +0000"
258
+ reflog_date = result.stdout.strip()
259
+ parts = reflog_date.rsplit(" ", 1) # Split off timezone
260
+ if len(parts) == 2:
261
+ dt_str, tz_str = parts
262
+ iso_str = dt_str.replace(" ", "T") + tz_str.replace(":", "")
263
+ checkout_time = datetime.fromisoformat(iso_str)
264
+ age_seconds = int((datetime.now(timezone.utc) - checkout_time.astimezone(timezone.utc)).total_seconds())
265
+ except Exception:
266
+ pass
267
+
268
+ # Format age
269
+ if age_seconds < 60:
270
+ age_display = f"{age_seconds}s"
271
+ elif age_seconds < 3600:
272
+ age_display = f"{age_seconds // 60}m"
273
+ else:
274
+ hours = age_seconds // 3600
275
+ mins = (age_seconds % 3600) // 60
276
+ age_display = f"{hours}h {mins}m"
277
+
278
+ return {
279
+ "agent_id": agent_id,
280
+ "branch": branch,
281
+ "age_seconds": age_seconds,
282
+ "age_display": age_display,
283
+ "is_stale": age_seconds > STALE_BRANCH_THRESHOLD,
284
+ }
285
+
286
+ except Exception:
287
+ return None
288
+
289
+ def clear(self) -> None:
290
+ """Clear all coordination data."""
291
+ self._locks.clear()
292
+ self._lock_timestamps.clear()
293
+ self._conflicts.clear()
294
+ self._branches.clear()
295
+
296
+
297
+ # Singleton instance
298
+ _service: Optional[CoordinationService] = None
299
+
300
+
301
+ def get_coordination_service() -> CoordinationService:
302
+ """Get singleton service instance."""
303
+ global _service
304
+ if _service is None:
305
+ _service = CoordinationService()
306
+ return _service
307
+
308
+
309
+ if __name__ == "__main__":
310
+ # Simple test
311
+ service = CoordinationService()
312
+
313
+ # Simulate two agents editing same file
314
+ test_agents = {
315
+ "agent-1": {
316
+ "project": "/tmp/test-project",
317
+ "recentTools": [
318
+ {"name": "Edit", "args": {"file_path": "/tmp/test-project/src/auth/login.ts"}},
319
+ ],
320
+ },
321
+ "agent-2": {
322
+ "project": "/tmp/test-project",
323
+ "recentTools": [
324
+ {"name": "Edit", "args": {"file_path": "/tmp/test-project/src/auth/logout.ts"}},
325
+ ],
326
+ },
327
+ }
328
+
329
+ data = service.update_from_registry(test_agents)
330
+ print("Locks:", data["file_locks"])
331
+ print("Conflicts:", data["conflicts"])