pi-do-always 0.4.4 → 0.4.5

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 CHANGED
@@ -112,7 +112,7 @@ Fields:
112
112
 
113
113
  In the object form you can also configure the selector shortcut:
114
114
 
115
- - `shortcut` (optional) — key that opens the selector, e.g. `"f4"` or `"ctrl+shift+p"`. Set to `null` to disable the shortcut. Defaults to `F4`. The project file's value wins over the global one.
115
+ - `shortcut` (optional) — key that opens the selector, e.g. `"f4"`. Set to `null` to disable the shortcut. Defaults to `F4`. The project file's value wins over the global one.
116
116
 
117
117
  Reload Pi (or start a new session) after editing a config file.
118
118
 
@@ -30,7 +30,7 @@
30
30
  "name": "Propose features",
31
31
  "category": "Plan",
32
32
  "description": "Propose new features (Plan)",
33
- "prompt": "Review this project and propose new features that would add value. For each idea, describe the problem it solves, the user benefit, and a rough implementation approach. Prioritize by impact and effort. Do not make any changes yet."
33
+ "prompt": "Review this project and propose new features that would add value. For each idea, describe the problem it solves, the user benefit, and a rough implementation approach. Prioritize by impact and effort. Do not make any changes yet. Try to evaluate how many lines this will be in term of changes, if this will breaks API, compatibility issue."
34
34
  },
35
35
  {
36
36
  "name": "Build",
@@ -57,6 +57,7 @@ import {
57
57
  parseConfig,
58
58
  orderTasksByCategory,
59
59
  renderPrompt,
60
+ evaluateGuards,
60
61
  resolveShortcut,
61
62
  resolveTask,
62
63
  shouldAutoRun,
@@ -123,6 +124,13 @@ function buildContext(cwd: string): PromptContext {
123
124
  const lastCommit = git(cwd, ["log", "-1", "--format=%s"]) ?? "unknown";
124
125
  const user = git(cwd, ["config", "user.name"]) ?? "unknown";
125
126
 
127
+ // repo = bare name of the git remote (owner/repo.git -> repo), falling back
128
+ // to the basename of cwd so monorepo work stays disambiguated everywhere.
129
+ const remoteUrl = git(cwd, ["config", "--get", "remote.origin.url"]);
130
+ const repo = remoteUrl
131
+ ? (remoteUrl.replace(/\.git$/, "").split("/").pop() ?? "unknown")
132
+ : cwd.split(/[\\/]/).filter(Boolean).pop() ?? "unknown";
133
+
126
134
  const files: string[] = [];
127
135
  const status = git(cwd, ["status", "--porcelain"]);
128
136
  if (status) {
@@ -151,6 +159,10 @@ function buildContext(cwd: string): PromptContext {
151
159
  files_changed: listed,
152
160
  files_changed_count: String(files.length),
153
161
  user,
162
+ diff_stat: git(cwd, ["diff", "--shortstat"]) ?? "none",
163
+ repo,
164
+ staged_files: git(cwd, ["diff", "--cached", "--name-only"]) ?? "none",
165
+ unstaged_files: git(cwd, ["diff", "--name-only"]) ?? "none",
154
166
  };
155
167
  }
156
168
 
@@ -182,7 +194,13 @@ export default function doAlwaysExtension(pi: ExtensionAPI) {
182
194
  async function fillPrompt(task: DoAlwaysTask, ctx: ExtensionContext): Promise<void> {
183
195
  // Render the prompt with the current context (branch, changed files, …)
184
196
  // so the injected text matches this directory at this moment.
185
- const prompt = renderPrompt(task.prompt, buildContext(ctx.cwd));
197
+ const context = buildContext(ctx.cwd);
198
+ const blocked = evaluateGuards(task, context);
199
+ if (blocked) {
200
+ ctx.ui.notify(`do-always: ${blocked}`, "info");
201
+ return;
202
+ }
203
+ const prompt = renderPrompt(task.prompt, context);
186
204
  if (shouldAutoRun(task)) {
187
205
  await pi.sendUserMessage(prompt);
188
206
  ctx.ui.notify(`do-always: auto-ran "${task.name}"`, "info");
@@ -19,6 +19,13 @@ export interface DoAlwaysTask {
19
19
  * category: "Plan" tasks auto-run, everything else fills the editor.
20
20
  */
21
21
  autoRun?: boolean;
22
+ /**
23
+ * Whether the task should be blocked when the working tree is clean
24
+ * (`files_changed_count === 0`). When set and unmet, the extension notifies
25
+ * instead of injecting, avoiding a no-op round-trip. Guards are evaluated
26
+ * against the current prompt context (see `evaluateGuards`).
27
+ */
28
+ requireDirty?: boolean;
22
29
  }
23
30
 
24
31
  /**
@@ -55,6 +62,10 @@ export const PROMPT_CONTEXT_KEYS = [
55
62
  "files_changed",
56
63
  "files_changed_count",
57
64
  "user",
65
+ "diff_stat", // NEW: "3 files changed, 41 insertions(+), 7 deletions(-)"
66
+ "repo", // NEW: basename of cwd or git remote (disambiguates monorepos)
67
+ "staged_files", // NEW: files staged for commit
68
+ "unstaged_files", // NEW: modified-but-unstaged files
58
69
  ] as const;
59
70
 
60
71
  /** A fully populated prompt context: one entry per PROMPT_CONTEXT_KEYS. */
@@ -66,8 +77,13 @@ export const DEFAULT_TASKS: DoAlwaysTask[] = [
66
77
  name: "Review",
67
78
  category: "Plan",
68
79
  description: "Review code and double-check changes (Plan)",
80
+ requireDirty: true,
69
81
  prompt:
70
- "Review the changes on branch {{branch}} ({{files_changed_count}} changed files: {{files_changed}}). Last commit: {{last_commit}}. Check `git status` and `git diff` to see what changed, then double-check the changes for bugs, edge cases, security issues, and consistency with the rest of the codebase. Do a plan proposal for the fixes if needed. Do a summary of your findings",
82
+ "Review the changes on branch {{branch}} ({{files_changed_count}} changed files: {{files_changed}}). " +
83
+ "Change summary: {{diff_stat}}. Last commit: {{last_commit}}. " +
84
+ "Check `git status` and `git diff` to see what changed, then double-check the changes for bugs, " +
85
+ "edge cases, security issues, and consistency with the rest of the codebase. " +
86
+ "Do a plan proposal for the fixes if needed. Do a summary of your findings",
71
87
  },
72
88
  {
73
89
  name: "Cleanup",
@@ -122,6 +138,7 @@ export const DEFAULT_TASKS: DoAlwaysTask[] = [
122
138
  name: "Commit",
123
139
  category: "Ops",
124
140
  description: "Prepare a clean commit",
141
+ requireDirty: true,
125
142
  prompt:
126
143
  "Prepare the working tree on branch {{branch}} ({{files_changed_count}} changed files: {{files_changed}}) for a clean commit: stage the relevant changes, and write a clear commit message describing what changed and why. Do not push.",
127
144
  },
@@ -130,7 +147,7 @@ export const DEFAULT_TASKS: DoAlwaysTask[] = [
130
147
  category: "Plan",
131
148
  description: "Propose new features (Plan)",
132
149
  prompt:
133
- "Review this project and propose new features that would add value. For each idea, describe the problem it solves, the user benefit, and a rough implementation approach. Prioritize by impact and effort. Do not make any changes yet.",
150
+ "Review this project and propose new features that would add value. For each idea, describe the problem it solves, the user benefit, and a rough implementation approach. Prioritize by impact and effort. Do not make any changes yet. Try to evaluate how many lines this will be in term of changes, if this will breaks API, compatibility issue.",
134
151
  },
135
152
  ];
136
153
 
@@ -170,8 +187,9 @@ export function parseConfig(
170
187
  };
171
188
  if (typeof t.description === "string") task.description = t.description;
172
189
  if (typeof t.category === "string" && t.category.trim() !== "") task.category = t.category.trim();
173
- if (typeof t.autoRun === "boolean") task.autoRun = t.autoRun;
174
- tasks.push(task);
190
+ if (typeof t.autoRun === "boolean") task.autoRun = t.autoRun;
191
+ if (typeof t.requireDirty === "boolean") task.requireDirty = t.requireDirty;
192
+ tasks.push(task);
175
193
  } else {
176
194
  onError(`do-always: skipping invalid task in ${path} (each task needs "name" and "prompt")`);
177
195
  }
@@ -301,6 +319,21 @@ export function shouldAutoRun(task: DoAlwaysTask): boolean {
301
319
  return (task.category ?? "").trim().toLowerCase() === "plan";
302
320
  }
303
321
 
322
+ /**
323
+ * Evaluate a task's guards against the current prompt context. Returns a
324
+ * notification message (caller should notify and abort) when a guard fails, or
325
+ * null when every guard is met and the task may proceed.
326
+ *
327
+ * Guards keep low-value round-trips down: e.g. `requireDirty` blocks Review and
328
+ * Commit on a clean tree so the agent is never asked to inspect nothing.
329
+ */
330
+ export function evaluateGuards(task: DoAlwaysTask, ctx: PromptContext): string | null {
331
+ if (task.requireDirty && ctx.files_changed_count === "0") {
332
+ return "working tree is clean — nothing to review";
333
+ }
334
+ return null;
335
+ }
336
+
304
337
  /**
305
338
  * Resolve a task from a command argument: by number (1-based) or by name (case-insensitive).
306
339
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-do-always",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "type": "module",
5
5
  "description": "Pi extension: /do-always — pick a common task by number, it fills your prompt",
6
6
  "author": {