claude-git-hooks 2.30.2 → 2.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,154 @@
1
+ /**
2
+ * File: reviewer-selector.js
3
+ * Purpose: Team-based reviewer selection for Pull Requests
4
+ *
5
+ * Resolves team members via GitHub Teams API, excludes the PR author
6
+ * and excluded reviewers (from remote config.json + local config),
7
+ * and returns eligible reviewers. Falls back to config-based reviewers
8
+ * if team resolution fails.
9
+ *
10
+ * Exclusion sources (merged, deduplicated):
11
+ * 1. Remote: config.json → general.github.pr.excludeReviewers (all repos)
12
+ * 2. Remote: config.json → {repoFullName}.github.pr.excludeReviewers (repo-specific)
13
+ * 3. Local: config.github.pr.excludeReviewers (per-repo override)
14
+ * 4. PR author (always excluded)
15
+ *
16
+ * Merge priority: remote general < remote repo-specific < local overrides
17
+ *
18
+ * Design: teamSlug is an explicit parameter (default: 'automation').
19
+ * Future auto-detection (e.g. from repos.listTeams) can be plugged in
20
+ * by the caller without changing this module.
21
+ */
22
+
23
+ import logger from './logger.js';
24
+ import { listTeamMembers } from './github-api.js';
25
+ import { fetchRemoteConfig } from './remote-config.js';
26
+
27
+ /** Default team slug — single place to change when auto-detection is added */
28
+ const DEFAULT_TEAM = 'automation';
29
+
30
+ /**
31
+ * Build the merged exclusion set from remote config.json + local config.
32
+ *
33
+ * @param {string} repoFullName - Full repo name (e.g. 'mscope-S-L/git-hooks')
34
+ * @param {string[]} localExclude - Local excludeReviewers array from config
35
+ * @returns {Promise<Set<string>>} Set of usernames to exclude
36
+ */
37
+ export async function _buildExclusionSet(repoFullName, localExclude = []) {
38
+ const excluded = new Set();
39
+
40
+ // Remote config: general + repo-specific
41
+ try {
42
+ const remoteConfig = await fetchRemoteConfig('config.json');
43
+ if (remoteConfig) {
44
+ const generalExclude =
45
+ remoteConfig.general?.github?.pr?.excludeReviewers || [];
46
+ const repoExclude =
47
+ remoteConfig[repoFullName]?.github?.pr?.excludeReviewers || [];
48
+
49
+ generalExclude.forEach((u) => excluded.add(u));
50
+ repoExclude.forEach((u) => excluded.add(u));
51
+
52
+ logger.debug('reviewer-selector - _buildExclusionSet', 'Remote exclusions loaded', {
53
+ generalCount: generalExclude.length,
54
+ repoSpecificCount: repoExclude.length,
55
+ repoFullName
56
+ });
57
+ }
58
+ } catch (err) {
59
+ logger.debug('reviewer-selector - _buildExclusionSet', 'Remote config fetch failed', {
60
+ error: err.message
61
+ });
62
+ }
63
+
64
+ // Local overrides (highest priority, additive)
65
+ localExclude.forEach((u) => excluded.add(u));
66
+
67
+ logger.debug('reviewer-selector - _buildExclusionSet', 'Final exclusion set', {
68
+ count: excluded.size,
69
+ users: [...excluded]
70
+ });
71
+
72
+ return excluded;
73
+ }
74
+
75
+ /**
76
+ * Select reviewers for a Pull Request from a GitHub team
77
+ *
78
+ * @param {Object} options
79
+ * @param {string} options.org - GitHub organization (e.g. 'mscope-S-L')
80
+ * @param {string} options.repoFullName - Full repo name (e.g. 'mscope-S-L/git-hooks')
81
+ * @param {string} [options.teamSlug='automation'] - Team slug to resolve members from
82
+ * @param {string} options.prAuthor - PR author login (excluded from selection)
83
+ * @param {string[]} [options.configReviewers=[]] - Fallback reviewers from config
84
+ * @param {string[]} [options.excludeReviewers=[]] - Local exclude list from config
85
+ * @returns {Promise<string[]>} Selected reviewer logins
86
+ */
87
+ export async function selectReviewers({
88
+ org,
89
+ repoFullName,
90
+ teamSlug = DEFAULT_TEAM,
91
+ prAuthor,
92
+ configReviewers = [],
93
+ excludeReviewers = []
94
+ }) {
95
+ logger.debug('reviewer-selector - selectReviewers', 'Starting reviewer selection', {
96
+ org,
97
+ repoFullName,
98
+ teamSlug,
99
+ prAuthor,
100
+ configReviewersCount: configReviewers.length,
101
+ localExcludeCount: excludeReviewers.length
102
+ });
103
+
104
+ // Build merged exclusion set (remote general + remote repo-specific + local)
105
+ const exclusionSet = await _buildExclusionSet(repoFullName, excludeReviewers);
106
+ // Always exclude the PR author
107
+ exclusionSet.add(prAuthor);
108
+
109
+ // Try team-based resolution
110
+ try {
111
+ const members = await listTeamMembers(org, teamSlug);
112
+ const eligible = members
113
+ .map((m) => m.login)
114
+ .filter((login) => !exclusionSet.has(login));
115
+
116
+ logger.debug('reviewer-selector - selectReviewers', 'Team members resolved', {
117
+ teamSlug,
118
+ totalMembers: members.length,
119
+ eligibleCount: eligible.length,
120
+ excludedCount: members.length - eligible.length
121
+ });
122
+
123
+ if (eligible.length > 0) {
124
+ logger.info(
125
+ `📋 Found ${eligible.length} reviewer(s) from team '${teamSlug}': ${eligible.join(', ')}`
126
+ );
127
+ return eligible;
128
+ }
129
+
130
+ logger.debug(
131
+ 'reviewer-selector - selectReviewers',
132
+ 'No eligible team members, falling back to config'
133
+ );
134
+ } catch (err) {
135
+ logger.warning(
136
+ `⚠️ Could not resolve team '${teamSlug}': ${err.message}. Falling back to config reviewers.`
137
+ );
138
+ logger.debug('reviewer-selector - selectReviewers', 'Team resolution failed', {
139
+ error: err.message,
140
+ teamSlug
141
+ });
142
+ }
143
+
144
+ // Fallback: config-based reviewers
145
+ const fallback = configReviewers.filter((r) => !exclusionSet.has(r));
146
+
147
+ if (fallback.length > 0) {
148
+ logger.info(`📋 Found ${fallback.length} reviewer(s) from config: ${fallback.join(', ')}`);
149
+ } else {
150
+ logger.info('📋 Found 0 reviewer(s): none');
151
+ }
152
+
153
+ return fallback;
154
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-git-hooks",
3
- "version": "2.30.2",
3
+ "version": "2.32.0",
4
4
  "description": "Git hooks with Claude CLI for code analysis and automatic commit messages",
5
5
  "type": "module",
6
6
  "bin": {