@@ -17,10 +17,7 @@ def run_gh_command(self, args):
1717 """Run GitHub CLI command and return output"""
1818 try :
1919 result = subprocess .run (
20- ["gh" ] + args ,
21- capture_output = True ,
22- text = True ,
23- check = True
20+ ["gh" ] + args , capture_output = True , text = True , check = True
2421 )
2522 return result .stdout .strip ()
2623 except subprocess .CalledProcessError as e :
@@ -32,18 +29,26 @@ def run_gh_command(self, args):
3229
3330 def get_assigned_issues (self ):
3431 """Get all assigned conductor task issues"""
35- output = self .run_gh_command ([
36- "issue" , "list" ,
37- "-l" , "conductor:task" ,
38- "--state" , "open" ,
39- "--assignee" , "*" , # Issues with any assignee
40- "--limit" , "1000" ,
41- "--json" , "number,title,assignees,labels"
42- ])
43-
32+ output = self .run_gh_command (
33+ [
34+ "issue" ,
35+ "list" ,
36+ "-l" ,
37+ "conductor:task" ,
38+ "--state" ,
39+ "open" ,
40+ "--assignee" ,
41+ "*" , # Issues with any assignee
42+ "--limit" ,
43+ "1000" ,
44+ "--json" ,
45+ "number,title,assignees,labels" ,
46+ ]
47+ )
48+
4449 if not output :
4550 return []
46-
51+
4752 try :
4853 return json .loads (output )
4954 except json .JSONDecodeError :
@@ -52,35 +57,44 @@ def get_assigned_issues(self):
5257 def check_issue_staleness (self , issue_number ):
5358 """Check if an issue is stale based on comment activity"""
5459 # Get recent comments
55- comments_output = self .run_gh_command ([
56- "issue" , "view" , str (issue_number ),
57- "--json" , "comments" ,
58- "--jq" , '.comments[-10:] | reverse | .[]' # Last 10 comments, newest first
59- ])
60-
60+ comments_output = self .run_gh_command (
61+ [
62+ "issue" ,
63+ "view" ,
64+ str (issue_number ),
65+ "--json" ,
66+ "comments" ,
67+ "--jq" ,
68+ ".comments[-10:] | reverse | .[]" , # Last 10 comments, newest first
69+ ]
70+ )
71+
6172 if not comments_output :
6273 return True , None , None # Consider stale if no comments
63-
74+
6475 current_time = datetime .utcnow ()
6576 latest_activity = None
6677 agent_info = None
67-
78+
6879 # Parse comments to find latest activity
69- for line in comments_output .strip ().split (' \n ' ):
80+ for line in comments_output .strip ().split (" \n " ):
7081 if line :
7182 try :
7283 comment = json .loads (line )
7384 body = comment .get ("body" , "" )
74-
85+
7586 # Check for agent activity (heartbeat, progress, etc.)
76- if any (marker in body for marker in ["Agent Claimed Task" , "Heartbeat" , "Progress" ]):
87+ if any (
88+ marker in body
89+ for marker in ["Agent Claimed Task" , "Heartbeat" , "Progress" ]
90+ ):
7791 comment_time = datetime .fromisoformat (
7892 comment ["createdAt" ].replace ("Z" , "+00:00" )
7993 ).replace (tzinfo = None )
80-
94+
8195 if not latest_activity or comment_time > latest_activity :
8296 latest_activity = comment_time
83-
97+
8498 # Try to extract agent info
8599 if "```json" in body :
86100 json_start = body .find ("```json" ) + 7
@@ -93,40 +107,50 @@ def check_issue_staleness(self, issue_number):
93107 pass
94108 except (json .JSONDecodeError , KeyError ):
95109 continue
96-
110+
97111 if latest_activity :
98112 is_stale = (current_time - latest_activity ) > self .timeout
99113 return is_stale , latest_activity , agent_info
100-
114+
101115 return True , None , None # Stale if no activity found
102116
103117 def clean_stale_issue (self , issue ):
104118 """Clean up a stale issue by unassigning and removing in-progress label"""
105119 issue_number = issue ["number" ]
106120 issue_title = issue ["title" ]
107-
121+
108122 # Check staleness
109123 is_stale , last_activity , agent_id = self .check_issue_staleness (issue_number )
110-
124+
111125 if not is_stale :
112126 return False
113-
127+
114128 print (f"🧹 Cleaning stale issue #{ issue_number } : { issue_title } " )
115-
129+
116130 # Remove all assignees
117131 if issue .get ("assignees" ):
118132 for assignee in issue ["assignees" ]:
119- self .run_gh_command ([
120- "issue" , "edit" , str (issue_number ),
121- "--remove-assignee" , assignee ["login" ]
122- ])
123-
133+ self .run_gh_command (
134+ [
135+ "issue" ,
136+ "edit" ,
137+ str (issue_number ),
138+ "--remove-assignee" ,
139+ assignee ["login" ],
140+ ]
141+ )
142+
124143 # Remove in-progress label
125- self .run_gh_command ([
126- "issue" , "edit" , str (issue_number ),
127- "--remove-label" , "conductor:in-progress"
128- ])
129-
144+ self .run_gh_command (
145+ [
146+ "issue" ,
147+ "edit" ,
148+ str (issue_number ),
149+ "--remove-label" ,
150+ "conductor:in-progress" ,
151+ ]
152+ )
153+
130154 # Add comment explaining the cleanup
131155 cleanup_comment = f"""### 🧹 Task Released - Stale Agent
132156
@@ -138,64 +162,76 @@ def clean_stale_issue(self, issue):
138162
139163The task is now available for other agents to claim.
140164"""
141-
142- self .run_gh_command ([
143- "issue" , "comment" , str (issue_number ),
144- "--body" , cleanup_comment
145- ])
146-
147- self .cleaned_agents .append ({
148- "issue_number" : issue_number ,
149- "task" : issue_title ,
150- "agent_id" : agent_id ,
151- "last_activity" : last_activity .isoformat () if last_activity else "Unknown"
152- })
153-
165+
166+ self .run_gh_command (
167+ ["issue" , "comment" , str (issue_number ), "--body" , cleanup_comment ]
168+ )
169+
170+ self .cleaned_agents .append (
171+ {
172+ "issue_number" : issue_number ,
173+ "task" : issue_title ,
174+ "agent_id" : agent_id ,
175+ "last_activity" : (
176+ last_activity .isoformat () if last_activity else "Unknown"
177+ ),
178+ }
179+ )
180+
154181 return True
155182
156183 def clean_stale_work (self ):
157184 """Remove stale agent work from issues"""
158185 print ("🔍 Checking for stale agents..." )
159-
186+
160187 # Get all assigned issues
161188 assigned_issues = self .get_assigned_issues ()
162-
189+
163190 if not assigned_issues :
164191 print ("✅ No assigned tasks found" )
165192 return True
166-
193+
167194 print (f"📋 Found { len (assigned_issues )} assigned tasks" )
168-
195+
169196 # Check each issue for staleness
170197 cleaned_count = 0
171198 for issue in assigned_issues :
172199 if self .clean_stale_issue (issue ):
173200 cleaned_count += 1
174-
201+
175202 if cleaned_count > 0 :
176203 print (f"\n ✅ Cleaned up { cleaned_count } stale tasks" )
177204 for agent in self .cleaned_agents :
178205 print (f" - Issue #{ agent ['issue_number' ]} : { agent ['task' ]} " )
179- print (f" Agent: { agent ['agent_id' ]} , Last activity: { agent ['last_activity' ]} " )
206+ print (
207+ f" Agent: { agent ['agent_id' ]} , Last activity: { agent ['last_activity' ]} "
208+ )
180209 else :
181210 print ("✅ No stale agents found" )
182-
211+
183212 return True
184213
185214 def update_status_issue (self ):
186215 """Update the status issue with cleanup information"""
187216 if not self .cleaned_agents :
188217 return
189-
218+
190219 # Find or create status issue
191- output = self .run_gh_command ([
192- "issue" , "list" ,
193- "-l" , "conductor:status" ,
194- "--state" , "open" ,
195- "--limit" , "1" ,
196- "--json" , "number"
197- ])
198-
220+ output = self .run_gh_command (
221+ [
222+ "issue" ,
223+ "list" ,
224+ "-l" ,
225+ "conductor:status" ,
226+ "--state" ,
227+ "open" ,
228+ "--limit" ,
229+ "1" ,
230+ "--json" ,
231+ "number" ,
232+ ]
233+ )
234+
199235 status_issue_number = None
200236 if output :
201237 try :
@@ -204,7 +240,7 @@ def update_status_issue(self):
204240 status_issue_number = issues [0 ]["number" ]
205241 except json .JSONDecodeError :
206242 pass
207-
243+
208244 if status_issue_number :
209245 # Add cleanup report as comment
210246 cleanup_report = f"""### 🧹 Stale Agent Cleanup Report
@@ -215,16 +251,17 @@ def update_status_issue(self):
215251#### Released Tasks:
216252"""
217253 for agent in self .cleaned_agents :
218- cleanup_report += f"- **Issue #{ agent ['issue_number' ]} **: { agent ['task' ]} \n "
254+ cleanup_report += (
255+ f"- **Issue #{ agent ['issue_number' ]} **: { agent ['task' ]} \n "
256+ )
219257 cleanup_report += f" - Agent: `{ agent ['agent_id' ]} `\n "
220258 cleanup_report += f" - Last activity: { agent ['last_activity' ]} \n "
221-
259+
222260 cleanup_report += "\n ---\n *Automated cleanup by Code-Conductor*"
223-
224- self .run_gh_command ([
225- "issue" , "comment" , str (status_issue_number ),
226- "--body" , cleanup_report
227- ])
261+
262+ self .run_gh_command (
263+ ["issue" , "comment" , str (status_issue_number ), "--body" , cleanup_report ]
264+ )
228265
229266
230267def main ():
@@ -238,27 +275,27 @@ def main():
238275 parser .add_argument (
239276 "--dry-run" ,
240277 action = "store_true" ,
241- help = "Show what would be cleaned without making changes"
278+ help = "Show what would be cleaned without making changes" ,
242279 )
243280
244281 args = parser .parse_args ()
245282
246283 cleaner = StaleCleaner (timeout_minutes = args .timeout )
247-
284+
248285 # Check GitHub CLI authentication
249286 if not cleaner .run_gh_command (["auth" , "status" ]):
250287 print ("❌ GitHub CLI not authenticated. Run 'gh auth login' first." )
251288 sys .exit (1 )
252-
289+
253290 # Run cleanup
254291 success = cleaner .clean_stale_work ()
255-
292+
256293 if success and cleaner .cleaned_agents :
257294 # Update status issue with cleanup report
258295 cleaner .update_status_issue ()
259-
296+
260297 sys .exit (0 if success else 1 )
261298
262299
263300if __name__ == "__main__" :
264- main ()
301+ main ()
0 commit comments