claude-comms 1.0.45 → 2.0.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 (46) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +12 -13
  3. package/bin/claude-setup.js +3 -4
  4. package/package.json +6 -10
  5. package/src/orchestrator/installer.js +5 -63
  6. package/.agents/AGENTS.md +0 -61
  7. package/.agents/repo.md +0 -18
  8. package/.agents/tools/README.md +0 -45
  9. package/.agents/tools/agent-job/README.md +0 -208
  10. package/.agents/tools/agent-job/agent_job.py +0 -866
  11. package/.agents/tools/agent-job/agent_monitor.py +0 -575
  12. package/.agents/tools/agent-job/consultant_template.txt +0 -73
  13. package/.agents/tools/chrome-devtools/README.md +0 -101
  14. package/.agents/tools/chrome-devtools/browsertools.py +0 -839
  15. package/.agents/tools/chrome-devtools/config.json +0 -24
  16. package/.claude/agents/core/architect.md +0 -316
  17. package/.claude/agents/core/consultants/codex.md +0 -39
  18. package/.claude/agents/core/consultants/gemini.md +0 -39
  19. package/.claude/agents/core/designer.md +0 -181
  20. package/.claude/agents/core/engineer.md +0 -157
  21. package/.claude/agents/core/planner.md +0 -251
  22. package/.claude/agents/core/uat.md +0 -147
  23. package/.claude/agents/util/feedback-executor.md +0 -29
  24. package/.claude/agents/util/meta-agent-builder.md +0 -309
  25. package/.claude/agents/util/session-review.md +0 -228
  26. package/.claude/commands/action-feedback-item.md +0 -22
  27. package/.claude/commands/analyse-document.md +0 -10
  28. package/.claude/commands/await-feedback-orchestrator.md +0 -12
  29. package/.claude/commands/await-feedback.md +0 -21
  30. package/.claude/commands/cook.md +0 -119
  31. package/.claude/commands/feedback.md +0 -140
  32. package/.claude/commands/hello-world.md +0 -1
  33. package/.claude/commands/install-feedback-widget.md +0 -599
  34. package/.claude/commands/learn.md +0 -32
  35. package/.claude/commands/uat-refine.md +0 -121
  36. package/.claude/hooks/comms/get_unread_messages.py +0 -71
  37. package/.claude/hooks/comms/register_subagent.py +0 -74
  38. package/.claude/hooks/comms/send_message.py +0 -87
  39. package/.claude/hooks/comms/update_subagent_completion.py +0 -126
  40. package/.claude/hooks/context/session_id.py +0 -35
  41. package/.claude/hooks/observability/send_event.py +0 -110
  42. package/.claude/hooks/session-data/get_session_data.py +0 -261
  43. package/.claude/hooks/session-data/intercept_session_id.py +0 -69
  44. package/.claude/hooks/utils/server_config.py +0 -17
  45. package/.claude/settings.json +0 -108
  46. package/CLAUDE.md +0 -240
@@ -1,575 +0,0 @@
1
- #!/usr/bin/env python3
2
- # /// script
3
- # requires-python = ">=3.11"
4
- # dependencies = []
5
- # ///
6
- """
7
- Agent Job Monitor - TUI for monitoring agent jobs.
8
-
9
- Real-time display of all agent jobs with status indicators.
10
- Similar to `top` but for agent jobs.
11
-
12
- Usage:
13
- uv run agent_monitor.py [--interval SECONDS]
14
- """
15
-
16
- import argparse
17
- import curses
18
- import json
19
- import os
20
- import signal
21
- import subprocess
22
- import sys
23
- import time
24
- from dataclasses import dataclass
25
- from datetime import datetime, timezone
26
- from pathlib import Path
27
- from typing import Optional
28
-
29
-
30
- @dataclass
31
- class JobInfo:
32
- job_id: str
33
- harness: str
34
- status: str
35
- status_reason: Optional[str]
36
- runtime_sec: float
37
- idle_sec: Optional[float]
38
- operations: int
39
- start_time: Optional[str]
40
- logs: Optional[str] = None
41
- pid: Optional[int] = None
42
-
43
-
44
- def get_jobs() -> list[JobInfo]:
45
- """Fetch current job list via agent_job.py"""
46
- script_dir = Path(__file__).parent
47
- agent_job = script_dir / "agent_job.py"
48
-
49
- try:
50
- result = subprocess.run(
51
- ["uv", "run", str(agent_job), "list"],
52
- capture_output=True,
53
- text=True,
54
- timeout=10,
55
- )
56
- if result.returncode != 0:
57
- return []
58
-
59
- jobs_data = json.loads(result.stdout)
60
- jobs = []
61
- for job in jobs_data:
62
- jobs.append(JobInfo(
63
- job_id=job.get("job_id", ""),
64
- harness=job.get("harness", ""),
65
- status=job.get("status", "unknown"),
66
- status_reason=job.get("status_reason"),
67
- runtime_sec=job.get("runtime_sec", 0),
68
- idle_sec=job.get("idle_sec"),
69
- operations=job.get("operations", 0),
70
- start_time=job.get("start_time"),
71
- logs=job.get("logs"),
72
- pid=job.get("pid"),
73
- ))
74
- return jobs
75
- except Exception:
76
- return []
77
-
78
-
79
- def read_log_file(log_path: str) -> list[str]:
80
- """Read log file and return lines"""
81
- try:
82
- with open(log_path, "r") as f:
83
- return f.readlines()
84
- except Exception:
85
- return ["(Unable to read log file)"]
86
-
87
-
88
- def get_job_status(job_id: str) -> Optional[dict]:
89
- """Fetch detailed status for a single job"""
90
- script_dir = Path(__file__).parent
91
- agent_job = script_dir / "agent_job.py"
92
-
93
- try:
94
- result = subprocess.run(
95
- ["uv", "run", str(agent_job), "status", job_id],
96
- capture_output=True,
97
- text=True,
98
- timeout=10,
99
- )
100
- if result.returncode != 0:
101
- return None
102
- return json.loads(result.stdout)
103
- except Exception:
104
- return None
105
-
106
-
107
- def kill_job(pid: int) -> bool:
108
- """Kill a job by PID. Returns True if successful."""
109
- if not pid:
110
- return False
111
- try:
112
- os.kill(pid, signal.SIGTERM)
113
- # Give it a moment, then force kill if still running
114
- time.sleep(0.5)
115
- try:
116
- os.kill(pid, 0) # Check if still running
117
- os.kill(pid, signal.SIGKILL) # Force kill
118
- except ProcessLookupError:
119
- pass # Already dead
120
- return True
121
- except ProcessLookupError:
122
- return True # Already dead
123
- except PermissionError:
124
- return False
125
- except Exception:
126
- return False
127
-
128
-
129
- def format_duration(seconds: float) -> str:
130
- """Format seconds as human-readable duration"""
131
- if seconds < 60:
132
- return f"{seconds:.1f}s"
133
- elif seconds < 3600:
134
- mins = int(seconds // 60)
135
- secs = int(seconds % 60)
136
- return f"{mins}m{secs:02d}s"
137
- else:
138
- hours = int(seconds // 3600)
139
- mins = int((seconds % 3600) // 60)
140
- return f"{hours}h{mins:02d}m"
141
-
142
-
143
- def format_time_ago(iso_time: Optional[str]) -> str:
144
- """Format ISO time as 'X ago'"""
145
- if not iso_time:
146
- return "?"
147
-
148
- try:
149
- if iso_time.endswith("Z"):
150
- iso_time = iso_time[:-1] + "+00:00"
151
- dt = datetime.fromisoformat(iso_time)
152
- now = datetime.now(timezone.utc)
153
- delta = (now - dt).total_seconds()
154
- return format_duration(delta) + " ago"
155
- except Exception:
156
- return "?"
157
-
158
-
159
- def truncate(s: str, max_len: int) -> str:
160
- """Truncate string with ellipsis if too long"""
161
- if len(s) <= max_len:
162
- return s
163
- return s[:max_len-1] + "…"
164
-
165
-
166
- def wrap_line(line: str, width: int, max_lines: int = 5) -> list[str]:
167
- """Wrap a line to fit within width, max number of wrapped lines.
168
-
169
- Returns a list of display lines. If content exceeds max_lines,
170
- the last line is truncated with ellipsis.
171
- """
172
- line = line.rstrip('\n\r')
173
- if not line:
174
- return [""]
175
-
176
- if len(line) <= width:
177
- return [line]
178
-
179
- wrapped = []
180
- remaining = line
181
-
182
- while remaining and len(wrapped) < max_lines:
183
- if len(remaining) <= width:
184
- wrapped.append(remaining)
185
- remaining = ""
186
- else:
187
- # Check if this will be the last allowed line
188
- if len(wrapped) == max_lines - 1:
189
- # Truncate with ellipsis
190
- wrapped.append(remaining[:width-1] + "…")
191
- remaining = ""
192
- else:
193
- wrapped.append(remaining[:width])
194
- remaining = remaining[width:]
195
-
196
- return wrapped if wrapped else [""]
197
-
198
-
199
- class Monitor:
200
- def __init__(self, stdscr, interval: float = 2.0):
201
- self.stdscr = stdscr
202
- self.interval = interval
203
- self.jobs: list[JobInfo] = []
204
- self.selected_idx = 0
205
- self.scroll_offset = 0
206
- self.show_log = False
207
- self.log_job: Optional[JobInfo] = None
208
- self.log_lines: list[str] = []
209
- self.log_scroll = 0
210
- self.log_display_line_count = 0 # Cached count of wrapped display lines
211
-
212
- # Setup colors
213
- curses.start_color()
214
- curses.use_default_colors()
215
- curses.init_pair(1, curses.COLOR_GREEN, -1) # complete
216
- curses.init_pair(2, curses.COLOR_YELLOW, -1) # running
217
- curses.init_pair(3, curses.COLOR_RED, -1) # error/timeout
218
- curses.init_pair(4, curses.COLOR_CYAN, -1) # header
219
- curses.init_pair(5, curses.COLOR_MAGENTA, -1) # selected
220
- curses.init_pair(6, curses.COLOR_WHITE, -1) # normal
221
-
222
- # Hide cursor
223
- curses.curs_set(0)
224
-
225
- # Non-blocking input
226
- self.stdscr.nodelay(True)
227
- self.stdscr.keypad(True)
228
-
229
- def get_status_color(self, status: str) -> int:
230
- """Get color pair for status"""
231
- if status == "complete":
232
- return curses.color_pair(1)
233
- elif status == "running":
234
- return curses.color_pair(2)
235
- elif status in ("error", "timeout"):
236
- return curses.color_pair(3)
237
- return curses.color_pair(6)
238
-
239
- def get_status_indicator(self, status: str) -> str:
240
- """Get status indicator symbol"""
241
- if status == "complete":
242
- return "✓"
243
- elif status == "running":
244
- return "●"
245
- elif status == "error":
246
- return "✗"
247
- elif status == "timeout":
248
- return "⏱"
249
- return "?"
250
-
251
- def draw_header(self):
252
- """Draw the header bar"""
253
- height, width = self.stdscr.getmaxyx()
254
-
255
- # Title
256
- title = " Agent Job Monitor "
257
- now = datetime.now().strftime("%H:%M:%S")
258
- header = f"{'─' * 2}{title}{'─' * (width - len(title) - len(now) - 6)} {now} ─"
259
-
260
- self.stdscr.attron(curses.color_pair(4) | curses.A_BOLD)
261
- self.stdscr.addnstr(0, 0, header, width - 1)
262
- self.stdscr.attroff(curses.color_pair(4) | curses.A_BOLD)
263
-
264
- def draw_column_headers(self, y: int):
265
- """Draw column headers"""
266
- height, width = self.stdscr.getmaxyx()
267
-
268
- # Column layout
269
- headers = f"{'ST':<3} {'HARNESS':<7} {'PID':<7} {'JOB ID':<20} {'STATUS':<26} {'STARTED':<10} {'RUNTIME':<8} {'OPS':<4}"
270
-
271
- self.stdscr.attron(curses.color_pair(4) | curses.A_REVERSE)
272
- self.stdscr.addnstr(y, 0, headers.ljust(width - 1), width - 1)
273
- self.stdscr.attroff(curses.color_pair(4) | curses.A_REVERSE)
274
-
275
- def draw_job_row(self, y: int, job: JobInfo, selected: bool):
276
- """Draw a single job row"""
277
- height, width = self.stdscr.getmaxyx()
278
-
279
- indicator = self.get_status_indicator(job.status)
280
- color = self.get_status_color(job.status)
281
-
282
- # Build status reason display
283
- reason = job.status_reason or job.status
284
- if job.status == "running" and job.idle_sec is not None:
285
- reason = f"{reason} (idle {format_duration(job.idle_sec)})"
286
-
287
- # Format start time as HH:MM:SS
288
- start_display = "?"
289
- if job.start_time:
290
- try:
291
- # Parse ISO time and format as local time
292
- ts = job.start_time
293
- if ts.endswith("Z"):
294
- ts = ts[:-1] + "+00:00"
295
- dt = datetime.fromisoformat(ts)
296
- start_display = dt.strftime("%H:%M:%S")
297
- except Exception:
298
- start_display = "?"
299
-
300
- # Format PID
301
- pid_display = str(job.pid) if job.pid else "?"
302
-
303
- # Format row
304
- job_id_short = job.job_id[:18] if len(job.job_id) > 18 else job.job_id
305
- row = f" {indicator} {job.harness:<7} {pid_display:<7} {job_id_short:<20} {truncate(reason, 24):<26} {start_display:<10} {format_duration(job.runtime_sec):<8} {job.operations:<4}"
306
-
307
- if selected:
308
- self.stdscr.attron(curses.color_pair(5) | curses.A_REVERSE)
309
- self.stdscr.addnstr(y, 0, row.ljust(width - 1), width - 1)
310
- self.stdscr.attroff(curses.color_pair(5) | curses.A_REVERSE)
311
- else:
312
- self.stdscr.attron(color)
313
- self.stdscr.addnstr(y, 0, row.ljust(width - 1), width - 1)
314
- self.stdscr.attroff(color)
315
-
316
- def draw_job_list(self):
317
- """Draw the job list"""
318
- height, width = self.stdscr.getmaxyx()
319
-
320
- # Draw column headers
321
- self.draw_column_headers(2)
322
-
323
- # Calculate visible area
324
- list_start = 3
325
- list_height = height - 5 # Leave room for header, columns, and footer
326
-
327
- # Adjust scroll offset
328
- if self.selected_idx < self.scroll_offset:
329
- self.scroll_offset = self.selected_idx
330
- elif self.selected_idx >= self.scroll_offset + list_height:
331
- self.scroll_offset = self.selected_idx - list_height + 1
332
-
333
- # Draw visible jobs
334
- for i, job in enumerate(self.jobs[self.scroll_offset:self.scroll_offset + list_height]):
335
- actual_idx = i + self.scroll_offset
336
- y = list_start + i
337
- if y >= height - 2:
338
- break
339
- self.draw_job_row(y, job, actual_idx == self.selected_idx)
340
-
341
- # Show if more items above/below
342
- if self.scroll_offset > 0:
343
- self.stdscr.addstr(list_start, width - 3, "▲")
344
- if self.scroll_offset + list_height < len(self.jobs):
345
- self.stdscr.addstr(min(list_start + list_height - 1, height - 3), width - 3, "▼")
346
-
347
- def draw_log_view(self):
348
- """Draw scrollable log view for selected job"""
349
- if not self.log_job:
350
- return
351
-
352
- height, width = self.stdscr.getmaxyx()
353
-
354
- # Header with job info
355
- job = self.log_job
356
- indicator = self.get_status_indicator(job.status)
357
- color = self.get_status_color(job.status)
358
-
359
- # Job header line
360
- self.stdscr.attron(curses.A_BOLD)
361
- header = f" {indicator} {job.harness} | {job.job_id} | {job.status_reason or job.status} | {format_duration(job.runtime_sec)}"
362
- self.stdscr.addnstr(2, 0, header, width - 1)
363
- self.stdscr.attroff(curses.A_BOLD)
364
-
365
- # Log file path
366
- self.stdscr.attron(curses.color_pair(4))
367
- self.stdscr.addnstr(3, 0, f" Log: {job.logs or '?'}", width - 1)
368
- self.stdscr.attroff(curses.color_pair(4))
369
-
370
- # Separator
371
- self.stdscr.addnstr(4, 0, "─" * (width - 1), width - 1)
372
-
373
- # Log content area
374
- log_start = 5
375
- log_height = height - log_start - 2
376
- content_width = width - 4 # Leave margin for wrap indicator
377
-
378
- # Build display lines (wrapped) from source lines
379
- display_lines = []
380
- for source_line in self.log_lines:
381
- wrapped = wrap_line(source_line, content_width, max_lines=5)
382
- display_lines.extend(wrapped)
383
-
384
- # Cache display line count for scroll handling
385
- self.log_display_line_count = len(display_lines)
386
-
387
- # Clamp scroll to display lines
388
- max_scroll = max(0, len(display_lines) - log_height)
389
- self.log_scroll = max(0, min(self.log_scroll, max_scroll))
390
-
391
- # Draw display lines
392
- visible_lines = display_lines[self.log_scroll:self.log_scroll + log_height]
393
- for i, line in enumerate(visible_lines):
394
- y = log_start + i
395
- if y >= height - 2:
396
- break
397
- self.stdscr.addnstr(y, 1, line, width - 2)
398
-
399
- # Scroll indicators
400
- if self.log_scroll > 0:
401
- self.stdscr.attron(curses.color_pair(4))
402
- self.stdscr.addstr(log_start, width - 2, "▲")
403
- self.stdscr.attroff(curses.color_pair(4))
404
- if self.log_scroll + log_height < len(display_lines):
405
- self.stdscr.attron(curses.color_pair(4))
406
- self.stdscr.addstr(min(log_start + log_height - 1, height - 3), width - 2, "▼")
407
- self.stdscr.attroff(curses.color_pair(4))
408
-
409
- # Line count
410
- total = len(display_lines)
411
- showing = f"Lines {self.log_scroll + 1}-{min(self.log_scroll + log_height, total)} of {total}"
412
- self.stdscr.attron(curses.color_pair(4))
413
- self.stdscr.addnstr(height - 2, width - len(showing) - 2, showing, len(showing))
414
- self.stdscr.attroff(curses.color_pair(4))
415
-
416
- def draw_footer(self):
417
- """Draw the footer with help"""
418
- height, width = self.stdscr.getmaxyx()
419
-
420
- # Summary
421
- running = sum(1 for j in self.jobs if j.status == "running")
422
- complete = sum(1 for j in self.jobs if j.status == "complete")
423
- failed = sum(1 for j in self.jobs if j.status in ("error", "timeout"))
424
-
425
- summary = f" Jobs: {len(self.jobs)} total, {running} running, {complete} complete, {failed} failed "
426
-
427
- # Help
428
- if self.show_log:
429
- help_text = " [↑↓/PgUp/PgDn] Scroll [r] Reload [ESC] Back [q] Quit "
430
- else:
431
- help_text = " [↑↓] Select [Enter] Log [x] Kill [r] Refresh [q] Quit "
432
-
433
- # Draw footer bar
434
- footer = f"{summary}{'─' * (width - len(summary) - len(help_text) - 2)}{help_text}"
435
-
436
- self.stdscr.attron(curses.color_pair(4))
437
- self.stdscr.addnstr(height - 1, 0, footer, width - 1)
438
- self.stdscr.attroff(curses.color_pair(4))
439
-
440
- def draw(self):
441
- """Draw the full screen"""
442
- self.stdscr.clear()
443
-
444
- self.draw_header()
445
-
446
- if self.show_log:
447
- self.draw_log_view()
448
- else:
449
- self.draw_job_list()
450
-
451
- self.draw_footer()
452
-
453
- self.stdscr.refresh()
454
-
455
- def handle_input(self) -> bool:
456
- """Handle keyboard input. Returns False to quit."""
457
- try:
458
- key = self.stdscr.getch()
459
- except Exception:
460
- return True
461
-
462
- if key == -1:
463
- return True
464
-
465
- if key == ord('q') or key == ord('Q'):
466
- return False
467
-
468
- if self.show_log:
469
- # Log view controls
470
- height, _ = self.stdscr.getmaxyx()
471
- log_height = height - 7 # Visible log lines
472
-
473
- if key == 27 or key == curses.KEY_BACKSPACE: # ESC or Backspace
474
- self.show_log = False
475
- self.log_job = None
476
- self.log_lines = []
477
- self.log_scroll = 0
478
- elif key == curses.KEY_UP or key == ord('k'):
479
- self.log_scroll = max(0, self.log_scroll - 1)
480
- elif key == curses.KEY_DOWN or key == ord('j'):
481
- self.log_scroll += 1
482
- elif key == curses.KEY_PPAGE: # Page Up
483
- self.log_scroll = max(0, self.log_scroll - log_height)
484
- elif key == curses.KEY_NPAGE: # Page Down
485
- self.log_scroll += log_height
486
- elif key == curses.KEY_HOME:
487
- self.log_scroll = 0
488
- elif key == curses.KEY_END:
489
- self.log_scroll = max(0, self.log_display_line_count - log_height)
490
- elif key == ord('r') or key == ord('R'):
491
- # Reload log file
492
- if self.log_job and self.log_job.logs:
493
- self.log_lines = read_log_file(self.log_job.logs)
494
- else:
495
- # Job list controls
496
- if key == curses.KEY_UP or key == ord('k'):
497
- self.selected_idx = max(0, self.selected_idx - 1)
498
- elif key == curses.KEY_DOWN or key == ord('j'):
499
- if self.selected_idx < len(self.jobs) - 1:
500
- self.selected_idx += 1
501
- elif key == ord('\n') or key == curses.KEY_ENTER:
502
- if self.jobs and 0 <= self.selected_idx < len(self.jobs):
503
- job = self.jobs[self.selected_idx]
504
- self.show_log = True
505
- self.log_job = job
506
- self.log_scroll = 0
507
- if job.logs:
508
- self.log_lines = read_log_file(job.logs)
509
- else:
510
- self.log_lines = ["(No log file available)"]
511
- elif key == ord('r') or key == ord('R'):
512
- self.jobs = get_jobs()
513
- elif key == ord('x') or key == ord('X'):
514
- # Kill selected job
515
- if self.jobs and 0 <= self.selected_idx < len(self.jobs):
516
- job = self.jobs[self.selected_idx]
517
- if job.pid and job.status == "running":
518
- kill_job(job.pid)
519
- # Refresh job list after kill
520
- time.sleep(0.2)
521
- self.jobs = get_jobs()
522
- elif key == curses.KEY_HOME:
523
- self.selected_idx = 0
524
- elif key == curses.KEY_END:
525
- self.selected_idx = max(0, len(self.jobs) - 1)
526
-
527
- return True
528
-
529
- def run(self):
530
- """Main loop"""
531
- last_refresh = 0
532
-
533
- while True:
534
- # Refresh data periodically
535
- now = time.time()
536
- if now - last_refresh >= self.interval:
537
- self.jobs = get_jobs()
538
- # Clamp selected index
539
- if self.jobs:
540
- self.selected_idx = min(self.selected_idx, len(self.jobs) - 1)
541
- else:
542
- self.selected_idx = 0
543
- last_refresh = now
544
-
545
- # Draw
546
- self.draw()
547
-
548
- # Handle input
549
- if not self.handle_input():
550
- break
551
-
552
- # Small sleep to reduce CPU usage
553
- time.sleep(0.05)
554
-
555
-
556
- def main(stdscr, interval: float):
557
- """Main entry point for curses"""
558
- monitor = Monitor(stdscr, interval)
559
- monitor.run()
560
-
561
-
562
- if __name__ == "__main__":
563
- parser = argparse.ArgumentParser(description="Agent Job Monitor TUI")
564
- parser.add_argument(
565
- "--interval", "-i",
566
- type=float,
567
- default=2.0,
568
- help="Refresh interval in seconds (default: 2.0)"
569
- )
570
- args = parser.parse_args()
571
-
572
- try:
573
- curses.wrapper(lambda stdscr: main(stdscr, args.interval))
574
- except KeyboardInterrupt:
575
- pass
@@ -1,73 +0,0 @@
1
-
2
-
3
-
4
- To deliver on your Assignment/Task You must manage and maintain your approach plan dynamically, refine your plan after every decision, and when new information presents itself.
5
- Populate your initial plan with your step by step WORKFLOW:
6
-
7
- [WORKFLOW]
8
- 🤝 Periodically Inbox Check after every few action, and dynamically add TEAMWORK Broadcast as per Communication Protocols 🤝
9
-
10
- 1. Consume AGENT OPERATING PROCEDURES (AOP) `.agents/AGENTS.md`. *You will execute 1 or more of Procedures in the following steps*
11
- 2. ALWAYS Execute AOP.CALIBRATE
12
- 3. *Decision:* Based on your Assignment/Task/Loop you may be either Implementing New, Assessing Only, or Review+Refine Existing. Choose your path accordingly:
13
- - *Implementing New:* Execute AOP.IMPLEMENT
14
- - *Assessing Only:* Execute AOP.ASSESS
15
- - *Review+Refine Existing:* Execute AOP.ASSESS then AOP.IMPLEMENT
16
- 4. Execute AOP.VALIDATE
17
- 5. Decision: If there are any failures or errors from AOP.VALIDATE: Loop through steps 3 & 4 Review+Refine Existing (AOP ASSESS -> IMPLEMENT -> VALIDATE). Continue itterating until 100% pass/green. *skip this loop step for Assessing Only path*
18
- 6. Finally, document implementation and/or assessment in `docs/project/phases/<phase-id>/`
19
-
20
-
21
- *Remember:* 🤝 Broadcast ASAP when you discover, before making decisions, and immidiatly after new teammate message recieved if you have critical feedback
22
-
23
- [/WORKFLOW]
24
-
25
-
26
-
27
- [TEAMWORK]
28
- You are part of a cross-disciplined team, and concurrently working with team-mates toward a common objective. Team communication is critical for success.
29
- You can Broadcast to and Check messages from your team-mates.
30
- You MUST promptly Broadcast information that may impact their trajectory, and Inbox Check for new Broadcasts from your team-mates frequently.
31
-
32
- 🤝 Communication Protocols
33
-
34
- **Inbox Check:**
35
- - After every few reads, before every write, and definitely before making a solution approach decision, run an Inbox Check command
36
- - If you don't Inbox Check frequently, you may be missing critical context from your team-mates!
37
- - PONDER every message recieved from your team-mates. Does it contradict, support, or suppliment your mental model? Should you change you approach?
38
- - Read source reference files provided when relevant to your task, to verify your team-mate's claims. Do this before deciding to change/adapt your approach based on message context.
39
- - If the verification proves your team-mate incorrect, you must IMMEDIATLY Broadcast feedback with reference files as proof.
40
-
41
- Inbox Check Command (run this in shell):
42
- ```
43
- uv run .claude/hooks/comms/get_unread_messages.py --name "YourAgentName"
44
- ```
45
- Replace "YourAgentName" with your actual agent name from your assignment.
46
-
47
- **Broadcast:**
48
- Keep your Broadcasts consice, unambiguous, and factually grounded in context you have gathered while operating.
49
-
50
- You MUST Broadcast:
51
- - Learnings from external research after searching web or using perplexity ask.
52
- - System relationships, patterns and issues you have discoverd through deep codebase analysis. Include file references
53
- - Decisions you make about your solution approach. Include your rationalle
54
- - Change summary after implmenting, documenting, fixing, or writing to file(s). Include purpose of change and file references
55
- - Status of lint, build, test, dev after running any of these commands. Detail failures and your suspected cause.
56
- - When you encounter an issue, Broadcast after each step in the fix cycle. initial issue, fix attempt, outcome, additional fix cycle loops.
57
- - Critical Feedback to teammate Broadcasts when their system understanding, decisions, approach, or changes, conflict with your mental model of the system or project requirements, will introduce issues, or have broader implications. Include file references as proof
58
-
59
-
60
- Broadcast Command (run this in shell):
61
- ```
62
- uv run .claude/hooks/comms/send_message.py --sender "YourAgentName" --message "Your message content"
63
- ```
64
- Replace "YourAgentName" with your actual agent name and "Your message content" with your broadcast.
65
-
66
- *Broadcast ASAP when you discover, before making decisions, and immidiatly to provide critical feedback to teammates*
67
-
68
- If your Team Role is support:
69
- 1. Monitor inbox for architectural and testing questions from engineers
70
- 2. Sleep 60 seconds, then Inbox Check. Loop until no new messages for 5 sequential checks.
71
- 3. When a new message is recieved, review, analyse, assess then Broadcast feedback.
72
-
73
- [/TEAMWORK]