Skip to content

Commit 6a995dc

Browse files
committed
ci(github): enhance scope matching with normalize and contains logic
1 parent 4edb73b commit 6a995dc

1 file changed

Lines changed: 45 additions & 4 deletions

File tree

.github/workflows/pr-reviewer.yml

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,60 @@ jobs:
4949
};
5050
const defaultReviewers = ['bladehan1', 'kuny0707'];
5151
52+
// ── Normalize helper ─────────────────────────────────────
53+
// Strip spaces, hyphens, underscores and lower-case so that
54+
// "VM", " json rpc ", "chain-base", "Json_Rpc" all normalize
55+
// to their canonical key form ("vm", "jsonrpc", "chainbase").
56+
const normalize = s => s.toLowerCase().replace(/[\s\-_]/g, '');
57+
5258
// ── Extract scope from conventional commit title ──────────
5359
// Format: type(scope): description
60+
// Also supports: type(scope1,scope2): description
5461
const scopeMatch = title.match(/^\w+\(([^)]+)\):/);
55-
const scope = scopeMatch ? scopeMatch[1].toLowerCase() : null;
62+
const rawScope = scopeMatch ? scopeMatch[1] : null;
5663
5764
core.info(`PR title : ${title}`);
58-
core.info(`Detected scope: ${scope || '(none — using default)'}`);
65+
core.info(`Raw scope: ${rawScope || '(none)'}`);
5966
6067
// ── Determine reviewers ───────────────────────────────────
61-
let reviewers = scope && scopeReviewers[scope]
62-
? scopeReviewers[scope]
68+
// 1. Split by comma to support multi-scope: feat(vm,rpc): ...
69+
// 2. Normalize each scope token
70+
// 3. Match against keys: exact match first, then contains match
71+
// (longest key wins to avoid "net" matching inside "jsonrpc")
72+
let matched = new Set();
73+
let matchedScopes = [];
74+
75+
if (rawScope) {
76+
const tokens = rawScope.split(',').map(s => normalize(s.trim()));
77+
// Pre-sort keys by length descending so longer keys match first
78+
const sortedKeys = Object.keys(scopeReviewers)
79+
.sort((a, b) => b.length - a.length);
80+
81+
for (const token of tokens) {
82+
if (!token) continue;
83+
// Exact match
84+
if (scopeReviewers[token]) {
85+
matchedScopes.push(token);
86+
scopeReviewers[token].forEach(r => matched.add(r));
87+
continue;
88+
}
89+
// Contains match: token contains a key, or key contains token
90+
// Prefer longest key that matches
91+
const found = sortedKeys.find(k => token.includes(k) || k.includes(token));
92+
if (found) {
93+
matchedScopes.push(`${token}→${found}`);
94+
scopeReviewers[found].forEach(r => matched.add(r));
95+
}
96+
}
97+
}
98+
99+
let reviewers = matched.size > 0
100+
? [...matched]
63101
: defaultReviewers;
64102
103+
core.info(`Matched scopes: ${matchedScopes.length > 0 ? matchedScopes.join(', ') : '(none — using default)'}`);
104+
core.info(`Candidate reviewers: ${reviewers.join(', ')}`);
105+
65106
// Exclude the PR author from the reviewer list
66107
reviewers = reviewers.filter(r => r.toLowerCase() !== prAuthor.toLowerCase());
67108

0 commit comments

Comments
 (0)