pi-do-always 0.18.0 → 0.19.0
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
|
@@ -11,6 +11,26 @@ type to filter, scroll or click, or navigate with arrows + Enter → the task's
|
|
|
11
11
|
|
|
12
12
|

|
|
13
13
|
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
Install it from npm as a Pi package, which loads the bundled `index.ts` (and its `tasks.ts`) without
|
|
17
|
+
managing symlinks:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pi install npm:pi-do-always
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Manage it with `pi list` (to see installed sources) and `pi remove <source>` using the same
|
|
24
|
+
source you installed with (e.g. `pi remove npm:pi-do-always`).
|
|
25
|
+
|
|
26
|
+
Alternatively, you can install from the git repo or symlink a local checkout for development:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
ln -s "$PWD" ~/.pi/agent/extensions/do-always # uninstall with: rm that symlink
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For development you can also load it explicitly: `npm run dev` (runs `pi --extension ./extensions/pi-do-always/index.ts`).
|
|
33
|
+
|
|
14
34
|
## Usage
|
|
15
35
|
|
|
16
36
|
|Command|What it does|
|
|
@@ -199,26 +219,6 @@ notification instead, so you can reply with the item numbers to execute. The
|
|
|
199
219
|
questionnaire is offered only on the single auto-run path (selector pick,
|
|
200
220
|
`/do-always <n>`, commit picker) — chain steps never get it.
|
|
201
221
|
|
|
202
|
-
## Install
|
|
203
|
-
|
|
204
|
-
Install it from npm as a Pi package, which loads the bundled `index.ts` (and its `tasks.ts`) without
|
|
205
|
-
managing symlinks:
|
|
206
|
-
|
|
207
|
-
```bash
|
|
208
|
-
pi install npm:pi-do-always
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
Manage it with `pi list` (to see installed sources) and `pi remove <source>` using the same
|
|
212
|
-
source you installed with (e.g. `pi remove npm:pi-do-always`).
|
|
213
|
-
|
|
214
|
-
Alternatively, you can install from the git repo or symlink a local checkout for development:
|
|
215
|
-
|
|
216
|
-
```bash
|
|
217
|
-
ln -s "$PWD" ~/.pi/agent/extensions/do-always # uninstall with: rm that symlink
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
For development you can also load it explicitly: `npm run dev` (runs `pi --extension ./extensions/pi-do-always/index.ts`).
|
|
221
|
-
|
|
222
222
|
## Tasks configuration
|
|
223
223
|
|
|
224
224
|
Tasks are read from JSON files (an array of tasks, or the object form `{"tasks": [...], "shortcut": "f4"}`):
|
|
@@ -246,6 +246,7 @@ locations above to make it your own:
|
|
|
246
246
|
Fields:
|
|
247
247
|
|
|
248
248
|
- `name` (required) — short unique id, used for `/do-always <name>`
|
|
249
|
+
- `aliases` (optional) — an array of short names that also resolve to this task on the command line. Each alias is case-insensitive and is tried before the task's own name, so `/do-always r` can stand in for `/do-always review`. An invalid value (not an array of strings) is ignored with a warning.
|
|
249
250
|
- `category` (optional) — group header the task is shown under in the selector (e.g. `"Plan"`, `"Do"`). Matching is case-insensitive and the header is title-cased, so `"plan"` and `"Plan"` land in the same `Plan` group. Tasks without a category fall under `Other`. The built-in defaults are grouped into `Plan`, `Browse`, `Do`, `Docs`, and `Ops`.
|
|
250
251
|
- `description` (optional) — one-line label shown in the selector
|
|
251
252
|
- `prompt` (required) — the text filled into the editor (supports `{{placeholders}}` — see [Prompt placeholders](#prompt-placeholders))
|
|
@@ -265,6 +266,14 @@ In the object form you can also configure the selector shortcut:
|
|
|
265
266
|
- `report` (optional) — whether chain runs write a Markdown report file in the project root (one per run, appended as each step finishes). Default `true`; set `false` to disable. The project file's value wins over the global one. See [Chains](#chains).
|
|
266
267
|
- `questionnaire` (optional) — whether completed auto-run tasks whose reply carries a plan block offer the selection questionnaire. Default `true`; set `false` to keep the plain summary notification. The project file's value wins over the global one. See [Plan questionnaire](#plan-questionnaire).
|
|
267
268
|
- `hidePlan` (optional) — whether the raw `plan` block is stripped from the transcript after a completed auto-run task (TUI only — in non-TUI modes the block is always kept). Default `true`; set `false` to keep the block visible in the conversation. The project file's value wins over the global one.
|
|
269
|
+
- `aliases` (optional) — a global alias map: an object that maps short alias strings to task names, e.g. `{ "rc": "Review changes", "bl": "Build" }`. Each alias is case-insensitive and is tried before the task's own name, so `/do-always rc` resolves to the task named `Review changes`. Keys and values are trimmed; entries with empty keys or non-string values are silently dropped. The project file's value wins over the global one.
|
|
270
|
+
|
|
271
|
+
```json
|
|
272
|
+
{
|
|
273
|
+
"tasks": [ { "name": "review", "prompt": "…" } ],
|
|
274
|
+
"aliases": { "r": "review", "rc": "review changes" }
|
|
275
|
+
}
|
|
276
|
+
```
|
|
268
277
|
|
|
269
278
|
Example project file that only *adds* tasks without overriding the global set:
|
|
270
279
|
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"name": "Build",
|
|
62
62
|
"category": "Do",
|
|
63
63
|
"description": "Test build is ok and fix issues",
|
|
64
|
-
"prompt": "Build the project (run the build
|
|
64
|
+
"prompt": "Build the project (run the project's build command — e.g. `npm run build`, `cargo build`, `go build`, `make`, `mvn package`; find the build entry point — plus type check if available). If the build fails, diagnose the errors and fix them, then re-build until it succeeds. Summarize what was broken and what you changed."
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
"name": "Tests",
|
|
@@ -73,14 +73,14 @@
|
|
|
73
73
|
"name": "Readme",
|
|
74
74
|
"category": "Docs",
|
|
75
75
|
"description": "Update the README.md",
|
|
76
|
-
"prompt": "Update the README.md to match the current state of the project. Check the code, scripts, and configuration, then update the README
|
|
76
|
+
"prompt": "Update the project's README (e.g. README.md) to match the current state of the project. Check the code, scripts, and configuration, then update the README sections that are now out of date (description, installation, usage, configuration). Keep it concise and accurate."
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
"name": "Release",
|
|
80
80
|
"category": "Ops",
|
|
81
81
|
"description": "Prepare a release (version, changelog, tag)",
|
|
82
82
|
"when": "git",
|
|
83
|
-
"prompt": "Prepare a release for this project (branch {{branch}}): check `git log` since the last tag, then bump the version in package.json
|
|
83
|
+
"prompt": "Prepare a release for this project (branch {{branch}}): check `git log` since the last tag, then bump the project's version to the next version in its version manifest (e.g. package.json, Cargo.toml, pyproject.toml, go.mod, pom.xml — find where the current version is declared), and add a changelog entry under that exact version summarizing the changes (in the project's changelog file, e.g. CHANGELOG.md). The changelog entry must use the same version number now set in the version manifest — never add a changelog section for a version the manifest does not yet contain, and never leave an 'Unreleased' or placeholder version heading. Create a git tag if git present. Do not push."
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
86
|
"name": "Commit",
|
|
@@ -86,6 +86,14 @@ export interface DoAlwaysTask {
|
|
|
86
86
|
* category by default).
|
|
87
87
|
*/
|
|
88
88
|
hidePlan?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Short names that resolve to this task on the command line. Each alias
|
|
91
|
+
* is case-insensitive and is tried before the task's own name. Aliases
|
|
92
|
+
* must be single words (no spaces) and must not collide with other
|
|
93
|
+
* tasks' aliases or names (a collision is silently resolved to the
|
|
94
|
+
* first-encountered task).
|
|
95
|
+
*/
|
|
96
|
+
aliases?: string[];
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
/** The set of known guard types (used for validation at parse time). */
|
|
@@ -147,6 +155,13 @@ type DoAlwaysConfig =
|
|
|
147
155
|
* block visible in the transcript.
|
|
148
156
|
*/
|
|
149
157
|
hidePlan?: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* A global alias map: `{ "rc": "Review changes" }` maps a short
|
|
160
|
+
* alias string to a task name. Aliases are case-insensitive and
|
|
161
|
+
* are tried before the task's own name. Invalid entries (non-string
|
|
162
|
+
* values, empty keys) are silently ignored.
|
|
163
|
+
*/
|
|
164
|
+
aliases?: Record<string, string>;
|
|
150
165
|
};
|
|
151
166
|
|
|
152
167
|
/** Shortcut used when neither config file specifies one. */
|
|
@@ -182,6 +197,11 @@ interface ParsedDoAlwaysConfig {
|
|
|
182
197
|
* undefined when the file does not set one (default: on).
|
|
183
198
|
*/
|
|
184
199
|
hidePlan: boolean | undefined;
|
|
200
|
+
/**
|
|
201
|
+
* A global alias map parsed from the config object: `{ "rc": "Review changes" }`.
|
|
202
|
+
* undefined when the file does not set one.
|
|
203
|
+
*/
|
|
204
|
+
aliases: Record<string, string> | undefined;
|
|
185
205
|
}
|
|
186
206
|
|
|
187
207
|
import { existsSync } from "node:fs";
|
|
@@ -426,14 +446,14 @@ export const DEFAULT_TASKS: DoAlwaysTask[] = [
|
|
|
426
446
|
category: "Docs",
|
|
427
447
|
description: "Update the README.md",
|
|
428
448
|
prompt:
|
|
429
|
-
"Update the README.md to match the current state of the project. Check the code, scripts, and configuration, then update the README
|
|
449
|
+
"Update the project's README (e.g. README.md) to match the current state of the project. Check the code, scripts, and configuration, then update the README sections that are now out of date (description, installation, usage, configuration). Keep it concise and accurate.",
|
|
430
450
|
},
|
|
431
451
|
{
|
|
432
452
|
name: "Build",
|
|
433
453
|
category: "Do",
|
|
434
454
|
description: "Test build is ok and fix issues",
|
|
435
455
|
prompt:
|
|
436
|
-
"Build the project (run the build
|
|
456
|
+
"Build the project (run the project's build command — e.g. `npm run build`, `cargo build`, `go build`, `make`, `mvn package`; find the build entry point — plus type check if available). If the build fails, diagnose the errors and fix them, then re-build until it succeeds. Summarize what was broken and what you changed.",
|
|
437
457
|
},
|
|
438
458
|
{
|
|
439
459
|
name: "Security",
|
|
@@ -462,7 +482,7 @@ export const DEFAULT_TASKS: DoAlwaysTask[] = [
|
|
|
462
482
|
description: "Prepare a release (version, changelog, tag)",
|
|
463
483
|
when: "git",
|
|
464
484
|
prompt:
|
|
465
|
-
"Prepare a release for this project (branch {{branch}}): check `git log` since the last tag, then bump the version in package.json
|
|
485
|
+
"Prepare a release for this project (branch {{branch}}): check `git log` since the last tag, then bump the project's version to the next version in its version manifest (e.g. package.json, Cargo.toml, pyproject.toml, go.mod, pom.xml — find where the current version is declared), and add a changelog entry under that exact version summarizing the changes (in the project's changelog file, e.g. CHANGELOG.md). The changelog entry must use the same version number now set in the version manifest — never add a changelog section for a version the manifest does not yet contain, and never leave an 'Unreleased' or placeholder version heading. Create a git tag if git present. Do not push.",
|
|
466
486
|
},
|
|
467
487
|
{
|
|
468
488
|
name: "Commit",
|
|
@@ -518,14 +538,14 @@ export function parseConfig(
|
|
|
518
538
|
data = JSON.parse(raw);
|
|
519
539
|
} catch (err) {
|
|
520
540
|
onError(`do-always: invalid JSON in ${path}: ${err}`);
|
|
521
|
-
return { tasks: [], shortcut: undefined, report: undefined, questionnaire: undefined, hidePlan: undefined };
|
|
541
|
+
return { tasks: [], shortcut: undefined, report: undefined, questionnaire: undefined, hidePlan: undefined, aliases: undefined };
|
|
522
542
|
}
|
|
523
543
|
|
|
524
544
|
const list = Array.isArray(data) ? data : data?.tasks;
|
|
525
545
|
|
|
526
546
|
if (!Array.isArray(list)) {
|
|
527
547
|
onError(`do-always: ${path} must be a JSON array of tasks or {"tasks": [...]}`);
|
|
528
|
-
return { tasks: [], shortcut: undefined, report: undefined, questionnaire: undefined, hidePlan: undefined };
|
|
548
|
+
return { tasks: [], shortcut: undefined, report: undefined, questionnaire: undefined, hidePlan: undefined, aliases: undefined };
|
|
529
549
|
}
|
|
530
550
|
|
|
531
551
|
const tasks: DoAlwaysTask[] = [];
|
|
@@ -544,6 +564,13 @@ export function parseConfig(
|
|
|
544
564
|
if (typeof t.notForCommits === "boolean") task.notForCommits = t.notForCommits;
|
|
545
565
|
if (typeof t.questionnaire === "boolean") task.questionnaire = t.questionnaire;
|
|
546
566
|
if (typeof t.hidePlan === "boolean") task.hidePlan = t.hidePlan;
|
|
567
|
+
if (t.aliases !== undefined) {
|
|
568
|
+
if (Array.isArray(t.aliases) && t.aliases.every((a) => typeof a === "string")) {
|
|
569
|
+
task.aliases = t.aliases.filter((a) => a.trim() !== "");
|
|
570
|
+
} else {
|
|
571
|
+
onError(`do-always: ignoring invalid "aliases" in ${path} (expected an array of strings)`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
547
574
|
if (t.browser !== undefined) {
|
|
548
575
|
if (typeof t.browser === "string" && BROWSER_TYPES.includes(t.browser as BrowserType)) {
|
|
549
576
|
task.browser = t.browser as BrowserType;
|
|
@@ -602,8 +629,20 @@ export function parseConfig(
|
|
|
602
629
|
if (typeof data.hidePlan === "boolean") hidePlan = data.hidePlan;
|
|
603
630
|
else onError(`do-always: ignoring invalid "hidePlan" in ${path} (expected true or false)`);
|
|
604
631
|
}
|
|
632
|
+
let aliases: Record<string, string> | undefined;
|
|
633
|
+
if (!Array.isArray(data) && "aliases" in data) {
|
|
634
|
+
if (typeof data.aliases === "object" && data.aliases !== null && !Array.isArray(data.aliases)) {
|
|
635
|
+
const map: Record<string, string> = {};
|
|
636
|
+
for (const [k, v] of Object.entries(data.aliases)) {
|
|
637
|
+
if (typeof k === "string" && k.trim() !== "" && typeof v === "string") {
|
|
638
|
+
map[k.trim()] = v.trim();
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (Object.keys(map).length > 0) aliases = map;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
605
644
|
|
|
606
|
-
return { tasks, shortcut, merge, report, questionnaire, hidePlan };
|
|
645
|
+
return { tasks, shortcut, merge, report, questionnaire, hidePlan, aliases };
|
|
607
646
|
}
|
|
608
647
|
|
|
609
648
|
const KEY_MODIFIERS = new Set(["ctrl", "shift", "alt", "super"]);
|
|
@@ -996,7 +1035,11 @@ export function resolveTask(tasks: DoAlwaysTask[], arg: string): DoAlwaysTask |
|
|
|
996
1035
|
const n = Number(a);
|
|
997
1036
|
return n >= 1 && n <= tasks.length ? tasks[n - 1] : undefined;
|
|
998
1037
|
}
|
|
999
|
-
|
|
1038
|
+
// Try aliases first (case-insensitive), then the task's own name.
|
|
1039
|
+
const lower = a.toLowerCase();
|
|
1040
|
+
const aliasMatch = tasks.find((t) => t.aliases?.some((alias) => alias.toLowerCase() === lower));
|
|
1041
|
+
if (aliasMatch) return aliasMatch;
|
|
1042
|
+
return tasks.find((t) => t.name.toLowerCase() === lower);
|
|
1000
1043
|
}
|
|
1001
1044
|
|
|
1002
1045
|
/** Matches a `{{key}}` placeholder: key is [A-Za-z0-9_]+, optional inner whitespace. */
|