ai-git-tool 1.1.0 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +123 -67
  2. package/dist/index.js +117 -49
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,122 +1,151 @@
1
- # ai-git
1
+ # ai-git-tool
2
2
 
3
3
  Groq API を使って、ステージ済み差分からコミットメッセージと PR 説明文を自動生成する TypeScript 製 CLI です。
4
4
 
5
- ## 必要環境
6
-
7
- - Node.js `18` 以上
8
- - `git`
9
- - Groq API キー(`GROQ_API_KEY`)
10
-
11
5
  ## セットアップ
12
6
 
13
- ### npm からインストール(推奨)
14
-
15
7
  ```bash
16
8
  npm install -g ai-git-tool
17
9
  ```
18
10
 
19
- ### ローカル開発版をリンク
11
+ ### 環境変数
12
+
13
+ API キーの取得先: [Groq Console](https://console.groq.com/keys)
14
+
15
+ **macOS / Linux (bash/zsh):**
20
16
 
21
17
  ```bash
22
- npm install
23
- npm run build
24
- npm link
18
+ export GROQ_API_KEY="your_api_key"
25
19
  ```
26
20
 
27
- `npm link` 後は、どの Git リポジトリでも `ai-git` が使えます。
21
+ 永続化する場合は `~/.bashrc` `~/.zshrc` に追記してください。
28
22
 
29
- ## 環境変数
23
+ **Windows (コマンドプロンプト):**
30
24
 
31
- ```bash
32
- export GROQ_API_KEY="your_api_key"
25
+ ```cmd
26
+ setx GROQ_API_KEY "your_api_key"
27
+ ```
28
+
29
+ **Windows (PowerShell):**
30
+
31
+ ```powershell
32
+ [System.Environment]::SetEnvironmentVariable("GROQ_API_KEY", "your_api_key", "User")
33
33
  ```
34
34
 
35
- 任意でモデル指定も可能です。
35
+ > `setx` / `SetEnvironmentVariable` はユーザー環境変数として永続保存されます。設定後は**ターミナルを再起動**してください。
36
+
37
+ **Windows (Git Bash):**
36
38
 
37
39
  ```bash
38
- export GROQ_MODEL="llama-3.1-8b-instant"
40
+ echo 'export GROQ_API_KEY="your_api_key"' >> ~/.bashrc
41
+ source ~/.bashrc
39
42
  ```
40
43
 
41
- API キーの取得先: [Groq Console](https://console.groq.com/keys)
44
+ 任意でモデル指定も可能です(デフォルト: `llama-3.1-8b-instant`)。
45
+
46
+ ```bash
47
+ # macOS / Linux / Git Bash
48
+ export GROQ_MODEL="llama-3.3-70b-versatile"
49
+
50
+ # Windows コマンドプロンプト
51
+ setx GROQ_MODEL "llama-3.3-70b-versatile"
52
+ ```
42
53
 
43
54
  ## 使い方
44
55
 
45
56
  ### コミットメッセージ生成
46
57
 
47
- 1. コミットメッセージを生成
48
-
49
58
  ```bash
50
- # 通常(タイトル + 箇条書き本文)
51
59
  ai-git commit
52
60
  ```
53
61
 
54
- `ai-git commit` はデフォルトで `git add .` を実行してから生成します。
62
+ デフォルトで `git add .` を実行してからコミットメッセージを生成します。
55
63
 
56
64
  ```bash
57
- # 自動 add を無効化(手動でステージした差分のみ使う)
65
+ # 手動でステージした差分のみ使う
58
66
  ai-git commit --no-add
59
67
  ```
60
68
 
61
- デフォルト言語は日本語です。
69
+ 確認プロンプトで操作を選択できます。
62
70
 
63
- ```bash
64
- # 今回だけ英語で生成
65
- ai-git commit --lang en
71
+ | 入力 | 動作 |
72
+ |------|------|
73
+ | `y` | そのままコミット |
74
+ | `n` | 中止 |
75
+ | `e` | エディタで編集してからコミット |
66
76
 
67
- # デフォルト言語を永続化
68
- ai-git --set-lang en
69
- ai-git --set-lang ja
77
+ **生成されるコミットメッセージの形式(Conventional Commits):**
78
+
79
+ ```
80
+ feat(auth): Google ログインを追加する
81
+
82
+ - GoogleAuthProvider の設定を auth.ts に追加する
83
+ - トークン期限切れ時の更新処理を追加する
84
+ - ネットワーク障害時のエラーハンドリングを追加する
85
+ ```
86
+
87
+ ### コミット & プッシュ
88
+
89
+ ```bash
90
+ ai-git push
70
91
  ```
71
92
 
72
- 2. 確認プロンプトで選択
93
+ `git add .` → AI によるコミットメッセージ生成 → コミット → `git push` を一括で実行します。
73
94
 
74
- - `y`: そのままコミット
75
- - `n`: 中止
76
- - `e`: エディタで編集してからコミット
95
+ - upstream が未設定の場合は `git push -u origin <branch>` で自動設定
96
+ - PR は作成しません(PR を作りたい場合は `ai-git pr` を使用)
77
97
 
78
- ### PR作成
98
+ 確認プロンプトで操作を選択できます。
79
99
 
80
- 1. ブランチで作業してコミット
100
+ | 入力 | 動作 |
101
+ |------|------|
102
+ | `y` | そのままコミット & プッシュ |
103
+ | `n` | 中止 |
104
+ | `e` | エディタで編集してからコミット & プッシュ |
81
105
 
82
106
  ```bash
83
- git checkout -b feature/new-feature
84
- git add .
85
- ai-git commit
107
+ # ステージ済みの差分のみ使う
108
+ ai-git push --no-add
86
109
  ```
87
110
 
88
- 2. PR説明文を生成してPR作成
111
+ ### PR 作成
89
112
 
90
113
  ```bash
91
114
  ai-git pr
92
-
93
- # 言語指定も可能
94
- ai-git pr --lang en
95
115
  ```
96
116
 
97
- `ai-git pr` は自動的に以下を実行します:
117
+ 自動的に以下を実行します。
118
+
119
+ - ブランチがまだ push されていない場合: `git push -u origin <branch>`
120
+ - ローカルに新しいコミットがある場合: `git push`
121
+ - PR 説明文を生成して PR を作成
122
+
123
+ 確認プロンプトで操作を選択できます。
98
124
 
99
- - ブランチがまだpushされていない場合、`git push -u origin <branch>` を実行
100
- - ローカルに新しいコミットがある場合、`git push` を実行
101
- - PR説明文を生成してPRを作成
125
+ | 入力 | 動作 |
126
+ |------|------|
127
+ | `y` | PR を作成 |
128
+ | `n` | 中止 |
129
+ | `e` | エディタで編集してから作成 |
102
130
 
103
- 3. 確認プロンプトで選択
131
+ **生成される PR 説明文の形式:**
104
132
 
105
- - `y`: PRを作成
106
- - `n`: 中止
107
- - `e`: エディタで編集してから作成
133
+ ```markdown
134
+ ## Summary
135
+ 変更の全体像を 2〜3 文で説明
136
+
137
+ ## Changes
138
+ - 具体的な変更内容(3〜7 項目)
139
+
140
+ ## Test plan
141
+ - テスト・確認方法(2〜4 項目)
142
+ ```
108
143
 
109
144
  **前提条件:**
110
145
 
111
146
  - GitHub CLI (`gh`) がインストール済み ([インストール方法](https://cli.github.com/))
112
147
  - `gh auth login` で認証済み
113
148
 
114
- **生成されるPR説明文のフォーマット:**
115
-
116
- - ## Summary: 変更の概要(2-3文)
117
- - ## Changes: 具体的な変更内容(箇条書き)
118
- - ## Test plan: テスト方法(箇条書き)
119
-
120
149
  ### ブランチ作成(自動命名)
121
150
 
122
151
  変更差分(ステージ済み + 未ステージ)からブランチ名を推定して作成します。
@@ -127,21 +156,48 @@ ai-git checkout
127
156
 
128
157
  命名例:
129
158
 
130
- - `feat/auth-login-flow`
131
- - `fix/api-error-handling`
132
- - `docs/readme-update`
159
+ ```
160
+ feat/google-login
161
+ fix/api-error-handling
162
+ docs/readme-update
163
+ ```
164
+
165
+ ### 言語設定
166
+
167
+ デフォルト言語は日本語です。
168
+
169
+ ```bash
170
+ # 今回だけ英語で生成
171
+ ai-git commit --lang en
172
+ ai-git pr --lang en
173
+
174
+ # デフォルト言語を永続化
175
+ ai-git --set-lang en
176
+ ai-git --set-lang ja
177
+ ```
133
178
 
134
- 推定できない場合は `feat/update-xxxxxx` のような名前を自動で使います。
179
+ ### ヘルプ
180
+
181
+ ```bash
182
+ ai-git --help
183
+ ```
135
184
 
136
185
  ## 開発
137
186
 
138
187
  ```bash
139
- npm run dev
188
+ npm install
140
189
  npm run build
190
+ npm link # どの Git リポジトリでも ai-git が使えるようになります
191
+ ```
192
+
193
+ ```bash
194
+ npm run dev
141
195
  ```
142
196
 
143
197
  ## トラブルシューティング
144
198
 
145
- - `No staged changes found. Run \`git add\` first.`: `git add` してから実行
146
- - `GROQ_API_KEY が未設定です`: `GROQ_API_KEY` を設定
147
- - `413 Request too large` / `TPM` 超過: `git add -p` でステージを分割
199
+ | エラー | 対処 |
200
+ |--------|------|
201
+ | `No staged changes found.` | `git add` してから実行 |
202
+ | `GROQ_API_KEY が未設定です` | `GROQ_API_KEY` を設定 |
203
+ | `413 Request too large` / TPM 超過 | `git add -p` でステージを分割 |
package/dist/index.js CHANGED
@@ -74,6 +74,7 @@ Usage: ai-git <command> [options]
74
74
 
75
75
  Commands:
76
76
  commit Generate commit message from staged changes
77
+ push Commit with AI message and push to remote (git add . + commit + push)
77
78
  pr Generate PR description and create pull request
78
79
  checkout Create branch from current changes
79
80
 
@@ -95,8 +96,11 @@ Environment:
95
96
  process.exit(0);
96
97
  }
97
98
  if (!subcommand ||
98
- (subcommand !== "commit" && subcommand !== "pr" && subcommand !== "checkout")) {
99
- console.error("Error: Please specify a command: 'commit', 'pr' or 'checkout'");
99
+ (subcommand !== "commit" &&
100
+ subcommand !== "push" &&
101
+ subcommand !== "pr" &&
102
+ subcommand !== "checkout")) {
103
+ console.error("Error: Please specify a command: 'commit', 'push', 'pr' or 'checkout'");
100
104
  console.error("Run 'ai-git --help' for usage information");
101
105
  process.exit(1);
102
106
  }
@@ -489,16 +493,8 @@ function branchExists(name) {
489
493
  return false;
490
494
  }
491
495
  }
492
- // ── メイン ───────────────────────────────────────────────
493
- async function main() {
494
- if (subcommand === "checkout") {
495
- await mainCheckout();
496
- return;
497
- }
498
- if (subcommand === "pr") {
499
- await mainPR();
500
- return;
501
- }
496
+ // ── コミットフロー共通処理 ────────────────────────────────
497
+ async function runCommitFlow() {
502
498
  if (!noAdd) {
503
499
  console.log("📦 変更をステージしています... (git add .)");
504
500
  stageAllChanges();
@@ -511,7 +507,6 @@ async function main() {
511
507
  console.log("🤖 コミットメッセージを生成中... (compact summary input)");
512
508
  const message = await generateCommitMessage(diff);
513
509
  console.log(`\n📝 Generated commit message:\n`);
514
- // 詳細モードは複数行なのでインデントして表示
515
510
  message.split("\n").forEach((line) => {
516
511
  console.log(` ${line}`);
517
512
  });
@@ -537,6 +532,53 @@ async function main() {
537
532
  doCommit(finalMessage);
538
533
  console.log(`\n✅ Committed successfully!`);
539
534
  }
535
+ // ── push サブコマンド ─────────────────────────────────────
536
+ async function mainPush() {
537
+ await runCommitFlow();
538
+ const currentBranch = (0, child_process_1.execSync)("git branch --show-current", {
539
+ encoding: "utf-8",
540
+ }).trim();
541
+ // upstream が設定済みか確認し、なければ -u origin で push
542
+ try {
543
+ (0, child_process_1.execSync)(`git rev-parse --abbrev-ref ${currentBranch}@{upstream}`, {
544
+ encoding: "utf-8",
545
+ stdio: "pipe",
546
+ });
547
+ console.log(`📤 push 中... (origin ${currentBranch})`);
548
+ const pushResult = (0, child_process_1.spawnSync)("git", ["push"], { stdio: "inherit" });
549
+ if (pushResult.status !== 0) {
550
+ console.error("❌ git push に失敗しました。");
551
+ process.exit(1);
552
+ }
553
+ }
554
+ catch {
555
+ console.log(`📤 push 中... (origin ${currentBranch} を新規作成)`);
556
+ const pushResult = (0, child_process_1.spawnSync)("git", ["push", "-u", "origin", currentBranch], {
557
+ stdio: "inherit",
558
+ });
559
+ if (pushResult.status !== 0) {
560
+ console.error("❌ git push に失敗しました。");
561
+ process.exit(1);
562
+ }
563
+ }
564
+ console.log(`\n✅ Push 完了!`);
565
+ }
566
+ // ── メイン ───────────────────────────────────────────────
567
+ async function main() {
568
+ if (subcommand === "checkout") {
569
+ await mainCheckout();
570
+ return;
571
+ }
572
+ if (subcommand === "pr") {
573
+ await mainPR();
574
+ return;
575
+ }
576
+ if (subcommand === "push") {
577
+ await mainPush();
578
+ return;
579
+ }
580
+ await runCommitFlow();
581
+ }
540
582
  main().catch((err) => {
541
583
  console.error("❌ 予期しないエラー:", err.message);
542
584
  process.exit(1);
@@ -755,16 +797,24 @@ function getBranchDiff(baseBranch) {
755
797
  function buildPRPrompt(commits, diff, lang) {
756
798
  if (lang === "ja") {
757
799
  return `あなたは GitHub の Pull Request 作成の専門家です。
758
- 次のコミット履歴と差分から、PR の説明文を生成してください。
800
+ 次のコミット履歴と差分から、PR のタイトルと説明文を生成してください。
759
801
 
760
- ルール:
761
- - GitHub の標準フォーマットを使用する
762
- - ## Summary: 2-3文で変更の概要を説明
802
+ 出力フォーマット(必ずこの順番で出力してください):
803
+ 1行目: Title: <Conventional Commits 形式のタイトル(72文字以内、日本語で)>
804
+ 2行目: 空行
805
+ 3行目以降: PR 説明文(以下の形式)
806
+
807
+ 説明文のルール:
808
+ - ## Summary: 1-2文で変更の概要を説明
763
809
  - ## Changes: "- " で始まる箇条書き(3-7個)で具体的な変更内容
764
810
  - ## Test plan: テスト方法の箇条書き(2-4個)
765
811
  - 命令形を使う
766
812
  - WHATとWHYを重視し、HOWは最小限に
767
- - 出力はPR説明文のみ(余計な説明は不要)
813
+ - 出力はタイトルとPR説明文のみ(余計な説明は不要)
814
+
815
+ タイトルの例:
816
+ Title: feat: push サブコマンドを追加する
817
+ Title: fix: コミットメッセージ生成のエラーハンドリングを修正する
768
818
 
769
819
  コミット履歴:
770
820
  ${commits}
@@ -773,16 +823,24 @@ ${commits}
773
823
  ${diff}`;
774
824
  }
775
825
  return `You are an expert at writing GitHub Pull Request descriptions.
776
- Generate a PR description from the following commit history and diff.
826
+ Generate a PR title and description from the following commit history and diff.
777
827
 
778
- Rules:
779
- - Use standard GitHub format
780
- - ## Summary: 2-3 sentences explaining the overall change
828
+ Output format (in this exact order):
829
+ Line 1: Title: <Conventional Commits title, max 72 chars>
830
+ Line 2: empty line
831
+ Line 3+: PR description in the following format
832
+
833
+ Description rules:
834
+ - ## Summary: 1-2 sentences explaining the overall change
781
835
  - ## Changes: bullet points (3-7 items) with "- " prefix detailing specific changes
782
836
  - ## Test plan: bullet points (2-4 items) describing how to test
783
837
  - Use imperative mood
784
838
  - Focus on WHAT and WHY, minimize HOW
785
- - Output ONLY the PR description, no extra explanation
839
+ - Output ONLY the title line and PR description, no extra explanation
840
+
841
+ Title examples:
842
+ Title: feat: add push subcommand
843
+ Title: fix: improve error handling in commit message generation
786
844
 
787
845
  Commit history:
788
846
  ${commits}
@@ -835,45 +893,51 @@ function isRequestTooLargeError(error) {
835
893
  lower.includes("tokens per minute") ||
836
894
  lower.includes("tpm"));
837
895
  }
838
- function makePRTitle(description) {
839
- const maxLen = 64;
840
- const lines = description
896
+ /**
897
+ * AI が出力した "Title: <text>" 行からタイトルを抽出する。
898
+ * 見つからない場合は description の先頭テキストからフォールバック。
899
+ */
900
+ function extractPRTitle(raw) {
901
+ const firstLine = raw.split("\n")[0]?.trim() ?? "";
902
+ const titleMatch = /^Title:\s*(.+)$/i.exec(firstLine);
903
+ if (titleMatch?.[1]) {
904
+ return titleMatch[1].trim();
905
+ }
906
+ // フォールバック: Summary 直下の最初の文
907
+ const lines = raw
841
908
  .split("\n")
842
909
  .map((v) => v.trim())
843
910
  .filter(Boolean);
844
- // Prefer the first sentence under ## Summary
845
911
  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
- }
912
+ let candidate = summaryIdx >= 0
913
+ ? (lines.slice(summaryIdx + 1).find((l) => !l.startsWith("##")) ?? "")
914
+ : (lines.find((l) => !l.startsWith("#") && !l.startsWith("-")) ?? "");
856
915
  candidate = candidate
857
916
  .replace(/^this pull request\s+(is|does)\s*/i, "")
858
917
  .replace(/^この\s*pull request\s*は、?/i, "")
859
918
  .replace(/^このprは、?/i, "")
860
919
  .replace(/\s+/g, " ")
861
920
  .trim();
862
- // Trim at first sentence boundary when possible.
863
921
  const sentenceCut = candidate.search(/[。.!?]/);
864
922
  if (sentenceCut > 0) {
865
923
  candidate = candidate.slice(0, sentenceCut);
866
924
  }
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;
925
+ return candidate || "Update project changes";
926
+ }
927
+ /**
928
+ * "Title: <text>" 行を description 本文から除いて返す。
929
+ */
930
+ function stripTitleLine(raw) {
931
+ const lines = raw.split("\n");
932
+ if (/^Title:\s*/i.test(lines[0]?.trim() ?? "")) {
933
+ // Title 行と直後の空行を除去
934
+ return lines.slice(lines[1]?.trim() === "" ? 2 : 1).join("\n").trimStart();
935
+ }
936
+ return raw;
874
937
  }
875
938
  function createPR(description, baseBranch, fallbackURL) {
876
- const titleLine = makePRTitle(description);
939
+ const titleLine = extractPRTitle(description);
940
+ const body = stripTitleLine(description);
877
941
  const result = (0, child_process_1.spawnSync)("gh", [
878
942
  "pr",
879
943
  "create",
@@ -882,7 +946,7 @@ function createPR(description, baseBranch, fallbackURL) {
882
946
  "--title",
883
947
  titleLine.trim(),
884
948
  "--body",
885
- description,
949
+ body,
886
950
  ], { encoding: "utf-8", stdio: "pipe" });
887
951
  if (result.stdout) {
888
952
  process.stdout.write(result.stdout);
@@ -964,8 +1028,12 @@ async function mainPR() {
964
1028
  }
965
1029
  console.log(`🤖 PR説明文を生成中... (${baseBranch}...${currentBranch}) [compact summary input]`);
966
1030
  const description = await generatePRDescription(baseBranch);
967
- console.log(`\n📝 Generated PR description:\n`);
968
- description.split("\n").forEach((line) => {
1031
+ console.log(`\n📝 Generated PR:\n`);
1032
+ console.log(` Title: ${extractPRTitle(description)}`);
1033
+ console.log();
1034
+ stripTitleLine(description)
1035
+ .split("\n")
1036
+ .forEach((line) => {
969
1037
  console.log(` ${line}`);
970
1038
  });
971
1039
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-git-tool",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "AI-powered git commit and PR description generator using Groq API",
5
5
  "main": "dist/index.js",
6
6
  "bin": {