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
  ![do-always — the numbered task selector](pi.image.png)
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 script, e.g. `npm run build`, 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."
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.md sections that are now out of date (description, installation, usage, configuration). Keep it concise and accurate."
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 (or the equivalent location) to the next version, and add a changelog entry under that exact version summarizing the changes. The changelog entry must use the same version number now set in package.json — never add a changelog section for a version that package.json does not yet contain, and never leave an 'Unreleased' or placeholder version heading. Create a git tag if git present. Do not push."
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.md sections that are now out of date (description, installation, usage, configuration). Keep it concise and accurate.",
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 script, e.g. `npm run build`, 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.",
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 (or the equivalent location) to the next version, and add a changelog entry under that exact version summarizing the changes. The changelog entry must use the same version number now set in package.json — never add a changelog section for a version that package.json does not yet contain, and never leave an 'Unreleased' or placeholder version heading. Create a git tag if git present. Do not push.",
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
- return tasks.find((t) => t.name.toLowerCase() === a.toLowerCase());
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. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-do-always",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "description": "Pi extension: /do-always — pick a common task by number, it fills your prompt",
6
6
  "author": {