pi-do-always 0.4.3 → 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"
|
|
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
|
|
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}}).
|
|
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
|
-
|
|
174
|
-
|
|
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
|
*/
|