pi-git-commit 1.0.7 → 1.0.8

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 (3) hide show
  1. package/README.md +1 -1
  2. package/index.ts +40 -29
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -67,7 +67,7 @@ The guard parses the command into segments (pipelines, `&&`, `||`, `;`, `&`, com
67
67
  A blocked command returns:
68
68
 
69
69
  ```text
70
- Mutative git commands are blocked. Use /toggle-allow-git to allow for this session.
70
+ Mutative git commands are blocked. Ask the user to run /toggle-allow-git to allow them for this session.
71
71
  ```
72
72
 
73
73
  ## Troubleshooting
package/index.ts CHANGED
@@ -15,10 +15,12 @@ export default function (pi: ExtensionAPI) {
15
15
  const GIT_META_OPTS = new Set(["--help", "-h", "--version"]);
16
16
  const GIT_OPTS_BARE = new Set(["--bare", "-p", "--paginate", "--no-pager", "--no-replace-objects", "--literal-pathspecs", "--glob-pathspecs", "--noglob-pathspecs", "--icase-pathspecs", "--no-optional-locks", "--html-path", "--man-path", "--info-path"]);
17
17
  const GIT_OPTS_WITH_ARG = new Set(["-c", "--git-dir", "--work-tree", "--namespace", "--exec-path", "--super-prefix", "--shallow-file", "--template", "--upload-pack"]);
18
+ const GIT_TAG_VALUE_FLAGS = new Set(["--sort", "--format", "-v"]);
19
+ const GIT_TAG_LIST_FORCING = new Set(["--contains", "--no-contains", "--merged", "--no-merged", "--points-at"]);
18
20
 
19
21
  const blockAll = () => true;
20
22
  const hasAny = (args: string[], values: string[]) => values.some((value) => args.includes(value));
21
- const allowOnly = (values: string[]) => (args: string[]) => !hasAny(args, values);
23
+ const blockUnlessAny = (values: string[]) => (args: string[]) => !hasAny(args, values);
22
24
  const hasShortFlag = (args: string[], flags: string) => args.some((arg) => arg.startsWith("-") && !arg.startsWith("--") && arg.length > 1 && [...arg.slice(1)].some((flag) => flags.includes(flag)));
23
25
 
24
26
  const GIT_RULES: Record<string, (args: string[]) => boolean> = {
@@ -64,15 +66,15 @@ export default function (pi: ExtensionAPI) {
64
66
  if (hasAny(args, ["--add", "--unset", "--unset-all", "--replace-all", "--remove-section", "--rename-section", "--edit", "-e"])) return true;
65
67
  return !hasAny(args, ["--list", "-l", "--get", "--get-all", "--get-regexp", "--show-origin", "--show-scope"]);
66
68
  },
67
- remote: (args) => !(args.length === 0 || args.includes("-v") || hasAny(args, ["show", "get-url"])),
68
- apply: allowOnly(["--check", "--stat"]),
69
- notes: allowOnly(["list", "show"]),
70
- lfs: allowOnly(["ls-files", "status"]),
71
- "sparse-checkout": allowOnly(["list"]),
72
- stash: allowOnly(["list", "show"]),
73
- submodule: allowOnly(["status", "init", "summary"]),
74
- worktree: allowOnly(["list"]),
75
- reflog: (args) => !(args.length === 0 || hasAny(args, ["show"])),
69
+ remote: (args) => args.length > 0 && blockUnlessAny(["-v", "show", "get-url"])(args),
70
+ apply: blockUnlessAny(["--check", "--stat"]),
71
+ notes: blockUnlessAny(["list", "show"]),
72
+ lfs: blockUnlessAny(["ls-files", "status"]),
73
+ "sparse-checkout": blockUnlessAny(["list"]),
74
+ stash: blockUnlessAny(["list", "show"]),
75
+ submodule: blockUnlessAny(["status", "init", "summary"]),
76
+ worktree: blockUnlessAny(["list"]),
77
+ reflog: (args) => args.length > 0 && blockUnlessAny(["show"])(args),
76
78
  branch: (args) => {
77
79
  if (args.includes("--")) return true;
78
80
  if (hasAny(args, ["--delete", "--move", "--copy", "--force", "--track", "--prune", "--unset-upstream", "--edit-description"]) || args.some((arg) => arg.startsWith("--set-upstream-to"))) return true;
@@ -82,9 +84,13 @@ export default function (pi: ExtensionAPI) {
82
84
  },
83
85
  tag: (args) => {
84
86
  if (args.length === 0) return false;
87
+ if (hasAny(args, ["--delete", "--annotate", "--force", "--sign", "--edit", "--cleanup"]) || hasShortFlag(args, "damfsueF")) return true;
85
88
  const first = args[0];
86
89
  if (first === "-l" || first === "--list" || first.startsWith("-n")) return false;
87
- return !["--contains", "--merged", "--no-merged", "--points-at", "--sort", "--format", "--column", "--no-column", "--color", "--ignore-case", "--verbose", "-v"].some((flag) => first.startsWith(flag));
90
+ const readOnlyFlags = ["--contains", "--no-contains", "--merged", "--no-merged", "--points-at", "--sort", "--format", "--column", "--no-column", "--color", "--ignore-case", "--verbose", "-i", "-v"];
91
+ if (!readOnlyFlags.some((flag) => first.startsWith(flag))) return true;
92
+ if ([...GIT_TAG_LIST_FORCING].some((flag) => args.some((arg) => arg === flag || arg.startsWith(`${flag}=`)))) return false;
93
+ return args.some((arg, index) => index > 0 && !arg.startsWith("-") && !GIT_TAG_VALUE_FLAGS.has(args[index - 1]));
88
94
  },
89
95
  checkout: (args) => {
90
96
  if (args[0] !== "--") return true;
@@ -98,7 +104,7 @@ export default function (pi: ExtensionAPI) {
98
104
  const maskHeredocBodies = (command: string): string => {
99
105
  let masked = "";
100
106
  let cursor = 0;
101
- const heredocRe = /<<-?\s*['"]?([A-Za-z_][A-Za-z0-9_]*)['"]?/g;
107
+ const heredocRe = /<<-?\s*\\?['"]?([A-Za-z_][A-Za-z0-9_]*)['"]?/g;
102
108
  let match: RegExpExecArray | null;
103
109
  while ((match = heredocRe.exec(command)) !== null) {
104
110
  const current = match;
@@ -161,6 +167,7 @@ export default function (pi: ExtensionAPI) {
161
167
  let subcommandIndex = -1;
162
168
  for (let i = 0; i < tokens.length; i++) {
163
169
  const token = tokens[i];
170
+ if (token === "git" || token === "git.exe") continue;
164
171
  if (GIT_META_OPTS.has(token)) return false;
165
172
  if (GIT_OPTS_BARE.has(token)) continue;
166
173
  if (GIT_OPTS_WITH_ARG.has(token)) {
@@ -205,7 +212,7 @@ export default function (pi: ExtensionAPI) {
205
212
  if (typeof command !== "string") return undefined;
206
213
  const trimmed = command.trim();
207
214
  if (gitBlocked && containsBlockedGitCommand(trimmed)) {
208
- return { block: true, reason: "Mutative git commands are blocked. Use /toggle-allow-git to allow for this session." };
215
+ return { block: true, reason: "Mutative git commands are blocked. Ask the user to run /toggle-allow-git to allow them for this session." };
209
216
  }
210
217
  return undefined;
211
218
  });
@@ -235,6 +242,8 @@ export default function (pi: ExtensionAPI) {
235
242
  return rest;
236
243
  };
237
244
 
245
+ const toolError = (text: string) => ({ content: [{ type: "text" as const, text }], details: {}, isError: true });
246
+
238
247
  pi.registerTool({
239
248
  name: "git_commit",
240
249
  label: "Git Commit",
@@ -257,17 +266,17 @@ export default function (pi: ExtensionAPI) {
257
266
  const { type, message } = params;
258
267
  const description = stripLeadingCommitType(type, message);
259
268
  if (!description) {
260
- return { content: [{ type: "text", text: "Commit message must not be empty." }], details: {}, isError: true };
269
+ return toolError("Commit message must not be empty.");
261
270
  }
262
271
  const fullMessage = `${type}: ${description}`;
263
272
  const addResult = await pi.exec("git", ["add", "."], { signal });
264
273
  if (addResult.code !== 0) {
265
- return { content: [{ type: "text", text: `Staging failed: ${addResult.stderr}` }], details: {}, isError: true };
274
+ return toolError(`Staging failed: ${addResult.stderr}`);
266
275
  }
267
276
 
268
277
  const result = await pi.exec("git", ["commit", "-m", fullMessage], { signal });
269
278
  if (result.code !== 0) {
270
- return { content: [{ type: "text", text: `Commit failed: ${result.stderr}` }], details: {}, isError: true };
279
+ return toolError(`Commit failed: ${result.stderr}`);
271
280
  }
272
281
  committed = true;
273
282
  pi.sendMessage(
@@ -316,26 +325,28 @@ export default function (pi: ExtensionAPI) {
316
325
  }
317
326
  commitFlowActive = true;
318
327
 
328
+ const execGit = async (label: string, args: string[]) => {
329
+ const result = await pi.exec("git", args);
330
+ if (result.code !== 0) {
331
+ ctx.ui.notify(`${label} failed: ${result.stderr}`, "error");
332
+ commitFlowActive = false;
333
+ return undefined;
334
+ }
335
+ return result;
336
+ };
337
+
319
338
  try {
320
339
  await ctx.ui.setWorkingMessage("Waiting for queued messages to complete...");
321
340
  await ctx.waitForIdle();
322
341
  if (!commitFlowActive) return;
323
342
 
324
343
  await ctx.ui.setWorkingMessage("Staging files...");
325
- const addResult = await pi.exec("git", ["add", "."]);
326
- if (addResult.code !== 0) {
327
- ctx.ui.notify(`git add failed: ${addResult.stderr}`, "error");
328
- commitFlowActive = false;
329
- return;
330
- }
344
+ const addResult = await execGit("git add", ["add", "."]);
345
+ if (!addResult || !commitFlowActive) return;
331
346
 
332
347
  await ctx.ui.setWorkingMessage("Getting diff...");
333
- const diffResult = await pi.exec("git", ["diff", "--staged"]);
334
- if (diffResult.code !== 0) {
335
- ctx.ui.notify(`git diff failed: ${diffResult.stderr}`, "error");
336
- commitFlowActive = false;
337
- return;
338
- }
348
+ const diffResult = await execGit("git diff", ["diff", "--staged"]);
349
+ if (!diffResult || !commitFlowActive) return;
339
350
 
340
351
  if (!diffResult.stdout.trim()) {
341
352
  ctx.ui.notify("Nothing to commit (empty diff). Stage files first.", "warning");
@@ -343,7 +354,7 @@ export default function (pi: ExtensionAPI) {
343
354
  return;
344
355
  }
345
356
 
346
- const diff = diffResult.stdout || "(no changes staged)";
357
+ const diff = diffResult.stdout;
347
358
 
348
359
  const statResult = await pi.exec("git", ["diff", "--staged", "--stat"]);
349
360
  if (!commitFlowActive) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-git-commit",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "type": "module",
5
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",