git-fish-log 1.0.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.
- package/README.md +26 -0
- package/dist/index.js +1534 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1534 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import fs2 from "fs";
|
|
7
|
+
import path3 from "path";
|
|
8
|
+
|
|
9
|
+
// src/config.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import os from "os";
|
|
13
|
+
var CONFIG_PATH = path.join(os.homedir(), ".fish-git-config.json");
|
|
14
|
+
function setGitLabConfig(token, host, name) {
|
|
15
|
+
const config = readConfig();
|
|
16
|
+
if (!config.gitlabs) {
|
|
17
|
+
config.gitlabs = [];
|
|
18
|
+
}
|
|
19
|
+
const targetHost = host || "https://gitlab.com";
|
|
20
|
+
const targetName = name || targetHost.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
21
|
+
const index = config.gitlabs.findIndex((g) => g.name.toLowerCase() === targetName.toLowerCase());
|
|
22
|
+
if (index !== -1) {
|
|
23
|
+
config.gitlabs[index] = { token, host: targetHost, name: targetName };
|
|
24
|
+
} else {
|
|
25
|
+
config.gitlabs.push({ token, host: targetHost, name: targetName });
|
|
26
|
+
}
|
|
27
|
+
config.gitlabToken = token;
|
|
28
|
+
config.gitlabHost = targetHost;
|
|
29
|
+
writeConfig(config);
|
|
30
|
+
}
|
|
31
|
+
function clearGitLabConfig(nameOrIndex) {
|
|
32
|
+
const config = readConfig();
|
|
33
|
+
if (!config.gitlabs || config.gitlabs.length === 0) {
|
|
34
|
+
delete config.gitlabToken;
|
|
35
|
+
delete config.gitlabHost;
|
|
36
|
+
writeConfig(config);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!nameOrIndex) {
|
|
40
|
+
config.gitlabs = [];
|
|
41
|
+
delete config.gitlabToken;
|
|
42
|
+
delete config.gitlabHost;
|
|
43
|
+
} else {
|
|
44
|
+
const index = config.gitlabs.findIndex(
|
|
45
|
+
(g, idx) => g.name.toLowerCase() === nameOrIndex.toLowerCase() || String(idx + 1) === nameOrIndex
|
|
46
|
+
);
|
|
47
|
+
if (index !== -1) {
|
|
48
|
+
config.gitlabs.splice(index, 1);
|
|
49
|
+
}
|
|
50
|
+
if (config.gitlabs.length > 0) {
|
|
51
|
+
config.gitlabToken = config.gitlabs[0].token;
|
|
52
|
+
config.gitlabHost = config.gitlabs[0].host;
|
|
53
|
+
} else {
|
|
54
|
+
delete config.gitlabToken;
|
|
55
|
+
delete config.gitlabHost;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
writeConfig(config);
|
|
59
|
+
}
|
|
60
|
+
function readConfig() {
|
|
61
|
+
try {
|
|
62
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
63
|
+
const data = fs.readFileSync(CONFIG_PATH, "utf-8");
|
|
64
|
+
const parsed = JSON.parse(data);
|
|
65
|
+
if (parsed && Array.isArray(parsed.projects)) {
|
|
66
|
+
if (!parsed.gitlabs) {
|
|
67
|
+
parsed.gitlabs = [];
|
|
68
|
+
}
|
|
69
|
+
if (parsed.gitlabToken) {
|
|
70
|
+
const host = parsed.gitlabHost || "https://gitlab.com";
|
|
71
|
+
const name = host.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
72
|
+
const exists = parsed.gitlabs.some((g) => g.token === parsed.gitlabToken && g.host === host);
|
|
73
|
+
if (!exists) {
|
|
74
|
+
parsed.gitlabs.push({
|
|
75
|
+
token: parsed.gitlabToken,
|
|
76
|
+
host,
|
|
77
|
+
name
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return parsed;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {
|
|
85
|
+
}
|
|
86
|
+
return { projects: [], gitlabs: [] };
|
|
87
|
+
}
|
|
88
|
+
function writeConfig(config) {
|
|
89
|
+
try {
|
|
90
|
+
const dir = path.dirname(CONFIG_PATH);
|
|
91
|
+
if (!fs.existsSync(dir)) {
|
|
92
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
93
|
+
}
|
|
94
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
95
|
+
} catch (e) {
|
|
96
|
+
console.error(`\x1B[31m[Error] Failed to write config to ${CONFIG_PATH}: ${e.message}\x1B[0m`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function getProjects() {
|
|
100
|
+
const config = readConfig();
|
|
101
|
+
if (config.projects.length === 0) {
|
|
102
|
+
if (!config.gitlabToken && (!config.gitlabs || config.gitlabs.length === 0)) {
|
|
103
|
+
return [process.cwd()];
|
|
104
|
+
}
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
return config.projects.filter((p) => {
|
|
108
|
+
try {
|
|
109
|
+
return fs.existsSync(p) && fs.statSync(p).isDirectory();
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function addProject(projectPath) {
|
|
116
|
+
const absolutePath = path.resolve(projectPath);
|
|
117
|
+
if (!fs.existsSync(absolutePath)) {
|
|
118
|
+
return { success: false, message: `Path does not exist: ${projectPath}` };
|
|
119
|
+
}
|
|
120
|
+
if (!fs.statSync(absolutePath).isDirectory()) {
|
|
121
|
+
return { success: false, message: `Path is not a directory: ${projectPath}` };
|
|
122
|
+
}
|
|
123
|
+
if (!fs.existsSync(path.join(absolutePath, ".git"))) {
|
|
124
|
+
return { success: false, message: `Path is not a Git repository (no .git directory found): ${projectPath}` };
|
|
125
|
+
}
|
|
126
|
+
const config = readConfig();
|
|
127
|
+
if (config.projects.includes(absolutePath)) {
|
|
128
|
+
return { success: false, message: `Project is already in the list: ${absolutePath}` };
|
|
129
|
+
}
|
|
130
|
+
config.projects.push(absolutePath);
|
|
131
|
+
writeConfig(config);
|
|
132
|
+
return { success: true, message: `Added project: ${absolutePath}` };
|
|
133
|
+
}
|
|
134
|
+
function removeProject(projectPath) {
|
|
135
|
+
const absolutePath = path.resolve(projectPath);
|
|
136
|
+
const config = readConfig();
|
|
137
|
+
const index = config.projects.indexOf(absolutePath);
|
|
138
|
+
if (index === -1) {
|
|
139
|
+
const indexStr = config.projects.indexOf(projectPath);
|
|
140
|
+
if (indexStr === -1) {
|
|
141
|
+
return { success: false, message: `Project not found in config: ${projectPath}` };
|
|
142
|
+
}
|
|
143
|
+
config.projects.splice(indexStr, 1);
|
|
144
|
+
} else {
|
|
145
|
+
config.projects.splice(index, 1);
|
|
146
|
+
}
|
|
147
|
+
writeConfig(config);
|
|
148
|
+
return { success: true, message: `Removed project: ${absolutePath}` };
|
|
149
|
+
}
|
|
150
|
+
function scanDirectory(rootPath) {
|
|
151
|
+
const absoluteRoot = path.resolve(rootPath);
|
|
152
|
+
if (!fs.existsSync(absoluteRoot) || !fs.statSync(absoluteRoot).isDirectory()) {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
const foundRepos = [];
|
|
156
|
+
if (fs.existsSync(path.join(absoluteRoot, ".git"))) {
|
|
157
|
+
foundRepos.push(absoluteRoot);
|
|
158
|
+
return foundRepos;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const items = fs.readdirSync(absoluteRoot);
|
|
162
|
+
for (const item of items) {
|
|
163
|
+
const itemPath = path.join(absoluteRoot, item);
|
|
164
|
+
try {
|
|
165
|
+
if (fs.existsSync(itemPath) && fs.statSync(itemPath).isDirectory()) {
|
|
166
|
+
if (fs.existsSync(path.join(itemPath, ".git"))) {
|
|
167
|
+
foundRepos.push(itemPath);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.error(`\x1B[31m[Error] Failed to scan path ${absoluteRoot}: ${e.message}\x1B[0m`);
|
|
175
|
+
}
|
|
176
|
+
return foundRepos;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/git.ts
|
|
180
|
+
import { execSync } from "child_process";
|
|
181
|
+
import path2 from "path";
|
|
182
|
+
function getUserEmail(cwd) {
|
|
183
|
+
try {
|
|
184
|
+
const email = execSync("git config user.email", { cwd, encoding: "utf8" }).trim();
|
|
185
|
+
if (email) return email;
|
|
186
|
+
} catch {
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const emailGlobal = execSync("git config --global user.email", { cwd, encoding: "utf8" }).trim();
|
|
190
|
+
if (emailGlobal) return emailGlobal;
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
function formatLocalDate(date) {
|
|
196
|
+
const Y = date.getFullYear();
|
|
197
|
+
const M = String(date.getMonth() + 1).padStart(2, "0");
|
|
198
|
+
const D = String(date.getDate()).padStart(2, "0");
|
|
199
|
+
const h = String(date.getHours()).padStart(2, "0");
|
|
200
|
+
const m = String(date.getMinutes()).padStart(2, "0");
|
|
201
|
+
const s = String(date.getSeconds()).padStart(2, "0");
|
|
202
|
+
return `${Y}-${M}-${D} ${h}:${m}:${s}`;
|
|
203
|
+
}
|
|
204
|
+
function getCommitsForProject(projectPath, since, until) {
|
|
205
|
+
const projectName = path2.basename(projectPath);
|
|
206
|
+
const email = getUserEmail(projectPath);
|
|
207
|
+
const sinceStr = formatLocalDate(since);
|
|
208
|
+
const untilStr = formatLocalDate(until);
|
|
209
|
+
let authorArg = "";
|
|
210
|
+
if (email) {
|
|
211
|
+
authorArg = `--author="${email.replace(/"/g, '\\"')}"`;
|
|
212
|
+
}
|
|
213
|
+
const delimiter = "|||";
|
|
214
|
+
const formatStr = `%aI${delimiter}%an${delimiter}%ae${delimiter}%h${delimiter}%s`;
|
|
215
|
+
const cmd = `git log --all --since="${sinceStr}" --until="${untilStr}" ${authorArg} --pretty=format:"${formatStr}" --shortstat --date=iso-strict`;
|
|
216
|
+
try {
|
|
217
|
+
const stdout = execSync(cmd, {
|
|
218
|
+
cwd: projectPath,
|
|
219
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
220
|
+
// 20MB buffer for large repos
|
|
221
|
+
encoding: "utf8"
|
|
222
|
+
});
|
|
223
|
+
if (!stdout.trim()) {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
const lines = stdout.split("\n").map((l) => l.trim());
|
|
227
|
+
const results = [];
|
|
228
|
+
let currentCommit = null;
|
|
229
|
+
for (const line of lines) {
|
|
230
|
+
if (line.length === 0) continue;
|
|
231
|
+
if (line.includes(delimiter)) {
|
|
232
|
+
if (currentCommit) {
|
|
233
|
+
results.push(currentCommit);
|
|
234
|
+
}
|
|
235
|
+
const parts = line.split(delimiter);
|
|
236
|
+
const [date, name, authorEmail, hash, message] = parts;
|
|
237
|
+
currentCommit = {
|
|
238
|
+
project: projectName,
|
|
239
|
+
hash: hash || "",
|
|
240
|
+
date: date || "",
|
|
241
|
+
message: message || "",
|
|
242
|
+
authorName: name || "",
|
|
243
|
+
authorEmail: authorEmail || "",
|
|
244
|
+
additions: 0,
|
|
245
|
+
deletions: 0
|
|
246
|
+
};
|
|
247
|
+
} else if (currentCommit) {
|
|
248
|
+
const insMatch = line.match(/(\d+)\s+insertions?\(\+\)/);
|
|
249
|
+
const delMatch = line.match(/(\d+)\s+deletions?\(-\)/);
|
|
250
|
+
currentCommit.additions = insMatch ? parseInt(insMatch[1], 10) : 0;
|
|
251
|
+
currentCommit.deletions = delMatch ? parseInt(delMatch[1], 10) : 0;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (currentCommit) {
|
|
255
|
+
results.push(currentCommit);
|
|
256
|
+
}
|
|
257
|
+
return results;
|
|
258
|
+
} catch (e) {
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function getLocalGitIdentity() {
|
|
263
|
+
const emails = [];
|
|
264
|
+
const names = [];
|
|
265
|
+
try {
|
|
266
|
+
const email = execSync("git config user.email", { encoding: "utf8" }).trim();
|
|
267
|
+
if (email) emails.push(email.toLowerCase());
|
|
268
|
+
} catch {
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
const emailGlobal = execSync("git config --global user.email", { encoding: "utf8" }).trim();
|
|
272
|
+
if (emailGlobal) emails.push(emailGlobal.toLowerCase());
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
const name = execSync("git config user.name", { encoding: "utf8" }).trim();
|
|
277
|
+
if (name) names.push(name.toLowerCase());
|
|
278
|
+
} catch {
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const nameGlobal = execSync("git config --global user.name", { encoding: "utf8" }).trim();
|
|
282
|
+
if (nameGlobal) names.push(nameGlobal.toLowerCase());
|
|
283
|
+
} catch {
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
emails: Array.from(new Set(emails)),
|
|
287
|
+
names: Array.from(new Set(names))
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/gitlab.ts
|
|
292
|
+
async function fetchGitLabCommits(since, until, source) {
|
|
293
|
+
const config = readConfig();
|
|
294
|
+
const gitlabs = config.gitlabs || [];
|
|
295
|
+
if (gitlabs.length === 0) {
|
|
296
|
+
if (config.gitlabToken) {
|
|
297
|
+
gitlabs.push({
|
|
298
|
+
token: config.gitlabToken,
|
|
299
|
+
host: config.gitlabHost || "https://gitlab.com",
|
|
300
|
+
name: (config.gitlabHost || "https://gitlab.com").replace(/^https?:\/\//, "").replace(/\/$/, "")
|
|
301
|
+
});
|
|
302
|
+
} else {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
let selectedGitLab = gitlabs[0];
|
|
307
|
+
if (source) {
|
|
308
|
+
const index = parseInt(source, 10) - 1;
|
|
309
|
+
if (!isNaN(index) && index >= 0 && index < gitlabs.length) {
|
|
310
|
+
selectedGitLab = gitlabs[index];
|
|
311
|
+
} else {
|
|
312
|
+
const found = gitlabs.find(
|
|
313
|
+
(g) => g.name.toLowerCase() === source.toLowerCase() || g.host.toLowerCase().includes(source.toLowerCase())
|
|
314
|
+
);
|
|
315
|
+
if (found) {
|
|
316
|
+
selectedGitLab = found;
|
|
317
|
+
} else {
|
|
318
|
+
console.warn(`\x1B[33m[Warning] GitLab source "${source}" not found. Using default: ${selectedGitLab.name}\x1B[0m`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const token = selectedGitLab.token;
|
|
323
|
+
const host = selectedGitLab.host;
|
|
324
|
+
const cleanHost = host.replace(/\/$/, "");
|
|
325
|
+
try {
|
|
326
|
+
const userUrl = `${cleanHost}/api/v4/user`;
|
|
327
|
+
const userRes = await fetch(userUrl, {
|
|
328
|
+
headers: { "PRIVATE-TOKEN": token }
|
|
329
|
+
});
|
|
330
|
+
if (!userRes.ok) {
|
|
331
|
+
console.warn(`\x1B[33m[Warning] Failed to fetch GitLab user info: ${userRes.statusText}\x1B[0m`);
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
const user = await userRes.json();
|
|
335
|
+
const lowerEmail = user.email.toLowerCase();
|
|
336
|
+
const lowerName = user.name.toLowerCase();
|
|
337
|
+
const lowerUsername = user.username.toLowerCase();
|
|
338
|
+
const activeAfter = since.toISOString();
|
|
339
|
+
const projectsUrl = `${cleanHost}/api/v4/projects?membership=true&last_activity_after=${activeAfter}&per_page=100`;
|
|
340
|
+
const projectsRes = await fetch(projectsUrl, {
|
|
341
|
+
headers: { "PRIVATE-TOKEN": token }
|
|
342
|
+
});
|
|
343
|
+
if (!projectsRes.ok) {
|
|
344
|
+
console.warn(`\x1B[33m[Warning] Failed to fetch GitLab projects: ${projectsRes.statusText}\x1B[0m`);
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
const projects = await projectsRes.json();
|
|
348
|
+
if (projects.length === 0) {
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
const sinceStr = since.toISOString();
|
|
352
|
+
const untilStr = until.toISOString();
|
|
353
|
+
const commitPromises = projects.map(async (project) => {
|
|
354
|
+
try {
|
|
355
|
+
const commitsUrl = `${cleanHost}/api/v4/projects/${project.id}/repository/commits?since=${sinceStr}&until=${untilStr}&per_page=100`;
|
|
356
|
+
const commitsRes = await fetch(commitsUrl, {
|
|
357
|
+
headers: { "PRIVATE-TOKEN": token }
|
|
358
|
+
});
|
|
359
|
+
if (!commitsRes.ok) {
|
|
360
|
+
return [];
|
|
361
|
+
}
|
|
362
|
+
const commits = await commitsRes.json();
|
|
363
|
+
const localIdentity = getLocalGitIdentity();
|
|
364
|
+
const userCommits = commits.filter((c) => {
|
|
365
|
+
const authorEmail = c.author_email.toLowerCase();
|
|
366
|
+
const authorName = c.author_name.toLowerCase();
|
|
367
|
+
const matchesGitLab = authorEmail === lowerEmail || authorName === lowerName || authorName === lowerUsername;
|
|
368
|
+
const matchesLocal = localIdentity.emails.includes(authorEmail) || localIdentity.names.includes(authorName);
|
|
369
|
+
return matchesGitLab || matchesLocal;
|
|
370
|
+
});
|
|
371
|
+
if (userCommits.length === 0) return [];
|
|
372
|
+
const statsResults = [];
|
|
373
|
+
const batchSize = 8;
|
|
374
|
+
for (let i = 0; i < userCommits.length; i += batchSize) {
|
|
375
|
+
const batch = userCommits.slice(i, i + batchSize);
|
|
376
|
+
const batchResults = await Promise.all(
|
|
377
|
+
batch.map(async (c) => {
|
|
378
|
+
let adds = 0;
|
|
379
|
+
let dels = 0;
|
|
380
|
+
try {
|
|
381
|
+
const detailUrl = `${cleanHost}/api/v4/projects/${project.id}/repository/commits/${encodeURIComponent(c.id)}`;
|
|
382
|
+
const detailRes = await fetch(detailUrl, {
|
|
383
|
+
headers: { "PRIVATE-TOKEN": token }
|
|
384
|
+
});
|
|
385
|
+
if (detailRes.ok) {
|
|
386
|
+
const detail = await detailRes.json();
|
|
387
|
+
if (detail.stats) {
|
|
388
|
+
adds = detail.stats.additions;
|
|
389
|
+
dels = detail.stats.deletions;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} catch {
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
project: project.name,
|
|
396
|
+
hash: c.short_id,
|
|
397
|
+
date: c.created_at,
|
|
398
|
+
message: c.title,
|
|
399
|
+
authorName: c.author_name,
|
|
400
|
+
authorEmail: c.author_email,
|
|
401
|
+
additions: adds,
|
|
402
|
+
deletions: dels
|
|
403
|
+
};
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
statsResults.push(...batchResults);
|
|
407
|
+
}
|
|
408
|
+
return statsResults;
|
|
409
|
+
} catch {
|
|
410
|
+
return [];
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
const results = await Promise.all(commitPromises);
|
|
414
|
+
return results.flat();
|
|
415
|
+
} catch (e) {
|
|
416
|
+
console.warn(`\x1B[33m[Warning] GitLab connection failed: ${e.message}\x1B[0m`);
|
|
417
|
+
return [];
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// src/analyzer.ts
|
|
422
|
+
var DAY_NAMES = ["\u5468\u4E00", "\u5468\u4E8C", "\u5468\u4E09", "\u5468\u56DB", "\u5468\u4E94", "\u5468\u516D", "\u5468\u65E5"];
|
|
423
|
+
function parseGitISODate(dateStr) {
|
|
424
|
+
const d = new Date(dateStr);
|
|
425
|
+
if (isNaN(d.getTime())) {
|
|
426
|
+
const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/);
|
|
427
|
+
if (match) {
|
|
428
|
+
return {
|
|
429
|
+
year: parseInt(match[1], 10),
|
|
430
|
+
month: parseInt(match[2], 10) - 1,
|
|
431
|
+
day: parseInt(match[3], 10),
|
|
432
|
+
hour: parseInt(match[4], 10),
|
|
433
|
+
minute: parseInt(match[5], 10),
|
|
434
|
+
second: parseInt(match[6], 10)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
year: d.getFullYear(),
|
|
440
|
+
month: d.getMonth(),
|
|
441
|
+
day: d.getDate(),
|
|
442
|
+
hour: d.getHours(),
|
|
443
|
+
minute: d.getMinutes(),
|
|
444
|
+
second: d.getSeconds()
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function getThisWeekRange(now = /* @__PURE__ */ new Date()) {
|
|
448
|
+
const start = new Date(now);
|
|
449
|
+
const day = start.getDay();
|
|
450
|
+
const diff = start.getDate() - (day === 0 ? 6 : day - 1);
|
|
451
|
+
start.setDate(diff);
|
|
452
|
+
start.setHours(0, 0, 0, 0);
|
|
453
|
+
const end = new Date(start);
|
|
454
|
+
end.setDate(start.getDate() + 6);
|
|
455
|
+
end.setHours(23, 59, 59, 999);
|
|
456
|
+
return { since: start, until: end };
|
|
457
|
+
}
|
|
458
|
+
function getThisMonthRange(now = /* @__PURE__ */ new Date()) {
|
|
459
|
+
const start = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0);
|
|
460
|
+
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
|
|
461
|
+
return { since: start, until: end };
|
|
462
|
+
}
|
|
463
|
+
function categorizeCommit(message) {
|
|
464
|
+
const msg = message.toLowerCase().trim();
|
|
465
|
+
if (/^(fix|bug|hotfix|resolve)/.test(msg) || msg.includes("fix") || msg.includes("bug")) {
|
|
466
|
+
return "fix";
|
|
467
|
+
}
|
|
468
|
+
if (/^(feat|feature|add|create)/.test(msg) || msg.includes("feat") || msg.includes("feature") || msg.includes("add ")) {
|
|
469
|
+
return "feat";
|
|
470
|
+
}
|
|
471
|
+
if (/^(chore|docs|doc|config|refactor|style|test|ci)/.test(msg) || msg.includes("chore") || msg.includes("doc") || msg.includes("refactor") || msg.includes("style") || msg.includes("test") || msg.includes("ci")) {
|
|
472
|
+
return "chore";
|
|
473
|
+
}
|
|
474
|
+
return "other";
|
|
475
|
+
}
|
|
476
|
+
function calculateDayIndices(commits, isWeekend = false) {
|
|
477
|
+
const N = commits.length;
|
|
478
|
+
if (N === 0) {
|
|
479
|
+
return {
|
|
480
|
+
fish: 90,
|
|
481
|
+
hardworking: 10,
|
|
482
|
+
nightOwl: 0,
|
|
483
|
+
builder: 0,
|
|
484
|
+
burst: 0,
|
|
485
|
+
density: 0,
|
|
486
|
+
tags: ["\u{1F41F} \u4ECA\u65E5\u6682\u65E0\u4EE3\u7801\u6D3B\u52A8"]
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
let earliest = 24;
|
|
490
|
+
let latest = 0;
|
|
491
|
+
let totalAdditions = 0;
|
|
492
|
+
let totalDeletions = 0;
|
|
493
|
+
let nightCount = 0;
|
|
494
|
+
for (const commit of commits) {
|
|
495
|
+
const parsed = parseGitISODate(commit.date);
|
|
496
|
+
const fh = parsed.hour + parsed.minute / 60 + parsed.second / 3600;
|
|
497
|
+
if (fh < earliest) earliest = fh;
|
|
498
|
+
if (fh > latest) latest = fh;
|
|
499
|
+
if (parsed.hour >= 0 && parsed.hour <= 5) nightCount++;
|
|
500
|
+
totalAdditions += commit.additions;
|
|
501
|
+
totalDeletions += commit.deletions;
|
|
502
|
+
}
|
|
503
|
+
const S = Math.max(0.1, latest - earliest);
|
|
504
|
+
const L = totalAdditions + totalDeletions;
|
|
505
|
+
const avgLines = L / Math.max(N, 1);
|
|
506
|
+
const D = N / (S + 1);
|
|
507
|
+
const commitScore = Math.min(100, Math.log2(N + 1) * 25);
|
|
508
|
+
const spanScore = Math.min(100, Math.sqrt(S) * 25);
|
|
509
|
+
const lineScore = Math.min(100, Math.log2(L + 1) * 12);
|
|
510
|
+
const densityScore = N <= 1 ? 0 : Math.min(100, D * 50);
|
|
511
|
+
const nightScore = Math.min(100, nightCount * 30);
|
|
512
|
+
let hardworking = commitScore * 0.3 + lineScore * 0.3 + densityScore * 0.2 + spanScore * 0.2;
|
|
513
|
+
if (isWeekend && N > 0) {
|
|
514
|
+
hardworking += 8;
|
|
515
|
+
}
|
|
516
|
+
hardworking = Math.min(100, Math.round(hardworking));
|
|
517
|
+
const fish = Math.max(1, 100 - hardworking);
|
|
518
|
+
const nightOwl = nightCount === 0 ? 0 : Math.min(100, Math.round(
|
|
519
|
+
nightScore * 0.7 + spanScore * 0.3
|
|
520
|
+
));
|
|
521
|
+
const builder = Math.min(100, Math.round(
|
|
522
|
+
lineScore * 0.7 + commitScore * 0.3
|
|
523
|
+
));
|
|
524
|
+
const burst = Math.min(100, Math.round(
|
|
525
|
+
Math.log2(avgLines + 1) * 12
|
|
526
|
+
));
|
|
527
|
+
const tags = [];
|
|
528
|
+
if (fish >= 80) tags.push("\u{1F41F} \u6478\u9C7C\u5B97\u5E08");
|
|
529
|
+
if (hardworking >= 80) tags.push("\u{1F525} \u7206\u809D\u6218\u795E");
|
|
530
|
+
if (nightOwl >= 80) tags.push("\u{1F319} \u6DF1\u591C\u4FEE\u4ED9\u8005");
|
|
531
|
+
if (builder >= 80) tags.push("\u{1F9F1} \u52E4\u6073\u642C\u7816\u4EBA");
|
|
532
|
+
if (burst >= 80 && N <= 2) tags.push(`\u{1F4A5} \u4E00\u628A\u68AD\u54C8\u578B\u7A0B\u5E8F\u5458 (\u5747${Math.round(avgLines)}\u884C/\u6B21)`);
|
|
533
|
+
if (commitScore >= 70 && lineScore <= 15) tags.push("\u{1F3F7}\uFE0F PPT \u67B6\u6784\u5E08");
|
|
534
|
+
if (N >= 10 && L <= 30) tags.push("\u{1F3F7}\uFE0F \u683C\u5F0F\u5316\u5927\u5E08");
|
|
535
|
+
if (N >= 15 && avgLines <= 2) tags.push("\u{1F3F7}\uFE0F Git \u804A\u5929\u8FBE\u4EBA");
|
|
536
|
+
if (nightOwl >= 80 && N <= 3) tags.push("\u{1F3F7}\uFE0F \u6DF1\u591C\u523A\u5BA2");
|
|
537
|
+
if (hardworking >= 90 && nightOwl >= 70 && isWeekend) tags.push("\u{1F3F7}\uFE0F \u751F\u4EA7\u961F\u7684\u9A74");
|
|
538
|
+
if (fish >= 95 && N <= 1) tags.push("\u{1F3F7}\uFE0F \u6478\u9C7C\u4ED9\u4EBA");
|
|
539
|
+
return {
|
|
540
|
+
fish,
|
|
541
|
+
hardworking,
|
|
542
|
+
nightOwl,
|
|
543
|
+
builder,
|
|
544
|
+
burst,
|
|
545
|
+
density: Math.round(Math.min(100, densityScore)),
|
|
546
|
+
tags
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
async function getAllCommits(projectPaths, since, until, source) {
|
|
550
|
+
let all = [];
|
|
551
|
+
for (const p of projectPaths) {
|
|
552
|
+
const commits = getCommitsForProject(p, since, until);
|
|
553
|
+
all = all.concat(commits);
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
const gitlabCommits = await fetchGitLabCommits(since, until, source);
|
|
557
|
+
all = all.concat(gitlabCommits);
|
|
558
|
+
} catch {
|
|
559
|
+
}
|
|
560
|
+
const seen = /* @__PURE__ */ new Set();
|
|
561
|
+
const unique = [];
|
|
562
|
+
for (const c of all) {
|
|
563
|
+
if (!seen.has(c.hash)) {
|
|
564
|
+
seen.add(c.hash);
|
|
565
|
+
unique.push(c);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return unique.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
|
569
|
+
}
|
|
570
|
+
async function analyzeWeekly(projectPaths, now = /* @__PURE__ */ new Date(), source) {
|
|
571
|
+
const { since, until } = getThisWeekRange(now);
|
|
572
|
+
const commits = await getAllCommits(projectPaths, since, until, source);
|
|
573
|
+
const commitsByDay = Array.from({ length: 7 }, () => []);
|
|
574
|
+
for (const commit of commits) {
|
|
575
|
+
const parsed = parseGitISODate(commit.date);
|
|
576
|
+
const localDate = new Date(parsed.year, parsed.month, parsed.day);
|
|
577
|
+
const dayOfWeek = (localDate.getDay() + 6) % 7;
|
|
578
|
+
commitsByDay[dayOfWeek].push(commit);
|
|
579
|
+
}
|
|
580
|
+
const days = DAY_NAMES.map((name, idx) => {
|
|
581
|
+
const dayCommits = commitsByDay[idx];
|
|
582
|
+
const projects = Array.from(new Set(dayCommits.map((c) => c.project)));
|
|
583
|
+
const indices = calculateDayIndices(dayCommits, idx >= 5);
|
|
584
|
+
return {
|
|
585
|
+
dayName: name,
|
|
586
|
+
commitsCount: dayCommits.length,
|
|
587
|
+
projects,
|
|
588
|
+
fish: indices.fish,
|
|
589
|
+
hardworking: indices.hardworking,
|
|
590
|
+
nightOwl: indices.nightOwl,
|
|
591
|
+
builder: indices.builder,
|
|
592
|
+
burst: indices.burst,
|
|
593
|
+
density: indices.density,
|
|
594
|
+
tags: indices.tags
|
|
595
|
+
};
|
|
596
|
+
});
|
|
597
|
+
const projMap = {};
|
|
598
|
+
let ghostCount = 0;
|
|
599
|
+
for (const commit of commits) {
|
|
600
|
+
projMap[commit.project] = (projMap[commit.project] || 0) + 1;
|
|
601
|
+
const parsed = parseGitISODate(commit.date);
|
|
602
|
+
if (parsed.hour >= 0 && parsed.hour <= 5) {
|
|
603
|
+
ghostCount++;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
const projectsRanked = Object.entries(projMap).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
|
|
607
|
+
const realNow = /* @__PURE__ */ new Date();
|
|
608
|
+
const { since: realSince } = getThisWeekRange(realNow);
|
|
609
|
+
const isPastWeek = since.getTime() < realSince.getTime();
|
|
610
|
+
const currentDayOfWeek = isPastWeek ? 6 : (now.getDay() + 6) % 7;
|
|
611
|
+
let mostProductiveDay = null;
|
|
612
|
+
let leastProductiveDay = null;
|
|
613
|
+
const activeDays = days.slice(0, currentDayOfWeek + 1);
|
|
614
|
+
const workingDays = activeDays.filter((d) => d.commitsCount > 0);
|
|
615
|
+
if (workingDays.length > 0) {
|
|
616
|
+
mostProductiveDay = [...workingDays].sort((a, b) => {
|
|
617
|
+
if (a.fish !== b.fish) return a.fish - b.fish;
|
|
618
|
+
return b.commitsCount - a.commitsCount;
|
|
619
|
+
})[0];
|
|
620
|
+
}
|
|
621
|
+
{
|
|
622
|
+
const sortedLeast = [...activeDays].sort((a, b) => {
|
|
623
|
+
if (a.fish !== b.fish) return b.fish - a.fish;
|
|
624
|
+
return a.commitsCount - b.commitsCount;
|
|
625
|
+
});
|
|
626
|
+
if (sortedLeast[0] && (mostProductiveDay === null || sortedLeast[0].dayName !== mostProductiveDay.dayName)) {
|
|
627
|
+
leastProductiveDay = sortedLeast[0];
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
const sumFish = activeDays.reduce((acc, d) => acc + d.fish, 0);
|
|
631
|
+
const sumHardworking = activeDays.reduce((acc, d) => acc + d.hardworking, 0);
|
|
632
|
+
const sumNightOwl = activeDays.reduce((acc, d) => acc + d.nightOwl, 0);
|
|
633
|
+
const sumBuilder = activeDays.reduce((acc, d) => acc + d.builder, 0);
|
|
634
|
+
const sumBurst = activeDays.reduce((acc, d) => acc + d.burst, 0);
|
|
635
|
+
const activeCount = activeDays.length || 1;
|
|
636
|
+
const averageFish = Math.round(sumFish / activeCount);
|
|
637
|
+
const averageHardworking = Math.round(sumHardworking / activeCount);
|
|
638
|
+
const averageNightOwl = Math.round(sumNightOwl / activeCount);
|
|
639
|
+
const averageBuilder = Math.round(sumBuilder / activeCount);
|
|
640
|
+
const averageBurst = Math.round(sumBurst / activeCount);
|
|
641
|
+
return {
|
|
642
|
+
days,
|
|
643
|
+
totalCommits: commits.length,
|
|
644
|
+
projectsRanked,
|
|
645
|
+
mostProductiveDay,
|
|
646
|
+
leastProductiveDay,
|
|
647
|
+
averageFish,
|
|
648
|
+
averageHardworking,
|
|
649
|
+
averageNightOwl,
|
|
650
|
+
averageBuilder,
|
|
651
|
+
averageBurst,
|
|
652
|
+
ghostCommitsCount: ghostCount
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
async function analyzeMonthly(projectPaths, now = /* @__PURE__ */ new Date(), source) {
|
|
656
|
+
const { since, until } = getThisMonthRange(now);
|
|
657
|
+
const commits = await getAllCommits(projectPaths, since, until, source);
|
|
658
|
+
const categories = { fix: 0, feat: 0, chore: 0, other: 0 };
|
|
659
|
+
const projMap = {};
|
|
660
|
+
let ghostCount = 0;
|
|
661
|
+
const commitsByDateStr = {};
|
|
662
|
+
for (const commit of commits) {
|
|
663
|
+
projMap[commit.project] = (projMap[commit.project] || 0) + 1;
|
|
664
|
+
const cat = categorizeCommit(commit.message);
|
|
665
|
+
categories[cat]++;
|
|
666
|
+
const parsed = parseGitISODate(commit.date);
|
|
667
|
+
if (parsed.hour >= 0 && parsed.hour <= 5) {
|
|
668
|
+
ghostCount++;
|
|
669
|
+
}
|
|
670
|
+
const dateStr = `${parsed.year}-${parsed.month + 1}-${parsed.day}`;
|
|
671
|
+
if (!commitsByDateStr[dateStr]) {
|
|
672
|
+
commitsByDateStr[dateStr] = [];
|
|
673
|
+
}
|
|
674
|
+
commitsByDateStr[dateStr].push(commit);
|
|
675
|
+
}
|
|
676
|
+
const projectsRanked = Object.entries(projMap).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
|
|
677
|
+
const realNow = /* @__PURE__ */ new Date();
|
|
678
|
+
const isCurrentMonth = realNow.getFullYear() === since.getFullYear() && realNow.getMonth() === since.getMonth();
|
|
679
|
+
const maxDay = isCurrentMonth ? realNow.getDate() : until.getDate();
|
|
680
|
+
let totalFish = 0;
|
|
681
|
+
let totalHardworking = 0;
|
|
682
|
+
let totalNightOwl = 0;
|
|
683
|
+
let totalBuilder = 0;
|
|
684
|
+
let totalBurst = 0;
|
|
685
|
+
const dailyIndices = [];
|
|
686
|
+
for (let d = 1; d <= maxDay; d++) {
|
|
687
|
+
const dateStr = `${since.getFullYear()}-${since.getMonth() + 1}-${d}`;
|
|
688
|
+
const dayCommits = commitsByDateStr[dateStr] || [];
|
|
689
|
+
const indices = calculateDayIndices(dayCommits, false);
|
|
690
|
+
totalFish += indices.fish;
|
|
691
|
+
totalHardworking += indices.hardworking;
|
|
692
|
+
totalNightOwl += indices.nightOwl;
|
|
693
|
+
totalBuilder += indices.builder;
|
|
694
|
+
totalBurst += indices.burst;
|
|
695
|
+
dailyIndices.push({
|
|
696
|
+
day: d,
|
|
697
|
+
commitsCount: dayCommits.length,
|
|
698
|
+
fish: indices.fish,
|
|
699
|
+
hardworking: indices.hardworking,
|
|
700
|
+
nightOwl: indices.nightOwl,
|
|
701
|
+
burst: indices.burst,
|
|
702
|
+
tags: indices.tags
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
const averageFish = Math.round(totalFish / maxDay);
|
|
706
|
+
const averageHardworking = Math.round(totalHardworking / maxDay);
|
|
707
|
+
const averageNightOwl = Math.round(totalNightOwl / maxDay);
|
|
708
|
+
const averageBuilder = Math.round(totalBuilder / maxDay);
|
|
709
|
+
const averageBurst = Math.round(totalBurst / maxDay);
|
|
710
|
+
return {
|
|
711
|
+
totalCommits: commits.length,
|
|
712
|
+
categories,
|
|
713
|
+
projectsRanked,
|
|
714
|
+
ghostCommitsCount: ghostCount,
|
|
715
|
+
averageFish,
|
|
716
|
+
averageHardworking,
|
|
717
|
+
averageNightOwl,
|
|
718
|
+
averageBuilder,
|
|
719
|
+
averageBurst,
|
|
720
|
+
dailyIndices
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
async function analyzeHourDistribution(projectPaths, since, until, source) {
|
|
724
|
+
const commits = await getAllCommits(projectPaths, since, until, source);
|
|
725
|
+
const hours = Array(24).fill(0);
|
|
726
|
+
for (const commit of commits) {
|
|
727
|
+
const parsed = parseGitISODate(commit.date);
|
|
728
|
+
hours[parsed.hour]++;
|
|
729
|
+
}
|
|
730
|
+
return hours.map((count, hour) => ({ hour, count }));
|
|
731
|
+
}
|
|
732
|
+
async function getGhostCommits(projectPaths, since, until, source) {
|
|
733
|
+
const commits = await getAllCommits(projectPaths, since, until, source);
|
|
734
|
+
return commits.filter((commit) => {
|
|
735
|
+
const parsed = parseGitISODate(commit.date);
|
|
736
|
+
return parsed.hour >= 0 && parsed.hour <= 5;
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// src/critic.ts
|
|
741
|
+
var SLACK_HIGH_CRITIQUES = [
|
|
742
|
+
"\u672C\u5468\u6478\u9C7C\u6307\u6570\u62C9\u6EE1\uFF01\u770B\u6765\u662F\u5728\u6DF1\u5EA6\u8D2F\u5F7B\u2018\u52B3\u9038\u7ED3\u5408\u2019\u7684\u6700\u9AD8\u751F\u4EA7\u529B\u539F\u5219\uFF0C\u8FDE\u4E0B\u5468\u4E2D\u5348\u5403\u4EC0\u4E48\u90FD\u89C4\u5212\u5F97\u4E95\u4E95\u6709\u6761\u3002",
|
|
743
|
+
"\u4F60\u7684 Commit \u5217\u8868\u5E72\u51C0\u5F97\u50CF\u521A\u6D17\u8FC7\u7684\u8138\u3002\u84C4\u52BF\u5F85\u53D1\u4E5F\u662F\u4E00\u79CD\u8282\u594F\uFF0C\u671F\u5F85\u4F60\u4E0B\u5468\u79EF\u6512\u7684\u5927\u62DB\uFF01",
|
|
744
|
+
"\u770B\u6765\u672C\u5468\u7684\u9879\u76EE\u8FDB\u5165\u4E86\u2018\u517B\u7CBE\u84C4\u9510\u2019\u9636\u6BB5\u2014\u2014\u624B\u673A\u592A\u597D\u73A9\uFF0C\u6216\u8005\u5E8A\u592A\u6696\u548C\u3002\u6478\u9C7C\u6280\u5DE7\u5DF2\u8FBE\u7089\u706B\u7EAF\u9752\u4E4B\u5883\u3002",
|
|
745
|
+
"\u4F60\u7684\u63D0\u4EA4\u9891\u7387\u50CF\u6781\u4E86\u9000\u6F6E\u540E\u7684\u6C99\u6EE9\uFF0C\u6BEB\u65E0\u6CE2\u6F9C\u3002\u5EFA\u8BAE\u4E0B\u5468\u7A0D\u5FAE\u52A8\u52A8\u624B\u6307\uFF0C\u8BA9 Git \u56FE\u6807\u91CD\u65B0\u4EAE\u8D77\u6765\u3002",
|
|
746
|
+
"\u8FD9\u63D0\u4EA4\u6B21\u6570\uFF0C\u4E24\u53EA\u624B\u90FD\u6570\u5F97\u8FC7\u6765\u3002\u770B\u6765\u662F\u5728\u8DF5\u884C\u2018\u4E0D\u5199\u4EE3\u7801\u5C31\u6CA1\u6709 bug\u2019\u7684\u81F3\u9AD8\u9632\u7EBF\uFF0C\u4E3B\u6253\u4E00\u4E2A\u7A33\u5B57\u5F53\u5934\u3002"
|
|
747
|
+
];
|
|
748
|
+
var SLACK_LOW_CRITIQUES = [
|
|
749
|
+
"\u672C\u5468\u7206\u809D\u6307\u6570\u7206\u8868\uFF01\u770B\u5230\u4F60\u8FD9\u5BC6\u5BC6\u9EBB\u9EBB\u7684\u63D0\u4EA4\u8BB0\u5F55\uFF0C\u9694\u58C1\u6D4B\u8BD5\u5C0F\u59D0\u59D0\u7684\u773C\u5708\u7EA2\u4E86\uFF0C\u8FDE\u4F60\u7684\u952E\u76D8\u90FD\u5728\u4E3A\u4F60\u957F\u9E23\u3002",
|
|
750
|
+
"\u4F60\u8FD9\u4E48\u62FC\u547D\u5DE5\u4F5C\uFF0C\u4EE3\u7801\u5E93\u7684\u534A\u58C1\u6C5F\u5C71\u90FD\u662F\u4F60\u6253\u4E0B\u6765\u7684\u3002\u4E0B\u5468\u5FC5\u987B\u5B89\u6392\u4E00\u676F\u5976\u8336\uFF0C\u597D\u597D\u7292\u52B3\u4E00\u4E0B\u81EA\u5DF1\uFF01",
|
|
751
|
+
"\u5144\u5F1F\uFF0C\u4F60\u8FD9\u5468\u7684\u5DE5\u65F6\u76F4\u63A5\u62C9\u6EE1\u4E86\u3002\u5EFA\u8BAE\u7ED9\u81EA\u5DF1\u653E\u4E2A\u5047\uFF0C\u4EE3\u7801\u53EF\u4EE5\u660E\u5929\u5199\uFF0C\u8EAB\u4F53\u53EF\u6CA1\u6709\u64A4\u9500\u952E\uFF0C\u597D\u597D\u4F11\u606F\u4E00\u4E0B\u3002",
|
|
752
|
+
"\u75AF\u72C2\u642C\u7816\uFF0C\u809D\u5929\u809D\u5730\u3002\u4E0D\u4EC5\u5728\u8DDF\u65F6\u95F4\u8D5B\u8DD1\uFF0C\u66F4\u662F\u5728\u7528\u4E00\u5DF1\u4E4B\u529B\u5E2E\u56E2\u961F\u586B\u5E73\u524D\u671F\u7684\u5404\u79CD\u6280\u672F\u5751\uFF0C\u8F9B\u82E6\u4E86\uFF01",
|
|
753
|
+
"\u63D0\u4EA4\u6B21\u6570\u591A\u5230\u8BA9\u4EBA\u5FC3\u75BC\u3002\u9879\u76EE\u662F\u957F\u671F\u7684\uFF0C\u8EAB\u4F53\u662F\u81EA\u5DF1\u7684\u3002\u7559\u70B9\u529B\u6C14\uFF0C\u4E0B\u5468\u6211\u4EEC\u7EE7\u7EED\u7EC6\u6C34\u957F\u6D41\u3002"
|
|
754
|
+
];
|
|
755
|
+
var GHOST_CRITIQUES = [
|
|
756
|
+
"\u3010\u5E7D\u7075\u63D0\u4EA4\u9884\u8B66\u3011\u6DF1\u591C\u7684 Commit \u95EA\u70C1\u7740\u7EFF\u5149\u3002\u4E0D\u8FC7\u4FEE\u4ED9\u5F52\u4FEE\u4ED9\uFF0C\u7761\u89C9\u4E5F\u662F\u7A0B\u5E8F\u5458\u7684\u91CD\u8981\u6280\u80FD\uFF0C\u5FEB\u53BB\u4F11\u606F\u5427\u3002",
|
|
757
|
+
"\u534A\u591C\u4E09\u66F4\u8FD8\u5728\u63D0\u4EA4\u4EE3\u7801\uFF0C\u4F60\u662F\u5728\u548C\u5730\u7403\u53E6\u4E00\u7AEF\u7684\u7A0B\u5E8F\u5458\u6253\u65F6\u5DEE\u6218\uFF0C\u8FD8\u662F\u5728\u4EAB\u53D7\u6DF1\u591C\u65E0\u6253\u6270\u7684\u7075\u611F\u7206\u53D1\uFF1F",
|
|
758
|
+
"\u6DF1\u591C 12 \u70B9\u540E\u7684\u63D0\u4EA4\u88AB\u68C0\u6D4B\u5230\uFF01\u4F60\u8FD9\u62FC\u547D\u7684\u67B6\u52BF\uFF0C\u662F\u5728\u7528\u6DF1\u591C\u7684\u7075\u611F\u4E3A\u9879\u76EE\u4FDD\u9A7E\u62A4\u822A\uFF0C\u4F46\u4E5F\u522B\u5FD8\u4E86\u7ED9\u8EAB\u4F53\u5145\u5145\u7535\u3002",
|
|
759
|
+
"\u522B\u809D\u4E86\u522B\u809D\u4E86\uFF0C\u6DF1\u591C\u8FD8\u5728\u63D0\u4EA4\uFF0C\u8FDE CI \u673A\u5668\u4EBA\u90FD\u60F3\u529D\u4F60\u65E9\u70B9\u7761\uFF0C\u5FEB\u5B58\u76D8\u4E0B\u73ED\uFF0C\u68A6\u91CC\u6CA1\u6709 bug\u3002"
|
|
760
|
+
];
|
|
761
|
+
var CATEGORY_FIX_CRITIQUES = [
|
|
762
|
+
"\u4F60\u5DF2\u7ECF\u6210\u4E3A\u56E2\u961F\u7684\u2018\u804C\u4E1A\u6551\u706B\u961F\u957F\u2019\uFF0C\u5929\u5929\u5728\u4EE3\u7801\u5E93\u91CC\u8003\u53E4\u548C\u6551\u706B\uFF0C\u56E2\u961F\u7684\u7A33\u5B9A\u9632\u7EBF\u5168\u9760\u4F60\u6B7B\u5B88\u3002",
|
|
763
|
+
"\u672C\u6708\u4E3B\u8981\u5DE5\u4F5C\u662F\u4FEE Bug\uFF0C\u5360\u6BD4\u9AD8\u8FBE %PERCENT%%\u3002\u662F\u5728\u4E00\u70B9\u70B9\u507F\u8FD8\u6280\u672F\u503A\uFF0C\u4E5F\u662F\u5728\u7ED9\u540E\u6765\u7684\u4EBA\u94FA\u5E73\u9053\u8DEF\u3002",
|
|
764
|
+
"\u5929\u5929\u90FD\u5728 fix\uFF0C\u770B\u6765\u8FD9\u4E2A\u6708\u7684\u7CFB\u7EDF\u7A33\u5B9A\u6027\u5168\u9760\u4F60\u5728\u7EBF\u5B88\u62A4\u4E86\uFF0C\u59A5\u59A5\u7684\u5E55\u540E\u82F1\u96C4\u3002",
|
|
765
|
+
"\u4EE3\u7801\u5E93\u7684\u6E05\u9053\u592B\uFF0CBug \u7EC8\u7ED3\u8005\u3002\u6BCF\u4FEE\u6389\u4E00\u4E2A\u95EE\u9898\uFF0C\u7CFB\u7EDF\u5C31\u5C11\u4E00\u5206\u9690\u60A3\uFF0C\u5B89\u5168\u611F\u76F4\u63A5\u62C9\u6EE1\u3002"
|
|
766
|
+
];
|
|
767
|
+
var CATEGORY_FEAT_CRITIQUES = [
|
|
768
|
+
"\u672C\u6708 Feat \u5360\u6BD4\u9AD8\u8FBE %PERCENT%%\u3002\u65B0\u529F\u80FD\u4E00\u9879\u63A5\u4E00\u9879\uFF0C\u5F00\u53D1\u706B\u529B\u5168\u5F00\uFF0C\u4E1A\u52A1\u7EBF\u56E0\u4F60\u800C\u98DE\u901F\u524D\u8FDB\uFF01",
|
|
769
|
+
"\u65B0\u529F\u80FD\u72C2\u9B54\uFF01\u4E0D\u505C\u5730\u63A8\u8FDB\u9700\u6C42\u548C\u65B0\u4E1A\u52A1\u3002\u8F93\u51FA\u62C9\u6EE1\u7684\u540C\u65F6\uFF0C\u4E5F\u522B\u5FD8\u4E86\u5076\u5C14\u56DE\u5934\u6253\u78E8\u4E00\u4E0B\u7EC6\u8282\u3002Btw\uFF1A\u4F60\u8F9B\u82E6\u5566\uFF01",
|
|
770
|
+
"\u75AF\u72C2\u8F93\u51FA feature\uFF01\u4EE3\u7801\u5E93\u7684\u8FB9\u754C\u53C8\u88AB\u4F60\u5411\u5916\u62D3\u5C55\u4E86\u4E00\u5708\uFF0C\u59A5\u59A5\u7684\u5F00\u62D3\u5148\u950B\u3002"
|
|
771
|
+
];
|
|
772
|
+
var CATEGORY_CHORE_CRITIQUES = [
|
|
773
|
+
"Chore/Docs \u5360\u4E86 %PERCENT%%\u3002\u597D\u7684\u9879\u76EE\u4E0D\u4EC5\u9700\u8981\u4EE3\u7801\uFF0C\u66F4\u9700\u8981\u6709\u4EBA\u628A\u6587\u6863\u6C89\u6DC0\u4E0B\u6765\uFF0C\u524D\u4EBA\u683D\u6811\u540E\u4EBA\u4E58\u51C9\u3002",
|
|
774
|
+
"\u5929\u5929\u90FD\u5728\u6539\u914D\u7F6E\u3001\u4FEE\u6587\u6863\u3001\u683C\u5F0F\u5316\u4EE3\u7801\u3002\u4F60\u7B80\u76F4\u662F\u4EE3\u7801\u5E93\u7684\u4F18\u79C0\u56ED\u4E01\uFF0C\u8FD9\u4E9B\u5DE5\u4F5C\u867D\u7136\u4F4E\u8C03\uFF0C\u5374\u8BA9\u9879\u76EE\u53D8\u5F97\u66F4\u5065\u5EB7\u3001\u66F4\u6613\u8BFB\u3002",
|
|
775
|
+
"Chore/Docs \u5360\u6BD4\u60CA\u4EBA\uFF0C\u9879\u76EE\u7684\u957F\u671F\u7A33\u5B9A\u8FD0\u884C\uFF0C\u79BB\u4E0D\u5F00\u8FD9\u4E9B\u770B\u4F3C\u4E0D\u8D77\u773C\u4F46\u6781\u4E3A\u5173\u952E\u7684\u7EF4\u62A4\u5DE5\u4F5C\u3002"
|
|
776
|
+
];
|
|
777
|
+
var GENERAL_CRITIQUES = [
|
|
778
|
+
"\u4F60\u7684\u63D0\u4EA4\u66F2\u7EBF\u50CF\u5FC3\u7535\u56FE\u4E00\u6837\u5E73\u7F13\uFF0C\u57FA\u672C\u4E0A\u53EA\u5728\u5F00\u4F1A\u524D\u548C\u4E0B\u73ED\u524D\u6709\u6CE2\u52A8\u3002\u5B8C\u7F8E\u5730\u638C\u63E1\u4E86\u5DE5\u4F5C\u4E0E\u4F11\u606F\u7684\u5F8B\u52A8\u3002",
|
|
779
|
+
"\u672C\u5468\u7684\u63D0\u4EA4\u96C6\u4E2D\u5728\u7279\u5B9A\u65F6\u95F4\u6BB5\u3002\u4E0A\u5348\u9759\u5982\u5904\u5B50\uFF0C\u4E0B\u5348\u52A8\u5982\u8131\u5154\u3002\u5B8C\u7F8E\u627E\u5230\u4E86\u5C5E\u4E8E\u81EA\u5DF1\u7684\u9AD8\u6548\u7387\u8282\u594F\u3002",
|
|
780
|
+
"\u770B\u7740\u4F60\u7684 Git Log\uFF0C\u6211\u4EFF\u4F5B\u770B\u5230\u4E86\u4E00\u4F4D\u8282\u594F\u5927\u5E08\u3002\u4E0D\u7D27\u4E0D\u6162\uFF0C\u5076\u5C14\u843D\u7B14\uFF0C\u5374\u603B\u662F\u6070\u5230\u597D\u5904\u5730\u628A\u5DE5\u4F5C\u5B8C\u6210\u5F97\u65E0\u53EF\u6311\u5254\u3002",
|
|
781
|
+
"\u4F60\u8FD9\u661F\u671F\u7684\u63D0\u4EA4\u6570\u636E\u975E\u5E38\u5747\u8861\u2014\u2014\u6BCF\u5929\u90FD\u4FDD\u6301\u7740\u5E73\u7A33\u7684\u8F93\u51FA\u8282\u594F\u3002\u7A33\u624E\u7A33\u6253\uFF0C\u4F60\u5C31\u662F\u56E2\u961F\u91CC\u7684\u5B9A\u6D77\u795E\u9488\u3002"
|
|
782
|
+
];
|
|
783
|
+
var TAG_FISH_MASTER_CRITIQUES = [
|
|
784
|
+
"\u6478\u9C7C\u5B97\u5E08\u79F0\u53F7\u5DF2\u89E3\u9501\uFF01\u628A\u52B3\u9038\u7ED3\u5408\u53D1\u6325\u5230\u4E86\u6781\u81F4\uFF0C\u8868\u9762\u7A33\u5982\u6CF0\u5C71\uFF0C\u5B9E\u9645\u4E0A\u8111\u6D77\u91CC\u5DF2\u7ECF\u628A\u5047\u671F\u89C4\u5212\u505A\u597D\u4E86\u3002",
|
|
785
|
+
"\u6478\u9C7C\u5B97\u5E08\uFF0C\u6CD5\u529B\u65E0\u8FB9\uFF01\u80FD\u628A\u751F\u4EA7\u529B\u7CBE\u786E\u63A7\u5236\u5728\u521A\u521A\u597D\u7684\u8212\u9002\u5708\uFF0C\u4E5F\u662F\u4E00\u79CD\u8BA9\u4EBA\u7FA1\u6155\u7684\u804C\u573A\u8D85\u80FD\u529B\u3002",
|
|
786
|
+
"\u606D\u559C\u83B7\u5F97\u300E\u6478\u9C7C\u5B97\u5E08\u300F\u6210\u5C31\uFF01Git \u7684\u7559\u767D\u662F\u4F60\u7684\u901A\u884C\u8BC1\uFF0C\u5B8C\u7F8E\u7684\u8282\u594F\u5927\u5E08\u5C31\u662F\u4F60\u3002"
|
|
787
|
+
];
|
|
788
|
+
var TAG_VOLUME_KING_CRITIQUES = [
|
|
789
|
+
"\u7206\u809D\u6218\u795E\uFF0C\u6050\u6016\u5982\u65AF\uFF01\u4F60\u7684 Git \u65F6\u95F4\u7EBF\u5DF2\u7ECF\u6EE1\u5F97\u50CF\u6625\u8FD0\u706B\u8F66\u7AD9\uFF0C\u5EFA\u8BAE\u7ED9\u952E\u76D8\u4E70\u4E2A\u610F\u5916\u9669\uFF0C\u8F9B\u82E6\u4E86\uFF01",
|
|
790
|
+
"\u7206\u809D\u6218\u795E\u5C31\u4F4D\uFF01\u4E00\u4E2A\u4EBA\u9876\u8D77\u4E00\u4E2A\u56E2\u961F\u7684\u4EA7\u51FA\uFF0C\u8FD9\u4E2A\u63D0\u4EA4\u91CF\uFF0C\u611F\u89C9\u4F60\u7684\u624B\u6307\u5DF2\u7ECF\u6572\u51FA\u4E86\u6B8B\u5F71\u3002",
|
|
791
|
+
"\u522B\u4EBA\u5728\u4F11\u606F\uFF0C\u4F60\u5728 commit\uFF1B\u522B\u4EBA\u5728\u7761\u89C9\uFF0C\u4F60\u8FD8\u5728 push\u3002\u7206\u809D\u6218\u795E\u5C31\u662F\u4F60\uFF01\u4E0D\u8FC7\u522B\u5FD8\u4E86\uFF0C\u7A0B\u5E8F\u8981\u8DD1\uFF0C\u4EBA\u4E5F\u8981\u597D\u597D\u4F11\u606F\u3002"
|
|
792
|
+
];
|
|
793
|
+
var TAG_NIGHT_OWL_CRITIQUES = [
|
|
794
|
+
"\u6DF1\u591C\u4FEE\u4ED9\u8005\uFF0C\u6CD5\u529B\u65E0\u8FB9\uFF01\u51CC\u6668\u7684\u4EE3\u7801\u95EA\u70C1\u7740\u72EC\u7279\u7684\u5149\u8292\uFF0C\u8FDE CI \u8DD1\u901A\u8FC7\u53BB\u7684\u901F\u5EA6\u90FD\u53D8\u5FEB\u4E86\u3002",
|
|
795
|
+
"\u6210\u4E3A\u6DF1\u591C\u4FEE\u4ED9\u8005\u610F\u5473\u7740\u4F60\u7684\u6700\u4F73\u5DE5\u4F5C\u65F6\u6BB5\u662F 00:00 ~ 05:00\u3002\u767D\u5929\u5728\u5DE5\u4F4D\u79EF\u6512\u7075\u611F\uFF0C\u665A\u4E0A\u5728\u952E\u76D8\u4E0A\u75AF\u72C2\u8F93\u51FA\uFF0C\u53CC\u91CD\u9891\u7387\u65E0\u7F1D\u5207\u6362\u3002",
|
|
796
|
+
"\u4FEE\u4ED9\u5927\u80FD\uFF01\u51CC\u6668\u7684 Git \u8BB0\u5F55\u89C1\u8BC1\u4E86\u4F60\u7684\u575A\u6301\u4E0E\u6267\u7740\u3002\u4E0D\u8FC7\u4FEE\u4ED9\u5F52\u4FEE\u4ED9\uFF0C\u8BB0\u5F97\u7ED9\u81EA\u5DF1\u7559\u8DB3\u7761\u7720\u65F6\u95F4\u3002"
|
|
797
|
+
];
|
|
798
|
+
var TAG_BUILDER_CRITIQUES = [
|
|
799
|
+
"\u52E4\u6073\u642C\u7816\u4EBA\uFF0C\u8E0F\u5B9E\u5982\u8001\u9EC4\u725B\uFF01\u6BCF\u4E00\u884C\u4EE3\u7801\u90FD\u662F\u4F60\u7528\u624B\u6572\u51FA\u6765\u7684\uFF0C\u7A33\u624E\u7A33\u6253\uFF0C\u662F\u6574\u4E2A\u9879\u76EE\u6700\u575A\u5B9E\u7684\u5E95\u5EA7\u3002",
|
|
800
|
+
"\u642C\u7816\u4EBA\u642C\u7816\u9B42\uFF0C\u4EE3\u7801\u57FA\u5EFA\u5168\u9760\u52E4\u3002\u4F60\u8FD9\u4E2A\u6708\u7684\u4EE3\u7801\u91CF\uFF0C\u5DF2\u7ECF\u9ED8\u9ED8\u4E3A\u9879\u76EE\u780C\u8D77\u4E86\u4E00\u5EA7\u9AD8\u5899\u3002",
|
|
801
|
+
"\u52E4\u6073\u642C\u7816\uFF0C\u7A33\u5982\u6CF0\u5C71\u3002\u4E0D\u662F\u6700\u7231\u79C0\u6280\u5DE7\u7684\u90A3\u4E2A\uFF0C\u4F46\u7EDD\u5BF9\u662F\u56E2\u961F\u91CC\u6700\u8BA9\u4EBA\u653E\u5FC3\u7684\u90A3\u9897\u87BA\u4E1D\u9489\u3002\u7EE7\u7EED\u52A0\u6CB9\uFF01"
|
|
802
|
+
];
|
|
803
|
+
var TAG_BURST_CODER_CRITIQUES = [
|
|
804
|
+
"\u4E00\u628A\u68AD\u54C8\u578B\u7A0B\u5E8F\u5458\uFF01\u8981\u4E48\u4E0D\u5199\uFF0C\u4E00\u5199\u5C31\u662F\u51E0\u5343\u884C\u3002\u4F60\u7684\u5927\u62DB\u5F0F Git Diff \u8BA9 reviewer \u9ED8\u9ED8\u6CE1\u4E86\u4E00\u676F\u5496\u5561\u51C6\u5907\u7EC6\u7EC6\u54C1\u5473\u3002",
|
|
805
|
+
"\u4E00\u628A\u68AD\u54C8\u73A9\u5BB6\uFF01\u5168\u90E8\u9700\u6C42\u4E00\u4E2A commit \u641E\u5B9A\uFF0C\u4EE3\u7801\u5728\u4F60\u7684\u8111\u6D77\u91CC\u65E9\u5DF2\u6210\u578B\uFF0C\u76F4\u63A5\u6765\u4E86\u4E00\u6CE2\u5B8C\u7F8E\u7684 Rush-B \u843D\u5730\u3002",
|
|
806
|
+
"\u4E00\u628A\u68AD\u54C8\u827A\u672F\u5BB6\uFF01\u4F60\u7684\u6BCF\u6B21\u63D0\u4EA4\u90FD\u662F\u4E00\u7BC7\u5185\u5BB9\u4E30\u5BCC\u7684\u4E2D\u7BC7\u5C0F\u8BF4\uFF0C\u4E0D\u9E23\u5219\u5DF2\uFF0C\u4E00\u9E23\u60CA\u4EBA\u3002"
|
|
807
|
+
];
|
|
808
|
+
var TAG_PPT_ARCHITECT_CRITIQUES = [
|
|
809
|
+
"\u67B6\u6784\u5E08\u98CE\u8303\u9690\u85CF\u6210\u5C31\u89E3\u9501\uFF01\u63D0\u4EA4\u591A\u3001\u6539\u52A8\u5C11\uFF0C\u4F60\u7684 Git \u5386\u53F2\u5C31\u50CF\u4E00\u90E8\u5145\u6EE1\u4EEA\u5F0F\u611F\u7684\u827A\u672F\u54C1\uFF0C\u91CD\u5728\u68B3\u7406\u903B\u8F91\u4E0E\u7ED3\u6784\u3002",
|
|
810
|
+
"\u606D\u559C\u83B7\u5F97\u300E\u4F18\u96C5\u67B6\u6784\u5E08\u300F\u79F0\u53F7\uFF01\u6BCF\u4E2A commit \u90FD\u5E26\u7740\u6E05\u6670\u7684\u601D\u8DEF\uFF0C\u91CD\u6784\u4E8E\u65E0\u5F62\u4E4B\u4E2D\uFF0Cdiff \u8F7B\u76C8\u5374\u81F3\u5173\u91CD\u8981\u3002"
|
|
811
|
+
];
|
|
812
|
+
var TAG_FORMAT_MASTER_CRITIQUES = [
|
|
813
|
+
"\u683C\u5F0F\u5316\u5927\u5E08\u9690\u85CF\u6210\u5C31\u89E3\u9501\uFF01\u63D0\u4EA4\u4E86 10 \u6B21\uFF0C\u6539\u4E86\u4E0D\u5230 30 \u884C\u2014\u2014\u4F60\u5BF9\u4EE3\u7801\u6D01\u7656\u7684\u575A\u6301\uFF0C\u8BA9\u6574\u4E2A\u9879\u76EE\u7115\u7136\u4E00\u65B0\u3002",
|
|
814
|
+
"\u683C\u5F0F\u5316\u5927\u5E08\uFF01\u51E0\u5341\u6B21\u63D0\u4EA4\u5168\u662F\u6539\u7A7A\u683C\u3001\u52A0\u6CE8\u91CA\u3001\u8C03\u7F29\u8FDB\u3002\u4EE3\u7801\u5E93\u7684\u989C\u503C\u88AB\u4F60\u62C9\u5230\u4E86\u5DC5\u5CF0\uFF0C\u53EF\u8BFB\u6027\u5927\u5927\u63D0\u5347\uFF01"
|
|
815
|
+
];
|
|
816
|
+
var TAG_GIT_CHATTER_CRITIQUES = [
|
|
817
|
+
"Git \u8BB0\u5F55\u8FBE\u4EBA\u9690\u85CF\u6210\u5C31\uFF0115 \u6B21\u63D0\u4EA4\uFF0C\u5E73\u5747\u6BCF\u6B21\u4E0D\u5230 2 \u884C\u2014\u2014\u4F60\u662F\u5728\u7528 Git \u8BB0\u5F55\u81EA\u5DF1\u7684\u5FC3\u8DEF\u5386\u7A0B\u5417\uFF1F\u7EC6\u9897\u7C92\u5EA6\u7684\u63D0\u4EA4\u8BA9\u7248\u672C\u56DE\u6EDA\u6BEB\u65E0\u538B\u529B\uFF01",
|
|
818
|
+
"Git \u8BB0\u5F55\u8FBE\u4EBA\uFF01\u628A commit \u5206\u62C6\u5F97\u6781\u5176\u7EC6\u817B\uFF0C\u5C31\u50CF\u5728\u5199\u5B9E\u65F6\u65E5\u5FD7\u3002\u8FD9\u79CD\u9AD8\u9891\u5FAE\u8C03\u7684\u4E60\u60EF\uFF0C\u8BA9\u4EE3\u7801\u8FFD\u8E2A\u53D8\u5F97\u7B80\u5355\u660E\u4E86\u3002"
|
|
819
|
+
];
|
|
820
|
+
var TAG_NIGHT_ASSASSIN_CRITIQUES = [
|
|
821
|
+
"\u6DF1\u591C\u523A\u5BA2\u9690\u85CF\u6210\u5C31\uFF01\u4FEE\u4ED9\u6307\u6570\u7206\u8868\u4F46\u63D0\u4EA4\u6781\u5176\u7CBE\u70BC\u2014\u2014\u4F60\u662F\u534A\u591C\u9876\u7740\u591C\u8272\u5077\u5077\u4E0A\u7EBF\u641E\u5B9A\u6838\u5FC3 bug\uFF0C\u6DF1\u85CF\u529F\u4E0E\u540D\u3002",
|
|
822
|
+
"\u6DF1\u591C\u523A\u5BA2\uFF01\u5BE5\u5BE5\u51E0\u6B21\u63D0\u4EA4\u5168\u5728\u51CC\u6668\uFF0C\u767D\u5929\u95ED\u76EE\u517B\u795E\uFF0C\u534A\u591C\u4E00\u51FB\u5FC5\u6740\uFF0C\u53CC\u9762\u6781\u5BA2\u4EBA\u751F\u5C5E\u5B9E\u7CBE\u5F69\u3002"
|
|
823
|
+
];
|
|
824
|
+
var TAG_DONKEY_CRITIQUES = [
|
|
825
|
+
"\u5168\u80FD\u6218\u795E\u9690\u85CF\u6210\u5C31\u89E3\u9501\uFF01\u5468\u672B\u7206\u809D + \u6DF1\u591C\u4FEE\u4ED9\u3002Git \u5DF2\u7ECF\u8BB0\u4F4F\u4E86\u4F60\u7684\u6BCF\u4E00\u5206\u52AA\u529B\u4E0E\u4ED8\u51FA\uFF0C\u4F46\u4E5F\u5E0C\u671B\u4F60\u522B\u5FD8\u4E86\u597D\u597D\u7167\u987E\u81EA\u5DF1\u3002",
|
|
826
|
+
"\u5468\u672B\u548C\u6DF1\u591C\u90FD\u7559\u4E0B\u4E86\u4F60\u7684\u8DB3\u8FF9\uFF0C\u8FD9\u4EFD\u8D23\u4EFB\u611F\u4E0E\u575A\u6301\u5DF2\u7ECF\u62C9\u6EE1\u3002\u8F9B\u82E6\u4ED8\u51FA\u7684\u540C\u65F6\uFF0C\u4E5F\u8BF7\u4E00\u5B9A\u8981\u7559\u51FA\u65F6\u95F4\u597D\u597D\u751F\u6D3B\u3002"
|
|
827
|
+
];
|
|
828
|
+
var TAG_FISH_IMMORTAL_CRITIQUES = [
|
|
829
|
+
"\u6478\u9C7C\u4ED9\u4EBA\u9690\u85CF\u6210\u5C31\uFF01\u6478\u9C7C\u6307\u6570\u7A81\u7834 95%\uFF0C\u4E00\u5929\u53EA\u63D0\u4EA4 0 \u6216 1 \u6B21\u2014\u2014\u4F60\u5DF2\u7ECF\u8D85\u8D8A\u4E86\u666E\u901A\u7684\u6478\u9C7C\uFF0C\u8FBE\u5230\u4E86\u65E0\u62DB\u80DC\u6709\u62DB\u7684\u65E0\u4E3A\u5883\u754C\u3002",
|
|
830
|
+
"\u6478\u9C7C\u4ED9\u4EBA\uFF0C\u5883\u754C\u5DF2\u8D85\u51E1\u4EBA\uFF01\u5BE5\u5BE5\u4E00\u6B21\u63D0\u4EA4\uFF0C\u9AD8\u8FBE 95%+ \u7684\u6DE1\u5B9A\u7387\u3002\u7528\u6700\u5C11\u7684\u52A8\u4F5C\u7EF4\u6301\u7CFB\u7EDF\u7684\u8FD0\u8F6C\uFF0C\u4E0D\u6127\u662F\u4E16\u5916\u9AD8\u4EBA\u3002"
|
|
831
|
+
];
|
|
832
|
+
function getRandomItem(arr) {
|
|
833
|
+
const idx = Math.floor(Math.random() * arr.length);
|
|
834
|
+
return arr[idx];
|
|
835
|
+
}
|
|
836
|
+
function getAICritic(weeklyStats) {
|
|
837
|
+
const parts = [];
|
|
838
|
+
if (weeklyStats.ghostCommitsCount > 0) {
|
|
839
|
+
parts.push(getRandomItem(GHOST_CRITIQUES));
|
|
840
|
+
}
|
|
841
|
+
if (weeklyStats.totalCommits === 0) {
|
|
842
|
+
parts.push("\u672C\u5468\u63D0\u4EA4\u6B21\u6570\u4E3A 0\u3002\u5B8C\u7F8E\u7684\u7A7A\u6C14\u7EA7\u8D21\u732E\uFF01\u8001\u677F\u53EF\u80FD\u8FD8\u6CA1\u53D1\u73B0\u4F60\u7684\u5DE5\u4F4D\u662F\u7A7A\u7684\u3002\u5EFA\u8BAE\u4E0B\u5468\u5077\u5077\u63D0\u4EA4\u4E00\u6B21\uFF0C\u5237\u4E00\u4E0B\u5B58\u5728\u611F\u3002");
|
|
843
|
+
} else if (weeklyStats.averageFish >= 75) {
|
|
844
|
+
parts.push(getRandomItem(SLACK_HIGH_CRITIQUES));
|
|
845
|
+
} else if (weeklyStats.averageFish <= 40) {
|
|
846
|
+
parts.push(getRandomItem(SLACK_LOW_CRITIQUES));
|
|
847
|
+
}
|
|
848
|
+
const tagCritics = getTagCriticsFromDays(weeklyStats.days);
|
|
849
|
+
if (tagCritics.length > 0) {
|
|
850
|
+
parts.push(...tagCritics.slice(0, 2));
|
|
851
|
+
}
|
|
852
|
+
if (parts.length === 0) {
|
|
853
|
+
parts.push(getRandomItem(GENERAL_CRITIQUES));
|
|
854
|
+
}
|
|
855
|
+
return parts.join("\n\n");
|
|
856
|
+
}
|
|
857
|
+
function getAICriticForMonth(monthlyStats) {
|
|
858
|
+
const parts = [];
|
|
859
|
+
if (monthlyStats.ghostCommitsCount > 3) {
|
|
860
|
+
parts.push(getRandomItem(GHOST_CRITIQUES));
|
|
861
|
+
}
|
|
862
|
+
const { fix, feat, chore, other } = monthlyStats.categories;
|
|
863
|
+
const total = fix + feat + chore + other;
|
|
864
|
+
if (total > 0) {
|
|
865
|
+
const fixPct = Math.round(fix / total * 100);
|
|
866
|
+
const featPct = Math.round(feat / total * 100);
|
|
867
|
+
const chorePct = Math.round(chore / total * 100);
|
|
868
|
+
if (fixPct >= 50) {
|
|
869
|
+
parts.push(getRandomItem(CATEGORY_FIX_CRITIQUES).replace("%PERCENT%%", `${fixPct}%`));
|
|
870
|
+
} else if (featPct >= 50) {
|
|
871
|
+
parts.push(getRandomItem(CATEGORY_FEAT_CRITIQUES).replace("%PERCENT%%", `${featPct}%`));
|
|
872
|
+
} else if (chorePct >= 40) {
|
|
873
|
+
parts.push(getRandomItem(CATEGORY_CHORE_CRITIQUES).replace("%PERCENT%%", `${chorePct}%`));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
if (monthlyStats.totalCommits === 0) {
|
|
877
|
+
parts.push("\u672C\u6708\u63D0\u4EA4\u6B21\u6570\u4E3A 0\uFF01\u4F60\u6210\u529F\u5730\u5728\u516C\u53F8\u84B8\u53D1\u4E86\u4E00\u4E2A\u6708\uFF0C\u62FF\u5230\u4E86\u5168\u989D\u5DE5\u8D44\u3002\u5EFA\u8BAE\u4F60\u4E0B\u4E2A\u6708\u7EE7\u7EED\u4FDD\u6301\u4F4E\u8C03\uFF0C\u4E0D\u8981\u8BA9 HR \u6CE8\u610F\u5230\u4F60\u3002");
|
|
878
|
+
} else if (monthlyStats.averageFish >= 70) {
|
|
879
|
+
parts.push(getRandomItem(SLACK_HIGH_CRITIQUES));
|
|
880
|
+
} else if (monthlyStats.averageFish <= 35) {
|
|
881
|
+
parts.push(getRandomItem(SLACK_LOW_CRITIQUES));
|
|
882
|
+
}
|
|
883
|
+
if (parts.length === 0) {
|
|
884
|
+
parts.push(getRandomItem(GENERAL_CRITIQUES));
|
|
885
|
+
}
|
|
886
|
+
return parts.join("\n\n");
|
|
887
|
+
}
|
|
888
|
+
var TAG_CRITIQUE_MAP = {
|
|
889
|
+
"\u{1F41F} \u6478\u9C7C\u5B97\u5E08": TAG_FISH_MASTER_CRITIQUES,
|
|
890
|
+
"\u{1F525} \u7206\u809D\u6218\u795E": TAG_VOLUME_KING_CRITIQUES,
|
|
891
|
+
"\u{1F319} \u6DF1\u591C\u4FEE\u4ED9\u8005": TAG_NIGHT_OWL_CRITIQUES,
|
|
892
|
+
"\u{1F9F1} \u52E4\u6073\u642C\u7816\u4EBA": TAG_BUILDER_CRITIQUES,
|
|
893
|
+
"\u{1F4A5} \u4E00\u628A\u68AD\u54C8\u578B\u7A0B\u5E8F\u5458": TAG_BURST_CODER_CRITIQUES,
|
|
894
|
+
"\u{1F3F7}\uFE0F PPT \u67B6\u6784\u5E08": TAG_PPT_ARCHITECT_CRITIQUES,
|
|
895
|
+
"\u{1F3F7}\uFE0F \u683C\u5F0F\u5316\u5927\u5E08": TAG_FORMAT_MASTER_CRITIQUES,
|
|
896
|
+
"\u{1F4AC} Git \u804A\u5929\u8FBE\u4EBA": TAG_GIT_CHATTER_CRITIQUES,
|
|
897
|
+
"\u{1F319} \u6DF1\u591C\u523A\u5BA2": TAG_NIGHT_ASSASSIN_CRITIQUES,
|
|
898
|
+
"\u{1F434} \u751F\u4EA7\u961F\u7684\u9A74": TAG_DONKEY_CRITIQUES,
|
|
899
|
+
"\u{1F41F}\uFE0F \u6478\u9C7C\u4ED9\u4EBA": TAG_FISH_IMMORTAL_CRITIQUES
|
|
900
|
+
};
|
|
901
|
+
function findTagPool(tag) {
|
|
902
|
+
if (TAG_CRITIQUE_MAP[tag]) return TAG_CRITIQUE_MAP[tag];
|
|
903
|
+
for (const [key, pool] of Object.entries(TAG_CRITIQUE_MAP)) {
|
|
904
|
+
if (tag.startsWith(key)) return pool;
|
|
905
|
+
}
|
|
906
|
+
return void 0;
|
|
907
|
+
}
|
|
908
|
+
function getTagCriticsFromDays(days) {
|
|
909
|
+
const seenPrefixes = /* @__PURE__ */ new Set();
|
|
910
|
+
const critics = [];
|
|
911
|
+
for (const day of days) {
|
|
912
|
+
if (!day.tags) continue;
|
|
913
|
+
for (const tag of day.tags) {
|
|
914
|
+
const prefix = tag.replace(/\s*\(.*\)$/, "");
|
|
915
|
+
if (seenPrefixes.has(prefix)) continue;
|
|
916
|
+
seenPrefixes.add(prefix);
|
|
917
|
+
const pool = findTagPool(tag);
|
|
918
|
+
if (pool && pool.length > 0) {
|
|
919
|
+
critics.push(getRandomItem(pool));
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
return critics;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// src/index.ts
|
|
927
|
+
var program = new Command();
|
|
928
|
+
var SPINNER_FRAMES = ["\u{1F41F} ", " \u{1F420} ", " \u{1F421} ", " \u{1F988} ", " \u{1F419} ", " \u{1F991} "];
|
|
929
|
+
function showLoading(message) {
|
|
930
|
+
let frameIdx = 0;
|
|
931
|
+
process.stdout.write("\x1B[?25l");
|
|
932
|
+
const timer = setInterval(() => {
|
|
933
|
+
process.stdout.write(`\r${chalk.cyan(SPINNER_FRAMES[frameIdx])} ${chalk.yellow(message)} `);
|
|
934
|
+
frameIdx = (frameIdx + 1) % SPINNER_FRAMES.length;
|
|
935
|
+
}, 150);
|
|
936
|
+
return {
|
|
937
|
+
update: (msg) => {
|
|
938
|
+
message = msg;
|
|
939
|
+
},
|
|
940
|
+
stop: () => {
|
|
941
|
+
clearInterval(timer);
|
|
942
|
+
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
943
|
+
process.stdout.write("\x1B[?25h");
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
function printBanner(title) {
|
|
948
|
+
console.log(chalk.cyan.bold("\n" + "=".repeat(50)));
|
|
949
|
+
console.log(chalk.blue.bold(` \u{1F41F} FISH - ${title}`));
|
|
950
|
+
console.log(chalk.cyan.bold("=".repeat(50) + "\n"));
|
|
951
|
+
}
|
|
952
|
+
function formatSlackIndex(fish) {
|
|
953
|
+
if (fish >= 90) {
|
|
954
|
+
return chalk.green(`${fish}% (\u7EC8\u6781\u6478\u9C7C \u{1F3A3})`);
|
|
955
|
+
} else if (fish >= 70) {
|
|
956
|
+
return chalk.green(`${fish}% (\u5408\u7406\u5212\u6C34 \u2615)`);
|
|
957
|
+
} else if (fish >= 40) {
|
|
958
|
+
return chalk.yellow(`${fish}% (\u6B63\u5E38\u8425\u4E1A \u{1F4BB})`);
|
|
959
|
+
} else {
|
|
960
|
+
return chalk.red(`${fish}% (\u706B\u529B\u5168\u5F00 \u{1F525})`);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
function formatNightOwlIndex(nightOwl) {
|
|
964
|
+
if (nightOwl >= 60) {
|
|
965
|
+
return chalk.red.bold(`${nightOwl}% (\u4FEE\u4ED9\u5927\u4F6C \u{1F9D9})`);
|
|
966
|
+
} else if (nightOwl >= 30) {
|
|
967
|
+
return chalk.red(`${nightOwl}% (\u591C\u732B\u51FA\u6CA1 \u{1F989})`);
|
|
968
|
+
} else if (nightOwl >= 10) {
|
|
969
|
+
return chalk.yellow(`${nightOwl}% (\u5076\u5C14\u71AC\u591C \u{1F319})`);
|
|
970
|
+
} else {
|
|
971
|
+
return "";
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
function formatOvertimeIndex(fish) {
|
|
975
|
+
if (fish >= 80) {
|
|
976
|
+
return chalk.green(`${fish}% (\u5468\u672B\u6478\u9C7C \u{1F3A3})`);
|
|
977
|
+
} else if (fish >= 50) {
|
|
978
|
+
return chalk.yellow(`${fish}% (\u8F7B\u5FAE\u52A0\u73ED \u{1F319})`);
|
|
979
|
+
} else if (fish >= 20) {
|
|
980
|
+
return chalk.red(`${fish}% (\u5468\u672B\u7206\u809D \u{1F525})`);
|
|
981
|
+
} else {
|
|
982
|
+
return chalk.red.bold(`${fish}% (\u7EC8\u6781\u7206\u809D \u2620\uFE0F)`);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
function getPersonalityTag(day) {
|
|
986
|
+
return day.tags.length > 0 ? day.tags.join(" ") : null;
|
|
987
|
+
}
|
|
988
|
+
function colorizeTags(tagStr) {
|
|
989
|
+
const colorMap = {
|
|
990
|
+
"\u{1F41F} \u6478\u9C7C\u5B97\u5E08": chalk.green,
|
|
991
|
+
"\u{1F525} \u7206\u809D\u6218\u795E": chalk.red.bold,
|
|
992
|
+
"\u{1F319} \u6DF1\u591C\u4FEE\u4ED9\u8005": chalk.red,
|
|
993
|
+
"\u{1F9F1} \u52E4\u6073\u642C\u7816\u4EBA": chalk.yellow,
|
|
994
|
+
"\u{1F4A5} \u4E00\u628A\u68AD\u54C8\u578B\u7A0B\u5E8F\u5458": chalk.magenta,
|
|
995
|
+
"\u{1F3F7}\uFE0F PPT \u67B6\u6784\u5E08": chalk.blue,
|
|
996
|
+
"\u{1F3F7}\uFE0F \u683C\u5F0F\u5316\u5927\u5E08": chalk.cyan,
|
|
997
|
+
"\u{1F3F7}\uFE0F Git \u804A\u5929\u8FBE\u4EBA": chalk.greenBright,
|
|
998
|
+
"\u{1F3F7}\uFE0F \u6DF1\u591C\u523A\u5BA2": chalk.redBright,
|
|
999
|
+
"\u{1F3F7}\uFE0F \u751F\u4EA7\u961F\u7684\u9A74": chalk.yellowBright,
|
|
1000
|
+
"\u{1F3F7}\uFE0F \u6478\u9C7C\u4ED9\u4EBA": chalk.green.bold,
|
|
1001
|
+
"\u{1F41F} \u4ECA\u65E5\u6682\u65E0\u4EE3\u7801\u6D3B\u52A8": chalk.green
|
|
1002
|
+
};
|
|
1003
|
+
const sortedKeys = Object.keys(colorMap).sort((a, b) => b.length - a.length);
|
|
1004
|
+
let result = tagStr;
|
|
1005
|
+
for (const key of sortedKeys) {
|
|
1006
|
+
const colorFn = colorMap[key];
|
|
1007
|
+
const regex = new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(\\s*\\([^)]*\\))?", "g");
|
|
1008
|
+
result = result.replace(regex, (match) => {
|
|
1009
|
+
const suffixMatch = match.match(/^(.+?)(\s*\(.*\))?$/);
|
|
1010
|
+
if (suffixMatch) {
|
|
1011
|
+
return colorFn(suffixMatch[1]) + (suffixMatch[2] || "");
|
|
1012
|
+
}
|
|
1013
|
+
return colorFn(match);
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
return result;
|
|
1017
|
+
}
|
|
1018
|
+
function visualWidth(s) {
|
|
1019
|
+
let w = 0;
|
|
1020
|
+
for (const ch of s) {
|
|
1021
|
+
w += /[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef\u{1f000}-\u{1ffff}]/u.test(ch) ? 2 : 1;
|
|
1022
|
+
}
|
|
1023
|
+
return w;
|
|
1024
|
+
}
|
|
1025
|
+
function visualPad(s, width) {
|
|
1026
|
+
const vw = visualWidth(s);
|
|
1027
|
+
if (vw >= width) return s;
|
|
1028
|
+
return s + " ".repeat(width - vw);
|
|
1029
|
+
}
|
|
1030
|
+
function colorizeFishRow(row) {
|
|
1031
|
+
const COL_WIDTH = 8;
|
|
1032
|
+
let result = "";
|
|
1033
|
+
for (let i = 0; i < row.length; i += COL_WIDTH) {
|
|
1034
|
+
const chunk = row.slice(i, i + COL_WIDTH);
|
|
1035
|
+
const val = parseInt(chunk.trim(), 10);
|
|
1036
|
+
if (isNaN(val)) {
|
|
1037
|
+
result += chunk;
|
|
1038
|
+
} else if (val >= 80) {
|
|
1039
|
+
result += chalk.green(chunk);
|
|
1040
|
+
} else if (val >= 50) {
|
|
1041
|
+
result += chalk.yellow(chunk);
|
|
1042
|
+
} else {
|
|
1043
|
+
result += chalk.red(chunk);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return result;
|
|
1047
|
+
}
|
|
1048
|
+
function getTargetDate(weeksAgo, isMonth) {
|
|
1049
|
+
const now = /* @__PURE__ */ new Date();
|
|
1050
|
+
if (weeksAgo <= 0) {
|
|
1051
|
+
return now;
|
|
1052
|
+
}
|
|
1053
|
+
if (isMonth) {
|
|
1054
|
+
now.setMonth(now.getMonth() - weeksAgo);
|
|
1055
|
+
} else {
|
|
1056
|
+
now.setDate(now.getDate() - weeksAgo * 7);
|
|
1057
|
+
}
|
|
1058
|
+
return now;
|
|
1059
|
+
}
|
|
1060
|
+
function randomPick(arr) {
|
|
1061
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
1062
|
+
}
|
|
1063
|
+
var COMMENT_POOLS = {
|
|
1064
|
+
"0-2": { color: chalk.red, tags: [
|
|
1065
|
+
"\u670D\u52A1\u5668\u548C\u4F60\uFF0C\u603B\u5F97\u6709\u4E00\u4E2A\u7761\u89C9 \u{1F635}\u200D\u{1F4AB}",
|
|
1066
|
+
"\u591C\u732B\u4FEE\u4ED9 \u{1F9D9}",
|
|
1067
|
+
"\u8FD9\u63D0\u4EA4\u4E0D\u50CF\u5DE5\u4F5C\uFF0C\u50CF\u62A5\u590D\u4EE3\u7801 \u{1F608}",
|
|
1068
|
+
"\u6DF1\u591C\u4FEE\u4ED9\u578B\u7A0B\u5E8F\u5458 \u{1F319}"
|
|
1069
|
+
] },
|
|
1070
|
+
"3-5": { color: chalk.redBright, tags: [
|
|
1071
|
+
"\u960E\u738B\uFF1A\u600E\u4E48\u53C8\u662F\u4F60\uFF1F\u{1F47B}",
|
|
1072
|
+
"\u751F\u7269\u949F\u5DF2\u9635\u4EA1 \u2620\uFE0F",
|
|
1073
|
+
"\u54E5\uFF0C\u4F60\u662F\u4F4F\u516C\u53F8\u4E86\u5417 \u{1F3E2}",
|
|
1074
|
+
"\u9E21\u9E23\u5373\u8D77\u578B\u725B\u9A6C \u{1F414}"
|
|
1075
|
+
] },
|
|
1076
|
+
"6-8": { color: chalk.gray, tags: [
|
|
1077
|
+
"\u9E21\u90FD\u6CA1\u8D77\uFF0C\u4F60\u5148\u4E0A\u73ED\u4E86 \u{1F414}",
|
|
1078
|
+
"\u5929\u9009\u725B\u9A6C\u5DF2\u4E0A\u7EBF \u{1F402}",
|
|
1079
|
+
"\u7206\u809D\u542F\u52A8\u6210\u529F \u{1F680}",
|
|
1080
|
+
"\u65E9\u8D77\u7684 commit \u6709 bug \u5403 \u{1F41B}"
|
|
1081
|
+
] },
|
|
1082
|
+
"9-11": { color: chalk.yellow, tags: [
|
|
1083
|
+
"\u5047\u88C5\u5F88\u5FD9\uFF0C\u5176\u5B9E\u5728\u7B49\u5348\u996D \u{1F371}",
|
|
1084
|
+
"\u6668\u95F4coding \u2615",
|
|
1085
|
+
"\u6B63\u5E38\u4EBA\u7C7B\u5DE5\u4F5C\u65F6\u95F4 \u2705",
|
|
1086
|
+
"\u4E0A\u5348\u8868\u6F14\u578B\u9009\u624B \u{1F680}"
|
|
1087
|
+
] },
|
|
1088
|
+
"12-13": { color: chalk.green, tags: [
|
|
1089
|
+
"\u5DE5\u4F4D\u5403\u996D\uFF0C\u7075\u9B42\u7EED\u547D \u{1F50B}",
|
|
1090
|
+
"\u5E72\u996D\u662F\u7B2C\u4E00\u751F\u4EA7\u529B \u{1F35A}",
|
|
1091
|
+
"\u4E00\u8FB9\u5403\u996D\u4E00\u8FB9 commit \u{1F35C}",
|
|
1092
|
+
"\u5E72\u996D\u7EED\u547D\u578B\u5DE5\u7A0B\u5E08 \u{1F371}"
|
|
1093
|
+
] },
|
|
1094
|
+
"14-15": { color: chalk.cyan, tags: [
|
|
1095
|
+
"\u5348\u7761\u672A\u9192\uFF0C\u4EBA\u5DF2\u5F00\u5DE5 \u{1F62A}",
|
|
1096
|
+
"CPU \u91CD\u542F\u4E2D \u{1F504}",
|
|
1097
|
+
// '午觉没睡成,拿代码出气 💢',
|
|
1098
|
+
"\u5348\u540E\u7075\u9B42\u51FA\u7A8D\u8005 \u{1F47B}"
|
|
1099
|
+
] },
|
|
1100
|
+
"16-17": { color: chalk.cyanBright, tags: [
|
|
1101
|
+
"\u5F00\u59CB\u601D\u8003\u4ECA\u665A\u5403\u4EC0\u4E48 \u{1F373}",
|
|
1102
|
+
"\u7075\u9B42\u5DF2\u4E0B\u73ED \u{1F47B}",
|
|
1103
|
+
"\u4E34\u8FD1\u4E0B\u73ED\u7A81\u7136\u52E4\u594B \u{1F914}",
|
|
1104
|
+
"\u7B49\u4E0B\u73ED\u89C2\u5BDF\u5458 \u{1F375}"
|
|
1105
|
+
] },
|
|
1106
|
+
"18-20": { color: chalk.magenta, tags: [
|
|
1107
|
+
"\u52A0\u73ED\u662F\u4E0D\u53EF\u80FD\u4E3B\u52A8\u52A0\u73ED\u7684 \u{1F4BC}",
|
|
1108
|
+
"\u5DE5\u4F4D\u5C01\u5370\u89E3\u9664 \u{1F513}",
|
|
1109
|
+
"\u767D\u5929\u5728\u5F00\u4F1A\uFF0C\u665A\u4E0A\u771F\u5E72\u6D3B \u{1F4BB}",
|
|
1110
|
+
"\u81EA\u613F\uFF08\u88AB\u8FEB\uFF09\u52A0\u73ED\u4EBA \u{1F4AA}"
|
|
1111
|
+
] },
|
|
1112
|
+
"21-22": { color: chalk.magentaBright, tags: [
|
|
1113
|
+
"\u8001\u677F\u4E0B\u73ED\u4E86\uFF0C\u4F60\u8FD8\u6CA1\u4E0B\u7EBF \u{1F62D}",
|
|
1114
|
+
"\u52A0\u73ED\u4ED9\u4EBA\u6E21\u52AB\u4E2D \u26A1",
|
|
1115
|
+
"\u4ECA\u65E5\u6700\u540E\u4E00\u4E2A commit\uFF08\u9A97\u81EA\u5DF1\uFF09\u{1F921}",
|
|
1116
|
+
"\u5927\u798F\u62A5\u65F6\u95F4 \u{1F525}"
|
|
1117
|
+
] },
|
|
1118
|
+
"23": { color: chalk.red, tags: [
|
|
1119
|
+
"\u4EE3\u7801\u548C\u5934\u53D1\u4E00\u8D77\u6389\u5149\u4E2D \u{1F9D1}\u200D\u{1F9B2}",
|
|
1120
|
+
"\u4ECA\u65E5 KPI\uFF1A\u6D3B\u7740\u5C31\u884C \u{1F60C}",
|
|
1121
|
+
"\u7761\u5427\uFF0CGit \u4E0D\u4F1A\u8DD1 \u{1F6CC}",
|
|
1122
|
+
"\u591C\u6DF1\u4E86\uFF0C\u5408\u4E0A\u7535\u8111\u5427 \u{1F4F4}"
|
|
1123
|
+
] }
|
|
1124
|
+
};
|
|
1125
|
+
function getHourTag(hour) {
|
|
1126
|
+
let pool;
|
|
1127
|
+
if (hour >= 0 && hour <= 2) pool = COMMENT_POOLS["0-2"];
|
|
1128
|
+
else if (hour >= 3 && hour <= 5) pool = COMMENT_POOLS["3-5"];
|
|
1129
|
+
else if (hour >= 6 && hour <= 8) pool = COMMENT_POOLS["6-8"];
|
|
1130
|
+
else if (hour >= 9 && hour <= 11) pool = COMMENT_POOLS["9-11"];
|
|
1131
|
+
else if (hour >= 12 && hour <= 13) pool = COMMENT_POOLS["12-13"];
|
|
1132
|
+
else if (hour >= 14 && hour <= 15) pool = COMMENT_POOLS["14-15"];
|
|
1133
|
+
else if (hour >= 16 && hour <= 17) pool = COMMENT_POOLS["16-17"];
|
|
1134
|
+
else if (hour >= 18 && hour <= 20) pool = COMMENT_POOLS["18-20"];
|
|
1135
|
+
else if (hour >= 21 && hour <= 22) pool = COMMENT_POOLS["21-22"];
|
|
1136
|
+
else pool = COMMENT_POOLS["23"];
|
|
1137
|
+
return pool.color(` (${randomPick(pool.tags)})`);
|
|
1138
|
+
}
|
|
1139
|
+
async function runWeeklyReport(weeksAgo, source) {
|
|
1140
|
+
const projects = getProjects();
|
|
1141
|
+
let timeTag = "";
|
|
1142
|
+
if (weeksAgo === 1) timeTag = " \u4E0A\u5468";
|
|
1143
|
+
else if (weeksAgo === 2) timeTag = " \u4E0A\u4E0A\u5468";
|
|
1144
|
+
else if (weeksAgo > 2) timeTag = ` ${weeksAgo}\u5468\u524D`;
|
|
1145
|
+
if (timeTag) {
|
|
1146
|
+
printBanner(`${timeTag} Git \u6478\u9C7C\u5468\u62A5`);
|
|
1147
|
+
} else {
|
|
1148
|
+
printBanner(`\u672C\u5468 Git \u6478\u9C7C\u5468\u62A5`);
|
|
1149
|
+
}
|
|
1150
|
+
if (projects.length === 1 && projects[0] === process.cwd()) {
|
|
1151
|
+
const hasGit = fs2.existsSync(path3.join(process.cwd(), ".git"));
|
|
1152
|
+
const config = readConfig();
|
|
1153
|
+
const hasGitLab = config.gitlabToken || config.gitlabs && config.gitlabs.length > 0;
|
|
1154
|
+
if (!hasGit && config.projects.length === 0 && !hasGitLab) {
|
|
1155
|
+
console.log(chalk.yellow(`\u26A0 \u63D0\u793A: \u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A Git \u4ED3\u5E93\u3002`));
|
|
1156
|
+
console.log(chalk.gray(`\u4F60\u53EF\u4EE5\u901A\u8FC7\u4EE5\u4E0B\u547D\u4EE4\u914D\u7F6E\u9879\u76EE\u6216 GitLab\uFF1A`));
|
|
1157
|
+
console.log(` ${chalk.cyan("fish config add <path>")} - \u624B\u52A8\u6DFB\u52A0\u672C\u5730\u4ED3\u5E93`);
|
|
1158
|
+
console.log(` ${chalk.cyan("fish config scan <dir>")} - \u81EA\u52A8\u626B\u63CF\u76EE\u5F55\u4E0B\u6240\u6709\u4ED3\u5E93`);
|
|
1159
|
+
console.log(` ${chalk.cyan("fish config gitlab <token> [host] [name]")} - \u914D\u7F6E GitLab \u4EE4\u724C\u4EE5\u8FDC\u7A0B\u540C\u6B65\u6240\u6709\u9879\u76EE
|
|
1160
|
+
`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
const targetDate = getTargetDate(weeksAgo, false);
|
|
1164
|
+
const realNow = /* @__PURE__ */ new Date();
|
|
1165
|
+
const { since: realSince } = getThisWeekRange(realNow);
|
|
1166
|
+
const { since: targetSince } = getThisWeekRange(targetDate);
|
|
1167
|
+
const isPastWeek = targetSince.getTime() < realSince.getTime();
|
|
1168
|
+
const currentDayOfWeek = isPastWeek ? 6 : (targetDate.getDay() + 6) % 7;
|
|
1169
|
+
const loading = showLoading("\u6B63\u5728\u6478\u904D\u6240\u6709\u4ED3\u5E93...");
|
|
1170
|
+
const stats = await analyzeWeekly(projects, targetDate, source);
|
|
1171
|
+
loading.stop();
|
|
1172
|
+
console.log(chalk.cyan.bold("\u{1F4C5} \u672C\u5468\u63D0\u4EA4\u8BE6\u60C5\uFF1A"));
|
|
1173
|
+
stats.days.forEach((day, idx) => {
|
|
1174
|
+
const isWeekend = idx >= 5;
|
|
1175
|
+
const isFuture = idx > currentDayOfWeek;
|
|
1176
|
+
if (isWeekend && day.commitsCount === 0) {
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
if (isFuture && day.commitsCount === 0) {
|
|
1180
|
+
console.log(` ${day.dayName}\uFF1A${chalk.gray("\u672A\u5230")}`);
|
|
1181
|
+
} else {
|
|
1182
|
+
const projStr = day.projects.length > 0 ? ` | ${day.projects.length}\u4E2A\u9879\u76EE (${day.projects.join(", ")})` : "";
|
|
1183
|
+
const commitStr = day.commitsCount > 0 ? chalk.white.bold(`${day.commitsCount} \u6B21`) : "0 \u6B21";
|
|
1184
|
+
const indices = [];
|
|
1185
|
+
if (isWeekend) {
|
|
1186
|
+
indices.push(`\u{1F4BC} \u52A0\u73ED\u6307\u6570: ${formatOvertimeIndex(day.fish)}`);
|
|
1187
|
+
} else {
|
|
1188
|
+
indices.push(`\u{1F41F} \u6478\u9C7C\u6307\u6570: ${formatSlackIndex(day.fish)}`);
|
|
1189
|
+
}
|
|
1190
|
+
if (day.nightOwl >= 10) {
|
|
1191
|
+
const nightStr = formatNightOwlIndex(day.nightOwl);
|
|
1192
|
+
if (nightStr) indices.push(`\u{1F319} \u4FEE\u4ED9\u6307\u6570: ${nightStr}`);
|
|
1193
|
+
}
|
|
1194
|
+
const extraIndices = indices.length > 0 ? ` | ${indices.join(" | ")}` : "";
|
|
1195
|
+
const tag = getPersonalityTag(day);
|
|
1196
|
+
const tagStr = tag ? ` \u{1F3F7} ${colorizeTags(tag)}` : "";
|
|
1197
|
+
console.log(` ${day.dayName}\uFF1A${commitStr}${extraIndices}${projStr}${tagStr}`);
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1200
|
+
console.log("\n" + chalk.gray("-".repeat(50)));
|
|
1201
|
+
if (stats.totalCommits > 0) {
|
|
1202
|
+
const activeDays = stats.days.filter((_, idx) => idx <= currentDayOfWeek);
|
|
1203
|
+
const workingDays = activeDays.filter((d) => d.commitsCount > 0);
|
|
1204
|
+
if (workingDays.length > 0) {
|
|
1205
|
+
const minFish = Math.min(...workingDays.map((d) => d.fish));
|
|
1206
|
+
const mostProductiveDays = workingDays.filter((d) => d.fish === minFish);
|
|
1207
|
+
if (minFish <= 40 && mostProductiveDays.length > 0) {
|
|
1208
|
+
const names = mostProductiveDays.map((d) => d.dayName).join("\u3001");
|
|
1209
|
+
const sample = mostProductiveDays[0];
|
|
1210
|
+
const isWeekend = sample.dayName === "\u5468\u516D" || sample.dayName === "\u5468\u65E5";
|
|
1211
|
+
const label = isWeekend ? "\u{1F4BC} \u52A0\u73ED\u6307\u6570" : "\u{1F41F} \u6478\u9C7C\u6307\u6570";
|
|
1212
|
+
console.log(`\u{1F3C6} ${chalk.red.bold("\u6700\u52AA\u529B\u7684\u65E5\u5B50")}\uFF1A${names} | ${label}\uFF1A${sample.fish}%`);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
const weekdayDays = activeDays.filter((d) => d.dayName !== "\u5468\u516D" && d.dayName !== "\u5468\u65E5");
|
|
1216
|
+
if (weekdayDays.length > 0) {
|
|
1217
|
+
const maxFish = Math.max(...weekdayDays.map((d) => d.fish));
|
|
1218
|
+
const minFish = workingDays.length > 0 ? Math.min(...workingDays.map((d) => d.fish)) : 100;
|
|
1219
|
+
const happyDays = weekdayDays.filter((d) => d.fish === maxFish);
|
|
1220
|
+
const filteredHappyDays = happyDays.filter(
|
|
1221
|
+
(d) => d.fish !== minFish || workingDays.length === 0
|
|
1222
|
+
);
|
|
1223
|
+
if (maxFish >= 70 && filteredHappyDays.length > 0) {
|
|
1224
|
+
const names = filteredHappyDays.map((d) => d.dayName).join("\u3001");
|
|
1225
|
+
const sample = filteredHappyDays[0];
|
|
1226
|
+
console.log(`\u2615 ${chalk.green.bold("\u6700\u5FEB\u4E50\u7684\u65E5\u5B50")}\uFF1A${names} | \u{1F41F} \u6478\u9C7C\u6307\u6570\uFF1A${sample.fish}%`);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
const weekendDays = activeDays.filter((d) => d.commitsCount > 0 && (d.dayName === "\u5468\u516D" || d.dayName === "\u5468\u65E5"));
|
|
1230
|
+
if (weekendDays.length > 0) {
|
|
1231
|
+
const minFish = Math.min(...weekendDays.map((d) => d.fish));
|
|
1232
|
+
const painfulDays = weekendDays.filter((d) => d.fish === minFish);
|
|
1233
|
+
const names = painfulDays.map((d) => d.dayName).join("\u3001");
|
|
1234
|
+
const sample = painfulDays[0];
|
|
1235
|
+
console.log(`\u{1F62D} ${chalk.magenta.bold("\u6700\u75DB\u82E6\u7684\u65E5\u5B50")}\uFF1A${names} | \u{1F4BC} \u52A0\u73ED\u6307\u6570\uFF1A${sample.fish}%`);
|
|
1236
|
+
}
|
|
1237
|
+
console.log(chalk.gray("-".repeat(50)));
|
|
1238
|
+
const avgNightStr = stats.averageNightOwl > 0 ? ` | \u{1F319} \u4FEE\u4ED9: ${stats.averageNightOwl}%` : "";
|
|
1239
|
+
console.log(chalk.dim(`\u{1F4CA} \u672C\u5468\u5747\u503C\uFF1A\u{1F41F} \u6478\u9C7C ${stats.averageFish}%${avgNightStr}`));
|
|
1240
|
+
console.log(chalk.gray("-".repeat(50)));
|
|
1241
|
+
console.log(`\u{1F916} ${chalk.magenta.bold("\u9510\u8BC4")}\uFF1A`);
|
|
1242
|
+
console.log(chalk.white(getAICritic(stats)));
|
|
1243
|
+
} else {
|
|
1244
|
+
console.log(chalk.yellow("\u{1F4A1} \u672C\u65F6\u95F4\u6BB5\u5185\u4F60\u8FD8\u6CA1\u63D0\u4EA4\u8FC7\u4EFB\u4F55\u4EE3\u7801\uFF01\u5B8C\u7F8E\u7684\u85AA\u6C34\u5C0F\u5077\u3002\u6216\u8005\u68C0\u67E5\u4F60\u7684\u914D\u7F6E\u5427\uFF01"));
|
|
1245
|
+
}
|
|
1246
|
+
console.log("");
|
|
1247
|
+
}
|
|
1248
|
+
async function runMonthlyReport(monthsAgo, source) {
|
|
1249
|
+
const projects = getProjects();
|
|
1250
|
+
let timeTag = "";
|
|
1251
|
+
if (monthsAgo === 1) timeTag = " \u4E0A\u6708";
|
|
1252
|
+
else if (monthsAgo === 2) timeTag = " \u4E0A\u4E0A\u6708";
|
|
1253
|
+
else if (monthsAgo > 2) timeTag = ` ${monthsAgo}\u6708\u524D`;
|
|
1254
|
+
if (timeTag) {
|
|
1255
|
+
printBanner(`${timeTag} Git \u6478\u9C7C\u6708\u62A5`);
|
|
1256
|
+
} else {
|
|
1257
|
+
printBanner(`\u672C\u6708 Git \u6478\u9C7C\u6708\u62A5`);
|
|
1258
|
+
}
|
|
1259
|
+
const targetDate = getTargetDate(monthsAgo, true);
|
|
1260
|
+
const loading = showLoading("\u6B63\u5728\u7FFB\u7BB1\u5012\u67DC\u67E5 Commit...");
|
|
1261
|
+
const stats = await analyzeMonthly(projects, targetDate, source);
|
|
1262
|
+
loading.stop();
|
|
1263
|
+
if (stats.totalCommits === 0) {
|
|
1264
|
+
console.log(chalk.yellow("\u{1F4A1} \u672C\u6708\u5728\u6B64\u4ED3\u5E93\u6682\u672A\u53D1\u73B0\u4EFB\u4F55 Git \u63D0\u4EA4\u6570\u636E\u3002\n"));
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
const { fix, feat, chore, other } = stats.categories;
|
|
1268
|
+
const total = fix + feat + chore + other;
|
|
1269
|
+
console.log(chalk.cyan.bold("\u{1F4C5} \u672C\u6708\u4E3B\u8981\u8D21\u732E\u5360\u6BD4 (\u57FA\u4E8E Commit Message \u6B63\u5219\u5F52\u7C7B)\uFF1A"));
|
|
1270
|
+
function drawRow(label, count, colorFn) {
|
|
1271
|
+
const percentage = total > 0 ? Math.round(count / total * 100) : 0;
|
|
1272
|
+
const barWidth = 15;
|
|
1273
|
+
const filled = Math.round(percentage / 100 * barWidth);
|
|
1274
|
+
const empty = barWidth - filled;
|
|
1275
|
+
const barStr = colorFn("\u25A0".repeat(filled)) + chalk.gray("\u25A1".repeat(empty));
|
|
1276
|
+
console.log(` - ${label}\uFF1A[${barStr}] ${chalk.bold(percentage + "%")} (${count} \u6B21)`);
|
|
1277
|
+
}
|
|
1278
|
+
drawRow("\u4FEE bug (fix) ", fix, chalk.red);
|
|
1279
|
+
drawRow("\u65B0\u529F\u80FD (feat) ", feat, chalk.green);
|
|
1280
|
+
drawRow("\u6742\u52A1\u4E0E\u6587\u6863 (chore) ", chore, chalk.yellow);
|
|
1281
|
+
drawRow("\u5176\u4ED6\u63D0\u4EA4 (other) ", other, chalk.gray);
|
|
1282
|
+
if (stats.dailyIndices && stats.dailyIndices.length > 0) {
|
|
1283
|
+
console.log("\n" + chalk.cyan.bold("\u{1F4C5} \u6BCF\u65E5\u6478\u9C7C\u6307\u6570\u6982\u89C8\uFF1A"));
|
|
1284
|
+
const monthLabel = `${targetDate.getMonth() + 1}\u6708`;
|
|
1285
|
+
const COL_WIDTH = 8;
|
|
1286
|
+
const lines = [];
|
|
1287
|
+
let dateRow = "";
|
|
1288
|
+
let fishRow = "";
|
|
1289
|
+
for (const d of stats.dailyIndices) {
|
|
1290
|
+
const dateRaw = `${monthLabel}${d.day}\u65E5`;
|
|
1291
|
+
const fishRaw = `${d.fish}%`;
|
|
1292
|
+
dateRow += visualPad(dateRaw, COL_WIDTH);
|
|
1293
|
+
fishRow += visualPad(fishRaw, COL_WIDTH);
|
|
1294
|
+
if (d.day % 7 === 0 || d === stats.dailyIndices[stats.dailyIndices.length - 1]) {
|
|
1295
|
+
lines.push(chalk.gray(dateRow.trimEnd()));
|
|
1296
|
+
fishRow = colorizeFishRow(fishRow);
|
|
1297
|
+
lines.push(fishRow);
|
|
1298
|
+
lines.push("");
|
|
1299
|
+
dateRow = "";
|
|
1300
|
+
fishRow = "";
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
if (lines[lines.length - 1] === "") lines.pop();
|
|
1304
|
+
for (const l of lines) {
|
|
1305
|
+
console.log(` ${l}`);
|
|
1306
|
+
}
|
|
1307
|
+
const dailyWithCommits = stats.dailyIndices.filter((d) => d.commitsCount > 0);
|
|
1308
|
+
if (dailyWithCommits.length > 0) {
|
|
1309
|
+
const mostFish = [...dailyWithCommits].sort((a, b) => b.fish - a.fish)[0];
|
|
1310
|
+
const mostWork = [...dailyWithCommits].sort((a, b) => a.fish - b.fish)[0];
|
|
1311
|
+
if (mostFish.fish >= 75) {
|
|
1312
|
+
console.log(chalk.green(`
|
|
1313
|
+
\u{1F3A3} \u6478\u9C7C\u738B: ${monthLabel}${mostFish.day}\u65E5 \u2192 \u6478\u9C7C\u6307\u6570 ${mostFish.fish}%` + (mostFish.tags.length > 0 ? ` ${mostFish.tags.join(" ")}` : "")));
|
|
1314
|
+
}
|
|
1315
|
+
if (mostWork.fish <= 40) {
|
|
1316
|
+
console.log(chalk.red(`
|
|
1317
|
+
\u{1F525} \u7206\u809D\u738B: ${monthLabel}${mostWork.day}\u65E5 \u2192 \u6478\u9C7C\u6307\u6570 ${mostWork.fish}%` + (mostWork.tags.length > 0 ? ` ${mostWork.tags.join(" ")}` : "")));
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
console.log("\n" + chalk.gray("-".repeat(50)));
|
|
1322
|
+
console.log(`\u{1F916} ${chalk.magenta.bold("\u9510\u8BC4")}\uFF1A`);
|
|
1323
|
+
console.log(chalk.white(getAICriticForMonth(stats)));
|
|
1324
|
+
console.log("");
|
|
1325
|
+
}
|
|
1326
|
+
async function runProjectReport(weeksAgo, source, isMonth = false) {
|
|
1327
|
+
const projects = getProjects();
|
|
1328
|
+
let timeTag = "";
|
|
1329
|
+
if (isMonth) {
|
|
1330
|
+
if (weeksAgo === 1) timeTag = " (\u4E0A\u6708)";
|
|
1331
|
+
else if (weeksAgo === 2) timeTag = " (\u4E0A\u4E0A\u6708)";
|
|
1332
|
+
else if (weeksAgo > 2) timeTag = ` (${weeksAgo}\u6708\u524D)`;
|
|
1333
|
+
} else {
|
|
1334
|
+
if (weeksAgo === 1) timeTag = " (\u4E0A\u5468)";
|
|
1335
|
+
else if (weeksAgo === 2) timeTag = " (\u4E0A\u4E0A\u5468)";
|
|
1336
|
+
else if (weeksAgo > 2) timeTag = ` (${weeksAgo}\u5468\u524D)`;
|
|
1337
|
+
}
|
|
1338
|
+
printBanner(`\u9879\u76EE\u7206\u809D\u6392\u884C${timeTag}`);
|
|
1339
|
+
const targetDate = getTargetDate(weeksAgo, isMonth);
|
|
1340
|
+
const { since, until } = isMonth ? getThisMonthRange(targetDate) : getThisWeekRange(targetDate);
|
|
1341
|
+
const loading = showLoading("\u6B63\u5728\u7EDF\u8BA1\u9879\u76EE\u7206\u809D\u7A0B\u5EA6...");
|
|
1342
|
+
const stats = isMonth ? await analyzeMonthly(projects, targetDate, source) : await analyzeWeekly(projects, targetDate, source);
|
|
1343
|
+
loading.stop();
|
|
1344
|
+
console.log(chalk.cyan.bold(`\u{1F4CA} \u5BF9\u5E94\u65F6\u6BB5\u9879\u76EE\u7206\u809D\u6392\u884C (${since.toLocaleDateString()} ~ ${until.toLocaleDateString()})`));
|
|
1345
|
+
const ranked = stats.projectsRanked;
|
|
1346
|
+
if (ranked.length === 0) {
|
|
1347
|
+
console.log(chalk.gray(" \u672C\u65F6\u6BB5\u6682\u65E0\u9879\u76EE\u63D0\u4EA4\u6570\u636E\u3002"));
|
|
1348
|
+
} else {
|
|
1349
|
+
const totalCommits = ranked.reduce((sum, p) => sum + p.count, 0);
|
|
1350
|
+
let hasPrimaryProject = false;
|
|
1351
|
+
ranked.forEach((proj, idx) => {
|
|
1352
|
+
const N = proj.count;
|
|
1353
|
+
const ratio = N / Math.max(1, totalCommits);
|
|
1354
|
+
let suffix;
|
|
1355
|
+
if (ratio >= 0.5 || N >= 30) {
|
|
1356
|
+
if (!hasPrimaryProject) {
|
|
1357
|
+
suffix = chalk.red(" (\u4E3B\u529B\u642C\u7816\u5730 \u{1F9F1})");
|
|
1358
|
+
hasPrimaryProject = true;
|
|
1359
|
+
} else {
|
|
1360
|
+
suffix = chalk.red(" (\u9B42\u5F52\u4E4B\u5904\uFF0C\u4EE3\u7801\u5728\u8FD9\u5BB6\u5C31\u5728 \u{1F3E0})");
|
|
1361
|
+
}
|
|
1362
|
+
} else if (ratio >= 0.2 || N >= 14) {
|
|
1363
|
+
suffix = chalk.magenta(" (\u591A\u7EBF\u7A0B\u5206\u51FA\u6765\u7684\u6253\u5DE5\u9B42 \u{1F9F5})");
|
|
1364
|
+
} else if (ratio >= 0.15 || N >= 5) {
|
|
1365
|
+
suffix = chalk.yellow(" (\u5076\u5C14\u4E0A\u53BB\u70B9\u4E00\u4E0B \u{1F41F})");
|
|
1366
|
+
} else if (N === 1) {
|
|
1367
|
+
suffix = chalk.cyan(" (\u6D4B\u5B8C\u5C31\u8DD1\uFF0C\u7EAF\u7CB9\u8DEF\u8FC7 \u{1F6AC})");
|
|
1368
|
+
} else {
|
|
1369
|
+
suffix = chalk.gray(" (\u8FB9\u7F18\u6302\u673A\u9879\u76EE \u{1F4A4})");
|
|
1370
|
+
}
|
|
1371
|
+
console.log(` ${idx + 1}. ${chalk.bold.white(proj.name)}: ${chalk.cyan(N + " \u6B21\u63D0\u4EA4")}${suffix}`);
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
console.log("");
|
|
1375
|
+
}
|
|
1376
|
+
async function runTimeReport(offset, source, isMonth) {
|
|
1377
|
+
const projects = getProjects();
|
|
1378
|
+
let timeTag = "";
|
|
1379
|
+
if (isMonth) {
|
|
1380
|
+
if (offset === 1) timeTag = " (\u4E0A\u6708)";
|
|
1381
|
+
else if (offset === 2) timeTag = " (\u4E0A\u4E0A\u6708)";
|
|
1382
|
+
else if (offset > 2) timeTag = ` (${offset}\u6708\u524D)`;
|
|
1383
|
+
} else {
|
|
1384
|
+
if (offset === 1) timeTag = " (\u4E0A\u5468)";
|
|
1385
|
+
else if (offset === 2) timeTag = " (\u4E0A\u4E0A\u5468)";
|
|
1386
|
+
else if (offset > 2) timeTag = ` (${offset}\u5468\u524D)`;
|
|
1387
|
+
}
|
|
1388
|
+
printBanner(`\u9EC4\u91D1\u5DE5\u4F5C\u65F6\u95F4\u6BB5\u5206\u6790 (24\u5C0F\u65F6\u5206\u5E03)${timeTag}`);
|
|
1389
|
+
const targetDate = getTargetDate(offset, !!isMonth);
|
|
1390
|
+
const { since, until } = isMonth ? getThisMonthRange(targetDate) : getThisWeekRange(targetDate);
|
|
1391
|
+
const loading = showLoading("\u6B63\u5728\u5206\u6790\u9EC4\u91D1\u65F6\u95F4\u6BB5...");
|
|
1392
|
+
const hours = await analyzeHourDistribution(projects, since, until, source);
|
|
1393
|
+
loading.stop();
|
|
1394
|
+
const maxCount = Math.max(...hours.map((h) => h.count));
|
|
1395
|
+
const barScale = maxCount > 0 ? 30 / maxCount : 1;
|
|
1396
|
+
console.log(chalk.cyan.bold(`\u{1F552} \u5BF9\u5E94\u65F6\u6BB5 24 \u5C0F\u65F6 commit \u9891\u6B21\u5206\u5E03\u56FE\uFF1A`));
|
|
1397
|
+
hours.forEach(({ hour, count }) => {
|
|
1398
|
+
const barLength = Math.round(count * barScale);
|
|
1399
|
+
const barStr = "\u2588".repeat(barLength);
|
|
1400
|
+
const hourStr = String(hour).padStart(2, "0") + ":00";
|
|
1401
|
+
let tag = count > 0 ? getHourTag(hour) : "";
|
|
1402
|
+
const barColorStr = count > 0 ? chalk.blue(barStr.padEnd(30, " ")) : chalk.gray("".padEnd(30, " "));
|
|
1403
|
+
console.log(` ${chalk.bold.cyan(hourStr)} | [${barColorStr}] ${chalk.white(count + " \u6B21")}${tag}`);
|
|
1404
|
+
});
|
|
1405
|
+
console.log("");
|
|
1406
|
+
}
|
|
1407
|
+
async function runGhostReport(weeksAgo, source) {
|
|
1408
|
+
const projects = getProjects();
|
|
1409
|
+
let timeTag = "";
|
|
1410
|
+
if (weeksAgo === 1) timeTag = " (\u4E0A\u5468)";
|
|
1411
|
+
else if (weeksAgo === 2) timeTag = " (\u4E0A\u4E0A\u5468)";
|
|
1412
|
+
else if (weeksAgo > 2) timeTag = ` (${weeksAgo}\u5468\u524D)`;
|
|
1413
|
+
printBanner(`\u5E7D\u7075\u63D0\u4EA4\u68C0\u6D4B (\u6DF1\u591C 00:00 ~ 05:00)${timeTag}`);
|
|
1414
|
+
const targetDate = getTargetDate(weeksAgo, false);
|
|
1415
|
+
const { since, until } = getThisWeekRange(targetDate);
|
|
1416
|
+
const loading = showLoading("\u6B63\u5728\u641C\u5BFB\u6DF1\u591C\u5E7D\u7075...");
|
|
1417
|
+
const ghosts = await getGhostCommits(projects, since, until, source);
|
|
1418
|
+
loading.stop();
|
|
1419
|
+
if (ghosts.length === 0) {
|
|
1420
|
+
console.log(chalk.green.bold("\u{1F389} \u606D\u559C\uFF01\u672C\u65F6\u95F4\u6BB5\u5185\u672A\u68C0\u6D4B\u5230\u4EFB\u4F55\u6DF1\u591C\u5E7D\u7075\u63D0\u4EA4\u3002"));
|
|
1421
|
+
console.log(chalk.white("\u4F60\u7684\u53D1\u9645\u7EBF\u5341\u5206\u5B89\u5168\uFF0C\u5927\u798F\u62A5\u5DF2\u88AB\u65E0\u60C5\u62D2\u6536\uFF0C\u7761\u7720\u5065\u5EB7\u5F97\u5206\uFF1A100 \u5206\uFF01\n"));
|
|
1422
|
+
} else {
|
|
1423
|
+
console.log(chalk.red.bold(`\u26A0\uFE0F \u8B66\u544A\uFF1A\u672C\u65F6\u95F4\u6BB5\u5185\u5171\u68C0\u6D4B\u5230 ${ghosts.length} \u6B21\u6DF1\u591C\u5E7D\u7075\u63D0\u4EA4\uFF01`));
|
|
1424
|
+
console.log(chalk.gray("\u6DF1\u591C\u7684 Commit \u95EA\u70C1\u7740\u7EFF\u5149\uFF0C\u6BCF\u4E00\u884C\u90FD\u662F\u7ED9\u8001\u677F\u5E93\u91CC\u5357\u52A0\u6CB9\u7684\u6C57\u6C34\u3002"));
|
|
1425
|
+
console.log(chalk.gray("-".repeat(50)));
|
|
1426
|
+
ghosts.forEach((c) => {
|
|
1427
|
+
console.log(` - [${chalk.yellow(c.project)}] ${chalk.cyan(c.date.slice(0, 19).replace("T", " "))} (${chalk.gray(c.hash)})`);
|
|
1428
|
+
console.log(` \u{1F4AC} ${chalk.italic.white(c.message)}`);
|
|
1429
|
+
});
|
|
1430
|
+
console.log(chalk.gray("-".repeat(50)));
|
|
1431
|
+
console.log(`\u{1F916} ${chalk.magenta.bold("\u9510\u8BC4")}\uFF1A`);
|
|
1432
|
+
console.log(chalk.red("\u547D\u662F\u81EA\u5DF1\u7684\uFF0C\u5927\u798F\u62A5\u7559\u7ED9\u8001\u677F\u5427\uFF01\u8D76\u7D27\u7761\u89C9\uFF0C\u4FDD\u547D\u8981\u7D27\uFF01\n"));
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
program.name("fish").description("\u{1F41F} Git \u6478\u9C7C & \u7206\u809D\u5206\u6790\u5668 CLI").version("1.0.0").option("-m, --month [monthsAgo]", "\u67E5\u770B\u6478\u9C7C/\u7206\u809D\u6708\u62A5 (\u9ED8\u8BA4 0 \u4E3A\u672C\u6708\uFF0C1 \u4E3A\u4E0A\u6708\uFF0C2 \u4E3A\u4E0A\u4E0A\u6708...)").option("-p, --project", "\u67E5\u770B\u9879\u76EE\u7206\u809D\u6392\u884C").option("-t, --time", "\u67E5\u770B\u9EC4\u91D1\u6478\u9C7C\u65F6\u95F4\u6BB5 analysis (24\u5C0F\u65F6\u5206\u5E03)").option("-g, --ghost", "\u68C0\u6D4B\u6DF1\u591C\u5E7D\u7075\u63D0\u4EA4").option("-w, --weeks-ago <number>", "\u67E5\u8BE2\u51E0\u5468\u524D/\u6708\u524D\u7684\u62A5\u544A (\u9ED8\u8BA4 0\uFF0C\u5373\u672C\u5468/\u672C\u6708)", "0").option("-s, --source <source>", "\u9009\u62E9\u8981\u67E5\u8BE2\u7684 GitLab \u6570\u636E\u6E90 (\u5E8F\u53F7\u6216\u522B\u540D/host)").action(async (options) => {
|
|
1436
|
+
const weeksAgo = parseInt(options.weeksAgo || "0", 10);
|
|
1437
|
+
const source = options.source;
|
|
1438
|
+
if (options.month !== void 0 && options.project) {
|
|
1439
|
+
const monthsAgo = options.month === true ? parseInt(options.weeksAgo || "0", 10) : parseInt(options.month || "0", 10);
|
|
1440
|
+
await runProjectReport(monthsAgo, source, true);
|
|
1441
|
+
} else if (options.month !== void 0 && options.time) {
|
|
1442
|
+
const monthsAgo = options.month === true ? parseInt(options.weeksAgo || "0", 10) : parseInt(options.month || "0", 10);
|
|
1443
|
+
await runTimeReport(monthsAgo, source, true);
|
|
1444
|
+
} else if (options.month !== void 0) {
|
|
1445
|
+
const monthsAgo = options.month === true ? parseInt(options.weeksAgo || "0", 10) : parseInt(options.month || "0", 10);
|
|
1446
|
+
await runMonthlyReport(monthsAgo, source);
|
|
1447
|
+
} else if (options.project) {
|
|
1448
|
+
await runProjectReport(weeksAgo, source);
|
|
1449
|
+
} else if (options.time) {
|
|
1450
|
+
await runTimeReport(weeksAgo, source);
|
|
1451
|
+
} else if (options.ghost) {
|
|
1452
|
+
await runGhostReport(weeksAgo, source);
|
|
1453
|
+
} else {
|
|
1454
|
+
await runWeeklyReport(weeksAgo, source);
|
|
1455
|
+
}
|
|
1456
|
+
});
|
|
1457
|
+
var configCmd = program.command("config").description("\u7BA1\u7406\u76D1\u63A7\u7684\u9879\u76EE\u8DEF\u5F84\u4E0E GitLab \u51ED\u8BC1");
|
|
1458
|
+
configCmd.command("add <path>").description("\u624B\u52A8\u6DFB\u52A0\u4E00\u4E2A\u672C\u5730 Git \u4ED3\u5E93\u8DEF\u5F84").action((projPath) => {
|
|
1459
|
+
const res = addProject(projPath);
|
|
1460
|
+
if (res.success) {
|
|
1461
|
+
console.log(chalk.green(`\u2714 ${res.message}`));
|
|
1462
|
+
} else {
|
|
1463
|
+
console.log(chalk.red(`\u2718 ${res.message}`));
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
configCmd.command("remove <path>").description("\u4ECE\u914D\u7F6E\u4E2D\u79FB\u9664\u4E00\u4E2A\u9879\u76EE\u8DEF\u5F84").action((projPath) => {
|
|
1467
|
+
const res = removeProject(projPath);
|
|
1468
|
+
if (res.success) {
|
|
1469
|
+
console.log(chalk.green(`\u2714 ${res.message}`));
|
|
1470
|
+
} else {
|
|
1471
|
+
console.log(chalk.red(`\u2718 ${res.message}`));
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
configCmd.command("list").description("\u5217\u51FA\u5F53\u524D\u6240\u6709\u76D1\u63A7\u7684\u9879\u76EE\u4E0E GitLab \u914D\u7F6E").action(() => {
|
|
1475
|
+
const config = readConfig();
|
|
1476
|
+
console.log(chalk.cyan.bold("\n\u{1F4C1} \u5F53\u524D\u76D1\u63A7\u7684\u672C\u5730\u9879\u76EE\u5217\u8868:"));
|
|
1477
|
+
if (config.projects.length === 0) {
|
|
1478
|
+
console.log(chalk.gray(` (\u76EE\u524D\u672A\u914D\u7F6E\u672C\u5730\u9879\u76EE\uFF0C\u82E5\u65E0 GitLab \u8FDC\u7A0B\u5219\u9ED8\u8BA4\u626B\u63CF\u5F53\u524D\u76EE\u5F55: ${process.cwd()})`));
|
|
1479
|
+
} else {
|
|
1480
|
+
config.projects.forEach((p, idx) => {
|
|
1481
|
+
console.log(` ${idx + 1}. ${chalk.white(p)}`);
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
console.log(chalk.cyan.bold("\n\u{1F98A} \u5F53\u524D\u914D\u7F6E\u7684 GitLab \u8FDC\u7A0B\u6E90:"));
|
|
1485
|
+
const gitlabs = config.gitlabs || [];
|
|
1486
|
+
if (gitlabs.length === 0) {
|
|
1487
|
+
console.log(chalk.gray(" (\u5C1A\u672A\u914D\u7F6E\u4EFB\u4F55 GitLab \u8FDC\u7A0B\u6E90)"));
|
|
1488
|
+
} else {
|
|
1489
|
+
gitlabs.forEach((g, idx) => {
|
|
1490
|
+
console.log(` ${idx + 1}. ${chalk.bold.white(g.name)} | Host: ${chalk.gray(g.host)}`);
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
console.log("");
|
|
1494
|
+
});
|
|
1495
|
+
configCmd.command("scan <dir>").description("\u81EA\u52A8\u626B\u63CF\u6307\u5B9A\u76EE\u5F55\u4E0B\u7684\u6240\u6709 Git \u4ED3\u5E93\u5E76\u6279\u91CF\u6DFB\u52A0").action((dir) => {
|
|
1496
|
+
console.log(chalk.cyan(`\u{1F50D} \u6B63\u5728\u626B\u63CF\u76EE\u5F55 ${dir} \u4E0B of Git \u4ED3\u5E93...`));
|
|
1497
|
+
const repos = scanDirectory(dir);
|
|
1498
|
+
if (repos.length === 0) {
|
|
1499
|
+
console.log(chalk.yellow(`\u26A0 \u672A\u5728 ${dir} \u4E0B\u53D1\u73B0\u4EFB\u4F55 Git \u4ED3\u5E93\u3002`));
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
const config = readConfig();
|
|
1503
|
+
let addedCount = 0;
|
|
1504
|
+
for (const repo of repos) {
|
|
1505
|
+
if (!config.projects.includes(repo)) {
|
|
1506
|
+
config.projects.push(repo);
|
|
1507
|
+
addedCount++;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
if (addedCount > 0) {
|
|
1511
|
+
writeConfig(config);
|
|
1512
|
+
console.log(chalk.green(`\u2714 \u6210\u529F\u53D1\u73B0\u5E76\u6DFB\u52A0\u4E86 ${addedCount} \u4E2A\u65B0 Git \u4ED3\u5E93\uFF1A`));
|
|
1513
|
+
repos.forEach((r) => console.log(` - ${chalk.gray(r)}`));
|
|
1514
|
+
} else {
|
|
1515
|
+
console.log(chalk.yellow(`\u26A0 \u626B\u63CF\u5230\u4E86 ${repos.length} \u4E2A\u4ED3\u5E93\uFF0C\u4F46\u90FD\u5DF2\u5728\u76D1\u63A7\u914D\u7F6E\u4E2D\u3002`));
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
configCmd.command("gitlab <token> [host] [name]").description("\u914D\u7F6E GitLab \u4E2A\u4EBA\u8BBF\u95EE\u4EE4\u724C(PAT)\u3001Host \u4E0E\u522B\u540D\uFF0C\u5F00\u542F GitLab \u8FDC\u7A0B\u626B\u63CF").action((token, host, name) => {
|
|
1519
|
+
setGitLabConfig(token, host, name);
|
|
1520
|
+
console.log(chalk.green(`\u2714 \u5DF2\u6210\u529F\u914D\u7F6E\u5E76\u4FDD\u5B58 GitLab \u8BBF\u95EE\u6E90\u3002`));
|
|
1521
|
+
const targetHost = host || "https://gitlab.com";
|
|
1522
|
+
const targetName = name || targetHost.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
1523
|
+
console.log(chalk.gray(`\u522B\u540D (Name): ${targetName}`));
|
|
1524
|
+
console.log(chalk.gray(`\u5730\u5740 (Host): ${targetHost}`));
|
|
1525
|
+
});
|
|
1526
|
+
configCmd.command("gitlab-clear [name_or_index]").description("\u6E05\u9664\u6307\u5B9A\u6216\u6240\u6709\u7684 GitLab \u8FDC\u7A0B\u626B\u63CF\u914D\u7F6E").action((nameOrIndex) => {
|
|
1527
|
+
clearGitLabConfig(nameOrIndex);
|
|
1528
|
+
if (nameOrIndex) {
|
|
1529
|
+
console.log(chalk.green(`\u2714 \u5DF2\u6E05\u9664\u6307\u5B9A\u7684 GitLab \u8BBF\u95EE\u914D\u7F6E [${nameOrIndex}]\u3002`));
|
|
1530
|
+
} else {
|
|
1531
|
+
console.log(chalk.green(`\u2714 \u5DF2\u6E05\u9664\u6240\u6709 GitLab \u8BBF\u95EE\u914D\u7F6E\u3002\u5DF2\u5173\u95ED GitLab \u8FDC\u7A0B\u626B\u63CF\u3002`));
|
|
1532
|
+
}
|
|
1533
|
+
});
|
|
1534
|
+
program.parse(process.argv);
|