@@ -38,6 +38,80 @@ def first(payload: dict, *keys, default=None):
3838 return default
3939
4040
41+ def coerce_line (value ):
42+ try :
43+ n = int (value )
44+ return n if n > 0 else None
45+ except Exception :
46+ return None
47+
48+
49+ def _add_line_range (out : set , start , end = None ):
50+ start_n = coerce_line (start )
51+ end_n = coerce_line (end if end is not None else start )
52+ if start_n is None or end_n is None :
53+ return
54+ for ln in range (min (start_n , end_n ), max (start_n , end_n ) + 1 ):
55+ out .add (ln )
56+
57+
58+ def _extract_line_from_position (value ):
59+ if isinstance (value , dict ):
60+ return first (value , "line" , "lineNumber" , "line_number" )
61+ return value
62+
63+
64+ def _add_lines_from_range_object (out : set , value ):
65+ if not isinstance (value , dict ):
66+ return
67+
68+ start = first (value , "startLine" , "start_line" , "line_start" , "line" , "lineNumber" , "line_number" )
69+ end = first (value , "endLine" , "end_line" , "line_end" , default = start )
70+ if start is not None :
71+ _add_line_range (out , start , end )
72+ return
73+
74+ start_pos = _extract_line_from_position (value .get ("start" ))
75+ end_pos = _extract_line_from_position (value .get ("end" ))
76+ if start_pos is not None :
77+ _add_line_range (out , start_pos , end_pos if end_pos is not None else start_pos )
78+
79+
80+ def extract_lines (value ):
81+ """Extract 1-indexed line numbers from common Cursor hook payload shapes."""
82+ out = set ()
83+ if isinstance (value , list ):
84+ for item in value :
85+ if isinstance (item , dict ):
86+ _add_lines_from_range_object (out , item )
87+ else :
88+ line = coerce_line (item )
89+ if line is not None :
90+ out .add (line )
91+ elif isinstance (value , dict ):
92+ _add_lines_from_range_object (out , value )
93+ else :
94+ line = coerce_line (value )
95+ if line is not None :
96+ out .add (line )
97+ return sorted (out )
98+
99+
100+ def extract_lines_from_changes (changes ):
101+ out = set ()
102+ if not isinstance (changes , list ):
103+ return []
104+ for change in changes :
105+ if not isinstance (change , dict ):
106+ continue
107+ before = len (out )
108+ _add_lines_from_range_object (out , change )
109+ if len (out ) == before :
110+ for key in ("range" , "newRange" , "new_range" , "selection" , "targetSelection" ):
111+ _add_lines_from_range_object (out , change .get (key ))
112+ return sorted (out )
113+
114+
41115def find_repo_root (cwd : str ) -> str :
42116 try :
43117 result = subprocess .run (
@@ -260,20 +334,12 @@ def main():
260334
261335 # Line numbers from payload
262336 if event_name == "afterFileEdit" :
263- old_lines = first (payload , "old_lines" , "oldLines" , default = [])
264- new_lines = first (payload , "new_lines" , "newLines" , default = [])
265- if not isinstance (old_lines , list ):
266- old_lines = []
267- if not isinstance (new_lines , list ):
268- new_lines = []
269- if not new_lines and isinstance (payload .get ("changes" ), list ):
270- for ch in payload ["changes" ]:
271- if not isinstance (ch , dict ):
272- continue
273- start = ch .get ("startLine" ) or ch .get ("line_start" )
274- end = ch .get ("endLine" ) or ch .get ("line_end" ) or start
275- if isinstance (start , int ) and isinstance (end , int ):
276- new_lines .extend (list (range (min (start , end ), max (start , end ) + 1 )))
337+ old_lines = extract_lines (first (payload , "old_lines" , "oldLines" , default = []))
338+ new_lines = extract_lines (first (payload , "new_lines" , "newLines" , "changed_lines" , "changedLines" , "lines" , default = []))
339+ if not new_lines :
340+ new_lines = extract_lines_from_changes (payload .get ("changes" ))
341+ if not new_lines :
342+ new_lines = extract_lines_from_changes (payload .get ("edits" ))
277343
278344 entry ["lines" ] = new_lines if new_lines else old_lines
279345 debug_log (
0 commit comments