Skip to content

Commit 6757d88

Browse files
committed
Add Assisted-By trailer rule
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-By: Opencode/Opus 4.6
1 parent b2bf5e0 commit 6757d88

File tree

3 files changed

+241
-0
lines changed

3 files changed

+241
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const id = 'assisted-by-is-trailer'
2+
3+
export default {
4+
id,
5+
meta: {
6+
description: 'enforce that "Assisted-by:" lines are trailers',
7+
recommended: true
8+
},
9+
defaults: {},
10+
options: {},
11+
validate: (context, rule) => {
12+
const parsed = context.toJSON()
13+
const lines = parsed.body.map((line, i) => [line, i])
14+
const re = /^Assisted-by:/gi
15+
const assisted = lines.filter(([line]) => re.test(line))
16+
if (assisted.length !== 0) {
17+
const firstAssisted = assisted[0]
18+
const emptyLines = lines.filter(([text]) => text.trim().length === 0)
19+
// There must be at least one empty line, and the last empty line must be
20+
// above the first Assisted-by line.
21+
const isTrailer = (emptyLines.length !== 0) &&
22+
emptyLines.at(-1)[1] < firstAssisted[1]
23+
if (isTrailer) {
24+
context.report({
25+
id,
26+
message: 'Assisted-by is a trailer',
27+
string: '',
28+
level: 'pass'
29+
})
30+
} else {
31+
context.report({
32+
id,
33+
message: 'Assisted-by must be a trailer',
34+
string: firstAssisted[0],
35+
line: firstAssisted[1],
36+
column: 0,
37+
level: 'fail'
38+
})
39+
}
40+
} else {
41+
context.report({
42+
id,
43+
message: 'no Assisted-by metadata',
44+
string: '',
45+
level: 'pass'
46+
})
47+
}
48+
}
49+
}

lib/validator.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Parser from 'gitlint-parser-node'
33
import BaseRule from './rule.js'
44

55
// Rules
6+
import assistedByIsTrailer from './rules/assisted-by-is-trailer.js'
67
import coAuthoredByIsTrailer from './rules/co-authored-by-is-trailer.js'
78
import fixesUrl from './rules/fixes-url.js'
89
import lineAfterTitle from './rules/line-after-title.js'
@@ -16,6 +17,7 @@ import titleFormat from './rules/title-format.js'
1617
import titleLength from './rules/title-length.js'
1718

1819
const RULES = {
20+
'assisted-by-is-trailer': assistedByIsTrailer,
1921
'co-authored-by-is-trailer': coAuthoredByIsTrailer,
2022
'fixes-url': fixesUrl,
2123
'line-after-title': lineAfterTitle,
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { test } from 'tap'
2+
import Rule from '../../lib/rules/assisted-by-is-trailer.js'
3+
import Commit from 'gitlint-parser-node'
4+
import Validator from '../../index.js'
5+
6+
test('rule: assisted-by-is-trailer', (t) => {
7+
t.test('no assisted-by', (tt) => {
8+
tt.plan(4)
9+
const v = new Validator()
10+
const context = new Commit({
11+
sha: 'e7c077c610afa371430180fbd447bfef60ebc5ea',
12+
author: {
13+
name: 'Foo',
14+
email: 'foo@example.com',
15+
date: '2016-04-12T19:42:23Z'
16+
},
17+
message: 'test: fix something\n' +
18+
'\n' +
19+
'fhqwhgads'
20+
}, v)
21+
22+
context.report = (opts) => {
23+
tt.pass('called report')
24+
tt.equal(opts.id, 'assisted-by-is-trailer', 'id')
25+
tt.equal(opts.message, 'no Assisted-by metadata', 'message')
26+
tt.equal(opts.level, 'pass', 'level')
27+
}
28+
29+
Rule.validate(context)
30+
})
31+
32+
t.test('no empty lines above', (tt) => {
33+
tt.plan(7)
34+
const v = new Validator()
35+
const context = new Commit({
36+
sha: 'e7c077c610afa371430180fbd447bfef60ebc5ea',
37+
author: {
38+
name: 'Foo',
39+
email: 'foo@example.com',
40+
date: '2016-04-12T19:42:23Z'
41+
},
42+
message: 'test: fix something\n' +
43+
'Assisted-by: GitHub Copilot'
44+
}, v)
45+
46+
context.report = (opts) => {
47+
tt.pass('called report')
48+
tt.equal(opts.id, 'assisted-by-is-trailer', 'id')
49+
tt.equal(opts.message,
50+
'Assisted-by must be a trailer', 'message')
51+
tt.equal(opts.string,
52+
'Assisted-by: GitHub Copilot', 'string')
53+
tt.equal(opts.line, 0, 'line')
54+
tt.equal(opts.column, 0, 'column')
55+
tt.equal(opts.level, 'fail', 'level')
56+
}
57+
58+
Rule.validate(context)
59+
})
60+
61+
t.test('not trailer - in body before metadata', (tt) => {
62+
tt.plan(7)
63+
const v = new Validator()
64+
const context = new Commit({
65+
sha: 'e7c077c610afa371430180fbd447bfef60ebc5ea',
66+
author: {
67+
name: 'Foo',
68+
email: 'foo@example.com',
69+
date: '2016-04-12T19:42:23Z'
70+
},
71+
message: 'test: fix something\n' +
72+
'\n' +
73+
'Some description.\n' +
74+
'\n' +
75+
'Assisted-by: Claude\n' +
76+
'\n' +
77+
'Reviewed-By: Bar <bar@example.com>'
78+
}, v)
79+
80+
context.report = (opts) => {
81+
tt.pass('called report')
82+
tt.equal(opts.id, 'assisted-by-is-trailer', 'id')
83+
tt.equal(opts.message,
84+
'Assisted-by must be a trailer', 'message')
85+
tt.equal(opts.string,
86+
'Assisted-by: Claude', 'string')
87+
tt.equal(opts.line, 3, 'line')
88+
tt.equal(opts.column, 0, 'column')
89+
tt.equal(opts.level, 'fail', 'level')
90+
}
91+
92+
Rule.validate(context)
93+
})
94+
95+
t.test('is trailer', (tt) => {
96+
tt.plan(4)
97+
const v = new Validator()
98+
const context = new Commit({
99+
sha: 'e7c077c610afa371430180fbd447bfef60ebc5ea',
100+
author: {
101+
name: 'Foo',
102+
email: 'foo@example.com',
103+
date: '2016-04-12T19:42:23Z'
104+
},
105+
message: 'test: fix something\n' +
106+
'\n' +
107+
'Some description.\n' +
108+
'\n' +
109+
'Assisted-by: GitHub Copilot\n' +
110+
'Reviewed-By: Bar <bar@example.com>'
111+
}, v)
112+
113+
context.report = (opts) => {
114+
tt.pass('called report')
115+
tt.equal(opts.id, 'assisted-by-is-trailer', 'id')
116+
tt.equal(opts.message,
117+
'Assisted-by is a trailer', 'message')
118+
tt.equal(opts.level, 'pass', 'level')
119+
}
120+
121+
Rule.validate(context)
122+
})
123+
124+
t.test('multiple assisted-by as trailers', (tt) => {
125+
tt.plan(4)
126+
const v = new Validator()
127+
const context = new Commit({
128+
sha: 'e7c077c610afa371430180fbd447bfef60ebc5ea',
129+
author: {
130+
name: 'Foo',
131+
email: 'foo@example.com',
132+
date: '2016-04-12T19:42:23Z'
133+
},
134+
message: 'test: fix something\n' +
135+
'\n' +
136+
'Some description.\n' +
137+
'\n' +
138+
'Assisted-by: Claude\n' +
139+
'Assisted-by: GitHub Copilot\n' +
140+
'Reviewed-By: Bar <bar@example.com>'
141+
}, v)
142+
143+
context.report = (opts) => {
144+
tt.pass('called report')
145+
tt.equal(opts.id, 'assisted-by-is-trailer', 'id')
146+
tt.equal(opts.message,
147+
'Assisted-by is a trailer', 'message')
148+
tt.equal(opts.level, 'pass', 'level')
149+
}
150+
151+
Rule.validate(context)
152+
})
153+
154+
t.test('not all are trailers', (tt) => {
155+
tt.plan(7)
156+
const v = new Validator()
157+
const context = new Commit({
158+
sha: 'e7c077c610afa371430180fbd447bfef60ebc5ea',
159+
author: {
160+
name: 'Foo',
161+
email: 'foo@example.com',
162+
date: '2016-04-12T19:42:23Z'
163+
},
164+
message: 'test: fix something\n' +
165+
'\n' +
166+
'Some description.\n' +
167+
'\n' +
168+
'Assisted-by: Claude\n' +
169+
'\n' +
170+
'Assisted-by: GitHub Copilot\n' +
171+
'Reviewed-By: Bar <bar@example.com>'
172+
}, v)
173+
174+
context.report = (opts) => {
175+
tt.pass('called report')
176+
tt.equal(opts.id, 'assisted-by-is-trailer', 'id')
177+
tt.equal(opts.message,
178+
'Assisted-by must be a trailer', 'message')
179+
tt.equal(opts.string,
180+
'Assisted-by: Claude', 'string')
181+
tt.equal(opts.line, 3, 'line')
182+
tt.equal(opts.column, 0, 'column')
183+
tt.equal(opts.level, 'fail', 'level')
184+
}
185+
186+
Rule.validate(context)
187+
})
188+
189+
t.end()
190+
})

0 commit comments

Comments
 (0)