1919import os
2020import sys
2121import firebase_github # Assumes firebase_github.py is in the same directory or python path
22+ import datetime
23+ from datetime import timezone , timedelta
24+
2225
2326# Attempt to configure logging for firebase_github if absl is available
2427try :
@@ -33,7 +36,7 @@ def main():
3336 default_repo = firebase_github .REPO
3437
3538 parser = argparse .ArgumentParser (
36- description = "Fetch review comments from a GitHub PR and format for use with Jules ." ,
39+ description = "Fetch review comments from a GitHub PR and format into a simple text output ." ,
3740 formatter_class = argparse .RawTextHelpFormatter
3841 )
3942 parser .add_argument (
@@ -63,7 +66,7 @@ def main():
6366 parser .add_argument (
6467 "--context-lines" ,
6568 type = int ,
66- default = 10 , # Default to 10 lines, 0 means full hunk.
69+ default = 10 ,
6770 help = "Number of context lines from the diff hunk. Use 0 for the full hunk. "
6871 "If > 0, shows the last N lines of the hunk. Default: 10."
6972 )
@@ -76,7 +79,7 @@ def main():
7679 parser .add_argument (
7780 "--skip-outdated" ,
7881 action = "store_true" ,
79- help = "If set, outdated comments will not be printed."
82+ help = "If set, comments marked [OUTDATED] or [FULLY_OUTDATED] will not be printed."
8083 )
8184
8285 args = parser .parse_args ()
@@ -90,13 +93,13 @@ def main():
9093 if not firebase_github .set_repo_url (repo_url ):
9194 sys .stderr .write (f"Error: Invalid repo URL: { args .owner } /{ args .repo } . Expected https://github.com/owner/repo\n " )
9295 sys .exit (1 )
93- print (f"Targeting repository: { firebase_github .OWNER } /{ firebase_github .REPO } " , file = sys . stderr )
96+ sys . stderr . write (f"Targeting repository: { firebase_github .OWNER } /{ firebase_github .REPO } \n " )
9497
95- print (f"Fetching comments for PR #{ args .pull_number } from { firebase_github .OWNER } /{ firebase_github .REPO } ..." , file = sys . stderr )
98+ sys . stderr . write (f"Fetching comments for PR #{ args .pull_number } from { firebase_github .OWNER } /{ firebase_github .REPO } ...\n " )
9699 if args .since :
97- print (f"Filtering comments created since: { args .since } " , file = sys . stderr )
100+ sys . stderr . write (f"Filtering comments created since: { args .since } \n " )
98101 if args .skip_outdated :
99- print ("Skipping outdated comments." , file = sys . stderr )
102+ sys . stderr . write ("Skipping outdated comments based on status. \n " )
100103
101104
102105 comments = firebase_github .get_pull_request_review_comments (
@@ -106,48 +109,79 @@ def main():
106109 )
107110
108111 if not comments :
109- print (f"No review comments found for PR #{ args .pull_number } (or matching filters), or an error occurred." , file = sys . stderr )
112+ sys . stderr . write (f"No review comments found for PR #{ args .pull_number } (or matching filters), or an error occurred.\n " )
110113 return
111114
115+ latest_created_at_obj = None
112116 print ("\n --- Review Comments ---" )
113117 for comment in comments :
114- # Determine outdated status and effective line for display
115- is_outdated = comment .get ("position" ) is None
116-
117- if args .skip_outdated and is_outdated :
118+ created_at_str = comment .get ("created_at" )
119+
120+ current_pos = comment .get ("position" )
121+ current_line = comment .get ("line" )
122+ original_line = comment .get ("original_line" )
123+
124+ status_text = ""
125+ line_to_display = None
126+ is_effectively_outdated = False
127+
128+ if current_pos is None : # Comment's specific diff context is gone
129+ status_text = "[FULLY_OUTDATED]"
130+ line_to_display = original_line # Show original line if available
131+ is_effectively_outdated = True
132+ elif original_line is not None and current_line != original_line : # Comment on a line that changed
133+ status_text = "[OUTDATED]"
134+ line_to_display = current_line # Show where the comment is now in the diff
135+ is_effectively_outdated = True
136+ else : # Comment is current or a file-level comment (original_line is None but current_pos exists)
137+ status_text = "[CURRENT]"
138+ line_to_display = current_line # For line comments, or None for file comments (handled by fallback)
139+ is_effectively_outdated = False
140+
141+ if line_to_display is None :
142+ line_to_display = "N/A"
143+
144+ if args .skip_outdated and is_effectively_outdated :
118145 continue
119146
120- line_to_display = comment .get ("original_line" ) if is_outdated else comment .get ("line" )
121- # Ensure line_to_display has a fallback if None from both
122- if line_to_display is None : line_to_display = "N/A"
123-
147+ # Update latest timestamp (only for comments that will be printed)
148+ if created_at_str :
149+ try :
150+ # GitHub ISO format "YYYY-MM-DDTHH:MM:SSZ"
151+ # Python <3.11 fromisoformat needs "+00:00" not "Z"
152+ if sys .version_info < (3 , 11 ):
153+ dt_str = created_at_str .replace ("Z" , "+00:00" )
154+ else :
155+ dt_str = created_at_str
156+ current_comment_dt = datetime .datetime .fromisoformat (dt_str )
157+ if latest_created_at_obj is None or current_comment_dt > latest_created_at_obj :
158+ latest_created_at_obj = current_comment_dt
159+ except ValueError :
160+ sys .stderr .write (f"Warning: Could not parse timestamp: { created_at_str } \n " )
124161
125162 user = comment .get ("user" , {}).get ("login" , "Unknown user" )
126163 path = comment .get ("path" , "N/A" )
127-
128164 body = comment .get ("body" , "" ).strip ()
129- if not body : # Skip comments with no actual text body
165+
166+ if not body :
130167 continue
131168
132169 diff_hunk = comment .get ("diff_hunk" )
133170 html_url = comment .get ("html_url" , "N/A" )
134171 comment_id = comment .get ("id" )
135172 in_reply_to_id = comment .get ("in_reply_to_id" )
136- created_at = comment .get ("created_at" )
137-
138- status_text = "[OUTDATED]" if is_outdated else "[CURRENT]"
139173
140- # Start printing comment details
141174 print (f"Comment by: { user } (ID: { comment_id } ){ f' (In Reply To: { in_reply_to_id } )' if in_reply_to_id else '' } " )
142- if created_at :
143- print (f"Timestamp: { created_at } " )
175+ if created_at_str :
176+ print (f"Timestamp: { created_at_str } " )
144177
145178 print (f"Status: { status_text } " )
146179 print (f"File: { path } " )
147180 print (f"Line in File Diff: { line_to_display } " )
148181 print (f"URL: { html_url } " )
149182
150183 print ("--- Diff Hunk Context ---" )
184+ print ("```" ) # Start of Markdown code block
151185 if diff_hunk and diff_hunk .strip ():
152186 hunk_lines = diff_hunk .split ('\n ' )
153187 if args .context_lines == 0 : # User wants the full hunk
@@ -157,14 +191,36 @@ def main():
157191 actual_lines_to_print = hunk_lines [- lines_to_print_count :]
158192 for line_content in actual_lines_to_print :
159193 print (line_content )
160- # If context_lines < 0, argparse should ideally prevent this or it's handled by default type int.
161- # No explicit handling here means it might behave unexpectedly or error if not positive/zero.
162194 else :
163195 print ("(No diff hunk available for this comment)" )
196+ print ("```" ) # End of Markdown code block
164197
165198 print ("--- Comment ---" )
166199 print (body )
167200 print ("----------------------------------------\n " )
168201
202+ if latest_created_at_obj :
203+ try :
204+ # Ensure it's UTC before adding timedelta, then format
205+ next_since_dt = latest_created_at_obj .astimezone (timezone .utc ) + timedelta (seconds = 1 )
206+ next_since_str = next_since_dt .strftime ('%Y-%m-%dT%H:%M:%SZ' )
207+
208+ new_cmd_args = [sys .argv [0 ]]
209+ skip_next_arg = False
210+ for i in range (1 , len (sys .argv )):
211+ if skip_next_arg :
212+ skip_next_arg = False
213+ continue
214+ if sys .argv [i ] == "--since" :
215+ skip_next_arg = True
216+ continue
217+ new_cmd_args .append (sys .argv [i ])
218+
219+ new_cmd_args .extend (["--since" , next_since_str ])
220+ suggested_cmd = " " .join (new_cmd_args )
221+ sys .stderr .write (f"\n To get comments created after the last one in this batch, try:\n { suggested_cmd } \n " )
222+ except Exception as e :
223+ sys .stderr .write (f"\n Warning: Could not generate next command suggestion: { e } \n " )
224+
169225if __name__ == "__main__" :
170226 main ()
0 commit comments