pi-git-commit 1.0.4 → 1.0.6
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/README.md +10 -6
- package/index.ts +51 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# pi-git-commit
|
|
2
2
|
|
|
3
|
-
Keeps mutative git operations out of the agent's bash and provides a safe, reviewable commit flow in [pi-coding-agent](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent): a bash guard, a `git_commit` tool, and `/commit`
|
|
3
|
+
Keeps mutative git operations out of the agent's bash and provides a safe, reviewable commit flow in [pi-coding-agent](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent): a bash guard, a `git_commit` tool, and `/commit`, `/stop-commit` and `/toggle-allow-git` commands.
|
|
4
4
|
|
|
5
5
|
## What you get
|
|
6
6
|
|
|
7
7
|
- **Bash git guard.** Mutative git commands are blocked in the agent's bash tool — `add`, `stage`, `commit`, `push`, `pull`, `merge`, `rebase`, `reset`, `clean`, `rm`, `restore`, `switch`, `cherry-pick`, `revert`, `mv`, `init`, `clone`, index/object plumbing (`read-tree`, `checkout-index`, `merge-file`, `prune-packed`), plus mutative forms of `branch` (including creation, `-u`, `-f`, `-c`/`-C`/`--copy`, `-D`/`-M`, `--force`, `-t`/`--track`), `tag` (including creation), `checkout` (including whole-tree restores like `checkout -- .`), `stash`, `submodule`, `worktree`, `config`, `remote`, `apply`, `notes`, `update-ref`, `gc` and more. Read-only commands (`status`, `diff`, `log`, `fetch`, `branch`, `tag`, `stash list`, ...) stay allowed.
|
|
8
8
|
- **`git_commit` tool.** The agent stages everything and commits with a `FIX` / `IMPROVE` / `NEW` type prefix. Inactive by default: `/commit` activates it for the commit flow and it is disabled again after use, so the agent cannot commit on its own at other times.
|
|
9
|
-
- **`/commit` command.** Waits for queued messages to finish, stages all changes, shows the staged diff, activates the `git_commit` tool, and asks the agent to review it and commit via `git_commit` — never via bash.
|
|
9
|
+
- **`/commit` command.** Waits for queued messages to finish, stages all changes, shows the staged diff, activates the `git_commit` tool, and asks the agent to review it and commit via `git_commit` — never via bash. Run `/stop-commit` at any point to abort the flow.
|
|
10
|
+
- **`/stop-commit` command.** Aborts a pending commit flow: deactivates the `git_commit` tool and cancels a `/commit` that is still waiting for queued messages, so no commit is made.
|
|
10
11
|
- **`/toggle-allow-git` command.** Temporarily allows mutative git commands in bash for the current session. The guard re-arms on the next session.
|
|
11
12
|
|
|
12
13
|
## Quick start
|
|
@@ -24,11 +25,13 @@ Keeps mutative git operations out of the agent's bash and provides a safe, revie
|
|
|
24
25
|
```json
|
|
25
26
|
{
|
|
26
27
|
"type": "FIX",
|
|
27
|
-
"message": "
|
|
28
|
+
"message": "Correct the off-by-one in the retry loop"
|
|
28
29
|
}
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
4.
|
|
32
|
+
4. Changed your mind? Run `/stop-commit` to abort the flow before the agent commits.
|
|
33
|
+
|
|
34
|
+
5. If you need to run mutative git yourself, allow it for the session:
|
|
32
35
|
|
|
33
36
|
```text
|
|
34
37
|
/toggle-allow-git
|
|
@@ -51,9 +54,9 @@ pi install /path/to/pi-git-commit
|
|
|
51
54
|
| Field | Description |
|
|
52
55
|
| --- | --- |
|
|
53
56
|
| `type` | `FIX` (bug fix), `IMPROVE` (improvement), or `NEW` (new feature). |
|
|
54
|
-
| `message` | Commit message in imperative mood. Multi-line allowed for detailed changes. |
|
|
57
|
+
| `message` | Commit message in imperative mood, without the type prefix (it is added automatically). A leading type word matching the chosen type is stripped (with `:`, whitespace, or `-`/`—` separators, any casing, repeats included) so the type is never duplicated. Multi-line allowed for detailed changes. |
|
|
55
58
|
|
|
56
|
-
The tool runs `git add .` followed by `git commit -m "<TYPE>: <message>"` and reports staging or commit failures as tool errors. The tool is inactive by default and only becomes available when you run `/commit`; it is deactivated again after a single use (success or failure), so the agent cannot commit at arbitrary points in the conversation. If a commit fails, run `/commit` again to retry.
|
|
59
|
+
The tool runs `git add .` followed by `git commit -m "<TYPE>: <message>"` and reports staging or commit failures as tool errors. A leading type word in the message is stripped whenever it repeats the chosen type — with `:`, whitespace, or `-`/`—` separators, at any casing, repeated prefixes included — so the type never ends up duplicated: `FIX: Fix: ...`, `FIX: Fix ...`, and `fix - ...` all become `FIX: ...`. The tool is inactive by default and only becomes available when you run `/commit`; it is deactivated again after a single use (success or failure), so the agent cannot commit at arbitrary points in the conversation. If a commit fails, run `/commit` again to retry.
|
|
57
60
|
|
|
58
61
|
## The bash guard
|
|
59
62
|
|
|
@@ -70,6 +73,7 @@ Mutative git commands are blocked. Use /toggle-allow-git to allow for this sessi
|
|
|
70
73
|
## Troubleshooting
|
|
71
74
|
|
|
72
75
|
- **The agent refuses to commit.** The guard blocks `git commit` in bash by design. Run `/commit` and let the agent use the `git_commit` tool.
|
|
76
|
+
- **The agent stopped committing.** You ran `/stop-commit`, which aborted the pending flow. Run `/commit` again to start a new one.
|
|
73
77
|
- **"Nothing to commit (empty diff)."** There are no staged changes — make edits first, then run `/commit` again.
|
|
74
78
|
- **I need git in bash right now.** Run `/toggle-allow-git`; the guard re-arms automatically on the next session start.
|
|
75
79
|
|
package/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ const COMMIT_TYPES = ["FIX", "IMPROVE", "NEW"] as const;
|
|
|
5
5
|
|
|
6
6
|
export default function (pi: ExtensionAPI) {
|
|
7
7
|
let gitBlocked = true;
|
|
8
|
+
let commitFlowActive = false;
|
|
8
9
|
|
|
9
10
|
const PREFIXES = new Set(["sudo", "env", "command", "nohup", "nice", "time", "exec", "builtin", "doas", "eval", "timeout", "runuser", "pkexec"]);
|
|
10
11
|
const CONTROL_KEYWORDS = new Set(["if", "then", "else", "elif", "while", "until", "do", "case", "select"]);
|
|
@@ -221,6 +222,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
221
222
|
}
|
|
222
223
|
};
|
|
223
224
|
|
|
225
|
+
const stripLeadingCommitType = (type: string, message: string): string => {
|
|
226
|
+
const typePrefixRe = new RegExp(`^${type}(?!\\p{L})\\s*[:–—\\-]?\\s*`, "iu");
|
|
227
|
+
let rest = message.trim();
|
|
228
|
+
let next = rest.replace(typePrefixRe, "").trim();
|
|
229
|
+
while (next !== rest) {
|
|
230
|
+
rest = next;
|
|
231
|
+
next = rest.replace(typePrefixRe, "").trim();
|
|
232
|
+
}
|
|
233
|
+
return rest;
|
|
234
|
+
};
|
|
235
|
+
|
|
224
236
|
pi.registerTool({
|
|
225
237
|
name: "git_commit",
|
|
226
238
|
label: "Git Commit",
|
|
@@ -240,11 +252,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
240
252
|
async execute(_toolCallId, params, signal, _onUpdate, _ctx) {
|
|
241
253
|
try {
|
|
242
254
|
const { type, message } = params;
|
|
243
|
-
const
|
|
244
|
-
if (!
|
|
255
|
+
const description = stripLeadingCommitType(type, message);
|
|
256
|
+
if (!description) {
|
|
245
257
|
return { content: [{ type: "text", text: "Commit message must not be empty." }], details: {}, isError: true };
|
|
246
258
|
}
|
|
247
|
-
const fullMessage = `${type}: ${
|
|
259
|
+
const fullMessage = `${type}: ${description}`;
|
|
248
260
|
const addResult = await pi.exec("git", ["add", "."], { signal });
|
|
249
261
|
if (addResult.code !== 0) {
|
|
250
262
|
return { content: [{ type: "text", text: `Staging failed: ${addResult.stderr}` }], details: {}, isError: true };
|
|
@@ -256,11 +268,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
256
268
|
}
|
|
257
269
|
|
|
258
270
|
pi.sendMessage(
|
|
259
|
-
{ customType: "git-commit-deactivated", content: "The git_commit tool is now deactivated
|
|
271
|
+
{ customType: "git-commit-deactivated", content: "The git_commit tool is now deactivated and cannot be used again until the user runs /commit to re-enable it. Do not mention this deactivation in your response.", display: false },
|
|
260
272
|
{ deliverAs: "steer" },
|
|
261
273
|
);
|
|
262
274
|
return { content: [{ type: "text", text: `✓ Committed: ${fullMessage}` }], details: {} };
|
|
263
275
|
} finally {
|
|
276
|
+
commitFlowActive = false;
|
|
264
277
|
deactivateGitCommit();
|
|
265
278
|
}
|
|
266
279
|
},
|
|
@@ -268,6 +281,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
268
281
|
|
|
269
282
|
pi.on("session_start", () => {
|
|
270
283
|
gitBlocked = true;
|
|
284
|
+
commitFlowActive = false;
|
|
271
285
|
deactivateGitCommit();
|
|
272
286
|
});
|
|
273
287
|
|
|
@@ -278,15 +292,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
278
292
|
ctx.ui.notify("commit requires interactive mode", "error");
|
|
279
293
|
return;
|
|
280
294
|
}
|
|
295
|
+
commitFlowActive = true;
|
|
281
296
|
|
|
282
297
|
try {
|
|
283
298
|
await ctx.ui.setWorkingMessage("Waiting for queued messages to complete...");
|
|
284
299
|
await ctx.waitForIdle();
|
|
300
|
+
if (!commitFlowActive) return;
|
|
285
301
|
|
|
286
302
|
await ctx.ui.setWorkingMessage("Staging files...");
|
|
287
303
|
const addResult = await pi.exec("git", ["add", "."]);
|
|
288
304
|
if (addResult.code !== 0) {
|
|
289
305
|
ctx.ui.notify(`git add failed: ${addResult.stderr}`, "error");
|
|
306
|
+
commitFlowActive = false;
|
|
290
307
|
return;
|
|
291
308
|
}
|
|
292
309
|
|
|
@@ -294,17 +311,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
294
311
|
const diffResult = await pi.exec("git", ["diff", "--staged"]);
|
|
295
312
|
if (diffResult.code !== 0) {
|
|
296
313
|
ctx.ui.notify(`git diff failed: ${diffResult.stderr}`, "error");
|
|
314
|
+
commitFlowActive = false;
|
|
297
315
|
return;
|
|
298
316
|
}
|
|
299
317
|
|
|
300
318
|
if (!diffResult.stdout.trim()) {
|
|
301
319
|
ctx.ui.notify("Nothing to commit (empty diff). Stage files first.", "warning");
|
|
320
|
+
commitFlowActive = false;
|
|
302
321
|
return;
|
|
303
322
|
}
|
|
304
323
|
|
|
305
324
|
const diff = diffResult.stdout || "(no changes staged)";
|
|
306
325
|
|
|
307
|
-
const prompt = `DO NOT use bash for git. Use ONLY the \`git_commit\` tool.\n\nReview staged changes:\n\`\`\`diff\n${diff}\`\`\`\n\nUse \`git_commit\` tool with:\n- type: FIX
|
|
326
|
+
const prompt = `DO NOT use bash for git. Use ONLY the \`git_commit\` tool.\n\nReview staged changes:\n\`\`\`diff\n${diff}\`\`\`\n\nUse \`git_commit\` tool with:\n- type: FIX, IMPROVE, or NEW\n- message: brief description (imperative mood)`;
|
|
327
|
+
if (!commitFlowActive) return;
|
|
308
328
|
activateGitCommit();
|
|
309
329
|
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
310
330
|
} finally {
|
|
@@ -312,6 +332,32 @@ export default function (pi: ExtensionAPI) {
|
|
|
312
332
|
}
|
|
313
333
|
},
|
|
314
334
|
});
|
|
335
|
+
|
|
336
|
+
pi.registerCommand("stop-commit", {
|
|
337
|
+
description: "Stop the pending commit flow started by /commit",
|
|
338
|
+
handler: async (_args, ctx) => {
|
|
339
|
+
if (!ctx.hasUI) {
|
|
340
|
+
ctx.ui.notify("stop-commit requires interactive mode", "error");
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const toolWasActive = pi.getActiveTools().includes("git_commit");
|
|
344
|
+
const flowWasActive = commitFlowActive;
|
|
345
|
+
commitFlowActive = false;
|
|
346
|
+
deactivateGitCommit();
|
|
347
|
+
if (toolWasActive) {
|
|
348
|
+
pi.sendMessage(
|
|
349
|
+
{ customType: "git-commit-stopped", content: "The user stopped the commit flow with /stop-commit. Do not attempt to commit. Do not mention this in your response.", display: false },
|
|
350
|
+
{ deliverAs: "steer" },
|
|
351
|
+
);
|
|
352
|
+
ctx.ui.notify("Commit flow stopped. The git_commit tool is deactivated.", "info");
|
|
353
|
+
} else if (flowWasActive) {
|
|
354
|
+
ctx.ui.notify("Commit flow stopped.", "info");
|
|
355
|
+
} else {
|
|
356
|
+
ctx.ui.notify("No commit flow in progress.", "info");
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
|
|
315
361
|
pi.registerCommand("toggle-allow-git", {
|
|
316
362
|
description: "Toggle whether mutative git commands are allowed in bash for this session",
|
|
317
363
|
handler: async (_args, ctx) => {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-git-commit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Pi extension: block mutative git commands in bash and provide a git_commit tool plus /commit and /toggle-allow-git commands",
|
|
5
|
+
"description": "Pi extension: block mutative git commands in bash and provide a git_commit tool plus /commit, /stop-commit and /toggle-allow-git commands",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|