@pi-unipi/ralph 2.14.2 → 2.16.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
@@ -18,6 +18,8 @@ Ralph is for work that takes more than one pass — migrating a codebase, implem
18
18
  | `/unipi:ralph list --archived` | Show archived loops |
19
19
  | `/unipi:ralph nuke [--yes]` | Delete all ralph data |
20
20
 
21
+ Command arguments autocomplete: subcommands with descriptions, live loop names with status, and `start` flags are suggested as you type.
22
+
21
23
  ### Options
22
24
 
23
25
  | Flag | Description |
package/SKILL.md CHANGED
@@ -24,6 +24,8 @@ Long-running iterative development loops. Run complex tasks across multiple iter
24
24
  | `/unipi:ralph list --archived` | Show archived loops |
25
25
  | `/unipi:ralph nuke [--yes]` | Delete all ralph data |
26
26
 
27
+ Arguments autocomplete: typing `/unipi:ralph ` suggests subcommands with descriptions; `resume`/`cancel`/`archive` suggest loop names with live status; `start` suggests its flags.
28
+
27
29
  ## Tools
28
30
 
29
31
  | Tool | Description |
package/completions.ts ADDED
@@ -0,0 +1,170 @@
1
+ /**
2
+ * @unipi/ralph — Argument completions for /unipi:ralph
3
+ *
4
+ * Pure, testable completion builder. index.ts wires it into
5
+ * registerCommand via getArgumentCompletions.
6
+ *
7
+ * NOTE: pi passes the ENTIRE argument text (everything after the command
8
+ * name, up to the cursor) and replaces ALL of it with item.value on accept.
9
+ * Nested suggestions must therefore return the full replacement string,
10
+ * e.g. "resume myloop" or "start mytask --max-iterations".
11
+ */
12
+
13
+ import { RALPH_STATUS_ICONS } from "@pi-unipi/core";
14
+ import type { LoopStatus } from "./ralph-loop.js";
15
+
16
+ export interface CompletionItem {
17
+ value: string;
18
+ label: string;
19
+ description?: string;
20
+ }
21
+
22
+ /** Minimal loop shape the completion builder needs. */
23
+ export interface LoopSummary {
24
+ name: string;
25
+ status: LoopStatus;
26
+ iteration: number;
27
+ maxIterations: number;
28
+ }
29
+
30
+ /** Subcommands, shown when no subcommand has been typed yet. */
31
+ const SUBCOMMANDS: CompletionItem[] = [
32
+ { value: "start", label: "start", description: "Start a new loop" },
33
+ { value: "stop", label: "stop", description: "Stop the current loop" },
34
+ { value: "resume", label: "resume", description: "Resume a paused loop" },
35
+ { value: "status", label: "status", description: "Show all loops" },
36
+ { value: "list", label: "list", description: "Show loops (--archived for archived)" },
37
+ { value: "cancel", label: "cancel", description: "Delete loop state" },
38
+ { value: "archive", label: "archive", description: "Move a loop to the archive" },
39
+ {
40
+ value: "clean",
41
+ label: "clean",
42
+ description: "Clean completed loops (--all also deletes task files)",
43
+ },
44
+ { value: "nuke", label: "nuke", description: "Delete all ralph data (--yes skips confirm)" },
45
+ ];
46
+
47
+ /** Flags accepted by `start` that take a numeric value. */
48
+ const START_VALUE_FLAGS = ["--max-iterations", "--items-per-iteration", "--reflect-every"];
49
+
50
+ const START_FLAGS: CompletionItem[] = [
51
+ {
52
+ value: "--max-iterations",
53
+ label: "--max-iterations",
54
+ description: "Stop after N iterations (default 50)",
55
+ },
56
+ {
57
+ value: "--items-per-iteration",
58
+ label: "--items-per-iteration",
59
+ description: "Process N items per iteration",
60
+ },
61
+ {
62
+ value: "--reflect-every",
63
+ label: "--reflect-every",
64
+ description: "Reflection checkpoint every N iterations",
65
+ },
66
+ ];
67
+
68
+ function describeLoop(l: LoopSummary): string {
69
+ const maxStr = l.maxIterations > 0 ? `/${l.maxIterations}` : "";
70
+ const icon = RALPH_STATUS_ICONS[l.status] ?? "•";
71
+ return `${icon} ${l.status} · iter ${l.iteration}${maxStr}`;
72
+ }
73
+
74
+ function suggestLoopNames(
75
+ cmd: string,
76
+ partial: string,
77
+ listLoops: () => LoopSummary[],
78
+ ): CompletionItem[] {
79
+ return listLoops()
80
+ .filter((l) => {
81
+ if (cmd === "resume") return l.status !== "completed";
82
+ if (cmd === "archive") return l.status !== "active";
83
+ return true; // cancel: any non-archived loop
84
+ })
85
+ .filter((l) => l.name.startsWith(partial) && l.name !== partial)
86
+ .sort((a, b) => a.name.localeCompare(b.name))
87
+ .map((l) => ({
88
+ value: `${cmd} ${l.name}`,
89
+ label: l.name,
90
+ description: describeLoop(l),
91
+ }));
92
+ }
93
+
94
+ /**
95
+ * Build completion items for the argument text after `/unipi:ralph`.
96
+ * `listLoops` supplies non-archived loop state (empty before session_start).
97
+ * Returns null when there is nothing to suggest.
98
+ */
99
+ export function buildRalphArgumentCompletions(
100
+ argumentText: string,
101
+ listLoops: () => LoopSummary[],
102
+ ): CompletionItem[] | null {
103
+ const endsWithSpace = /\s$/.test(argumentText);
104
+ const tokens = argumentText.trim().split(/\s+/).filter((t) => t.length > 0);
105
+ const complete = endsWithSpace ? tokens : tokens.slice(0, -1);
106
+ const partial = endsWithSpace ? "" : (tokens[tokens.length - 1] ?? "");
107
+ // Everything typed before the in-flight token — every suggestion must
108
+ // preserve it, because pi replaces the whole argument text with item.value.
109
+ const head = endsWithSpace
110
+ ? argumentText
111
+ : argumentText.slice(0, argumentText.length - partial.length);
112
+
113
+ // No subcommand yet → suggest subcommands
114
+ if (complete.length === 0) {
115
+ const items = SUBCOMMANDS.filter((s) => s.value.startsWith(partial) && s.value !== partial).map(
116
+ (s) => ({ ...s, value: head + s.value }),
117
+ );
118
+ return items.length > 0 ? items : null;
119
+ }
120
+
121
+ const cmd = complete[0];
122
+
123
+ // Typing the numeric value of a start flag → no suggestions
124
+ if (START_VALUE_FLAGS.includes(complete[complete.length - 1])) return null;
125
+
126
+ switch (cmd) {
127
+ case "resume":
128
+ case "cancel":
129
+ case "archive": {
130
+ if (complete.length > 1) return null; // name chosen, nothing more
131
+ const items = suggestLoopNames(cmd, partial, listLoops).map((i) => ({
132
+ ...i,
133
+ value: head + i.label,
134
+ }));
135
+ return items.length > 0 ? items : null;
136
+ }
137
+
138
+ case "start": {
139
+ if (complete.length === 1) return null; // loop name is free-form
140
+ const used = complete.slice(1).filter((t) => t.startsWith("--"));
141
+ const items = START_FLAGS.filter(
142
+ (f) => !used.includes(f.value) && f.value.startsWith(partial) && f.value !== partial,
143
+ ).map((f) => ({
144
+ value: head + f.value,
145
+ label: f.label,
146
+ description: f.description,
147
+ }));
148
+ return items.length > 0 ? items : null;
149
+ }
150
+
151
+ case "list":
152
+ case "clean":
153
+ case "nuke": {
154
+ const flag = cmd === "list" ? "--archived" : cmd === "clean" ? "--all" : "--yes";
155
+ const desc =
156
+ cmd === "list"
157
+ ? "Show archived loops"
158
+ : cmd === "clean"
159
+ ? "Also delete task files"
160
+ : "Skip confirmation prompt";
161
+ if (complete.slice(1).includes(flag) || flag === partial || !flag.startsWith(partial)) {
162
+ return null;
163
+ }
164
+ return [{ value: head + flag, label: flag, description: desc }];
165
+ }
166
+
167
+ default:
168
+ return null; // stop / status take no arguments
169
+ }
170
+ }
package/index.ts CHANGED
@@ -24,6 +24,7 @@ function getInfoRegistry() {
24
24
  }
25
25
  import { RalphLoopManager } from "./ralph-loop.js";
26
26
  import { registerRalphTools } from "./tools.js";
27
+ import { buildRalphArgumentCompletions } from "./completions.js";
27
28
 
28
29
  /** Package version */
29
30
  const VERSION = getPackageVersion(dirname(fileURLToPath(import.meta.url)));
@@ -200,6 +201,8 @@ To stop: press ESC to interrupt, then run /unipi:ralph-stop when idle`;
200
201
 
201
202
  pi.registerCommand("unipi:ralph", {
202
203
  description: "Ralph loop commands (start, stop, resume, status, etc.)",
204
+ getArgumentCompletions: (argumentText: string) =>
205
+ buildRalphArgumentCompletions(argumentText, () => manager?.listLoops(false) ?? []),
203
206
  handler: async (args, ctx) => {
204
207
  const parts = parseArgs(args);
205
208
  const cmd = parts[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/ralph",
3
- "version": "2.14.2",
3
+ "version": "2.16.0",
4
4
  "description": "Long-running iterative development loops for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -31,8 +31,8 @@
31
31
  "test": "npx tsx --test reminder.test.ts"
32
32
  },
33
33
  "dependencies": {
34
- "@pi-unipi/core": "2.14.2",
35
- "@pi-unipi/info-screen": "2.14.2"
34
+ "@pi-unipi/core": "2.16.0",
35
+ "@pi-unipi/info-screen": "2.16.0"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@earendil-works/pi-ai": "^0.84.0",