@335g/pi-git 0.0.1

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 (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +121 -0
  3. package/README.md +121 -0
  4. package/dist/commands/agg-commit.d.ts +9 -0
  5. package/dist/commands/agg-commit.d.ts.map +1 -0
  6. package/dist/commands/agg-commit.js +144 -0
  7. package/dist/commands/agg-commit.js.map +1 -0
  8. package/dist/commands/auto-agg-commit.d.ts +9 -0
  9. package/dist/commands/auto-agg-commit.d.ts.map +1 -0
  10. package/dist/commands/auto-agg-commit.js +113 -0
  11. package/dist/commands/auto-agg-commit.js.map +1 -0
  12. package/dist/commands/branch.d.ts +8 -0
  13. package/dist/commands/branch.d.ts.map +1 -0
  14. package/dist/commands/branch.js +197 -0
  15. package/dist/commands/branch.js.map +1 -0
  16. package/dist/commands/config.d.ts +10 -0
  17. package/dist/commands/config.d.ts.map +1 -0
  18. package/dist/commands/config.js +234 -0
  19. package/dist/commands/config.js.map +1 -0
  20. package/dist/commands/git-diff.d.ts +10 -0
  21. package/dist/commands/git-diff.d.ts.map +1 -0
  22. package/dist/commands/git-diff.js +228 -0
  23. package/dist/commands/git-diff.js.map +1 -0
  24. package/dist/commands/git-log.d.ts +8 -0
  25. package/dist/commands/git-log.d.ts.map +1 -0
  26. package/dist/commands/git-log.js +100 -0
  27. package/dist/commands/git-log.js.map +1 -0
  28. package/dist/core/auto-commit-message.d.ts +15 -0
  29. package/dist/core/auto-commit-message.d.ts.map +1 -0
  30. package/dist/core/auto-commit-message.js +121 -0
  31. package/dist/core/auto-commit-message.js.map +1 -0
  32. package/dist/core/auto-commit.d.ts +12 -0
  33. package/dist/core/auto-commit.d.ts.map +1 -0
  34. package/dist/core/auto-commit.js +67 -0
  35. package/dist/core/auto-commit.js.map +1 -0
  36. package/dist/core/commit-message.d.ts +25 -0
  37. package/dist/core/commit-message.d.ts.map +1 -0
  38. package/dist/core/commit-message.js +127 -0
  39. package/dist/core/commit-message.js.map +1 -0
  40. package/dist/core/diff-analyzer.d.ts +23 -0
  41. package/dist/core/diff-analyzer.d.ts.map +1 -0
  42. package/dist/core/diff-analyzer.js +245 -0
  43. package/dist/core/diff-analyzer.js.map +1 -0
  44. package/dist/core/git.d.ts +50 -0
  45. package/dist/core/git.d.ts.map +1 -0
  46. package/dist/core/git.js +152 -0
  47. package/dist/core/git.js.map +1 -0
  48. package/dist/core/resolve-model.d.ts +18 -0
  49. package/dist/core/resolve-model.d.ts.map +1 -0
  50. package/dist/core/resolve-model.js +33 -0
  51. package/dist/core/resolve-model.js.map +1 -0
  52. package/dist/index.d.ts +8 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +61 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/tui/hunk-review.d.ts +49 -0
  57. package/dist/tui/hunk-review.d.ts.map +1 -0
  58. package/dist/tui/hunk-review.js +300 -0
  59. package/dist/tui/hunk-review.js.map +1 -0
  60. package/dist/types.d.ts +23 -0
  61. package/dist/types.d.ts.map +1 -0
  62. package/dist/types.js +5 -0
  63. package/dist/types.js.map +1 -0
  64. package/dist/utils/footer-manager.d.ts +68 -0
  65. package/dist/utils/footer-manager.d.ts.map +1 -0
  66. package/dist/utils/footer-manager.js +138 -0
  67. package/dist/utils/footer-manager.js.map +1 -0
  68. package/dist/utils/lang.d.ts +12 -0
  69. package/dist/utils/lang.d.ts.map +1 -0
  70. package/dist/utils/lang.js +16 -0
  71. package/dist/utils/lang.js.map +1 -0
  72. package/dist/utils/settings.d.ts +47 -0
  73. package/dist/utils/settings.d.ts.map +1 -0
  74. package/dist/utils/settings.js +151 -0
  75. package/dist/utils/settings.js.map +1 -0
  76. package/package.json +60 -0
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Conventional Commits message validation and sanitization
3
+ *
4
+ * Since diff-analyzer.ts already generates Conventional Commits messages via AI,
5
+ * this module focuses on post-processing: validation, cleanup, and fallback.
6
+ */
7
+ /** Valid Conventional Commits types */
8
+ const VALID_TYPES = [
9
+ "feat",
10
+ "fix",
11
+ "docs",
12
+ "style",
13
+ "refactor",
14
+ "test",
15
+ "chore",
16
+ "perf",
17
+ "ci",
18
+ "build",
19
+ "revert",
20
+ ];
21
+ /** Pattern: type(scope)!: subject or type!: subject */
22
+ const CONVENTIONAL_COMMIT_PATTERN = /^(\w+)(\([^)]+\))?(!)?:\s*(.+)$/;
23
+ const MAX_SUBJECT_LENGTH = 50;
24
+ /**
25
+ * Check if a message follows Conventional Commits format.
26
+ */
27
+ export function isConventionalCommit(message) {
28
+ const match = CONVENTIONAL_COMMIT_PATTERN.exec(message);
29
+ if (!match)
30
+ return false;
31
+ const type = match[1];
32
+ return VALID_TYPES.includes(type);
33
+ }
34
+ /**
35
+ * Build a conventional commit message with a valid type.
36
+ */
37
+ function buildMessage(type, scope, subject, breaking = false) {
38
+ const scopePart = scope ? `(${scope})` : "";
39
+ const breakingPart = breaking ? "!" : "";
40
+ return `${type}${scopePart}${breakingPart}: ${subject}`;
41
+ }
42
+ /**
43
+ * Infer commit type from file paths and content.
44
+ */
45
+ function inferTypeFromFiles(files) {
46
+ const allPaths = files.join(" ").toLowerCase();
47
+ if (/test|spec|\.test\.|\.spec\./.test(allPaths))
48
+ return "test";
49
+ if (/readme|\.md$|docs?\//.test(allPaths))
50
+ return "docs";
51
+ if (/\.css$|\.scss$|\.less$|\.svg$|\.png$|\.jpg$/.test(allPaths))
52
+ return "style";
53
+ if (/package\.json|package-lock|yarn\.lock|pnpm-lock|cargo\.lock|\.lock$|makefile|dockerfile|\.yml$|\.yaml$|\.toml$/.test(allPaths)) {
54
+ return "chore";
55
+ }
56
+ if (/\.github|\.ci|\.ci\//.test(allPaths))
57
+ return "ci";
58
+ if (/\.config\.|config\/|\.env|\.rc/.test(allPaths))
59
+ return "chore";
60
+ return "chore";
61
+ }
62
+ /**
63
+ * Sanitize and validate a commit message.
64
+ * Returns a clean, valid Conventional Commits message.
65
+ */
66
+ export function sanitizeCommitMessage(message, files) {
67
+ let sanitized = message.trim();
68
+ // Remove trailing period from subject
69
+ sanitized = sanitized.replace(/\.$/, "");
70
+ // Check if already valid
71
+ if (isConventionalCommit(sanitized)) {
72
+ const match = CONVENTIONAL_COMMIT_PATTERN.exec(sanitized);
73
+ if (!match) {
74
+ // Should not happen since isConventionalCommit passed
75
+ return sanitized;
76
+ }
77
+ let type = match[1];
78
+ const scope = match[2]?.slice(1, -1); // remove parentheses
79
+ let subject = match[4];
80
+ // Normalize type
81
+ if (!VALID_TYPES.includes(type)) {
82
+ type = "chore";
83
+ }
84
+ // Truncate subject if too long
85
+ if (subject.length > MAX_SUBJECT_LENGTH) {
86
+ subject = `${subject.slice(0, MAX_SUBJECT_LENGTH - 3)}...`;
87
+ }
88
+ return buildMessage(type, scope, subject, match[3] === "!");
89
+ }
90
+ // Not a conventional commit - try to fix or fallback
91
+ // If it has a colon, maybe it's an unknown format
92
+ const colonIndex = sanitized.indexOf(":");
93
+ if (colonIndex > 0) {
94
+ const possibleSubject = sanitized.slice(colonIndex + 1).trim();
95
+ if (possibleSubject.length > 0) {
96
+ const type = files ? inferTypeFromFiles(files) : "chore";
97
+ return buildMessage(type, undefined, possibleSubject);
98
+ }
99
+ }
100
+ // Fallback: treat entire message as subject
101
+ const fallbackType = files ? inferTypeFromFiles(files) : "chore";
102
+ const subject = sanitized.length > MAX_SUBJECT_LENGTH
103
+ ? `${sanitized.slice(0, MAX_SUBJECT_LENGTH - 3)}...`
104
+ : sanitized;
105
+ return buildMessage(fallbackType, undefined, subject || "update files");
106
+ }
107
+ /**
108
+ * Generate a fallback message when AI generation fails entirely.
109
+ */
110
+ export function generateFallbackMessage(files) {
111
+ const type = inferTypeFromFiles(files);
112
+ if (files.length === 1) {
113
+ const fileName = files[0].split("/").pop() || files[0];
114
+ return `${type}: update ${fileName}`;
115
+ }
116
+ return `${type}: update ${files.length} files`;
117
+ }
118
+ /**
119
+ * Sanitize a hunk's message in place.
120
+ */
121
+ export function sanitizeHunk(hunk) {
122
+ return {
123
+ ...hunk,
124
+ message: sanitizeCommitMessage(hunk.message, hunk.files),
125
+ };
126
+ }
127
+ //# sourceMappingURL=commit-message.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commit-message.js","sourceRoot":"","sources":["../../src/core/commit-message.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Diff analysis and hunk splitting logic
3
+ *
4
+ * Uses the configured or session AI model to analyze git diff and split changes into
5
+ * logical hunks with Conventional Commits messages.
6
+ */
7
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
8
+ import type { FileStats, Hunk } from "../types.js";
9
+ export declare function analyzeDiff(_pi: ExtensionAPI, ctx: ExtensionContext, diff: string): Promise<Hunk[]>;
10
+ /**
11
+ * Post-process AI-generated hunks: sanitize commit messages, deduplicate files
12
+ * across hunks (each file belongs only to its first hunk), and remove empty hunks.
13
+ */
14
+ export declare function processHunks(hunks: Hunk[]): Hunk[];
15
+ /**
16
+ * Split a full diff into per-file diff line arrays, keyed by file path.
17
+ */
18
+ export declare function splitDiffByFile(fullDiff: string): Map<string, string[]>;
19
+ /**
20
+ * Parse addition/deletion counts for each file from a full diff.
21
+ */
22
+ export declare function parseDiffStats(fullDiff: string): Map<string, FileStats>;
23
+ //# sourceMappingURL=diff-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-analyzer.d.ts","sourceRoot":"","sources":["../../src/core/diff-analyzer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EACjB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AA8HnD,wBAAsB,WAAW,CAC/B,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,EAAE,CAAC,CA4CjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAalD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CA0BvE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CA8BvE"}
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Diff analysis and hunk splitting logic
3
+ *
4
+ * Uses the configured or session AI model to analyze git diff and split changes into
5
+ * logical hunks with Conventional Commits messages.
6
+ */
7
+ import { completeSimple } from "@earendil-works/pi-ai";
8
+ import { isJapanese } from "../utils/lang.js";
9
+ import { getLanguage } from "../utils/settings.js";
10
+ import { sanitizeHunk } from "./commit-message.js";
11
+ import { resolveModel } from "./resolve-model.js";
12
+ function getSystemPrompt(lang) {
13
+ if (isJapanese(lang)) {
14
+ return `あなたはgit diff解析ツールです。git diffを分析し、変更を論理的なhunkに分割してください。
15
+
16
+ ルール:
17
+ - 各hunkは単一の論理的な変更を表す(例:「機能Xを追加」「バグYを修正」「Zをリファクタリング」)
18
+ - 同じ論理的な変更に属するファイル変更はグループ化する
19
+ - 1つのファイルに複数の独立した変更が含まれる場合は、別々のhunkに分割する
20
+ - 新規ファイルの場合は、内容から論理的な目的を推定する
21
+
22
+ 各hunkに対して以下を提供してください:
23
+ - files: このhunkに含まれるファイルパスの配列
24
+ - message: Conventional Commits形式のメッセージ。typeは feat, fix, docs, style, refactor, test, chore から選択
25
+ - サブジェクトは50文字以内に収める
26
+ - 命令形を使用する(例:「追加」でなく「追加する」→英語のimperative moodに相当する日本語表現)
27
+ - スコープはリポジトリの文脈から明確に推定できる場合のみ含める
28
+ - 日本語でメッセージを記述する
29
+
30
+ 以下の形式のJSON配列のみを返してください。マークダウンのコードフェンスや追加のテキストは不要です:
31
+ [
32
+ {
33
+ "files": ["path/to/file1.ts", "path/to/file2.ts"],
34
+ "message": "feat: ユーザー認証機能を追加"
35
+ }
36
+ ]`;
37
+ }
38
+ return `You are a git diff analyzer. Your task is to analyze a git diff and split the changes into logical hunks.
39
+
40
+ Rules:
41
+ - Each hunk should represent a single logical change (e.g., "add feature X", "fix bug Y", "refactor Z")
42
+ - Group related file changes together if they belong to the same logical change
43
+ - If a single file contains multiple independent changes, split them into separate hunks
44
+ - For new files, infer the logical purpose from the content
45
+
46
+ For each hunk, provide:
47
+ - files: array of file paths included in this hunk
48
+ - message: a Conventional Commits style message. Choose type from: feat, fix, docs, style, refactor, test, chore
49
+ - Keep the subject under 50 characters
50
+ - Use imperative mood (e.g., "add" not "added")
51
+ - Include scope only if clearly inferable from the repository context
52
+
53
+ Return ONLY a JSON array in this exact format, with no markdown code fences or additional text:
54
+ [
55
+ {
56
+ "files": ["path/to/file1.ts", "path/to/file2.ts"],
57
+ "message": "feat(scope): add user authentication"
58
+ }
59
+ ]`;
60
+ }
61
+ function buildPrompt(diff, lang) {
62
+ if (isJapanese(lang)) {
63
+ return `以下のgit diffを分析し、論理的なhunkに分割してください:
64
+
65
+ \`\`\`diff
66
+ ${diff}
67
+ \`\`\`
68
+
69
+ 指定された形式のJSON配列のみを返してください。`;
70
+ }
71
+ return `Here is the git diff to analyze. Split it into logical hunks:
72
+
73
+ \`\`\`diff
74
+ ${diff}
75
+ \`\`\`
76
+
77
+ Respond with ONLY a JSON array of hunks as specified.`;
78
+ }
79
+ function parseHunks(text) {
80
+ // Extract JSON from the response (handle code fences)
81
+ let jsonText = text.trim();
82
+ const codeFenceMatch = jsonText.match(/```(?:json)?\s*([\s\S]*?)```/);
83
+ if (codeFenceMatch) {
84
+ jsonText = codeFenceMatch[1].trim();
85
+ }
86
+ try {
87
+ const parsed = JSON.parse(jsonText);
88
+ if (!Array.isArray(parsed)) {
89
+ throw new Error("Response is not an array");
90
+ }
91
+ return parsed.map((item) => {
92
+ if (typeof item !== "object" || item === null) {
93
+ throw new Error("Invalid hunk item");
94
+ }
95
+ const hunk = item;
96
+ const files = Array.isArray(hunk.files)
97
+ ? hunk.files.filter((f) => typeof f === "string")
98
+ : [];
99
+ const message = typeof hunk.message === "string" ? hunk.message : "chore: update files";
100
+ return { files, message };
101
+ });
102
+ }
103
+ catch {
104
+ return [];
105
+ }
106
+ }
107
+ function fallbackFileBasedHunks(diff) {
108
+ // Parse diff to extract file paths
109
+ const hunks = [];
110
+ const fileRegex = /^diff --git a\/(.+) b\/(.+)$/gm;
111
+ let match;
112
+ while (true) {
113
+ match = fileRegex.exec(diff);
114
+ if (match === null)
115
+ break;
116
+ const filePath = match[2]; // Use 'b/' path (new version)
117
+ hunks.push({
118
+ files: [filePath],
119
+ message: `chore: update ${filePath}`,
120
+ });
121
+ }
122
+ return hunks;
123
+ }
124
+ export async function analyzeDiff(_pi, ctx, diff) {
125
+ const model = resolveModel(ctx);
126
+ if (!model) {
127
+ return fallbackFileBasedHunks(diff);
128
+ }
129
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
130
+ if (!auth.ok) {
131
+ return fallbackFileBasedHunks(diff);
132
+ }
133
+ try {
134
+ const lang = getLanguage();
135
+ const context = {
136
+ systemPrompt: getSystemPrompt(lang),
137
+ messages: [
138
+ {
139
+ role: "user",
140
+ content: buildPrompt(diff, lang),
141
+ timestamp: Date.now(),
142
+ },
143
+ ],
144
+ };
145
+ const result = await completeSimple(model, context, {
146
+ apiKey: auth.apiKey,
147
+ headers: auth.headers,
148
+ signal: ctx.signal,
149
+ reasoning: "minimal",
150
+ });
151
+ const text = result.content
152
+ .filter((c) => c.type === "text")
153
+ .map((c) => c.text)
154
+ .join("");
155
+ const hunks = parseHunks(text);
156
+ if (hunks.length === 0) {
157
+ return fallbackFileBasedHunks(diff);
158
+ }
159
+ return hunks;
160
+ }
161
+ catch {
162
+ return fallbackFileBasedHunks(diff);
163
+ }
164
+ }
165
+ /**
166
+ * Post-process AI-generated hunks: sanitize commit messages, deduplicate files
167
+ * across hunks (each file belongs only to its first hunk), and remove empty hunks.
168
+ */
169
+ export function processHunks(hunks) {
170
+ const sanitized = hunks.map(sanitizeHunk);
171
+ const seenFiles = new Set();
172
+ return sanitized
173
+ .map((hunk) => ({
174
+ ...hunk,
175
+ files: hunk.files.filter((f) => {
176
+ if (seenFiles.has(f))
177
+ return false;
178
+ seenFiles.add(f);
179
+ return true;
180
+ }),
181
+ }))
182
+ .filter((hunk) => hunk.files.length > 0);
183
+ }
184
+ /**
185
+ * Split a full diff into per-file diff line arrays, keyed by file path.
186
+ */
187
+ export function splitDiffByFile(fullDiff) {
188
+ const result = new Map();
189
+ const lines = fullDiff.split("\n");
190
+ let currentFile = null;
191
+ let currentLines = [];
192
+ for (const line of lines) {
193
+ if (line.startsWith("diff --git")) {
194
+ if (currentFile) {
195
+ result.set(currentFile, currentLines);
196
+ }
197
+ const match = line.match(/diff --git a\/(.+?) b\/(.+?)$/);
198
+ if (match) {
199
+ currentFile = match[2];
200
+ currentLines = [line];
201
+ }
202
+ }
203
+ else if (currentFile) {
204
+ currentLines.push(line);
205
+ }
206
+ }
207
+ if (currentFile) {
208
+ result.set(currentFile, currentLines);
209
+ }
210
+ return result;
211
+ }
212
+ /**
213
+ * Parse addition/deletion counts for each file from a full diff.
214
+ */
215
+ export function parseDiffStats(fullDiff) {
216
+ const result = new Map();
217
+ const lines = fullDiff.split("\n");
218
+ let currentFile = null;
219
+ let additions = 0;
220
+ let deletions = 0;
221
+ for (const line of lines) {
222
+ if (line.startsWith("diff --git")) {
223
+ if (currentFile) {
224
+ result.set(currentFile, { path: currentFile, additions, deletions });
225
+ }
226
+ const match = line.match(/diff --git a\/(.+?) b\/(.+?)$/);
227
+ if (match) {
228
+ currentFile = match[2];
229
+ additions = 0;
230
+ deletions = 0;
231
+ }
232
+ }
233
+ else if (line.startsWith("+") && !line.startsWith("+++")) {
234
+ additions++;
235
+ }
236
+ else if (line.startsWith("-") && !line.startsWith("---")) {
237
+ deletions++;
238
+ }
239
+ }
240
+ if (currentFile) {
241
+ result.set(currentFile, { path: currentFile, additions, deletions });
242
+ }
243
+ return result;
244
+ }
245
+ //# sourceMappingURL=diff-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-analyzer.js","sourceRoot":"","sources":["../../src/core/diff-analyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Git command wrappers using pi.exec
3
+ */
4
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
+ export declare function isGitRepository(pi: ExtensionAPI, cwd?: string): Promise<boolean>;
6
+ export declare function getStatus(pi: ExtensionAPI, cwd?: string): Promise<string>;
7
+ export declare function hasChanges(pi: ExtensionAPI, cwd?: string): Promise<boolean>;
8
+ export declare function stageFiles(pi: ExtensionAPI, files: string[], cwd?: string): Promise<void>;
9
+ export declare function resetStaging(pi: ExtensionAPI, cwd?: string): Promise<void>;
10
+ export declare function getCurrentBranch(pi: ExtensionAPI, cwd?: string): Promise<string>;
11
+ export declare function getBranches(pi: ExtensionAPI, cwd?: string): Promise<{
12
+ name: string;
13
+ isRemote: boolean;
14
+ }[]>;
15
+ export declare function switchBranch(pi: ExtensionAPI, branch: string, cwd?: string): Promise<{
16
+ success: boolean;
17
+ message: string;
18
+ }>;
19
+ export declare function createAndSwitchBranch(pi: ExtensionAPI, branch: string, cwd?: string): Promise<{
20
+ success: boolean;
21
+ message: string;
22
+ }>;
23
+ export declare function deleteBranch(pi: ExtensionAPI, branch: string, cwd?: string): Promise<{
24
+ success: boolean;
25
+ message: string;
26
+ }>;
27
+ /**
28
+ * Check that the working directory is a git repository with pending changes.
29
+ * Returns null if ready, or a failure reason string.
30
+ */
31
+ export declare function ensureReadyToCommit(pi: ExtensionAPI, cwd?: string): Promise<"not_git_repo" | "no_changes" | null>;
32
+ /**
33
+ * Get git log with specified options
34
+ */
35
+ export declare function getLog(pi: ExtensionAPI, options: {
36
+ maxCount?: number | "all";
37
+ all?: boolean;
38
+ graph?: boolean;
39
+ }, cwd?: string): Promise<string>;
40
+ /**
41
+ * Collect the full working tree diff by stashing changes (including untracked
42
+ * files), capturing the stash diff, and popping the stash to restore the
43
+ * working tree. This "freezes" the diff so concurrent edits do not affect
44
+ * analysis.
45
+ *
46
+ * @returns The diff string, or `null` if the stash operation failed.
47
+ * An empty string means there are no effective changes.
48
+ */
49
+ export declare function collectDiff(pi: ExtensionAPI, cwd?: string): Promise<string | null>;
50
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/core/git.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAapE,wBAAsB,eAAe,CACnC,EAAE,EAAE,YAAY,EAChB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,wBAAsB,SAAS,CAC7B,EAAE,EAAE,YAAY,EAChB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,YAAY,EAChB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,MAAM,EAAE,EACf,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,YAAY,EAChB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,YAAY,EAChB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED,wBAAsB,WAAW,CAC/B,EAAE,EAAE,YAAY,EAChB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,EAAE,CAAC,CAgBhD;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,YAAY,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAQhD;AAED,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,YAAY,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAUhD;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,YAAY,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAUhD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,YAAY,EAChB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,cAAc,GAAG,YAAY,GAAG,IAAI,CAAC,CAQ/C;AAED;;GAEG;AACH,wBAAsB,MAAM,CAC1B,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE;IACP,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC1B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,EACD,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAoBjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,YAAY,EAChB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCxB"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Git command wrappers using pi.exec
3
+ */
4
+ class GitError extends Error {
5
+ command;
6
+ code;
7
+ constructor(message, command, code) {
8
+ super(message);
9
+ this.command = command;
10
+ this.code = code;
11
+ this.name = "GitError";
12
+ }
13
+ }
14
+ export async function isGitRepository(pi, cwd) {
15
+ const { code } = await pi.exec("git", ["rev-parse", "--git-dir"], { cwd });
16
+ return code === 0;
17
+ }
18
+ export async function getStatus(pi, cwd) {
19
+ const { stdout, code } = await pi.exec("git", ["status", "--porcelain"], {
20
+ cwd,
21
+ });
22
+ if (code !== 0) {
23
+ throw new GitError("Failed to get git status", "git status --porcelain", code);
24
+ }
25
+ return stdout;
26
+ }
27
+ export async function hasChanges(pi, cwd) {
28
+ const status = await getStatus(pi, cwd);
29
+ return status.trim().length > 0;
30
+ }
31
+ export async function stageFiles(pi, files, cwd) {
32
+ if (files.length === 0)
33
+ return;
34
+ const { code } = await pi.exec("git", ["add", "--", ...files], { cwd });
35
+ if (code !== 0) {
36
+ throw new GitError(`Failed to stage files: ${files.join(", ")}`, "git add", code);
37
+ }
38
+ }
39
+ export async function resetStaging(pi, cwd) {
40
+ const { code } = await pi.exec("git", ["reset"], { cwd });
41
+ if (code !== 0) {
42
+ throw new GitError("Failed to reset staging area", "git reset", code);
43
+ }
44
+ }
45
+ export async function getCurrentBranch(pi, cwd) {
46
+ const { stdout, code } = await pi.exec("git", ["branch", "--show-current"], {
47
+ cwd,
48
+ });
49
+ if (code !== 0) {
50
+ throw new GitError("Failed to get current branch", "git branch --show-current", code);
51
+ }
52
+ return stdout.trim();
53
+ }
54
+ export async function getBranches(pi, cwd) {
55
+ const { stdout, code } = await pi.exec("git", ["branch", "-a", "--format=%(refname:short)"], { cwd });
56
+ if (code !== 0) {
57
+ throw new GitError("Failed to get branches", "git branch -a", code);
58
+ }
59
+ return stdout
60
+ .split("\n")
61
+ .filter((line) => line.trim())
62
+ .map((name) => ({
63
+ name: name.trim(),
64
+ isRemote: name.startsWith("origin/"),
65
+ }));
66
+ }
67
+ export async function switchBranch(pi, branch, cwd) {
68
+ const { stdout, stderr, code } = await pi.exec("git", ["switch", branch], {
69
+ cwd,
70
+ });
71
+ if (code !== 0) {
72
+ return { success: false, message: stderr || stdout };
73
+ }
74
+ return { success: true, message: stdout };
75
+ }
76
+ export async function createAndSwitchBranch(pi, branch, cwd) {
77
+ const { stdout, stderr, code } = await pi.exec("git", ["switch", "-c", branch], { cwd });
78
+ if (code !== 0) {
79
+ return { success: false, message: stderr || stdout };
80
+ }
81
+ return { success: true, message: stdout };
82
+ }
83
+ export async function deleteBranch(pi, branch, cwd) {
84
+ const { stdout, stderr, code } = await pi.exec("git", ["branch", "-d", branch], { cwd });
85
+ if (code !== 0) {
86
+ return { success: false, message: stderr || stdout };
87
+ }
88
+ return { success: true, message: stdout };
89
+ }
90
+ /**
91
+ * Check that the working directory is a git repository with pending changes.
92
+ * Returns null if ready, or a failure reason string.
93
+ */
94
+ export async function ensureReadyToCommit(pi, cwd) {
95
+ if (!(await isGitRepository(pi, cwd))) {
96
+ return "not_git_repo";
97
+ }
98
+ if (!(await hasChanges(pi, cwd))) {
99
+ return "no_changes";
100
+ }
101
+ return null;
102
+ }
103
+ /**
104
+ * Get git log with specified options
105
+ */
106
+ export async function getLog(pi, options, cwd) {
107
+ const args = ["log", "--oneline", "--decorate", "--color=always"];
108
+ if (options.maxCount !== "all" && options.maxCount !== undefined) {
109
+ args.push(`-n`, String(options.maxCount));
110
+ }
111
+ if (options.all) {
112
+ args.push("--all");
113
+ }
114
+ if (options.graph) {
115
+ args.push("--graph");
116
+ }
117
+ const { stdout, code } = await pi.exec("git", args, { cwd });
118
+ if (code !== 0) {
119
+ throw new GitError("Failed to get git log", `git ${args.join(" ")}`, code);
120
+ }
121
+ return stdout;
122
+ }
123
+ /**
124
+ * Collect the full working tree diff by stashing changes (including untracked
125
+ * files), capturing the stash diff, and popping the stash to restore the
126
+ * working tree. This "freezes" the diff so concurrent edits do not affect
127
+ * analysis.
128
+ *
129
+ * @returns The diff string, or `null` if the stash operation failed.
130
+ * An empty string means there are no effective changes.
131
+ */
132
+ export async function collectDiff(pi, cwd) {
133
+ const { code: stashCode } = await pi.exec("git", ["stash", "push", "-u", "-m", "pi-git"], { cwd });
134
+ if (stashCode !== 0) {
135
+ return null;
136
+ }
137
+ let diff = "";
138
+ try {
139
+ const { stdout: stashDiff } = await pi.exec("git", ["stash", "show", "-p", "stash@{0}"], { cwd });
140
+ diff = stashDiff;
141
+ // stash@{0}^3 contains untracked files when -u was used
142
+ const { stdout: untrackedDiff, code: untrackedCode } = await pi.exec("git", ["diff", "HEAD", "stash@{0}^3"], { cwd });
143
+ if (untrackedCode === 0 && untrackedDiff.trim()) {
144
+ diff += (diff ? "\n" : "") + untrackedDiff;
145
+ }
146
+ }
147
+ finally {
148
+ await pi.exec("git", ["stash", "pop"], { cwd });
149
+ }
150
+ return diff;
151
+ }
152
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/core/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Model resolution helper for pi-git commands.
3
+ *
4
+ * Resolves the AI model to use based on configuration or session context.
5
+ */
6
+ import type { Api, Model } from "@earendil-works/pi-ai";
7
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
8
+ /**
9
+ * Resolve the model to use for AI operations.
10
+ *
11
+ * Priority:
12
+ * 1. Configured `analysis_model` in settings (format: "provider/model-id")
13
+ * 2. Current session model (`ctx.model`)
14
+ *
15
+ * @returns The resolved model, or undefined if no model is available
16
+ */
17
+ export declare function resolveModel(ctx: ExtensionContext): Model<Api> | undefined;
18
+ //# sourceMappingURL=resolve-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-model.d.ts","sourceRoot":"","sources":["../../src/core/resolve-model.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAGxE;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAiB1E"}