1515
1616VALID_BODY = """\
1717 ## Linear issue
18- - https://linear.app/autonominetwork/issue/V2-719/add-the-standard-pr-template
18+ - Closes https://linear.app/autonominetwork/issue/V2-719/add-the-standard-pr-template
1919
2020## Risk tier
2121- [x] T0 — docs / tooling / CI.
@@ -52,13 +52,24 @@ def body_without(section_swap):
5252# Unfilled template: the only Linear-looking token is the example in a comment.
5353UNFILLED = """\
5454 ## Linear issue
55- <!-- REQUIRED. Link the issue: an issue key like V2-123, or a linear.app URL. -->
55+ <!-- REQUIRED. Use the closing form, one line per issue: Closes V2-123 -->
5656## Risk tier
5757- [ ] T0
5858## Semver impact
5959- [ ] fix
6060"""
6161
62+ # Linear's closing magic words, from https://linear.app/docs/github. Restated here
63+ # rather than imported from check_pr.py so the matrix is an independent assertion
64+ # about what the checker must accept, not a tautology.
65+ CLOSING_MAGIC_WORDS = (
66+ "close" , "closes" , "closed" , "closing" ,
67+ "fix" , "fixes" , "fixed" , "fixing" ,
68+ "resolve" , "resolves" , "resolved" , "resolving" ,
69+ "complete" , "completes" , "completed" , "completing" ,
70+ "implement" , "implements" , "implemented" , "implementing" ,
71+ )
72+
6273# (name, mode, env, expected_exit)
6374CASES = [
6475 # --- linear-link: rejections ---
@@ -67,10 +78,35 @@ def body_without(section_swap):
6778 ("linear: linear.app/changelog" , "linear" , {"PR_BODY" : "see https://linear.app/changelog" , "PR_BRANCH" : "x" }, 1 ),
6879 ("linear: linear.app/not-an-issue" , "linear" , {"PR_BODY" : "https://linear.app/not-an-issue" , "PR_BRANCH" : "x" }, 1 ),
6980 ("linear: unfilled template (example in comment)" , "linear" , {"PR_BODY" : UNFILLED , "PR_BRANCH" : "x" }, 1 ),
70- # --- linear-link: acceptances ---
71- ("linear: issue URL in body" , "linear" , {"PR_BODY" : "https://linear.app/autonominetwork/issue/V2-719/foo" }, 0 ),
81+ # A bare reference in the body does not link the PR in Linear (V2-1161).
82+ ("linear: bare key in body only" , "linear" , {"PR_BODY" : "V2-1161" , "PR_BRANCH" : "x" }, 1 ),
83+ ("linear: bare issue URL in body only" , "linear" , {"PR_BODY" : "https://linear.app/autonominetwork/issue/V2-719/foo" , "PR_BRANCH" : "x" }, 1 ),
84+ ("linear: magic word without a key" , "linear" , {"PR_BODY" : "Closes the gap" , "PR_BRANCH" : "x" }, 1 ),
85+ ("linear: magic word on its own line from the key" , "linear" , {"PR_BODY" : "Closes\n \n V2-1161" , "PR_BRANCH" : "x" }, 1 ),
86+ # Linear's linking-only families attach the PR but do not drive the Merged
87+ # transition, so they are not accepted as the closing form.
88+ ("linear: 'part of' is linking-only" , "linear" , {"PR_BODY" : "part of V2-1161" , "PR_BRANCH" : "x" }, 1 ),
89+ ("linear: 'ref' is linking-only" , "linear" , {"PR_BODY" : "ref V2-1161" , "PR_BRANCH" : "x" }, 1 ),
90+ ("linear: 'towards' is linking-only" , "linear" , {"PR_BODY" : "towards V2-1161" , "PR_BRANCH" : "x" }, 1 ),
91+ ("linear: 'relates to' is linking-only" , "linear" , {"PR_BODY" : "relates to V2-1161" , "PR_BRANCH" : "x" }, 1 ),
92+ ("linear: magic word as a word prefix" , "linear" , {"PR_BODY" : "prefix V2-1161" , "PR_BRANCH" : "x" }, 1 ),
93+ ("linear: magic word as a word suffix" , "linear" , {"PR_BODY" : "fixture V2-1161" , "PR_BRANCH" : "x" }, 1 ),
94+ # --- linear-link: acceptances (Linear's full closing set, any tense) ---
95+ ("linear: Closes + key in body" , "linear" , {"PR_BODY" : "Closes V2-1161" , "PR_BRANCH" : "x" }, 0 ),
96+ ("linear: lower-cased magic word" , "linear" , {"PR_BODY" : "closes v2-1161" , "PR_BRANCH" : "x" }, 0 ),
97+ ("linear: Closes + issue URL in body" , "linear" , {"PR_BODY" : "Closes https://linear.app/autonominetwork/issue/V2-719/foo" , "PR_BRANCH" : "x" }, 0 ),
98+ ("linear: closing form inside prose" , "linear" , {"PR_BODY" : "This one closes V2-1161 at last." , "PR_BRANCH" : "x" }, 0 ),
99+ ("linear: 'linear issue' phrase" , "linear" , {"PR_BODY" : "Linear issue V2-1161" , "PR_BRANCH" : "x" }, 0 ),
72100 ("linear: key in branch (lowercased)" , "linear" , {"PR_BRANCH" : "chrisoneil/v2-720-ci-check" }, 0 ),
73101 ("linear: key in title" , "linear" , {"PR_TITLE" : "AUTO-42 do the thing" , "PR_BRANCH" : "x" }, 0 ),
102+ ] + [
103+ # One case per closing magic word Linear documents, capitalised as an author
104+ # would write it — e.g. "Fixed V2-1161" links and closes in Linear, so it must
105+ # pass here too.
106+ (f"linear: '{ word } '" , "linear" ,
107+ {"PR_BODY" : f"{ word .capitalize ()} V2-1161" , "PR_BRANCH" : "x" }, 0 )
108+ for word in CLOSING_MAGIC_WORDS
109+ ] + [
74110 # --- pr-template: acceptances ---
75111 ("template: valid T0 body" , "template" , {"PR_BASE" : "main" , "PR_BODY" : VALID_BODY }, 0 ),
76112 ("template: rc-* base is a no-op pass" , "template" , {"PR_BASE" : "rc-2025.10" , "PR_BODY" : "anything" }, 0 ),
@@ -86,6 +122,9 @@ def body_without(section_swap):
86122 ("- [x] T0 — docs / tooling / CI." , "- [x] T2 — behavioural." ))}, 1 ),
87123 ("template: no tier checked" , "template" , {"PR_BASE" : "main" , "PR_BODY" : body_without (
88124 ("- [x] T0 — docs / tooling / CI." , "- [ ] T0 — docs / tooling / CI." ))}, 1 ),
125+ ("template: bare key under '## Linear issue'" , "template" , {"PR_BASE" : "main" , "PR_BODY" : body_without (
126+ ("- Closes https://linear.app/autonominetwork/issue/V2-719/add-the-standard-pr-template" ,
127+ "- V2-719" ))}, 1 ),
89128 ("template: two tiers checked" , "template" , {"PR_BASE" : "main" , "PR_BODY" : body_without (
90129 ("- [x] T0 — docs / tooling / CI." , "- [x] T0 a\n - [x] T2 b" ))}, 1 ),
91130]
0 commit comments