@amnesia2k/git-aic 2.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/LICENSE +21 -0
- package/README.md +312 -0
- package/bin/cli.ts +546 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +394 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/src/config.d.ts +4 -0
- package/dist/src/config.js +6 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/git.d.ts +14 -0
- package/dist/src/git.js +116 -0
- package/dist/src/git.js.map +1 -0
- package/dist/src/llm.d.ts +9 -0
- package/dist/src/llm.js +201 -0
- package/dist/src/llm.js.map +1 -0
- package/dist/src/prompt.d.ts +7 -0
- package/dist/src/prompt.js +127 -0
- package/dist/src/prompt.js.map +1 -0
- package/package.json +51 -0
- package/src/config.ts +7 -0
- package/src/git.ts +141 -0
- package/src/llm.ts +340 -0
- package/src/prompt.ts +144 -0
package/dist/src/llm.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { getStoredApiKey } from "./config.js";
|
|
4
|
+
import { buildBatchDiffExplanationsPrompt, buildDiffExplanationPrompt, buildDiffFileNamePrompt, buildPrompt, } from "./prompt.js";
|
|
5
|
+
const VALID_DIFF_FILE_TYPES = new Set([
|
|
6
|
+
"feat",
|
|
7
|
+
"fix",
|
|
8
|
+
"refactor",
|
|
9
|
+
"chore",
|
|
10
|
+
"docs",
|
|
11
|
+
"style",
|
|
12
|
+
"test",
|
|
13
|
+
"perf",
|
|
14
|
+
"bugfix",
|
|
15
|
+
]);
|
|
16
|
+
const API_URL = "https://generativelanguage.googleapis.com/v1/models/gemini-2.5-flash:generateContent";
|
|
17
|
+
const MAX_RETRIES = 4;
|
|
18
|
+
const getApiKey = () => process.env.GEMINI_COMMIT_MESSAGE_API_KEY || getStoredApiKey() || "";
|
|
19
|
+
const ensureApiKey = () => {
|
|
20
|
+
const API_KEY = getApiKey();
|
|
21
|
+
if (!API_KEY) {
|
|
22
|
+
console.error(chalk.red("\nMissing GEMINI_COMMIT_MESSAGE_API_KEY environment variable.\n"));
|
|
23
|
+
console.log("Please set your API key before running this command.\n");
|
|
24
|
+
console.log(chalk.yellow("How to fix this:\n"));
|
|
25
|
+
console.log(chalk.cyan("macOS / Linux:"));
|
|
26
|
+
console.log(" export GEMINI_COMMIT_MESSAGE_API_KEY=your_api_key_here\n");
|
|
27
|
+
console.log(chalk.cyan("Windows (PowerShell):"));
|
|
28
|
+
console.log(' setx GEMINI_COMMIT_MESSAGE_API_KEY "your_api_key_here"\n');
|
|
29
|
+
console.log(chalk.gray("After setting the variable, restart your terminal.\n"));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
return API_KEY;
|
|
33
|
+
};
|
|
34
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
35
|
+
const getOperationLabel = ({ operation, target }) => {
|
|
36
|
+
if (operation === "diff-filename") {
|
|
37
|
+
return "generating diff filename";
|
|
38
|
+
}
|
|
39
|
+
if (operation === "diff-explanations") {
|
|
40
|
+
return target
|
|
41
|
+
? `generating diff explanations for ${target}`
|
|
42
|
+
: "generating diff explanations";
|
|
43
|
+
}
|
|
44
|
+
return "generating commit message";
|
|
45
|
+
};
|
|
46
|
+
const getFallbackLabel = ({ operation, target }) => {
|
|
47
|
+
if (operation === "diff-filename") {
|
|
48
|
+
return "using fallback filename";
|
|
49
|
+
}
|
|
50
|
+
if (operation === "diff-explanations") {
|
|
51
|
+
return target
|
|
52
|
+
? `using fallback explanations for ${target}`
|
|
53
|
+
: "using fallback explanations";
|
|
54
|
+
}
|
|
55
|
+
return "using fallback commit message";
|
|
56
|
+
};
|
|
57
|
+
const isRetryableError = (error) => {
|
|
58
|
+
if (!axios.isAxiosError(error)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const status = error.response?.status;
|
|
62
|
+
if (status === 429) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (status && status >= 500) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return [
|
|
69
|
+
"ECONNABORTED",
|
|
70
|
+
"ETIMEDOUT",
|
|
71
|
+
"ECONNRESET",
|
|
72
|
+
"ENOTFOUND",
|
|
73
|
+
"EAI_AGAIN",
|
|
74
|
+
].includes(error.code || "");
|
|
75
|
+
};
|
|
76
|
+
const getRetryDelay = (error, attempt) => {
|
|
77
|
+
const retryAfterHeader = error.response?.headers?.["retry-after"];
|
|
78
|
+
if (typeof retryAfterHeader === "string") {
|
|
79
|
+
const retryAfterSeconds = Number(retryAfterHeader);
|
|
80
|
+
if (!Number.isNaN(retryAfterSeconds) && retryAfterSeconds > 0) {
|
|
81
|
+
return retryAfterSeconds * 1000;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return Math.min(750 * 2 ** (attempt - 1), 3000);
|
|
85
|
+
};
|
|
86
|
+
const requestText = async (prompt, fallback, context) => {
|
|
87
|
+
const API_KEY = ensureApiKey();
|
|
88
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt += 1) {
|
|
89
|
+
try {
|
|
90
|
+
const response = await axios.post(API_URL, {
|
|
91
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
92
|
+
}, {
|
|
93
|
+
headers: {
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
"x-goog-api-key": API_KEY,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
return response.data.candidates?.[0]?.content?.parts?.[0]?.text?.trim()
|
|
99
|
+
? response.data.candidates[0].content.parts[0].text.trim()
|
|
100
|
+
: fallback;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (axios.isAxiosError(error) && isRetryableError(error)) {
|
|
104
|
+
if (attempt < MAX_RETRIES) {
|
|
105
|
+
const delay = getRetryDelay(error, attempt);
|
|
106
|
+
const status = error.response?.status || error.code || "unknown";
|
|
107
|
+
console.log(chalk.yellow(`Gemini request retry ${attempt}/${MAX_RETRIES - 1} while ${getOperationLabel(context)} (${status}). Waiting ${Math.ceil(delay / 1000)}s...`));
|
|
108
|
+
await sleep(delay);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const status = axios.isAxiosError(error)
|
|
113
|
+
? error.response?.status || error.code || "request-error"
|
|
114
|
+
: "request-error";
|
|
115
|
+
console.log(chalk.yellow(`Gemini request failed while ${getOperationLabel(context)} (${status}); ${getFallbackLabel(context)}.`));
|
|
116
|
+
return fallback;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return fallback;
|
|
120
|
+
};
|
|
121
|
+
const sanitizeTopic = (value) => {
|
|
122
|
+
const sanitized = value
|
|
123
|
+
.trim()
|
|
124
|
+
.toLowerCase()
|
|
125
|
+
.replace(/\.md$/i, "")
|
|
126
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
127
|
+
.replace(/-+/g, "-")
|
|
128
|
+
.replace(/^-|-$/g, "");
|
|
129
|
+
const tokens = sanitized.split("-").filter(Boolean).slice(0, 3);
|
|
130
|
+
return tokens.join("-");
|
|
131
|
+
};
|
|
132
|
+
const parseDiffFileName = (value) => {
|
|
133
|
+
const lines = value
|
|
134
|
+
.split(/\r?\n/)
|
|
135
|
+
.map((line) => line.trim())
|
|
136
|
+
.filter(Boolean);
|
|
137
|
+
const typeLine = lines.find((line) => /^type\s*:/i.test(line));
|
|
138
|
+
const topicLine = lines.find((line) => /^topic\s*:/i.test(line));
|
|
139
|
+
const rawType = typeLine
|
|
140
|
+
?.replace(/^type\s*:/i, "")
|
|
141
|
+
.trim()
|
|
142
|
+
.toLowerCase() || "";
|
|
143
|
+
const rawTopic = topicLine?.replace(/^topic\s*:/i, "").trim() || "";
|
|
144
|
+
const type = VALID_DIFF_FILE_TYPES.has(rawType) ? rawType : "chore";
|
|
145
|
+
const topic = sanitizeTopic(rawTopic) || "changes";
|
|
146
|
+
return `${type}-${topic}`;
|
|
147
|
+
};
|
|
148
|
+
export const generateCommitMessage = async (rawDiff, branchName) => {
|
|
149
|
+
const prompt = buildPrompt(rawDiff, branchName);
|
|
150
|
+
return requestText(prompt, "chore: update code", {
|
|
151
|
+
operation: "commit-message",
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
export const generateDiffExplanation = async (filePath, rawDiff, branchName) => {
|
|
155
|
+
const prompt = buildDiffExplanationPrompt(filePath, rawDiff, branchName);
|
|
156
|
+
return requestText(prompt, `Updates ${filePath} with the selected changes shown below.`, {
|
|
157
|
+
operation: "diff-explanations",
|
|
158
|
+
target: filePath,
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
const parseBatchDiffExplanations = (value, filePaths) => {
|
|
162
|
+
const result = new Map();
|
|
163
|
+
const blockRegex = /FILE:\s*(.+?)\r?\nEXPLANATION:\s*([\s\S]*?)\r?\nEND_FILE/g;
|
|
164
|
+
let match;
|
|
165
|
+
while ((match = blockRegex.exec(value)) !== null) {
|
|
166
|
+
const filePath = match[1].trim();
|
|
167
|
+
const explanation = match[2].trim();
|
|
168
|
+
if (filePaths.includes(filePath) && explanation.length > 0) {
|
|
169
|
+
result.set(filePath, explanation);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
};
|
|
174
|
+
export const generateDiffExplanations = async (fileDiffs, branchName) => {
|
|
175
|
+
const prompt = buildBatchDiffExplanationsPrompt(fileDiffs, branchName);
|
|
176
|
+
const response = await requestText(prompt, "", {
|
|
177
|
+
operation: "diff-explanations",
|
|
178
|
+
target: `${fileDiffs.length} files`,
|
|
179
|
+
});
|
|
180
|
+
const explanations = parseBatchDiffExplanations(response, fileDiffs.map(({ filePath }) => filePath));
|
|
181
|
+
if (explanations.size === fileDiffs.length) {
|
|
182
|
+
return explanations;
|
|
183
|
+
}
|
|
184
|
+
if (response.trim().length > 0) {
|
|
185
|
+
console.log(chalk.yellow("Gemini returned incomplete diff explanations; using fallback text for missing files."));
|
|
186
|
+
}
|
|
187
|
+
fileDiffs.forEach(({ filePath }) => {
|
|
188
|
+
if (!explanations.has(filePath)) {
|
|
189
|
+
explanations.set(filePath, `Updates ${filePath} with the selected changes shown below.`);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
return explanations;
|
|
193
|
+
};
|
|
194
|
+
export const generateDiffFileName = async (rawDiff) => {
|
|
195
|
+
const prompt = buildDiffFileNamePrompt(rawDiff);
|
|
196
|
+
const rawName = await requestText(prompt, "type: chore\ntopic: changes", {
|
|
197
|
+
operation: "diff-filename",
|
|
198
|
+
});
|
|
199
|
+
return parseDiffFileName(rawName);
|
|
200
|
+
};
|
|
201
|
+
//# sourceMappingURL=llm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../src/llm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const buildPrompt: (diff: string, branchName: string) => string;
|
|
2
|
+
export declare const buildDiffExplanationPrompt: (filePath: string, diff: string, branchName: string) => string;
|
|
3
|
+
export declare const buildBatchDiffExplanationsPrompt: (fileDiffs: Array<{
|
|
4
|
+
filePath: string;
|
|
5
|
+
diff: string;
|
|
6
|
+
}>, branchName: string) => string;
|
|
7
|
+
export declare const buildDiffFileNamePrompt: (diff: string) => string;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export const buildPrompt = (diff, branchName) => `
|
|
2
|
+
CRITICAL INSTRUCTIONS - READ CAREFULLY:
|
|
3
|
+
You are an expert Git commit message writer. You MUST follow ALL these rules:
|
|
4
|
+
|
|
5
|
+
1. FORMAT: Use Conventional Commits format: <type>(<scope>/<branch_name>): <description>
|
|
6
|
+
- type: MUST be one of: feat, fix, refactor, chore, docs, style, test, perf, bugfix
|
|
7
|
+
- scope: Should be the module/file affected (e.g., "auth", "api", "ui", "config", "feature")
|
|
8
|
+
- branch_name: The current Git branch you are on. It is "${branchName || "unknown"}"
|
|
9
|
+
- description: Clear, imperative description in present tense
|
|
10
|
+
|
|
11
|
+
2. DESCRIPTION REQUIREMENTS:
|
|
12
|
+
- Start with an imperative verb (add, fix, remove, update, refactor, etc.)
|
|
13
|
+
- Be specific about what changed
|
|
14
|
+
- Keep the first line (the summary) under 72 characters total
|
|
15
|
+
- NO trailing punctuation on the summary line
|
|
16
|
+
- NO emojis ever
|
|
17
|
+
- MUST be a complete sentence
|
|
18
|
+
- Group related file changes together. If you change a service, controller, and route for the same feature (e.g. "auth"), summarize the collective change in just ONE line.
|
|
19
|
+
- Do NOT list every file that changed.
|
|
20
|
+
|
|
21
|
+
3. MESSAGE STRUCTURE:
|
|
22
|
+
- The first line must be the summary: type(scope/${branchName || "unknown"}): description
|
|
23
|
+
- If there is ONLY ONE logical feature/fix being made (even if across multiple files), the commit message MUST BE EXACTLY ONE LINE. Do not use bullet points.
|
|
24
|
+
- ONLY if there are entirely UNRELATED distinct features changed at once, you may add a blank line after the summary, followed by a bulleted list summarizing those distinct features.
|
|
25
|
+
- Example 1 (Single logical change spanning multiple files): "feat(auth/${branchName || "unknown"}): implement jwt authentication flow"
|
|
26
|
+
- Example 2 (Unrelated distinct changes):
|
|
27
|
+
feat(core/${branchName || "unknown"}): update foundational systems
|
|
28
|
+
|
|
29
|
+
- handle null response in user endpoint
|
|
30
|
+
- add rate limiting to requests
|
|
31
|
+
|
|
32
|
+
4. QUALITY CHECKS - YOUR OUTPUT MUST PASS:
|
|
33
|
+
- Contains opening and closing parentheses
|
|
34
|
+
- Has a colon after the parentheses
|
|
35
|
+
- Description exists and is not empty
|
|
36
|
+
- Summary line total length ≤ 72 characters
|
|
37
|
+
- No markdown formatting
|
|
38
|
+
- No code blocks
|
|
39
|
+
- No explanations or notes
|
|
40
|
+
|
|
41
|
+
5. FAILURE MODE:
|
|
42
|
+
- If you cannot generate a proper message, return exactly: "chore: update code"
|
|
43
|
+
|
|
44
|
+
YOUR TASK:
|
|
45
|
+
Analyze this git diff and generate exactly ONE proper commit message following all rules above.
|
|
46
|
+
|
|
47
|
+
Git diff:
|
|
48
|
+
${diff}
|
|
49
|
+
|
|
50
|
+
Commit message:
|
|
51
|
+
`.trim();
|
|
52
|
+
export const buildDiffExplanationPrompt = (filePath, diff, branchName) => `
|
|
53
|
+
You are explaining a staged git diff to a developer.
|
|
54
|
+
|
|
55
|
+
Rules:
|
|
56
|
+
- Explain only the changes shown for this file
|
|
57
|
+
- Write 2 to 4 short sentences
|
|
58
|
+
- Be concrete and precise
|
|
59
|
+
- Focus on what changed and why it matters
|
|
60
|
+
- No markdown headings
|
|
61
|
+
- No bullet points
|
|
62
|
+
- No code fences
|
|
63
|
+
- Do not restate the entire diff line by line
|
|
64
|
+
- The current branch is "${branchName || "unknown"}"
|
|
65
|
+
|
|
66
|
+
File:
|
|
67
|
+
${filePath}
|
|
68
|
+
|
|
69
|
+
Diff:
|
|
70
|
+
${diff}
|
|
71
|
+
|
|
72
|
+
Explanation:
|
|
73
|
+
`.trim();
|
|
74
|
+
export const buildBatchDiffExplanationsPrompt = (fileDiffs, branchName) => `
|
|
75
|
+
You are explaining staged git diffs to a developer.
|
|
76
|
+
|
|
77
|
+
Rules:
|
|
78
|
+
- Write exactly one explanation block for each file shown below
|
|
79
|
+
- Keep each explanation to 2 to 4 short sentences
|
|
80
|
+
- Be concrete and precise
|
|
81
|
+
- Focus on what changed and why it matters
|
|
82
|
+
- No markdown headings
|
|
83
|
+
- No bullet points
|
|
84
|
+
- No code fences
|
|
85
|
+
- Do not restate the diff line by line
|
|
86
|
+
- Use the exact file path provided
|
|
87
|
+
- Return blocks in this exact format:
|
|
88
|
+
FILE: <exact file path>
|
|
89
|
+
EXPLANATION: <plain text explanation>
|
|
90
|
+
END_FILE
|
|
91
|
+
- Return nothing except these blocks
|
|
92
|
+
- The current branch is "${branchName || "unknown"}"
|
|
93
|
+
|
|
94
|
+
Files and diffs:
|
|
95
|
+
${fileDiffs
|
|
96
|
+
.map(({ filePath, diff }) => `
|
|
97
|
+
FILE: ${filePath}
|
|
98
|
+
DIFF:
|
|
99
|
+
${diff}
|
|
100
|
+
END_DIFF
|
|
101
|
+
`.trim())
|
|
102
|
+
.join("\n\n")}
|
|
103
|
+
|
|
104
|
+
Result:
|
|
105
|
+
`.trim();
|
|
106
|
+
export const buildDiffFileNamePrompt = (diff) => `
|
|
107
|
+
Classify these staged changes and produce a concise structured filename basis.
|
|
108
|
+
|
|
109
|
+
Rules:
|
|
110
|
+
- Return exactly two lines and nothing else
|
|
111
|
+
- First line format: type: <value>
|
|
112
|
+
- Second line format: topic: <value>
|
|
113
|
+
- type must be one of: feat, fix, refactor, chore, docs, style, test, perf, bugfix
|
|
114
|
+
- topic must be 1 to 3 lowercase hyphen-separated words
|
|
115
|
+
- topic must describe the actual change area
|
|
116
|
+
- Do not include branch names
|
|
117
|
+
- Do not include dates
|
|
118
|
+
- Do not include the .md extension
|
|
119
|
+
- Do not include quotes, markdown, bullets, or explanations
|
|
120
|
+
- Avoid generic filler words like proposed, report, diff, and changes unless absolutely necessary
|
|
121
|
+
|
|
122
|
+
Git diff:
|
|
123
|
+
${diff}
|
|
124
|
+
|
|
125
|
+
Result:
|
|
126
|
+
`.trim();
|
|
127
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/prompt.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,UAAkB,EAAU,EAAE,CACtE;;;;;;;8DAO4D,UAAU,IAAI,SAAS;;;;;;;;;;;;;;sDAc/B,UAAU,IAAI,SAAS;;;6EAGA,UAAU,IAAI,SAAS;;YAExF,UAAU,IAAI,SAAS;;;;;;;;;;;;;;;;;;;;;EAqBjC,IAAI;;;CAGL,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,0BAA0B,GAAG,CACxC,QAAgB,EAChB,IAAY,EACZ,UAAkB,EACV,EAAE,CACV;;;;;;;;;;;;2BAYyB,UAAU,IAAI,SAAS;;;EAGhD,QAAQ;;;EAGR,IAAI;;;CAGL,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAC9C,SAAoD,EACpD,UAAkB,EACV,EAAE,CACV;;;;;;;;;;;;;;;;;;2BAkByB,UAAU,IAAI,SAAS;;;EAGhD,SAAS;KACR,GAAG,CACF,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QACpB,QAAQ;;EAEd,IAAI;;CAEL,CAAC,IAAI,EAAE,CACL;KACA,IAAI,CAAC,MAAM,CAAC;;;CAGd,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,IAAY,EACJ,EAAE,CACV;;;;;;;;;;;;;;;;;EAiBA,IAAI;;;CAGL,CAAC,IAAI,EAAE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@amnesia2k/git-aic",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "2.1.0",
|
|
5
|
+
"description": "Git AIC",
|
|
6
|
+
"main": "dist/bin/cli.js",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"private": false,
|
|
11
|
+
"bin": {
|
|
12
|
+
"git-aic": "dist/bin/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/",
|
|
16
|
+
"bin/",
|
|
17
|
+
"src/",
|
|
18
|
+
"README.md",
|
|
19
|
+
"package.json"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.build.json",
|
|
23
|
+
"dev": "tsx bin/cli.ts",
|
|
24
|
+
"commit": "tsx bin/cli.ts"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"git",
|
|
28
|
+
"aic",
|
|
29
|
+
"ai",
|
|
30
|
+
"git-aic"
|
|
31
|
+
],
|
|
32
|
+
"author": "Olatilewa Olatoye",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/bun": "latest",
|
|
36
|
+
"@types/conf": "^2.1.0",
|
|
37
|
+
"@types/node": "^25.5.0",
|
|
38
|
+
"ts-node": "^10.9.2",
|
|
39
|
+
"tsx": "^4.21.0",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@clack/prompts": "^1.1.0",
|
|
44
|
+
"axios": "^1.13.6",
|
|
45
|
+
"chalk": "^5.6.2",
|
|
46
|
+
"cli-loaders": "^3.0.0",
|
|
47
|
+
"commander": "^14.0.3",
|
|
48
|
+
"conf": "^15.1.0",
|
|
49
|
+
"simple-git": "^3.32.3"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/config.ts
ADDED
package/src/git.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { simpleGit } from "simple-git";
|
|
2
|
+
import type { SimpleGit } from "simple-git";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
|
+
|
|
5
|
+
const git: SimpleGit = simpleGit();
|
|
6
|
+
|
|
7
|
+
export interface FileDiff {
|
|
8
|
+
filePath: string;
|
|
9
|
+
diff: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface HeadCommitInfo {
|
|
13
|
+
short: string;
|
|
14
|
+
full: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const getGitDiff = async () => {
|
|
18
|
+
try {
|
|
19
|
+
await git.raw(["config", "core.autocrlf", "true"]);
|
|
20
|
+
const diff = await git.diff(["--cached", "--ignore-space-at-eol"]);
|
|
21
|
+
return diff || "";
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error(error);
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const getBranchName = async () => {
|
|
29
|
+
try {
|
|
30
|
+
const status = await git.status();
|
|
31
|
+
return status.current || "";
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(error);
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const getStagedFileDiffs = async (): Promise<FileDiff[]> => {
|
|
39
|
+
try {
|
|
40
|
+
const status = await git.status();
|
|
41
|
+
const fileDiffs = await Promise.all(
|
|
42
|
+
status.staged.map(async (filePath) => {
|
|
43
|
+
const diff = await git.diff([
|
|
44
|
+
"--cached",
|
|
45
|
+
"--ignore-space-at-eol",
|
|
46
|
+
"--",
|
|
47
|
+
filePath,
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
filePath,
|
|
52
|
+
diff: diff || "",
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return fileDiffs.filter((entry) => entry.diff.trim().length > 0);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(error);
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const createUntrackedFileDiff = (filePath: string) => {
|
|
65
|
+
try {
|
|
66
|
+
const contents = readFileSync(filePath, "utf8");
|
|
67
|
+
const lines = contents.split(/\r?\n/);
|
|
68
|
+
const body = lines.map((line) => `+${line}`).join("\n");
|
|
69
|
+
|
|
70
|
+
return [
|
|
71
|
+
`diff --git a/${filePath} b/${filePath}`,
|
|
72
|
+
"new file mode 100644",
|
|
73
|
+
"index 0000000..0000000",
|
|
74
|
+
"--- /dev/null",
|
|
75
|
+
`+++ b/${filePath}`,
|
|
76
|
+
`@@ -0,0 +1,${lines.length} @@`,
|
|
77
|
+
body,
|
|
78
|
+
].join("\n");
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(error);
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const getSelectedFileDiffs = async (
|
|
86
|
+
filePaths: string[],
|
|
87
|
+
): Promise<FileDiff[]> => {
|
|
88
|
+
try {
|
|
89
|
+
const status = await git.status();
|
|
90
|
+
const fileDiffs = await Promise.all(
|
|
91
|
+
filePaths.map(async (filePath) => {
|
|
92
|
+
if (status.not_added.includes(filePath)) {
|
|
93
|
+
return {
|
|
94
|
+
filePath,
|
|
95
|
+
diff: createUntrackedFileDiff(filePath),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const diff = await git.diff([
|
|
100
|
+
"HEAD",
|
|
101
|
+
"--ignore-space-at-eol",
|
|
102
|
+
"--",
|
|
103
|
+
filePath,
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
filePath,
|
|
108
|
+
diff: diff || "",
|
|
109
|
+
};
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return fileDiffs.filter((entry) => entry.diff.trim().length > 0);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error(error);
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const getSelectedFilesDiff = async (filePaths: string[]) => {
|
|
121
|
+
const fileDiffs = await getSelectedFileDiffs(filePaths);
|
|
122
|
+
return fileDiffs.map(({ diff }) => diff).join("\n\n");
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const getHeadCommitInfo = async (): Promise<HeadCommitInfo> => {
|
|
126
|
+
try {
|
|
127
|
+
const full = (await git.revparse(["HEAD"])).trim();
|
|
128
|
+
const short = (await git.revparse(["--short", "HEAD"])).trim();
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
short,
|
|
132
|
+
full,
|
|
133
|
+
};
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error(error);
|
|
136
|
+
return {
|
|
137
|
+
short: "unknown",
|
|
138
|
+
full: "unknown",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
};
|