gut-cli 0.1.22 → 0.1.24
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/.gut/config.json +2 -1
- package/.gut/ja/branch.md +11 -0
- package/.gut/ja/changelog.md +18 -0
- package/.gut/ja/checkout.md +17 -0
- package/.gut/ja/commit.md +12 -0
- package/.gut/ja/explain-file.md +13 -0
- package/.gut/ja/explain.md +12 -0
- package/.gut/ja/find.md +13 -0
- package/.gut/ja/gitignore.md +16 -0
- package/.gut/ja/merge.md +12 -0
- package/.gut/ja/pr.md +14 -0
- package/.gut/ja/review.md +11 -0
- package/.gut/ja/stash.md +10 -0
- package/.gut/ja/summary.md +11 -0
- package/dist/index.js +1762 -1558
- package/dist/index.js.map +1 -1
- package/dist/lib/index.d.ts +7 -7
- package/dist/lib/index.js +272 -203
- package/dist/lib/index.js.map +1 -1
- package/package.json +22 -10
package/dist/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
type Language = 'en' | 'ja';
|
|
4
|
+
declare function getLanguage(): Language;
|
|
5
|
+
declare function setLanguage(lang: Language, local?: boolean): void;
|
|
6
|
+
declare function getLanguageInstruction(lang: Language): string;
|
|
7
|
+
|
|
3
8
|
type Provider = 'gemini' | 'openai' | 'anthropic' | 'ollama';
|
|
4
9
|
declare function saveApiKey(provider: Provider, apiKey: string): Promise<void>;
|
|
5
10
|
declare function getApiKey(provider: Provider): Promise<string | null>;
|
|
@@ -9,11 +14,6 @@ declare function listProviders(): Promise<{
|
|
|
9
14
|
hasKey: boolean;
|
|
10
15
|
}[]>;
|
|
11
16
|
|
|
12
|
-
type Language = 'en' | 'ja';
|
|
13
|
-
declare function getLanguage(): Language;
|
|
14
|
-
declare function setLanguage(lang: Language, local?: boolean): void;
|
|
15
|
-
declare function getLanguageInstruction(lang: Language): string;
|
|
16
|
-
|
|
17
17
|
interface AIOptions {
|
|
18
18
|
provider: Provider;
|
|
19
19
|
model?: string;
|
|
@@ -61,7 +61,6 @@ declare const CodeReviewSchema: z.ZodObject<{
|
|
|
61
61
|
}>, "many">;
|
|
62
62
|
positives: z.ZodArray<z.ZodString, "many">;
|
|
63
63
|
}, "strip", z.ZodTypeAny, {
|
|
64
|
-
summary: string;
|
|
65
64
|
issues: {
|
|
66
65
|
message: string;
|
|
67
66
|
severity: "critical" | "warning" | "suggestion";
|
|
@@ -69,9 +68,9 @@ declare const CodeReviewSchema: z.ZodObject<{
|
|
|
69
68
|
suggestion?: string | undefined;
|
|
70
69
|
line?: number | undefined;
|
|
71
70
|
}[];
|
|
71
|
+
summary: string;
|
|
72
72
|
positives: string[];
|
|
73
73
|
}, {
|
|
74
|
-
summary: string;
|
|
75
74
|
issues: {
|
|
76
75
|
message: string;
|
|
77
76
|
severity: "critical" | "warning" | "suggestion";
|
|
@@ -79,6 +78,7 @@ declare const CodeReviewSchema: z.ZodObject<{
|
|
|
79
78
|
suggestion?: string | undefined;
|
|
80
79
|
line?: number | undefined;
|
|
81
80
|
}[];
|
|
81
|
+
summary: string;
|
|
82
82
|
positives: string[];
|
|
83
83
|
}>;
|
|
84
84
|
type CodeReview = z.infer<typeof CodeReviewSchema>;
|
package/dist/lib/index.js
CHANGED
|
@@ -1,14 +1,121 @@
|
|
|
1
1
|
// src/lib/ai.ts
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
3
|
+
import { homedir as homedir2 } from "os";
|
|
4
|
+
import { dirname, join as join2 } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
3
7
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
4
8
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
5
|
-
import {
|
|
9
|
+
import { generateObject, generateText } from "ai";
|
|
6
10
|
import { createOllama } from "ollama-ai-provider";
|
|
7
11
|
import { z } from "zod";
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
|
|
13
|
+
// src/lib/config.ts
|
|
14
|
+
import { execSync } from "child_process";
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
10
16
|
import { homedir } from "os";
|
|
11
|
-
import {
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
var DEFAULT_CONFIG = {
|
|
19
|
+
lang: "en"
|
|
20
|
+
};
|
|
21
|
+
var DEFAULT_MODELS = {
|
|
22
|
+
gemini: "gemini-2.5-flash",
|
|
23
|
+
openai: "gpt-4.1-mini",
|
|
24
|
+
anthropic: "claude-sonnet-4-5",
|
|
25
|
+
ollama: "llama3.3"
|
|
26
|
+
};
|
|
27
|
+
function getGlobalConfigPath() {
|
|
28
|
+
const configDir = join(homedir(), ".config", "gut");
|
|
29
|
+
return join(configDir, "config.json");
|
|
30
|
+
}
|
|
31
|
+
function getRepoRoot() {
|
|
32
|
+
try {
|
|
33
|
+
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function getLocalConfigPath() {
|
|
39
|
+
const repoRoot = getRepoRoot();
|
|
40
|
+
if (!repoRoot) return null;
|
|
41
|
+
return join(repoRoot, ".gut", "config.json");
|
|
42
|
+
}
|
|
43
|
+
function ensureGlobalConfigDir() {
|
|
44
|
+
const configDir = join(homedir(), ".config", "gut");
|
|
45
|
+
if (!existsSync(configDir)) {
|
|
46
|
+
mkdirSync(configDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function ensureLocalConfigDir() {
|
|
50
|
+
const repoRoot = getRepoRoot();
|
|
51
|
+
if (!repoRoot) return;
|
|
52
|
+
const gutDir = join(repoRoot, ".gut");
|
|
53
|
+
if (!existsSync(gutDir)) {
|
|
54
|
+
mkdirSync(gutDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function readConfigFile(path) {
|
|
58
|
+
if (!existsSync(path)) return {};
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
61
|
+
} catch {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function getGlobalConfig() {
|
|
66
|
+
const globalPath = getGlobalConfigPath();
|
|
67
|
+
return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
|
|
68
|
+
}
|
|
69
|
+
function getLocalConfig() {
|
|
70
|
+
const localPath = getLocalConfigPath();
|
|
71
|
+
if (!localPath) return {};
|
|
72
|
+
return readConfigFile(localPath);
|
|
73
|
+
}
|
|
74
|
+
function getConfig() {
|
|
75
|
+
const globalConfig = getGlobalConfig();
|
|
76
|
+
const localConfig = getLocalConfig();
|
|
77
|
+
return { ...globalConfig, ...localConfig };
|
|
78
|
+
}
|
|
79
|
+
function setGlobalConfig(key, value) {
|
|
80
|
+
ensureGlobalConfigDir();
|
|
81
|
+
const config = getGlobalConfig();
|
|
82
|
+
config[key] = value;
|
|
83
|
+
writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2));
|
|
84
|
+
}
|
|
85
|
+
function setLocalConfig(key, value) {
|
|
86
|
+
const localPath = getLocalConfigPath();
|
|
87
|
+
if (!localPath) {
|
|
88
|
+
throw new Error("Not in a git repository");
|
|
89
|
+
}
|
|
90
|
+
ensureLocalConfigDir();
|
|
91
|
+
const config = getLocalConfig();
|
|
92
|
+
config[key] = value;
|
|
93
|
+
writeFileSync(localPath, JSON.stringify(config, null, 2));
|
|
94
|
+
}
|
|
95
|
+
function getLanguage() {
|
|
96
|
+
return getConfig().lang;
|
|
97
|
+
}
|
|
98
|
+
function setLanguage(lang, local = false) {
|
|
99
|
+
if (local) {
|
|
100
|
+
setLocalConfig("lang", lang);
|
|
101
|
+
} else {
|
|
102
|
+
setGlobalConfig("lang", lang);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function getLanguageInstruction(lang) {
|
|
106
|
+
switch (lang) {
|
|
107
|
+
case "ja":
|
|
108
|
+
return "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044).";
|
|
109
|
+
default:
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function getConfiguredModel() {
|
|
114
|
+
return getConfig().model;
|
|
115
|
+
}
|
|
116
|
+
function getDefaultModel(provider) {
|
|
117
|
+
return DEFAULT_MODELS[provider] || DEFAULT_MODELS.gemini;
|
|
118
|
+
}
|
|
12
119
|
|
|
13
120
|
// src/lib/credentials.ts
|
|
14
121
|
import { createRequire } from "module";
|
|
@@ -86,36 +193,36 @@ var __dirname = dirname(__filename);
|
|
|
86
193
|
function findGutRoot() {
|
|
87
194
|
let current = __dirname;
|
|
88
195
|
for (let i = 0; i < 5; i++) {
|
|
89
|
-
const gutPath =
|
|
90
|
-
if (
|
|
196
|
+
const gutPath = join2(current, ".gut");
|
|
197
|
+
if (existsSync2(gutPath)) {
|
|
91
198
|
return current;
|
|
92
199
|
}
|
|
93
200
|
current = dirname(current);
|
|
94
201
|
}
|
|
95
|
-
return
|
|
202
|
+
return join2(__dirname, "..");
|
|
96
203
|
}
|
|
97
204
|
var GUT_ROOT = findGutRoot();
|
|
98
205
|
function loadTemplate(name) {
|
|
99
|
-
const templatePath =
|
|
100
|
-
if (
|
|
101
|
-
return
|
|
206
|
+
const templatePath = join2(GUT_ROOT, ".gut", `${name}.md`);
|
|
207
|
+
if (existsSync2(templatePath)) {
|
|
208
|
+
return readFileSync2(templatePath, "utf-8");
|
|
102
209
|
}
|
|
103
210
|
throw new Error(`Template not found: ${templatePath}`);
|
|
104
211
|
}
|
|
105
212
|
function getGlobalTemplatesDir() {
|
|
106
|
-
return
|
|
213
|
+
return join2(homedir2(), ".config", "gut", "templates");
|
|
107
214
|
}
|
|
108
215
|
function findGlobalTemplate(templateName) {
|
|
109
|
-
const templatePath =
|
|
110
|
-
if (
|
|
111
|
-
return
|
|
216
|
+
const templatePath = join2(getGlobalTemplatesDir(), `${templateName}.md`);
|
|
217
|
+
if (existsSync2(templatePath)) {
|
|
218
|
+
return readFileSync2(templatePath, "utf-8");
|
|
112
219
|
}
|
|
113
220
|
return null;
|
|
114
221
|
}
|
|
115
222
|
function findTemplate(repoRoot, templateName) {
|
|
116
|
-
const projectTemplatePath =
|
|
117
|
-
if (
|
|
118
|
-
return
|
|
223
|
+
const projectTemplatePath = join2(repoRoot, ".gut", `${templateName}.md`);
|
|
224
|
+
if (existsSync2(projectTemplatePath)) {
|
|
225
|
+
return readFileSync2(projectTemplatePath, "utf-8");
|
|
119
226
|
}
|
|
120
227
|
const globalTemplate = findGlobalTemplate(templateName);
|
|
121
228
|
if (globalTemplate) {
|
|
@@ -141,16 +248,12 @@ ${value}
|
|
|
141
248
|
<output-format>
|
|
142
249
|
${outputFormat}
|
|
143
250
|
</output-format>` : "";
|
|
144
|
-
return contextXml
|
|
251
|
+
return `${contextXml}<instructions>
|
|
252
|
+
${template}${langInstruction}
|
|
253
|
+
</instructions>${outputSection}`;
|
|
145
254
|
}
|
|
146
|
-
var DEFAULT_MODELS = {
|
|
147
|
-
gemini: "gemini-2.0-flash",
|
|
148
|
-
openai: "gpt-4o-mini",
|
|
149
|
-
anthropic: "claude-sonnet-4-20250514",
|
|
150
|
-
ollama: "llama3.2"
|
|
151
|
-
};
|
|
152
255
|
async function getModel(options) {
|
|
153
|
-
const modelName = options.model ||
|
|
256
|
+
const modelName = options.model || getConfiguredModel() || getDefaultModel(options.provider);
|
|
154
257
|
async function resolveApiKey() {
|
|
155
258
|
if (options.apiKey) return options.apiKey;
|
|
156
259
|
return getApiKey(options.provider);
|
|
@@ -189,9 +292,15 @@ async function getModel(options) {
|
|
|
189
292
|
}
|
|
190
293
|
async function generateCommitMessage(diff, options, template) {
|
|
191
294
|
const model = await getModel(options);
|
|
192
|
-
const prompt = buildPrompt(
|
|
193
|
-
|
|
194
|
-
|
|
295
|
+
const prompt = buildPrompt(
|
|
296
|
+
template,
|
|
297
|
+
"commit",
|
|
298
|
+
{
|
|
299
|
+
diff: diff.slice(0, 8e3)
|
|
300
|
+
},
|
|
301
|
+
options.language,
|
|
302
|
+
"Respond with ONLY the commit message, nothing else."
|
|
303
|
+
);
|
|
195
304
|
const result = await generateText({
|
|
196
305
|
model,
|
|
197
306
|
prompt,
|
|
@@ -199,34 +308,29 @@ async function generateCommitMessage(diff, options, template) {
|
|
|
199
308
|
});
|
|
200
309
|
return result.text.trim();
|
|
201
310
|
}
|
|
311
|
+
var PRDescriptionSchema = z.object({
|
|
312
|
+
title: z.string().describe("Concise PR title (50-72 chars)"),
|
|
313
|
+
body: z.string().describe("PR description in markdown format")
|
|
314
|
+
});
|
|
202
315
|
async function generatePRDescription(context, options, template) {
|
|
203
316
|
const model = await getModel(options);
|
|
204
|
-
const prompt = buildPrompt(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const result = await generateText({
|
|
317
|
+
const prompt = buildPrompt(
|
|
318
|
+
template,
|
|
319
|
+
"pr",
|
|
320
|
+
{
|
|
321
|
+
baseBranch: context.baseBranch,
|
|
322
|
+
currentBranch: context.currentBranch,
|
|
323
|
+
commits: context.commits.map((c) => `- ${c}`).join("\n"),
|
|
324
|
+
diff: context.diff.slice(0, 6e3)
|
|
325
|
+
},
|
|
326
|
+
options.language
|
|
327
|
+
);
|
|
328
|
+
const result = await generateObject({
|
|
217
329
|
model,
|
|
218
|
-
|
|
219
|
-
|
|
330
|
+
schema: PRDescriptionSchema,
|
|
331
|
+
prompt
|
|
220
332
|
});
|
|
221
|
-
|
|
222
|
-
const cleaned = result.text.replace(/```json\n?|\n?```/g, "").trim();
|
|
223
|
-
return JSON.parse(cleaned);
|
|
224
|
-
} catch {
|
|
225
|
-
return {
|
|
226
|
-
title: context.currentBranch.replace(/[-_]/g, " "),
|
|
227
|
-
body: result.text
|
|
228
|
-
};
|
|
229
|
-
}
|
|
333
|
+
return result.object;
|
|
230
334
|
}
|
|
231
335
|
var CodeReviewSchema = z.object({
|
|
232
336
|
summary: z.string().describe("Brief overall assessment"),
|
|
@@ -243,9 +347,14 @@ var CodeReviewSchema = z.object({
|
|
|
243
347
|
});
|
|
244
348
|
async function generateCodeReview(diff, options, template) {
|
|
245
349
|
const model = await getModel(options);
|
|
246
|
-
const prompt = buildPrompt(
|
|
247
|
-
|
|
248
|
-
|
|
350
|
+
const prompt = buildPrompt(
|
|
351
|
+
template,
|
|
352
|
+
"review",
|
|
353
|
+
{
|
|
354
|
+
diff: diff.slice(0, 1e4)
|
|
355
|
+
},
|
|
356
|
+
options.language
|
|
357
|
+
);
|
|
249
358
|
const result = await generateObject({
|
|
250
359
|
model,
|
|
251
360
|
schema: CodeReviewSchema,
|
|
@@ -267,13 +376,18 @@ var ChangelogSchema = z.object({
|
|
|
267
376
|
async function generateChangelog(context, options, template) {
|
|
268
377
|
const model = await getModel(options);
|
|
269
378
|
const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message} (${c.author})`).join("\n");
|
|
270
|
-
const prompt = buildPrompt(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
379
|
+
const prompt = buildPrompt(
|
|
380
|
+
template,
|
|
381
|
+
"changelog",
|
|
382
|
+
{
|
|
383
|
+
fromRef: context.fromRef,
|
|
384
|
+
toRef: context.toRef,
|
|
385
|
+
commits: commitList,
|
|
386
|
+
diff: context.diff.slice(0, 8e3),
|
|
387
|
+
todayDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
388
|
+
},
|
|
389
|
+
options.language
|
|
390
|
+
);
|
|
277
391
|
const result = await generateObject({
|
|
278
392
|
model,
|
|
279
393
|
schema: ChangelogSchema,
|
|
@@ -301,10 +415,15 @@ var ExplanationSchema = z.object({
|
|
|
301
415
|
async function generateExplanation(context, options, template) {
|
|
302
416
|
const model = await getModel(options);
|
|
303
417
|
if (context.type === "file-content") {
|
|
304
|
-
const prompt2 = buildPrompt(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
418
|
+
const prompt2 = buildPrompt(
|
|
419
|
+
template,
|
|
420
|
+
"explain-file",
|
|
421
|
+
{
|
|
422
|
+
filePath: context.metadata.filePath || "",
|
|
423
|
+
content: context.content?.slice(0, 15e3) || ""
|
|
424
|
+
},
|
|
425
|
+
options.language
|
|
426
|
+
);
|
|
308
427
|
const result2 = await generateObject({
|
|
309
428
|
model,
|
|
310
429
|
schema: ExplanationSchema,
|
|
@@ -338,11 +457,16 @@ Author: ${context.metadata.author}
|
|
|
338
457
|
Date: ${context.metadata.date}`;
|
|
339
458
|
targetType = "commit";
|
|
340
459
|
}
|
|
341
|
-
const prompt = buildPrompt(
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
460
|
+
const prompt = buildPrompt(
|
|
461
|
+
template,
|
|
462
|
+
"explain",
|
|
463
|
+
{
|
|
464
|
+
targetType,
|
|
465
|
+
contextInfo,
|
|
466
|
+
diff: context.diff?.slice(0, 12e3) || ""
|
|
467
|
+
},
|
|
468
|
+
options.language
|
|
469
|
+
);
|
|
346
470
|
const result = await generateObject({
|
|
347
471
|
model,
|
|
348
472
|
schema: ExplanationSchema,
|
|
@@ -361,12 +485,19 @@ var CommitSearchSchema = z.object({
|
|
|
361
485
|
});
|
|
362
486
|
async function searchCommits(query, commits, options, maxResults = 5, template) {
|
|
363
487
|
const model = await getModel(options);
|
|
364
|
-
const commitList = commits.map(
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
488
|
+
const commitList = commits.map(
|
|
489
|
+
(c) => `${c.hash.slice(0, 7)} | ${c.author} | ${c.date.split("T")[0]} | ${c.message.split("\n")[0]}`
|
|
490
|
+
).join("\n");
|
|
491
|
+
const prompt = buildPrompt(
|
|
492
|
+
template,
|
|
493
|
+
"find",
|
|
494
|
+
{
|
|
495
|
+
query,
|
|
496
|
+
commits: commitList,
|
|
497
|
+
maxResults: String(maxResults)
|
|
498
|
+
},
|
|
499
|
+
options.language
|
|
500
|
+
);
|
|
370
501
|
const result = await generateObject({
|
|
371
502
|
model,
|
|
372
503
|
schema: CommitSearchSchema,
|
|
@@ -395,11 +526,17 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
|
|
|
395
526
|
}
|
|
396
527
|
async function generateBranchName(description, options, context, template) {
|
|
397
528
|
const model = await getModel(options);
|
|
398
|
-
const prompt = buildPrompt(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
529
|
+
const prompt = buildPrompt(
|
|
530
|
+
template,
|
|
531
|
+
"branch",
|
|
532
|
+
{
|
|
533
|
+
description,
|
|
534
|
+
type: context?.type,
|
|
535
|
+
issue: context?.issue
|
|
536
|
+
},
|
|
537
|
+
options.language,
|
|
538
|
+
"Respond with ONLY the branch name, nothing else."
|
|
539
|
+
);
|
|
403
540
|
const result = await generateText({
|
|
404
541
|
model,
|
|
405
542
|
prompt,
|
|
@@ -409,9 +546,15 @@ async function generateBranchName(description, options, context, template) {
|
|
|
409
546
|
}
|
|
410
547
|
async function generateBranchNameFromDiff(diff, options, template) {
|
|
411
548
|
const model = await getModel(options);
|
|
412
|
-
const prompt = buildPrompt(
|
|
413
|
-
|
|
414
|
-
|
|
549
|
+
const prompt = buildPrompt(
|
|
550
|
+
template,
|
|
551
|
+
"checkout",
|
|
552
|
+
{
|
|
553
|
+
diff: diff.slice(0, 8e3)
|
|
554
|
+
},
|
|
555
|
+
options.language,
|
|
556
|
+
"Respond with ONLY the branch name, nothing else."
|
|
557
|
+
);
|
|
415
558
|
const result = await generateText({
|
|
416
559
|
model,
|
|
417
560
|
prompt,
|
|
@@ -421,9 +564,15 @@ async function generateBranchNameFromDiff(diff, options, template) {
|
|
|
421
564
|
}
|
|
422
565
|
async function generateStashName(diff, options, template) {
|
|
423
566
|
const model = await getModel(options);
|
|
424
|
-
const prompt = buildPrompt(
|
|
425
|
-
|
|
426
|
-
|
|
567
|
+
const prompt = buildPrompt(
|
|
568
|
+
template,
|
|
569
|
+
"stash",
|
|
570
|
+
{
|
|
571
|
+
diff: diff.slice(0, 4e3)
|
|
572
|
+
},
|
|
573
|
+
options.language,
|
|
574
|
+
"Respond with ONLY the stash name, nothing else."
|
|
575
|
+
);
|
|
427
576
|
const result = await generateText({
|
|
428
577
|
model,
|
|
429
578
|
prompt,
|
|
@@ -453,13 +602,18 @@ async function generateWorkSummary(context, options, format = "custom", template
|
|
|
453
602
|
const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message.split("\n")[0]} (${c.date.split("T")[0]})`).join("\n");
|
|
454
603
|
const formatHint = format === "daily" ? "This is a daily report. Focus on today's accomplishments." : format === "weekly" ? "This is a weekly report. Summarize the week's work at a higher level." : `This is a summary from ${context.since}${context.until ? ` to ${context.until}` : ""}.`;
|
|
455
604
|
const period = `${context.since}${context.until ? ` to ${context.until}` : " to now"}`;
|
|
456
|
-
const prompt = buildPrompt(
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
605
|
+
const prompt = buildPrompt(
|
|
606
|
+
template,
|
|
607
|
+
"summary",
|
|
608
|
+
{
|
|
609
|
+
author: context.author,
|
|
610
|
+
period,
|
|
611
|
+
format: formatHint,
|
|
612
|
+
commits: commitList,
|
|
613
|
+
diff: context.diff?.slice(0, 6e3)
|
|
614
|
+
},
|
|
615
|
+
options.language
|
|
616
|
+
);
|
|
463
617
|
const result = await generateObject({
|
|
464
618
|
model,
|
|
465
619
|
schema: WorkSummarySchema,
|
|
@@ -475,12 +629,17 @@ async function generateWorkSummary(context, options, format = "custom", template
|
|
|
475
629
|
}
|
|
476
630
|
async function resolveConflict(conflictedContent, context, options, template) {
|
|
477
631
|
const model = await getModel(options);
|
|
478
|
-
const prompt = buildPrompt(
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
632
|
+
const prompt = buildPrompt(
|
|
633
|
+
template,
|
|
634
|
+
"merge",
|
|
635
|
+
{
|
|
636
|
+
filename: context.filename,
|
|
637
|
+
oursRef: context.oursRef,
|
|
638
|
+
theirsRef: context.theirsRef,
|
|
639
|
+
content: conflictedContent
|
|
640
|
+
},
|
|
641
|
+
options.language
|
|
642
|
+
);
|
|
484
643
|
const result = await generateObject({
|
|
485
644
|
model,
|
|
486
645
|
schema: ConflictResolutionSchema,
|
|
@@ -490,11 +649,17 @@ async function resolveConflict(conflictedContent, context, options, template) {
|
|
|
490
649
|
}
|
|
491
650
|
async function generateGitignore(context, options, template) {
|
|
492
651
|
const model = await getModel(options);
|
|
493
|
-
const prompt = buildPrompt(
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
652
|
+
const prompt = buildPrompt(
|
|
653
|
+
template,
|
|
654
|
+
"gitignore",
|
|
655
|
+
{
|
|
656
|
+
files: context.files,
|
|
657
|
+
configFiles: context.configFiles,
|
|
658
|
+
existingGitignore: context.existingGitignore
|
|
659
|
+
},
|
|
660
|
+
options.language,
|
|
661
|
+
"Respond with ONLY the .gitignore content, nothing else. No explanations or markdown code blocks."
|
|
662
|
+
);
|
|
498
663
|
const result = await generateText({
|
|
499
664
|
model,
|
|
500
665
|
prompt,
|
|
@@ -502,102 +667,6 @@ async function generateGitignore(context, options, template) {
|
|
|
502
667
|
});
|
|
503
668
|
return result.text.trim();
|
|
504
669
|
}
|
|
505
|
-
|
|
506
|
-
// src/lib/config.ts
|
|
507
|
-
import { homedir as homedir2 } from "os";
|
|
508
|
-
import { join as join2 } from "path";
|
|
509
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
510
|
-
import { execSync } from "child_process";
|
|
511
|
-
var DEFAULT_CONFIG = {
|
|
512
|
-
lang: "en"
|
|
513
|
-
};
|
|
514
|
-
function getGlobalConfigPath() {
|
|
515
|
-
const configDir = join2(homedir2(), ".config", "gut");
|
|
516
|
-
return join2(configDir, "config.json");
|
|
517
|
-
}
|
|
518
|
-
function getRepoRoot() {
|
|
519
|
-
try {
|
|
520
|
-
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
521
|
-
} catch {
|
|
522
|
-
return null;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
function getLocalConfigPath() {
|
|
526
|
-
const repoRoot = getRepoRoot();
|
|
527
|
-
if (!repoRoot) return null;
|
|
528
|
-
return join2(repoRoot, ".gut", "config.json");
|
|
529
|
-
}
|
|
530
|
-
function ensureGlobalConfigDir() {
|
|
531
|
-
const configDir = join2(homedir2(), ".config", "gut");
|
|
532
|
-
if (!existsSync2(configDir)) {
|
|
533
|
-
mkdirSync(configDir, { recursive: true });
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
function ensureLocalConfigDir() {
|
|
537
|
-
const repoRoot = getRepoRoot();
|
|
538
|
-
if (!repoRoot) return;
|
|
539
|
-
const gutDir = join2(repoRoot, ".gut");
|
|
540
|
-
if (!existsSync2(gutDir)) {
|
|
541
|
-
mkdirSync(gutDir, { recursive: true });
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
function readConfigFile(path) {
|
|
545
|
-
if (!existsSync2(path)) return {};
|
|
546
|
-
try {
|
|
547
|
-
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
548
|
-
} catch {
|
|
549
|
-
return {};
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
function getGlobalConfig() {
|
|
553
|
-
const globalPath = getGlobalConfigPath();
|
|
554
|
-
return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
|
|
555
|
-
}
|
|
556
|
-
function getLocalConfig() {
|
|
557
|
-
const localPath = getLocalConfigPath();
|
|
558
|
-
if (!localPath) return {};
|
|
559
|
-
return readConfigFile(localPath);
|
|
560
|
-
}
|
|
561
|
-
function getConfig() {
|
|
562
|
-
const globalConfig = getGlobalConfig();
|
|
563
|
-
const localConfig = getLocalConfig();
|
|
564
|
-
return { ...globalConfig, ...localConfig };
|
|
565
|
-
}
|
|
566
|
-
function setGlobalConfig(key, value) {
|
|
567
|
-
ensureGlobalConfigDir();
|
|
568
|
-
const config = getGlobalConfig();
|
|
569
|
-
config[key] = value;
|
|
570
|
-
writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2));
|
|
571
|
-
}
|
|
572
|
-
function setLocalConfig(key, value) {
|
|
573
|
-
const localPath = getLocalConfigPath();
|
|
574
|
-
if (!localPath) {
|
|
575
|
-
throw new Error("Not in a git repository");
|
|
576
|
-
}
|
|
577
|
-
ensureLocalConfigDir();
|
|
578
|
-
const config = getLocalConfig();
|
|
579
|
-
config[key] = value;
|
|
580
|
-
writeFileSync(localPath, JSON.stringify(config, null, 2));
|
|
581
|
-
}
|
|
582
|
-
function getLanguage() {
|
|
583
|
-
return getConfig().lang;
|
|
584
|
-
}
|
|
585
|
-
function setLanguage(lang, local = false) {
|
|
586
|
-
if (local) {
|
|
587
|
-
setLocalConfig("lang", lang);
|
|
588
|
-
} else {
|
|
589
|
-
setGlobalConfig("lang", lang);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
function getLanguageInstruction(lang) {
|
|
593
|
-
switch (lang) {
|
|
594
|
-
case "ja":
|
|
595
|
-
return "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044).";
|
|
596
|
-
case "en":
|
|
597
|
-
default:
|
|
598
|
-
return "";
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
670
|
export {
|
|
602
671
|
deleteApiKey,
|
|
603
672
|
findTemplate,
|