@@ -64,33 +64,25 @@ def _structural_lines(path: Path) -> list[tuple[int, int, str]]:
6464 return out
6565
6666def _push_branches (path : Path ) -> set [str ]:
67- lines = path .read_text (encoding = "utf-8" ).splitlines ()
68- push_index = next (
69- (i for i , line in enumerate (lines ) if line .rstrip () == " push:" ),
70- None ,
71- )
72- if push_index is None :
73- raise ValueError (f"{ path } : missing top-level push trigger" )
74-
75- for line in lines [push_index + 1 :]:
76- if line and not line .startswith (" " ):
77- break
78- stripped = line .strip ()
79- if not stripped .startswith ("branches:" ):
80- continue
81- raw = stripped .split (":" , 1 )[1 ].strip ()
82- if not (raw .startswith ("[" ) and raw .endswith ("]" )):
83- raise ValueError (
84- f"{ path } : agent-trigger contract expects inline branches list, got { raw !r} "
85- )
86- items = []
87- for item in raw [1 :- 1 ].split ("," ):
88- value = item .strip ().strip ('"' ).strip ("'" )
89- if value :
90- items .append (value )
91- return set (items )
92- raise ValueError (f"{ path } : push trigger has no branches list" )
93-
67+ lines = _structural_lines (path )
68+ for i , (_ , indent , stripped ) in enumerate (lines ):
69+ if indent == 2 and stripped == "push:" :
70+ for _ , child_indent , child in lines [i + 1 :]:
71+ if child_indent <= 2 :
72+ break
73+ if child_indent == 4 and child .startswith ("branches:" ):
74+ raw = child .split (":" , 1 )[1 ].strip ()
75+ if not (raw .startswith ("[" ) and raw .endswith ("]" )):
76+ raise ValueError (
77+ f"{ path } : contract expects inline push branches list"
78+ )
79+ return {
80+ item .strip ().strip (chr (34 )).strip (chr (39 ))
81+ for item in raw [1 :- 1 ].split ("," )
82+ if item .strip ()
83+ }
84+ raise ValueError (f"{ path } : push trigger has no branches list" )
85+ raise ValueError (f"{ path } : missing top-level push trigger" )
9486
9587def _yaml_code (line : str ) -> str :
9688 """Return the structural part of a simple repository workflow line."""
@@ -99,73 +91,46 @@ def _yaml_code(line: str) -> str:
9991
10092def _pull_request_is_unfiltered (path : Path ) -> bool :
10193 """Require default PR activity coverage with no base/path suppression."""
102- lines = path .read_text (encoding = "utf-8" ).splitlines ()
103- pr_index = next (
104- (
105- i
106- for i , line in enumerate (lines )
107- if _yaml_code (line ).rstrip () == " pull_request:"
108- ),
109- None ,
110- )
111- if pr_index is None :
112- return False
113-
94+ lines = _structural_lines (path )
11495 forbidden = ("branches:" , "branches-ignore:" , "paths:" , "paths-ignore:" , "types:" )
115- for line in lines [pr_index + 1 :]:
116- code = _yaml_code (line )
117- if not code .strip ():
118- continue
119- indent = len (code ) - len (code .lstrip (" " ))
120- if indent < 4 :
121- break
122- stripped = code .strip ()
123- if stripped .startswith (forbidden ):
124- return False
125- return True
126-
96+ for i , (_ , indent , stripped ) in enumerate (lines ):
97+ if indent == 2 and stripped == "pull_request:" :
98+ for _ , child_indent , child in lines [i + 1 :]:
99+ if child_indent <= 2 :
100+ break
101+ if child_indent >= 4 and child .startswith (forbidden ):
102+ return False
103+ return True
104+ return False
127105
128106def _checkout_ref_values (path : Path ) -> list [str | None ]:
129- """Return with.ref for every actions/checkout step, None if absent ."""
130- lines = path . read_text ( encoding = "utf-8" ). splitlines ( )
107+ """Return with.ref for every structural actions/checkout step."""
108+ lines = _structural_lines ( path )
131109 refs : list [str | None ] = []
132- for i , line in enumerate (lines ):
133- code = _yaml_code (line )
134- stripped = code .strip ()
110+ for i , (_ , uses_indent , stripped ) in enumerate (lines ):
135111 short_form = stripped .startswith ("- uses: actions/checkout@" )
136112 named_form = stripped .startswith ("uses: actions/checkout@" )
137113 if not (short_form or named_form ):
138114 continue
139- uses_indent = len (code ) - len (code .lstrip (" " ))
140- # ``- uses:`` is itself the list item; ``uses:`` under ``- name:`` is
141- # a peer property. In both cases this is the indentation where ``with:``
142- # must appear.
143115 property_indent = uses_indent + 2 if short_form else uses_indent
144116 in_with = False
145117 ref_value : str | None = None
146- for next_line in lines [i + 1 :]:
147- next_code = _yaml_code (next_line )
148- if not next_code .strip ():
149- continue
150- indent = len (next_code ) - len (next_code .lstrip (" " ))
151- next_stripped = next_code .strip ()
152- if indent < property_indent :
118+ for _ , next_indent , child in lines [i + 1 :]:
119+ if next_indent < property_indent :
153120 break
154- if indent == property_indent :
155- if next_stripped == "with:" :
121+ if next_indent == property_indent :
122+ if child == "with:" :
156123 in_with = True
157124 continue
158- # Another peer property or the next list item ends ``with``.
159125 in_with = False
160- if next_stripped .startswith ("- " ):
126+ if child .startswith ("- " ):
161127 break
162128 continue
163- if in_with and indent > property_indent and next_stripped .startswith ("ref:" ):
164- ref_value = next_stripped .split (":" , 1 )[1 ].strip ().strip (chr (34 )).strip (chr (39 ))
129+ if in_with and next_indent > property_indent and child .startswith ("ref:" ):
130+ ref_value = child .split (":" , 1 )[1 ].strip ().strip (chr (34 )).strip (chr (39 ))
165131 refs .append (ref_value )
166132 return refs
167133
168-
169134def _checks_out_exact_pr_head (path : Path ) -> bool :
170135 """At least one checkout must bind to the submitted PR head SHA."""
171136 return PR_HEAD_REF in _checkout_ref_values (path )
0 commit comments