sales-frontend-gemini-cli 0.4.2 → 0.4.4
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/dist/common/helper.cjs +581 -53
- package/dist/common/helper.cjs.map +1 -1
- package/dist/common/helper.d.cts +114 -3
- package/dist/common/helper.d.ts +114 -3
- package/dist/common/helper.js +570 -54
- package/dist/common/helper.js.map +1 -1
- package/dist/common/types.d.cts +24 -1
- package/dist/common/types.d.ts +24 -1
- package/dist/pr-review/claude/claude-commander.cjs +60 -12
- package/dist/pr-review/claude/claude-commander.cjs.map +1 -1
- package/dist/pr-review/claude/claude-commander.js +60 -12
- package/dist/pr-review/claude/claude-commander.js.map +1 -1
- package/dist/pr-review/claude/installation-claude.cjs +42 -6
- package/dist/pr-review/claude/installation-claude.cjs.map +1 -1
- package/dist/pr-review/claude/installation-claude.js +42 -6
- package/dist/pr-review/claude/installation-claude.js.map +1 -1
- package/dist/pr-review/codex/codex-commander.cjs +63 -12
- package/dist/pr-review/codex/codex-commander.cjs.map +1 -1
- package/dist/pr-review/codex/codex-commander.d.cts +1 -1
- package/dist/pr-review/codex/codex-commander.d.ts +1 -1
- package/dist/pr-review/codex/codex-commander.js +63 -12
- package/dist/pr-review/codex/codex-commander.js.map +1 -1
- package/dist/pr-review/codex/installation-codex.cjs +42 -6
- package/dist/pr-review/codex/installation-codex.cjs.map +1 -1
- package/dist/pr-review/codex/installation-codex.js +42 -6
- package/dist/pr-review/codex/installation-codex.js.map +1 -1
- package/dist/pr-review/gemini/gemini-commander.cjs +76 -16
- package/dist/pr-review/gemini/gemini-commander.cjs.map +1 -1
- package/dist/pr-review/gemini/gemini-commander.js +76 -16
- package/dist/pr-review/gemini/gemini-commander.js.map +1 -1
- package/dist/pr-review/gemini/installation-gemini.cjs +42 -6
- package/dist/pr-review/gemini/installation-gemini.cjs.map +1 -1
- package/dist/pr-review/gemini/installation-gemini.js +42 -6
- package/dist/pr-review/gemini/installation-gemini.js.map +1 -1
- package/dist/pr-review/review-one-by-one.cjs +699 -106
- package/dist/pr-review/review-one-by-one.cjs.map +1 -1
- package/dist/pr-review/review-one-by-one.js +700 -107
- package/dist/pr-review/review-one-by-one.js.map +1 -1
- package/dist/pr-review/review.cjs +722 -105
- package/dist/pr-review/review.cjs.map +1 -1
- package/dist/pr-review/review.js +722 -105
- package/dist/pr-review/review.js.map +1 -1
- package/package.json +4 -7
- package/src/common/rules/coding-convention.md +393 -0
- package/src/common/rules/coding-convention.pdf +0 -0
- package/src/common/rules/naming-rule.md +347 -0
- package/src/common/rules/naming-rule.pdf +0 -0
package/dist/common/helper.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
1
|
+
import { execSync, execFileSync } from 'child_process';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import readline from 'readline';
|
|
@@ -8,14 +8,52 @@ import { inspect } from 'util';
|
|
|
8
8
|
// src/common/helper.ts
|
|
9
9
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
var traceMessages = [];
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
var GEMINI_CLI_PACKAGE_NAME = "sales-frontend-gemini-cli";
|
|
12
|
+
var cachedPackageRootPath = "";
|
|
13
|
+
function isGeminiCliPackageRoot(directory) {
|
|
14
|
+
const packageJsonPath = path.join(directory, "package.json");
|
|
15
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
20
|
+
return packageJson.name === GEMINI_CLI_PACKAGE_NAME;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function resolveGeminiCliPackageRoot(startDirectory = __dirname) {
|
|
26
|
+
if (cachedPackageRootPath) {
|
|
27
|
+
return cachedPackageRootPath;
|
|
28
|
+
}
|
|
29
|
+
let currentDirectory = startDirectory;
|
|
30
|
+
while (true) {
|
|
31
|
+
if (isGeminiCliPackageRoot(currentDirectory)) {
|
|
32
|
+
cachedPackageRootPath = currentDirectory;
|
|
33
|
+
return cachedPackageRootPath;
|
|
34
|
+
}
|
|
35
|
+
const parentDirectory = path.dirname(currentDirectory);
|
|
36
|
+
if (parentDirectory === currentDirectory) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
currentDirectory = parentDirectory;
|
|
40
|
+
}
|
|
41
|
+
cachedPackageRootPath = path.resolve(startDirectory, "../..");
|
|
42
|
+
return cachedPackageRootPath;
|
|
43
|
+
}
|
|
44
|
+
function resolvePackageAssetPath(relativeFilePath) {
|
|
45
|
+
return path.resolve(resolveGeminiCliPackageRoot(), relativeFilePath);
|
|
46
|
+
}
|
|
47
|
+
var rulesPath = resolvePackageAssetPath("src/common/rules/review-rules.md");
|
|
48
|
+
var namingRulesPath = resolvePackageAssetPath("src/common/rules/naming-rule.md");
|
|
49
|
+
var codingConventionRulesPath = resolvePackageAssetPath("src/common/rules/coding-convention.md");
|
|
50
|
+
var reviewFormPath = resolvePackageAssetPath("src/common/form/review-form.md");
|
|
51
|
+
var reviewFormOneByOnePath = resolvePackageAssetPath("src/common/form/review-form-one-by-one.md");
|
|
16
52
|
var REPORT_DIR = ".review-report";
|
|
17
53
|
var tempDiffPath = "temp_diff.txt";
|
|
18
54
|
var AIServices = ["gemini", "claude", "codex"];
|
|
55
|
+
var COMMIT_FETCH_LIMIT = 20;
|
|
56
|
+
var COMMIT_SELECTION_WINDOW = 8;
|
|
19
57
|
var ignoreList = [
|
|
20
58
|
"package.json",
|
|
21
59
|
"*.yml",
|
|
@@ -33,12 +71,224 @@ var ignoreList = [
|
|
|
33
71
|
function isTestMode(args = process.argv.slice(2)) {
|
|
34
72
|
return args.includes("--test");
|
|
35
73
|
}
|
|
74
|
+
function shouldStreamAIOutput(args = process.argv.slice(2)) {
|
|
75
|
+
return args.includes("--stream-output");
|
|
76
|
+
}
|
|
36
77
|
function clearTraceMessages() {
|
|
37
78
|
traceMessages.length = 0;
|
|
38
79
|
}
|
|
39
80
|
function getTraceMessages() {
|
|
40
81
|
return [...traceMessages];
|
|
41
82
|
}
|
|
83
|
+
var ANSI = {
|
|
84
|
+
bold: "\x1B[1m",
|
|
85
|
+
cyan: "\x1B[36m",
|
|
86
|
+
dim: "\x1B[2m",
|
|
87
|
+
green: "\x1B[32m",
|
|
88
|
+
reset: "\x1B[0m",
|
|
89
|
+
yellow: "\x1B[33m"
|
|
90
|
+
};
|
|
91
|
+
var ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
92
|
+
var COMBINING_MARK_PATTERN = /\p{Mark}/u;
|
|
93
|
+
var GRAPHEME_SEGMENTER = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter("ko", { granularity: "grapheme" }) : null;
|
|
94
|
+
function getGitDiffPathspecs() {
|
|
95
|
+
return {
|
|
96
|
+
excludePatterns: ignoreList.map((item) => `:(exclude)${item}`),
|
|
97
|
+
includePatterns: ["*.ts", "*.tsx", "*.js", "*.jsx"]
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function segmentGraphemes(value) {
|
|
101
|
+
if (!GRAPHEME_SEGMENTER) {
|
|
102
|
+
return [...value];
|
|
103
|
+
}
|
|
104
|
+
return [...GRAPHEME_SEGMENTER.segment(value)].map(({ segment }) => segment);
|
|
105
|
+
}
|
|
106
|
+
function isWideCodePoint(codePoint) {
|
|
107
|
+
return codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 12871 && codePoint !== 12351 || codePoint >= 12880 && codePoint <= 19903 || codePoint >= 19968 && codePoint <= 42182 || codePoint >= 43360 && codePoint <= 43388 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65131 || codePoint >= 65281 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127488 && codePoint <= 127569 || codePoint >= 131072 && codePoint <= 262141);
|
|
108
|
+
}
|
|
109
|
+
function isEmojiCodePoint(codePoint) {
|
|
110
|
+
return codePoint >= 127462 && codePoint <= 127487 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 9728 && codePoint <= 10175;
|
|
111
|
+
}
|
|
112
|
+
function getGraphemeWidth(grapheme) {
|
|
113
|
+
let width = 0;
|
|
114
|
+
for (const character of grapheme) {
|
|
115
|
+
const codePoint = character.codePointAt(0);
|
|
116
|
+
if (!codePoint || COMBINING_MARK_PATTERN.test(character) || codePoint === 8205) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (isWideCodePoint(codePoint) || isEmojiCodePoint(codePoint)) {
|
|
123
|
+
width = Math.max(width, 2);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
width = Math.max(width, 1);
|
|
127
|
+
}
|
|
128
|
+
return width;
|
|
129
|
+
}
|
|
130
|
+
function tokenizePlainText(value) {
|
|
131
|
+
return segmentGraphemes(value).map((segment) => ({
|
|
132
|
+
value: segment,
|
|
133
|
+
visibleWidth: getGraphemeWidth(segment)
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
function tokenizeVisibleText(value) {
|
|
137
|
+
const tokens = [];
|
|
138
|
+
let lastIndex = 0;
|
|
139
|
+
for (const match of value.matchAll(ANSI_PATTERN)) {
|
|
140
|
+
const index = match.index ?? 0;
|
|
141
|
+
if (index > lastIndex) {
|
|
142
|
+
tokens.push(...tokenizePlainText(value.slice(lastIndex, index)));
|
|
143
|
+
}
|
|
144
|
+
tokens.push({
|
|
145
|
+
value: match[0],
|
|
146
|
+
visibleWidth: 0
|
|
147
|
+
});
|
|
148
|
+
lastIndex = index + match[0].length;
|
|
149
|
+
}
|
|
150
|
+
if (lastIndex < value.length) {
|
|
151
|
+
tokens.push(...tokenizePlainText(value.slice(lastIndex)));
|
|
152
|
+
}
|
|
153
|
+
return tokens;
|
|
154
|
+
}
|
|
155
|
+
function truncateLineForTerminal(value, maxWidth) {
|
|
156
|
+
if (maxWidth <= 0) {
|
|
157
|
+
return "";
|
|
158
|
+
}
|
|
159
|
+
const tokens = tokenizeVisibleText(value);
|
|
160
|
+
const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
|
|
161
|
+
if (totalWidth <= maxWidth) {
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
const ellipsis = "...";
|
|
165
|
+
const ellipsisWidth = 3;
|
|
166
|
+
const targetWidth = Math.max(0, maxWidth - ellipsisWidth);
|
|
167
|
+
let usedWidth = 0;
|
|
168
|
+
let result = "";
|
|
169
|
+
for (const token of tokens) {
|
|
170
|
+
if (token.visibleWidth === 0) {
|
|
171
|
+
result += token.value;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (usedWidth + token.visibleWidth > targetWidth) {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
result += token.value;
|
|
178
|
+
usedWidth += token.visibleWidth;
|
|
179
|
+
}
|
|
180
|
+
return `${result}${ellipsis}${ANSI.reset}`;
|
|
181
|
+
}
|
|
182
|
+
function fitLinesToTerminal(lines) {
|
|
183
|
+
const maxWidth = Math.max(20, (process.stdout.columns || 120) - 1);
|
|
184
|
+
return lines.map((line) => truncateLineForTerminal(line, maxWidth));
|
|
185
|
+
}
|
|
186
|
+
function runGitCommand(args, options = {}) {
|
|
187
|
+
const { allowFailure = false, trimOutput = true } = options;
|
|
188
|
+
try {
|
|
189
|
+
const output = execFileSync("git", args, {
|
|
190
|
+
encoding: "utf8",
|
|
191
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
192
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
193
|
+
});
|
|
194
|
+
return trimOutput ? output.trim() : output;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
helperTrace("git-command:failed", `${args.join(" ")} | ${getErrorSummary(error)}`);
|
|
197
|
+
if (allowFailure) {
|
|
198
|
+
return "";
|
|
199
|
+
}
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function executeShellCommandWithProgress(command, options = {}) {
|
|
204
|
+
const { progressIntervalMs = 1e4, progressMessage = "\u23F3 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...", streamOutput = false } = options;
|
|
205
|
+
const { spawn } = await import('child_process');
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
let stdout = "";
|
|
208
|
+
let stderr = "";
|
|
209
|
+
const startedAt = Date.now();
|
|
210
|
+
console.log(progressMessage);
|
|
211
|
+
const child = spawn("/bin/zsh", ["-lc", command], {
|
|
212
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
213
|
+
});
|
|
214
|
+
const progressTimer = setInterval(() => {
|
|
215
|
+
const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1e3));
|
|
216
|
+
console.log(`${progressMessage} (${elapsedSeconds}s \uACBD\uACFC)`);
|
|
217
|
+
}, progressIntervalMs);
|
|
218
|
+
child.stdout.on("data", (chunk) => {
|
|
219
|
+
const text = chunk.toString();
|
|
220
|
+
stdout += text;
|
|
221
|
+
if (streamOutput) {
|
|
222
|
+
process.stdout.write(text);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
child.stderr.on("data", (chunk) => {
|
|
226
|
+
const text = chunk.toString();
|
|
227
|
+
stderr += text;
|
|
228
|
+
if (streamOutput) {
|
|
229
|
+
process.stderr.write(text);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
child.on("error", (error) => {
|
|
233
|
+
clearInterval(progressTimer);
|
|
234
|
+
reject(error);
|
|
235
|
+
});
|
|
236
|
+
child.on("close", (code, signal) => {
|
|
237
|
+
clearInterval(progressTimer);
|
|
238
|
+
if (code === 0) {
|
|
239
|
+
resolve({
|
|
240
|
+
stderr,
|
|
241
|
+
stdout
|
|
242
|
+
});
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const exitSummary = signal ? `signal=${signal}` : `code=${String(code ?? "unknown")}`;
|
|
246
|
+
const failureDetails = {
|
|
247
|
+
code,
|
|
248
|
+
command,
|
|
249
|
+
signal,
|
|
250
|
+
stderr,
|
|
251
|
+
stdout
|
|
252
|
+
};
|
|
253
|
+
reject(createShellCommandExecutionError(failureDetails, exitSummary));
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function getShellCommandFailurePreview(failureDetails) {
|
|
258
|
+
const stderrText = failureDetails.stderr.trim();
|
|
259
|
+
const stdoutText = failureDetails.stdout.trim();
|
|
260
|
+
const combinedOutput = stderrText || stdoutText;
|
|
261
|
+
if (!combinedOutput) {
|
|
262
|
+
return "";
|
|
263
|
+
}
|
|
264
|
+
const MAX_PREVIEW_LENGTH = 4e3;
|
|
265
|
+
if (combinedOutput.length <= MAX_PREVIEW_LENGTH) {
|
|
266
|
+
return combinedOutput;
|
|
267
|
+
}
|
|
268
|
+
return combinedOutput.slice(-4e3);
|
|
269
|
+
}
|
|
270
|
+
function createShellCommandExecutionError(failureDetails, exitSummary) {
|
|
271
|
+
const failurePreview = getShellCommandFailurePreview(failureDetails);
|
|
272
|
+
const error = new Error(`\uC258 \uBA85\uB839 \uC2E4\uD589 \uC2E4\uD328 (${exitSummary})${failurePreview ? `
|
|
273
|
+
${failurePreview}` : ""}`);
|
|
274
|
+
error.code = failureDetails.code;
|
|
275
|
+
error.signal = failureDetails.signal;
|
|
276
|
+
error.stdout = failureDetails.stdout;
|
|
277
|
+
error.stderr = failureDetails.stderr;
|
|
278
|
+
error.command = failureDetails.command;
|
|
279
|
+
return error;
|
|
280
|
+
}
|
|
281
|
+
function formatReviewTargetFiles(files, visibleCount = 5) {
|
|
282
|
+
if (files.length === 0) {
|
|
283
|
+
return "(\uC5C6\uC74C)";
|
|
284
|
+
}
|
|
285
|
+
const visibleFiles = files.slice(0, visibleCount);
|
|
286
|
+
const hiddenCount = Math.max(0, files.length - visibleFiles.length);
|
|
287
|
+
if (hiddenCount === 0) {
|
|
288
|
+
return visibleFiles.join(", ");
|
|
289
|
+
}
|
|
290
|
+
return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
|
|
291
|
+
}
|
|
42
292
|
function createTraceLogger(scope, args = process.argv.slice(2)) {
|
|
43
293
|
const enabled = isTestMode(args);
|
|
44
294
|
return (step, detail) => {
|
|
@@ -100,7 +350,7 @@ function serializeError(error) {
|
|
|
100
350
|
}
|
|
101
351
|
if (error && typeof error === "object") {
|
|
102
352
|
const errorLike = error;
|
|
103
|
-
const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs"];
|
|
353
|
+
const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs", "command"];
|
|
104
354
|
extraKeys.forEach((key) => {
|
|
105
355
|
if (errorLike[key] !== void 0) {
|
|
106
356
|
serialized[key] = errorLike[key];
|
|
@@ -206,6 +456,87 @@ ${section.markdown}`).join("\n")}
|
|
|
206
456
|
return "";
|
|
207
457
|
}
|
|
208
458
|
}
|
|
459
|
+
function getExecutionLogSummary(status, title) {
|
|
460
|
+
if (title) {
|
|
461
|
+
return title;
|
|
462
|
+
}
|
|
463
|
+
switch (status) {
|
|
464
|
+
case "success":
|
|
465
|
+
return "\uB9AC\uBDF0 \uC2E4\uD589\uC774 \uC131\uACF5\uC801\uC73C\uB85C \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
|
|
466
|
+
case "failed":
|
|
467
|
+
return "\uB9AC\uBDF0 \uC2E4\uD589 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
|
|
468
|
+
case "partial_failure":
|
|
469
|
+
return "\uB9AC\uBDF0 \uC2E4\uD589\uC740 \uC644\uB8CC\uB418\uC5C8\uC9C0\uB9CC \uC77C\uBD80 \uB2E8\uACC4\uC5D0\uC11C \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
|
|
470
|
+
default:
|
|
471
|
+
return "\uB9AC\uBDF0 \uC2E4\uD589\uC774 \uCDE8\uC18C\uB418\uC5C8\uAC70\uB098 \uB9AC\uBDF0 \uB300\uC0C1\uC774 \uC5C6\uC5B4 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function formatExecutionDuration(startedAt, finishedAt) {
|
|
475
|
+
const durationMs = Math.max(0, finishedAt.getTime() - startedAt.getTime());
|
|
476
|
+
if (durationMs < 1e3) {
|
|
477
|
+
return `${durationMs}ms`;
|
|
478
|
+
}
|
|
479
|
+
const durationSeconds = durationMs / 1e3;
|
|
480
|
+
if (durationSeconds < 60) {
|
|
481
|
+
return `${durationSeconds.toFixed(1)}s`;
|
|
482
|
+
}
|
|
483
|
+
const minutes = Math.floor(durationSeconds / 60);
|
|
484
|
+
const seconds = Math.round(durationSeconds % 60);
|
|
485
|
+
return `${minutes}m ${seconds}s`;
|
|
486
|
+
}
|
|
487
|
+
function writeExecutionLog(options = {}) {
|
|
488
|
+
try {
|
|
489
|
+
const startedAt = options.startedAt ?? /* @__PURE__ */ new Date();
|
|
490
|
+
const finishedAt = options.finishedAt ?? /* @__PURE__ */ new Date();
|
|
491
|
+
const status = options.status ?? "success";
|
|
492
|
+
helperTrace("execution-log:write:start", options.scope || "unknown");
|
|
493
|
+
createReportDirectory();
|
|
494
|
+
const reportPath = getAvailableFilePath(REPORT_DIR, `${getNowString(finishedAt)}-execution-log`, ".md");
|
|
495
|
+
const traceSnapshot = options.traceMessages ?? getTraceMessages();
|
|
496
|
+
const extraSections = options.extraSections || [];
|
|
497
|
+
const serializedError = options.error ? serializeError(options.error) : null;
|
|
498
|
+
const report = `# Execution Log
|
|
499
|
+
|
|
500
|
+
- \uC2DC\uC791 \uC2DC\uAC01: ${getHumanReadableNowString(startedAt)}
|
|
501
|
+
- \uC885\uB8CC \uC2DC\uAC01: ${getHumanReadableNowString(finishedAt)}
|
|
502
|
+
- \uC2E4\uD589 \uC2DC\uAC04: ${formatExecutionDuration(startedAt, finishedAt)}
|
|
503
|
+
- \uC0C1\uD0DC: \`${status}\`
|
|
504
|
+
- Scope: \`${options.scope || "unknown"}\`
|
|
505
|
+
- \uC791\uC5C5 \uACBD\uB85C: \`${process.cwd()}\`
|
|
506
|
+
- \uC2E4\uD589 \uC778\uC790: \`${JSON.stringify(options.args ?? process.argv.slice(2))}\`
|
|
507
|
+
- \uC2E4\uD589 \uD658\uACBD: \`${process.platform} ${process.arch} / Node ${process.version}\`
|
|
508
|
+
|
|
509
|
+
## Summary
|
|
510
|
+
|
|
511
|
+
${getExecutionLogSummary(status, options.title)}
|
|
512
|
+
${serializedError ? `
|
|
513
|
+
|
|
514
|
+
## Error
|
|
515
|
+
|
|
516
|
+
\`\`\`json
|
|
517
|
+
${JSON.stringify(serializedError, null, 2)}
|
|
518
|
+
\`\`\`` : ""}
|
|
519
|
+
|
|
520
|
+
## Trace
|
|
521
|
+
|
|
522
|
+
\`\`\`json
|
|
523
|
+
${JSON.stringify(traceSnapshot, null, 2)}
|
|
524
|
+
\`\`\`${extraSections.length ? `
|
|
525
|
+
${extraSections.map((section) => `
|
|
526
|
+
## ${section.heading}
|
|
527
|
+
|
|
528
|
+
${section.markdown}`).join("\n")}
|
|
529
|
+
` : "\n"}
|
|
530
|
+
`;
|
|
531
|
+
fs.writeFileSync(reportPath, report);
|
|
532
|
+
helperTrace("execution-log:write:done", reportPath);
|
|
533
|
+
return reportPath;
|
|
534
|
+
} catch (writeError) {
|
|
535
|
+
console.error("\u26A0\uFE0F \uC2E4\uD589 \uB85C\uADF8 \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
|
|
536
|
+
console.error(writeError);
|
|
537
|
+
return "";
|
|
538
|
+
}
|
|
539
|
+
}
|
|
209
540
|
function exitWithError(message, options = {}) {
|
|
210
541
|
const reportPath = writeErrorReport(options.error || new Error(message), {
|
|
211
542
|
...options,
|
|
@@ -251,13 +582,90 @@ ${JSON.stringify(AIServices, null, 2)}
|
|
|
251
582
|
);
|
|
252
583
|
}
|
|
253
584
|
function getGitDiffFilter() {
|
|
254
|
-
const
|
|
255
|
-
const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);
|
|
585
|
+
const { includePatterns, excludePatterns } = getGitDiffPathspecs();
|
|
256
586
|
const quote = (pattern) => `"${pattern}"`;
|
|
257
|
-
const includeParams =
|
|
587
|
+
const includeParams = includePatterns.map(quote).join(" ");
|
|
258
588
|
const excludeParams = excludePatterns.map(quote).join(" ");
|
|
259
589
|
return { includeParams, excludeParams };
|
|
260
590
|
}
|
|
591
|
+
function truncateCommitSubject(subject) {
|
|
592
|
+
if (subject.length <= 72) {
|
|
593
|
+
return subject;
|
|
594
|
+
}
|
|
595
|
+
return `${subject.slice(0, 69)}...`;
|
|
596
|
+
}
|
|
597
|
+
function getRecentCommitOptions() {
|
|
598
|
+
const output = runGitCommand(
|
|
599
|
+
["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
|
|
600
|
+
{ allowFailure: true }
|
|
601
|
+
);
|
|
602
|
+
if (!output) {
|
|
603
|
+
return [];
|
|
604
|
+
}
|
|
605
|
+
return output.split("\n").map((line) => {
|
|
606
|
+
const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
|
|
607
|
+
const subject = subjectParts.join(" ").trim();
|
|
608
|
+
return {
|
|
609
|
+
author,
|
|
610
|
+
description: `${author} | ${relativeDate}`,
|
|
611
|
+
hash,
|
|
612
|
+
label: `${hash} | ${truncateCommitSubject(subject)}`,
|
|
613
|
+
relativeDate,
|
|
614
|
+
subject
|
|
615
|
+
};
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
function buildSelectedCommitSummary(commits) {
|
|
619
|
+
return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
|
|
620
|
+
}
|
|
621
|
+
function getReviewPathspecArgs() {
|
|
622
|
+
const { includePatterns, excludePatterns } = getGitDiffPathspecs();
|
|
623
|
+
return [...includePatterns, ...excludePatterns];
|
|
624
|
+
}
|
|
625
|
+
function buildSelectedCommitDiff(commits) {
|
|
626
|
+
const reviewPathspecArgs = getReviewPathspecArgs();
|
|
627
|
+
const sections = commits.map((commit) => {
|
|
628
|
+
const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
|
|
629
|
+
allowFailure: true,
|
|
630
|
+
trimOutput: false
|
|
631
|
+
}).trim();
|
|
632
|
+
if (!diff) {
|
|
633
|
+
return "";
|
|
634
|
+
}
|
|
635
|
+
return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
|
|
636
|
+
}).filter(Boolean).join("\n\n");
|
|
637
|
+
if (!sections) {
|
|
638
|
+
return "";
|
|
639
|
+
}
|
|
640
|
+
return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
|
|
641
|
+
}
|
|
642
|
+
function getSelectedCommitFiles(commits) {
|
|
643
|
+
const reviewPathspecArgs = getReviewPathspecArgs();
|
|
644
|
+
const files = /* @__PURE__ */ new Set();
|
|
645
|
+
commits.forEach((commit) => {
|
|
646
|
+
const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
|
|
647
|
+
allowFailure: true
|
|
648
|
+
});
|
|
649
|
+
output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
|
|
650
|
+
});
|
|
651
|
+
return [...files];
|
|
652
|
+
}
|
|
653
|
+
function buildSelectedFileDiff(commits, filePath) {
|
|
654
|
+
const sections = commits.map((commit) => {
|
|
655
|
+
const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", filePath], {
|
|
656
|
+
allowFailure: true,
|
|
657
|
+
trimOutput: false
|
|
658
|
+
}).trim();
|
|
659
|
+
if (!diff) {
|
|
660
|
+
return "";
|
|
661
|
+
}
|
|
662
|
+
return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
|
|
663
|
+
}).filter(Boolean).join("\n\n");
|
|
664
|
+
if (!sections) {
|
|
665
|
+
return "";
|
|
666
|
+
}
|
|
667
|
+
return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", `# \uD30C\uC77C: ${filePath}`, sections].join("\n\n");
|
|
668
|
+
}
|
|
261
669
|
function openReport(reportPath) {
|
|
262
670
|
const resolvedPath = path.resolve(reportPath);
|
|
263
671
|
const { platform } = process;
|
|
@@ -307,49 +715,155 @@ function openReport(reportPath) {
|
|
|
307
715
|
helperTrace("open-report:unsupported-platform", platform);
|
|
308
716
|
console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
|
|
309
717
|
}
|
|
310
|
-
function
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
helperTrace(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
718
|
+
function ensureInteractiveSelectionAvailable(scope, message) {
|
|
719
|
+
if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
helperTrace(`${scope}:tty-missing`);
|
|
723
|
+
exitWithError(message, {
|
|
724
|
+
scope: `helper:${scope}`
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
function renderSelectionBlock(lines, previousLineCount) {
|
|
728
|
+
if (previousLineCount > 0) {
|
|
729
|
+
readline.moveCursor(process.stdout, 0, -previousLineCount);
|
|
730
|
+
readline.clearScreenDown(process.stdout);
|
|
731
|
+
}
|
|
732
|
+
const fittedLines = fitLinesToTerminal(lines);
|
|
733
|
+
process.stdout.write(`${fittedLines.join("\n")}
|
|
734
|
+
`);
|
|
735
|
+
return fittedLines.length;
|
|
736
|
+
}
|
|
737
|
+
function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
|
|
738
|
+
if (optionCount <= windowSize) {
|
|
739
|
+
return {
|
|
740
|
+
end: optionCount,
|
|
741
|
+
start: 0
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
const halfWindow = Math.floor(windowSize / 2);
|
|
745
|
+
const maxStart = optionCount - windowSize;
|
|
746
|
+
const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
|
|
747
|
+
return {
|
|
748
|
+
end: Math.min(optionCount, start + windowSize),
|
|
749
|
+
start
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
|
|
753
|
+
const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
|
|
754
|
+
const lines = [
|
|
755
|
+
`${ANSI.bold}${question}${ANSI.reset}`,
|
|
756
|
+
`${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
|
|
757
|
+
`${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
|
|
758
|
+
];
|
|
759
|
+
for (let index = start; index < end; index += 1) {
|
|
760
|
+
const option = options[index];
|
|
761
|
+
if (!option) {
|
|
762
|
+
continue;
|
|
324
763
|
}
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
764
|
+
const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
|
|
765
|
+
const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
|
|
766
|
+
const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
|
|
767
|
+
lines.push(`${cursor} ${checked} ${option.label}${description}`);
|
|
768
|
+
}
|
|
769
|
+
if (options.length > windowSize) {
|
|
770
|
+
lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
|
|
771
|
+
}
|
|
772
|
+
return lines;
|
|
773
|
+
}
|
|
774
|
+
async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
|
|
775
|
+
ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
776
|
+
let selectedIndex = 0;
|
|
777
|
+
let renderedLineCount = 0;
|
|
778
|
+
const toggled = /* @__PURE__ */ new Set();
|
|
779
|
+
const rl = readline.createInterface({
|
|
780
|
+
input: process.stdin,
|
|
781
|
+
output: process.stdout,
|
|
782
|
+
terminal: true
|
|
783
|
+
});
|
|
784
|
+
process.stdout.write("\x1B[?25l");
|
|
785
|
+
const cleanup = () => {
|
|
786
|
+
if (renderedLineCount > 0) {
|
|
787
|
+
readline.moveCursor(process.stdout, 0, -renderedLineCount);
|
|
788
|
+
readline.clearScreenDown(process.stdout);
|
|
789
|
+
renderedLineCount = 0;
|
|
332
790
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
791
|
+
process.stdin.removeListener("data", onData);
|
|
792
|
+
process.stdin.setRawMode(false);
|
|
793
|
+
process.stdin.pause();
|
|
794
|
+
rl.close();
|
|
795
|
+
process.stdout.write("\x1B[?25h");
|
|
796
|
+
};
|
|
797
|
+
const render = () => {
|
|
798
|
+
const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
|
|
799
|
+
renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
|
|
800
|
+
};
|
|
801
|
+
const confirmSelection = (resolve) => {
|
|
802
|
+
const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
|
|
803
|
+
cleanup();
|
|
804
|
+
resolve(values);
|
|
805
|
+
};
|
|
806
|
+
const cancelSelection = (resolve) => {
|
|
807
|
+
cleanup();
|
|
808
|
+
resolve([]);
|
|
809
|
+
};
|
|
810
|
+
let onData = (_data) => {
|
|
811
|
+
};
|
|
812
|
+
render();
|
|
813
|
+
return new Promise((resolve) => {
|
|
814
|
+
onData = (data) => {
|
|
815
|
+
const key = data.toString();
|
|
816
|
+
if (key === "") {
|
|
817
|
+
cleanup();
|
|
818
|
+
process.exit(0);
|
|
346
819
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
820
|
+
if (key === "\x1B") {
|
|
821
|
+
cancelSelection(resolve);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
if (key === "\x1B[A") {
|
|
825
|
+
selectedIndex = (selectedIndex - 1 + options.length) % options.length;
|
|
826
|
+
render();
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (key === "\x1B[B") {
|
|
830
|
+
selectedIndex = (selectedIndex + 1) % options.length;
|
|
831
|
+
render();
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (key === " ") {
|
|
835
|
+
if (toggled.has(selectedIndex)) {
|
|
836
|
+
toggled.delete(selectedIndex);
|
|
837
|
+
} else {
|
|
838
|
+
toggled.add(selectedIndex);
|
|
839
|
+
}
|
|
840
|
+
render();
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
if (key === "\r" || key === "\n") {
|
|
844
|
+
confirmSelection(resolve);
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
process.stdin.setRawMode(true);
|
|
848
|
+
process.stdin.resume();
|
|
849
|
+
process.stdin.on("data", onData);
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
async function selectReviewCommits() {
|
|
853
|
+
const commits = getRecentCommitOptions();
|
|
854
|
+
if (commits.length === 0) {
|
|
855
|
+
console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
856
|
+
return [];
|
|
350
857
|
}
|
|
351
|
-
|
|
352
|
-
|
|
858
|
+
return showMultiSelect(
|
|
859
|
+
"\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
|
|
860
|
+
commits.map((commit) => ({
|
|
861
|
+
description: commit.description,
|
|
862
|
+
label: commit.label,
|
|
863
|
+
value: commit
|
|
864
|
+
})),
|
|
865
|
+
COMMIT_SELECTION_WINDOW
|
|
866
|
+
);
|
|
353
867
|
}
|
|
354
868
|
function selectAIService() {
|
|
355
869
|
const service = parseServiceFromArgs();
|
|
@@ -367,10 +881,11 @@ async function showSelectionAIService() {
|
|
|
367
881
|
if (selectedServiceFromArgs) {
|
|
368
882
|
helperTrace("show-selection:from-args", selectedServiceFromArgs);
|
|
369
883
|
console.log(`
|
|
370
|
-
\u2705
|
|
884
|
+
\u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
|
|
371
885
|
`);
|
|
372
886
|
return selectedServiceFromArgs;
|
|
373
887
|
}
|
|
888
|
+
ensureInteractiveSelectionAvailable("showSelectionAIService", "\u274C AI \uC11C\uBE44\uC2A4 \uC120\uD0DD UI\uB294 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
374
889
|
helperTrace("show-selection:interactive:start");
|
|
375
890
|
let selectedIndex = 0;
|
|
376
891
|
const rl = readline.createInterface({
|
|
@@ -388,11 +903,12 @@ async function showSelectionAIService() {
|
|
|
388
903
|
helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
|
|
389
904
|
readline.clearScreenDown(process.stdout);
|
|
390
905
|
process.stdout.write(
|
|
391
|
-
|
|
906
|
+
`\u{1F916} AI \uC11C\uBE44\uC2A4\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694 (${ANSI.yellow}\u2191\u2193 \uBC29\uD5A5\uD0A4${ANSI.reset} \uC774\uB3D9, ${ANSI.yellow}Enter${ANSI.reset} \uC120\uD0DD):
|
|
907
|
+
`
|
|
392
908
|
);
|
|
393
909
|
AIServices.forEach((service, index) => {
|
|
394
910
|
if (index === selectedIndex) {
|
|
395
|
-
process.stdout.write(`
|
|
911
|
+
process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
|
|
396
912
|
`);
|
|
397
913
|
} else {
|
|
398
914
|
process.stdout.write(` \u25EF ${service}
|
|
@@ -422,7 +938,7 @@ async function showSelectionAIService() {
|
|
|
422
938
|
rl.close();
|
|
423
939
|
process.stdout.write("\x1B[?25h");
|
|
424
940
|
console.log(`
|
|
425
|
-
\u2705
|
|
941
|
+
\u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
|
|
426
942
|
`);
|
|
427
943
|
const result = AIServices[selectedIndex];
|
|
428
944
|
if (result) {
|
|
@@ -437,6 +953,6 @@ async function showSelectionAIService() {
|
|
|
437
953
|
});
|
|
438
954
|
}
|
|
439
955
|
|
|
440
|
-
export { AIServices, REPORT_DIR, clearTraceMessages, codingConventionRulesPath, createReportDirectory, createTraceLogger, deleteFile, deleteTempDiff, exitWithError,
|
|
956
|
+
export { AIServices, COMMIT_FETCH_LIMIT, COMMIT_SELECTION_WINDOW, REPORT_DIR, buildSelectedCommitDiff, buildSelectedCommitSummary, buildSelectedFileDiff, clearTraceMessages, codingConventionRulesPath, createReportDirectory, createTraceLogger, deleteFile, deleteTempDiff, executeShellCommandWithProgress, exitWithError, formatReviewTargetFiles, getAvailableFilePath, getErrorLogTimestamp, getErrorSummary, getGitDiffFilter, getNextFilePath, getNowString, getRecentCommitOptions, getSelectedCommitFiles, getTraceMessages, ignoreList, isTestMode, namingRulesPath, openReport, reviewFormOneByOnePath, reviewFormPath, rulesPath, selectAIService, selectReviewCommits, shouldStreamAIOutput, showMultiSelect, showSelectionAIService, tempDiffPath, writeErrorReport, writeExecutionLog };
|
|
441
957
|
//# sourceMappingURL=helper.js.map
|
|
442
958
|
//# sourceMappingURL=helper.js.map
|