mitsupi 1.0.1 → 1.0.3

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.
@@ -1,248 +0,0 @@
1
- /**
2
- * Commit Extension - Interactive git commit workflow
3
- *
4
- * Provides:
5
- * - /commit command: User selects mode (auto/staged/changed/other), then LLM drafts commit
6
- * - git_commit_with_user_approval tool: LLM calls this when user should confirm a commit
7
- *
8
- * Usage:
9
- * /commit - Select what to commit, LLM drafts message
10
- * /commit message - Quick commit with provided message hint
11
- */
12
-
13
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
14
- import { Text } from "@mariozechner/pi-tui";
15
- import { Type } from "@sinclair/typebox";
16
-
17
- const COMMIT_FORMAT_GUIDE = `
18
- Commit message format guidelines:
19
- - Start with a short prefix followed by colon and space (feat:, fix:, docs:, refactor:, test:, chore:, etc.)
20
- - feat: for user-visible features, fix: for bug fixes
21
- - A scope MAY be added in parentheses, e.g. fix(parser): - only when it meaningfully improves clarity
22
- - Short description in imperative mood explaining what changed, not how
23
- - Body MAY be included after one blank line for context, rationale, or non-obvious behavior
24
- - Footers MAY be included (Token: value format, use - instead of spaces in tokens)
25
- - Breaking changes should be explained clearly in description or body, no special marking required
26
- - Clarity and usefulness matter more than strict conformance
27
- `.trim();
28
-
29
- export default function commit(pi: ExtensionAPI) {
30
- // Command: /commit
31
- pi.registerCommand("commit", {
32
- description: "Draft and create a git commit with LLM assistance",
33
- handler: async (args, ctx) => {
34
- if (!ctx.hasUI) {
35
- ctx.ui.notify("commit requires interactive mode", "error");
36
- return;
37
- }
38
-
39
- let mode: string;
40
- let instruction = args.trim();
41
-
42
- if (instruction) {
43
- // If args provided, treat as "other" mode with instruction
44
- mode = "other";
45
- } else {
46
- // Show mode selection
47
- const selection = await ctx.ui.select("What do you want to commit?", [
48
- "auto - Let the agent figure out what to commit",
49
- "staged - Commit currently staged files",
50
- "changed - Commit all changed files",
51
- "other - Describe what you want to commit",
52
- ]);
53
-
54
- if (!selection) {
55
- ctx.ui.notify("Cancelled", "info");
56
- return;
57
- }
58
-
59
- mode = selection.split(" - ")[0];
60
-
61
- if (mode === "other") {
62
- const input = await ctx.ui.input("What do you want to commit?");
63
- if (!input) {
64
- ctx.ui.notify("Cancelled", "info");
65
- return;
66
- }
67
- instruction = input;
68
- }
69
- }
70
-
71
- // Build prompt for the LLM
72
- let prompt: string;
73
- switch (mode) {
74
- case "auto":
75
- prompt = `Analyze the current git status and changes. Determine what should be committed, stage the appropriate files, and draft a commit message. Use git_commit_with_user_approval to let me review and confirm the commit.
76
-
77
- IMPORTANT: Be very selective about what you commit. Only include files that are clearly related to recent work in this session or the task at hand. Do NOT commit:
78
- - Untracked files unless they are clearly part of the current work
79
- - Unrelated local changes that may have been sitting in the working directory
80
- - Configuration files, logs, or other artifacts that shouldn't be in version control
81
-
82
- When in doubt, leave a file out. The user can always add more files manually.`;
83
- break;
84
- case "staged":
85
- prompt = `Check what files are currently staged (git diff --cached). Draft a commit message for the staged changes. Use git_commit_with_user_approval to let me review and confirm the commit. Do not stage any additional files.`;
86
- break;
87
- case "changed":
88
- prompt = `Stage all tracked files that have been modified (git add -u) and draft a commit message based on the changes. Use git_commit_with_user_approval to let me review and confirm the commit.
89
-
90
- NOTE: This only stages already-tracked files that have been modified, not untracked files. This is equivalent to what 'git commit -a' does.`;
91
- break;
92
- case "other":
93
- prompt = `I want to commit: ${instruction}
94
-
95
- Analyze the git status and stage ONLY the files that are directly relevant to this request. Draft a commit message. Use git_commit_with_user_approval to let me review and confirm the commit.
96
-
97
- IMPORTANT: Be very conservative about what you include. Only stage files that are clearly related to the requested commit. Do NOT include:
98
- - Unrelated local changes that happen to be in the working directory
99
- - Untracked files unless explicitly part of the request
100
- - Files that seem like they might be leftover from other work
101
-
102
- When in doubt, leave a file out.`;
103
- break;
104
- default:
105
- ctx.ui.notify("Unknown mode", "error");
106
- return;
107
- }
108
-
109
- // Send to LLM
110
- pi.sendUserMessage(prompt);
111
- },
112
- });
113
-
114
- // Tool: git_commit_with_user_approval
115
- pi.registerTool({
116
- name: "git_commit_with_user_approval",
117
- label: "Git Commit (with approval)",
118
- description: `Create a git commit with user review and approval. Use this tool when the user should confirm and potentially edit the commit message before committing. For automated commits where no user confirmation is needed, use the regular git commit command via bash instead.
119
-
120
- ${COMMIT_FORMAT_GUIDE}`,
121
- parameters: Type.Object({
122
- message: Type.String({
123
- description: "Proposed commit message (subject line, optionally followed by blank line and body)",
124
- }),
125
- files: Type.Optional(
126
- Type.Array(Type.String(), {
127
- description: "Files to stage before committing. If empty or omitted, commits whatever is currently staged.",
128
- })
129
- ),
130
- }),
131
-
132
- async execute(_toolCallId, params, _onUpdate, ctx, signal) {
133
- if (!ctx.hasUI) {
134
- return {
135
- content: [{ type: "text", text: "Error: UI not available (running in non-interactive mode)" }],
136
- details: { committed: false, reason: "no-ui" },
137
- };
138
- }
139
-
140
- // Stage files if provided
141
- if (params.files && params.files.length > 0) {
142
- const stageResult = await pi.exec("git", ["add", "--", ...params.files], { signal });
143
- if (stageResult.code !== 0) {
144
- return {
145
- content: [{ type: "text", text: `Error staging files: ${stageResult.stderr}` }],
146
- details: { committed: false, reason: "stage-failed", error: stageResult.stderr },
147
- };
148
- }
149
- }
150
-
151
- // Check if there's anything to commit
152
- const statusResult = await pi.exec("git", ["diff", "--cached", "--quiet"], { signal });
153
- if (statusResult.code === 0) {
154
- return {
155
- content: [{ type: "text", text: "Nothing staged to commit" }],
156
- details: { committed: false, reason: "nothing-staged" },
157
- };
158
- }
159
-
160
- // Show what will be committed
161
- const diffStatResult = await pi.exec("git", ["diff", "--cached", "--stat"], { signal });
162
- const stagedInfo = diffStatResult.stdout.trim();
163
-
164
- // Let user edit the commit message
165
- const editorPrompt = `Staged changes:\n${stagedInfo}\n\n───────────────────────────────────────\nEdit commit message (save to commit, cancel to abort):`;
166
- const finalMessage = await ctx.ui.editor(editorPrompt, params.message);
167
-
168
- if (finalMessage === undefined || finalMessage.trim() === "") {
169
- // User cancelled or cleared the message
170
- return {
171
- content: [{ type: "text", text: "Commit cancelled by user" }],
172
- details: { committed: false, reason: "user-cancelled" },
173
- };
174
- }
175
-
176
- // Execute the commit
177
- const commitResult = await pi.exec("git", ["commit", "-m", finalMessage.trim()], { signal });
178
-
179
- if (commitResult.code !== 0) {
180
- return {
181
- content: [{ type: "text", text: `Commit failed: ${commitResult.stderr}` }],
182
- details: { committed: false, reason: "commit-failed", error: commitResult.stderr },
183
- };
184
- }
185
-
186
- // Get the commit hash
187
- const hashResult = await pi.exec("git", ["rev-parse", "--short", "HEAD"], { signal });
188
- const commitHash = hashResult.stdout.trim();
189
-
190
- return {
191
- content: [
192
- {
193
- type: "text",
194
- text: `Committed ${commitHash}: ${finalMessage.trim().split("\n")[0]}`,
195
- },
196
- ],
197
- details: {
198
- committed: true,
199
- hash: commitHash,
200
- message: finalMessage.trim(),
201
- files: params.files || [],
202
- },
203
- };
204
- },
205
-
206
- renderCall(args, theme) {
207
- const message = (args.message as string) || "";
208
- const subject = message.split("\n")[0];
209
- const files = (args.files as string[]) || [];
210
-
211
- let text = theme.fg("toolTitle", theme.bold("git commit "));
212
- text += theme.fg("muted", `"${subject}"`);
213
- if (files.length > 0) {
214
- text += theme.fg("dim", ` (${files.length} file${files.length !== 1 ? "s" : ""})`);
215
- }
216
- return new Text(text, 0, 0);
217
- },
218
-
219
- renderResult(result, _options, theme) {
220
- const details = result.details as
221
- | { committed: boolean; reason?: string; hash?: string; message?: string; error?: string }
222
- | undefined;
223
-
224
- if (!details) {
225
- const text = result.content[0];
226
- return new Text(text?.type === "text" ? text.text : "", 0, 0);
227
- }
228
-
229
- if (!details.committed) {
230
- const reason = details.reason || "unknown";
231
- if (reason === "user-cancelled") {
232
- return new Text(theme.fg("warning", "Cancelled"), 0, 0);
233
- }
234
- if (reason === "nothing-staged") {
235
- return new Text(theme.fg("warning", "Nothing to commit"), 0, 0);
236
- }
237
- return new Text(theme.fg("error", `Failed: ${details.error || reason}`), 0, 0);
238
- }
239
-
240
- const subject = (details.message || "").split("\n")[0];
241
- return new Text(
242
- theme.fg("success", "✓ ") + theme.fg("accent", details.hash || "") + theme.fg("muted", ` ${subject}`),
243
- 0,
244
- 0
245
- );
246
- },
247
- });
248
- }