ai-git-tool 1.0.0 → 1.1.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 +8 -7
- package/dist/index.js +105 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Groq API を使って、ステージ済み差分からコミットメッセー
|
|
|
13
13
|
### npm からインストール(推奨)
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install -g
|
|
16
|
+
npm install -g ai-git-tool
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
### ローカル開発版をリンク
|
|
@@ -44,17 +44,18 @@ API キーの取得先: [Groq Console](https://console.groq.com/keys)
|
|
|
44
44
|
|
|
45
45
|
### コミットメッセージ生成
|
|
46
46
|
|
|
47
|
-
1.
|
|
47
|
+
1. コミットメッセージを生成
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
|
|
50
|
+
# 通常(タイトル + 箇条書き本文)
|
|
51
|
+
ai-git commit
|
|
51
52
|
```
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
`ai-git commit` はデフォルトで `git add .` を実行してから生成します。
|
|
54
55
|
|
|
55
56
|
```bash
|
|
56
|
-
#
|
|
57
|
-
ai-git commit
|
|
57
|
+
# 自動 add を無効化(手動でステージした差分のみ使う)
|
|
58
|
+
ai-git commit --no-add
|
|
58
59
|
```
|
|
59
60
|
|
|
60
61
|
デフォルト言語は日本語です。
|
|
@@ -68,7 +69,7 @@ ai-git --set-lang en
|
|
|
68
69
|
ai-git --set-lang ja
|
|
69
70
|
```
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
2. 確認プロンプトで選択
|
|
72
73
|
|
|
73
74
|
- `y`: そのままコミット
|
|
74
75
|
- `n`: 中止
|
package/dist/index.js
CHANGED
|
@@ -51,6 +51,7 @@ const subcommandArgs = args.slice(1);
|
|
|
51
51
|
const showHelp = args.includes("--help") || args.includes("-h") || subcommand === "help";
|
|
52
52
|
const setLangArg = getOptionValue(args, "--set-lang");
|
|
53
53
|
const langArg = getOptionValue(subcommandArgs, "--lang");
|
|
54
|
+
const noAdd = subcommandArgs.includes("--no-add");
|
|
54
55
|
const CONFIG_DIR = path.join(os.homedir(), ".ai-commit");
|
|
55
56
|
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
56
57
|
const defaultGroqModel = "llama-3.1-8b-instant";
|
|
@@ -78,6 +79,7 @@ Commands:
|
|
|
78
79
|
|
|
79
80
|
Commit Options:
|
|
80
81
|
--lang <ja|en> Set language for this run
|
|
82
|
+
--no-add Skip automatic git add .
|
|
81
83
|
|
|
82
84
|
PR Options:
|
|
83
85
|
--lang <ja|en> Set language for this run
|
|
@@ -114,17 +116,69 @@ async function generateCommitMessage(diff) {
|
|
|
114
116
|
const inputDiff = truncateByChars(diff, COMMIT_DIFF_COMPACT_CHARS);
|
|
115
117
|
const prompt = buildPrompt(inputDiff, language);
|
|
116
118
|
try {
|
|
117
|
-
|
|
119
|
+
const raw = await generateText(prompt);
|
|
120
|
+
return normalizeGeneratedCommitMessage(raw, diff, language);
|
|
118
121
|
}
|
|
119
122
|
catch (error) {
|
|
120
123
|
if (isRequestTooLargeError(error)) {
|
|
121
124
|
const smallerDiff = truncateByChars(diff, 1800);
|
|
122
|
-
|
|
125
|
+
const retryRaw = await generateText(buildPrompt(smallerDiff, language));
|
|
126
|
+
return normalizeGeneratedCommitMessage(retryRaw, diff, language);
|
|
123
127
|
}
|
|
124
128
|
handleGroqError(error);
|
|
125
129
|
process.exit(1);
|
|
126
130
|
}
|
|
127
131
|
}
|
|
132
|
+
function normalizeGeneratedCommitMessage(raw, diff, lang) {
|
|
133
|
+
const lines = raw
|
|
134
|
+
.split("\n")
|
|
135
|
+
.map((line) => line.replace(/\s+$/g, ""))
|
|
136
|
+
.filter((_, idx, arr) => !(idx > 0 && arr[idx - 1] === "" && arr[idx] === ""));
|
|
137
|
+
if (lines.length === 0) {
|
|
138
|
+
return buildFallbackCommitMessage(diff, lang);
|
|
139
|
+
}
|
|
140
|
+
const subjectLine = lines[0]?.trim() || "";
|
|
141
|
+
const conventionalMatch = /^[a-z]+(\([^)]+\))?:\s+(.+)$/.exec(subjectLine);
|
|
142
|
+
const hasConventionalPrefix = Boolean(conventionalMatch);
|
|
143
|
+
const shortDescription = conventionalMatch?.[2]?.trim() || subjectLine;
|
|
144
|
+
if (!hasConventionalPrefix ||
|
|
145
|
+
looksLikePathOnly(shortDescription) ||
|
|
146
|
+
shortDescription.length < 8) {
|
|
147
|
+
lines[0] = buildFallbackSubjectLine(diff, lang);
|
|
148
|
+
}
|
|
149
|
+
return lines.join("\n").trim();
|
|
150
|
+
}
|
|
151
|
+
function looksLikePathOnly(value) {
|
|
152
|
+
const normalized = value.trim().toLowerCase();
|
|
153
|
+
if (!normalized) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const hasPathToken = normalized.includes("/") || normalized.includes(".");
|
|
157
|
+
return hasPathToken && /^[a-z0-9/_\-.]+$/.test(normalized);
|
|
158
|
+
}
|
|
159
|
+
function buildFallbackCommitMessage(diff, lang) {
|
|
160
|
+
const subject = buildFallbackSubjectLine(diff, lang);
|
|
161
|
+
if (lang === "ja") {
|
|
162
|
+
return `${subject}\n\n- 差分の主目的を明確にし、変更理由が伝わる形に整える`;
|
|
163
|
+
}
|
|
164
|
+
return `${subject}\n\n- Clarify the main change intent so the reason is easy to understand`;
|
|
165
|
+
}
|
|
166
|
+
function buildFallbackSubjectLine(diff, lang) {
|
|
167
|
+
const files = getChangedFiles();
|
|
168
|
+
const type = inferBranchType(files, diff);
|
|
169
|
+
const rawTopic = inferTopic(files, diff);
|
|
170
|
+
const topic = rawTopic ? rawTopic.replace(/-/g, " ") : "";
|
|
171
|
+
if (lang === "ja") {
|
|
172
|
+
const description = topic
|
|
173
|
+
? `${topic} の変更意図を分かりやすく整理する`
|
|
174
|
+
: "変更意図が伝わるように更新内容を整理する";
|
|
175
|
+
return `${type}: ${description}`;
|
|
176
|
+
}
|
|
177
|
+
const description = topic
|
|
178
|
+
? `clarify the intent behind ${topic} changes`
|
|
179
|
+
: "clarify update intent in one clear sentence";
|
|
180
|
+
return `${type}: ${description}`;
|
|
181
|
+
}
|
|
128
182
|
function buildPrompt(diff, lang) {
|
|
129
183
|
if (lang === "ja") {
|
|
130
184
|
return `あなたは git のコミットメッセージ作成の専門家です。
|
|
@@ -208,6 +262,13 @@ function doCommit(message) {
|
|
|
208
262
|
process.exit(1);
|
|
209
263
|
}
|
|
210
264
|
}
|
|
265
|
+
function stageAllChanges() {
|
|
266
|
+
const result = (0, child_process_1.spawnSync)("git", ["add", "."], { stdio: "inherit" });
|
|
267
|
+
if (result.status !== 0) {
|
|
268
|
+
console.error("Error: git add . failed");
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
211
272
|
async function mainCheckout() {
|
|
212
273
|
const suggested = await suggestBranchName();
|
|
213
274
|
const branchName = ensureAvailableBranchName(suggested);
|
|
@@ -438,6 +499,10 @@ async function main() {
|
|
|
438
499
|
await mainPR();
|
|
439
500
|
return;
|
|
440
501
|
}
|
|
502
|
+
if (!noAdd) {
|
|
503
|
+
console.log("📦 変更をステージしています... (git add .)");
|
|
504
|
+
stageAllChanges();
|
|
505
|
+
}
|
|
441
506
|
const diff = getStagedDiff();
|
|
442
507
|
if (!diff.trim()) {
|
|
443
508
|
console.log("No staged changes found. Run `git add` first.");
|
|
@@ -770,10 +835,45 @@ function isRequestTooLargeError(error) {
|
|
|
770
835
|
lower.includes("tokens per minute") ||
|
|
771
836
|
lower.includes("tpm"));
|
|
772
837
|
}
|
|
838
|
+
function makePRTitle(description) {
|
|
839
|
+
const maxLen = 64;
|
|
840
|
+
const lines = description
|
|
841
|
+
.split("\n")
|
|
842
|
+
.map((v) => v.trim())
|
|
843
|
+
.filter(Boolean);
|
|
844
|
+
// Prefer the first sentence under ## Summary
|
|
845
|
+
const summaryIdx = lines.findIndex((l) => l.toLowerCase().startsWith("## summary"));
|
|
846
|
+
let candidate = "";
|
|
847
|
+
if (summaryIdx >= 0) {
|
|
848
|
+
const summaryLine = lines
|
|
849
|
+
.slice(summaryIdx + 1)
|
|
850
|
+
.find((l) => !l.startsWith("##"));
|
|
851
|
+
candidate = summaryLine || "";
|
|
852
|
+
}
|
|
853
|
+
if (!candidate) {
|
|
854
|
+
candidate = lines.find((l) => !l.startsWith("#") && !l.startsWith("-")) || "";
|
|
855
|
+
}
|
|
856
|
+
candidate = candidate
|
|
857
|
+
.replace(/^this pull request\s+(is|does)\s*/i, "")
|
|
858
|
+
.replace(/^この\s*pull request\s*は、?/i, "")
|
|
859
|
+
.replace(/^このprは、?/i, "")
|
|
860
|
+
.replace(/\s+/g, " ")
|
|
861
|
+
.trim();
|
|
862
|
+
// Trim at first sentence boundary when possible.
|
|
863
|
+
const sentenceCut = candidate.search(/[。.!?]/);
|
|
864
|
+
if (sentenceCut > 0) {
|
|
865
|
+
candidate = candidate.slice(0, sentenceCut);
|
|
866
|
+
}
|
|
867
|
+
if (!candidate) {
|
|
868
|
+
candidate = "Update project changes";
|
|
869
|
+
}
|
|
870
|
+
if (candidate.length > maxLen) {
|
|
871
|
+
candidate = `${candidate.slice(0, maxLen - 1).trimEnd()}…`;
|
|
872
|
+
}
|
|
873
|
+
return candidate;
|
|
874
|
+
}
|
|
773
875
|
function createPR(description, baseBranch, fallbackURL) {
|
|
774
|
-
|
|
775
|
-
const lines = description.split("\n");
|
|
776
|
-
const titleLine = lines.find((l) => l.trim() && !l.startsWith("#")) || "Pull Request";
|
|
876
|
+
const titleLine = makePRTitle(description);
|
|
777
877
|
const result = (0, child_process_1.spawnSync)("gh", [
|
|
778
878
|
"pr",
|
|
779
879
|
"create",
|