@pi-unipi/command-enchantment 2.0.1 → 2.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/command-enchantment",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Enhanced TUI autocomplete for /unipi:* commands — colored, sorted, and grouped by package",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,120 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { existsSync, globSync, readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+
5
+ function findRepoRoot(start: string): string {
6
+ let dir = start;
7
+ while (dir !== dirname(dir)) {
8
+ if (existsSync(join(dir, "packages", "autocomplete", "src", "constants.ts"))) return dir;
9
+ dir = dirname(dir);
10
+ }
11
+ return start;
12
+ }
13
+
14
+ const root = findRepoRoot(process.cwd());
15
+
16
+ function read(path: string): string {
17
+ return readFileSync(join(root, path), "utf-8");
18
+ }
19
+
20
+ function collectConstants(): Map<string, string> {
21
+ const constants = new Map<string, string>([["UNIPI_PREFIX", "unipi:"]]);
22
+
23
+ for (const path of globSync("packages/**/*.ts", { cwd: root })) {
24
+ const text = read(path);
25
+ for (const obj of text.matchAll(/(?:export\s+)?const\s+(\w+)\s*=\s*\{([\s\S]*?)\}\s*as\s+const/g)) {
26
+ const [, name, body] = obj;
27
+ for (const item of body.matchAll(/(\w+):\s*"([^"]+)"/g)) {
28
+ constants.set(`${name}.${item[1]}`, item[2]);
29
+ }
30
+ }
31
+ }
32
+
33
+ return constants;
34
+ }
35
+
36
+ function evaluateCommandExpression(expr: string, constants: Map<string, string>): string | null {
37
+ const trimmed = expr.trim();
38
+
39
+ if (trimmed.startsWith('"') || trimmed.startsWith("'")) {
40
+ const quote = trimmed[0];
41
+ const end = trimmed.indexOf(quote, 1);
42
+ return end > 0 ? trimmed.slice(1, end) : null;
43
+ }
44
+
45
+ if (trimmed.startsWith("`")) {
46
+ const end = trimmed.lastIndexOf("`");
47
+ if (end <= 0) return null;
48
+ return trimmed.slice(1, end).replace(/\$\{([^}]+)\}/g, (_match, key: string) => {
49
+ const resolved = constants.get(key.trim());
50
+ return resolved ?? `\${${key}}`;
51
+ });
52
+ }
53
+
54
+ return constants.get(trimmed) ?? null;
55
+ }
56
+
57
+ function registeredCommands(): { commands: Set<string>; nonUnipi: string[]; unresolved: string[] } {
58
+ const constants = collectConstants();
59
+ const commands = new Set<string>();
60
+ const nonUnipi: string[] = [];
61
+ const unresolved: string[] = [];
62
+
63
+ for (const path of globSync("packages/**/*.ts", { cwd: root }).sort()) {
64
+ const text = read(path);
65
+ if (!text.includes("registerCommand")) continue;
66
+
67
+ for (const match of text.matchAll(/\.registerCommand\(\s*([^,\n]+)/g)) {
68
+ const expr = match[1].trim();
69
+
70
+ // Workflow registers a loop over WORKFLOW_COMMANDS via local `fullCommand`.
71
+ if (path === "packages/workflow/commands.ts" && expr === "fullCommand") {
72
+ for (const [key, value] of constants) {
73
+ if (key.startsWith("WORKFLOW_COMMANDS.")) commands.add(`unipi:${value}`);
74
+ }
75
+ continue;
76
+ }
77
+
78
+ const command = evaluateCommandExpression(expr, constants);
79
+ if (!command || command.includes("${")) {
80
+ unresolved.push(`${path}: ${expr}`);
81
+ continue;
82
+ }
83
+
84
+ if (command.startsWith("unipi:")) {
85
+ commands.add(command);
86
+ } else {
87
+ nonUnipi.push(`${command} (${path})`);
88
+ }
89
+ }
90
+ }
91
+
92
+ return { commands, nonUnipi, unresolved };
93
+ }
94
+
95
+ function autocompleteRegistry(): { registry: Set<string>; descriptions: Set<string>; registryPackages: Set<string>; labels: Set<string> } {
96
+ const text = read("packages/autocomplete/src/constants.ts");
97
+ const registryBody = text.match(/export const COMMAND_REGISTRY[^=]*= \{([\s\S]*?)\n\};/)?.[1] ?? "";
98
+ const descriptionsBody = text.match(/export const COMMAND_DESCRIPTIONS[^=]*= \{([\s\S]*?)\n\};/)?.[1] ?? "";
99
+ const labelsBody = text.match(/export const PACKAGE_LABELS[^=]*= \{([\s\S]*?)\n\};/)?.[1] ?? "";
100
+
101
+ const registry = new Set([...registryBody.matchAll(/"(unipi:[^"]+)"\s*:/g)].map((m) => m[1]));
102
+ const descriptions = new Set([...descriptionsBody.matchAll(/"(unipi:[^"]+)"\s*:/g)].map((m) => m[1]));
103
+ const registryPackages = new Set([...registryBody.matchAll(/"unipi:[^"]+"\s*:\s*"([^"]+)"/g)].map((m) => m[1]));
104
+ const labels = new Set([...labelsBody.matchAll(/^\s*"?([a-z][a-z0-9-]*)"?:\s*"/gm)].map((m) => m[1]));
105
+
106
+ return { registry, descriptions, registryPackages, labels };
107
+ }
108
+
109
+ describe("autocomplete command registry audit", () => {
110
+ it("mirrors every registered /unipi:* command and has no non-unipi package commands", () => {
111
+ const registered = registeredCommands();
112
+ const autocomplete = autocompleteRegistry();
113
+
114
+ expect(registered.unresolved, "unresolved registerCommand expressions").toEqual([]);
115
+ expect(registered.nonUnipi, "package commands must use the unipi: prefix").toEqual([]);
116
+ expect([...registered.commands].sort()).toEqual([...autocomplete.registry].sort());
117
+ expect([...autocomplete.registry].sort()).toEqual([...autocomplete.descriptions].sort());
118
+ expect([...autocomplete.registryPackages].filter((pkg) => !autocomplete.labels.has(pkg)).sort()).toEqual([]);
119
+ });
120
+ });
package/src/constants.ts CHANGED
@@ -20,6 +20,7 @@ export const PACKAGE_ORDER: string[] = [
20
20
  "workflow",
21
21
  "ralph",
22
22
  "memory",
23
+ "btw",
23
24
  "milestone",
24
25
  "mcp",
25
26
  "utility",
@@ -41,6 +42,7 @@ export const PACKAGE_COLORS: Record<string, string> = {
41
42
  workflow: `${ESC}[91m`, // Bright Red
42
43
  ralph: `${ESC}[33m`, // Yellow/Orange
43
44
  memory: `${ESC}[93m`, // Bright Yellow
45
+ btw: `${ESC}[95m`, // Bright Magenta
44
46
  milestone: `${ESC}[32m`, // Green
45
47
  mcp: `${ESC}[32m`, // Green
46
48
  utility: `${ESC}[36m`, // Cyan
@@ -57,7 +59,7 @@ export const PACKAGE_COLORS: Record<string, string> = {
57
59
  };
58
60
 
59
61
  // ─── Command Registry ────────────────────────────────────────────────
60
- /** Mapping of full command name → package name (80 verified commands) */
62
+ /** Mapping of full command name → package name (88 verified commands) */
61
63
  export const COMMAND_REGISTRY: Record<string, string> = {
62
64
  // workflow (20 commands)
63
65
  "unipi:brainstorm": "workflow",
@@ -95,6 +97,14 @@ export const COMMAND_REGISTRY: Record<string, string> = {
95
97
  "unipi:global-memory-list": "memory",
96
98
  "unipi:memory-settings": "memory",
97
99
 
100
+ // btw (6 commands)
101
+ "unipi:btw": "btw",
102
+ "unipi:btw-tangent": "btw",
103
+ "unipi:btw-new": "btw",
104
+ "unipi:btw-clear": "btw",
105
+ "unipi:btw-inject": "btw",
106
+ "unipi:btw-summarize": "btw",
107
+
98
108
  // mcp (5 commands)
99
109
  "unipi:mcp-status": "mcp",
100
110
  "unipi:mcp-sync": "mcp",
@@ -126,14 +136,16 @@ export const COMMAND_REGISTRY: Record<string, string> = {
126
136
  "unipi:web-settings": "web-api",
127
137
  "unipi:web-cache-clear": "web-api",
128
138
 
129
- // compact (7 commands)
139
+ // compact (9 commands)
130
140
  "unipi:lossless-compact": "compact",
131
141
  "unipi:compact": "compact",
142
+ "unipi:session-recall": "compact",
132
143
  "unipi:compact-recall": "compact",
133
144
  "unipi:compact-stats": "compact",
134
145
  "unipi:compact-doctor": "compact",
135
146
  "unipi:compact-settings": "compact",
136
147
  "unipi:compact-preset": "compact",
148
+ "unipi:compact-help": "compact",
137
149
 
138
150
  // cocoindex (5 commands)
139
151
  "unipi:cocoindex-update": "cocoindex",
@@ -208,6 +220,13 @@ export const COMMAND_DESCRIPTIONS: Record<string, string> = {
208
220
  "unipi:global-memory-list": "List all project memories",
209
221
  "unipi:memory-settings": "Configure memory settings",
210
222
 
223
+ "unipi:btw": "Run a parallel side conversation",
224
+ "unipi:btw-tangent": "Start a contextless BTW tangent thread",
225
+ "unipi:btw-new": "Start a fresh BTW thread with session context",
226
+ "unipi:btw-clear": "Dismiss and clear the BTW thread",
227
+ "unipi:btw-inject": "Inject the BTW thread into the main agent",
228
+ "unipi:btw-summarize": "Summarize and inject the BTW thread",
229
+
211
230
  "unipi:mcp-status": "Show MCP server status",
212
231
  "unipi:mcp-sync": "Sync MCP server connections",
213
232
  "unipi:mcp-add": "Add a new MCP server",
@@ -238,11 +257,13 @@ export const COMMAND_DESCRIPTIONS: Record<string, string> = {
238
257
 
239
258
  "unipi:lossless-compact": "Immediate zero-LLM compaction",
240
259
  "unipi:compact": "(DEPRECATED) Use /unipi:lossless-compact instead",
241
- "unipi:compact-recall": "Recall a compacted session",
260
+ "unipi:session-recall": "Search session history, including compacted-away messages",
261
+ "unipi:compact-recall": "(DEPRECATED) Use /unipi:session-recall instead",
242
262
  "unipi:compact-stats": "Show compaction statistics",
243
263
  "unipi:compact-doctor": "Diagnose compaction issues",
244
264
  "unipi:compact-settings": "Configure compaction settings",
245
265
  "unipi:compact-preset": "Manage compaction presets",
266
+ "unipi:compact-help": "Show compactor command help",
246
267
  "unipi:cocoindex-update": "Run CocoIndex update to index project",
247
268
  "unipi:cocoindex-status": "Show CocoIndex indexing status",
248
269
  "unipi:cocoindex-init": "Initialize CocoIndex pipeline",
@@ -276,6 +297,7 @@ export const PACKAGE_LABELS: Record<string, string> = {
276
297
  workflow: "workflow",
277
298
  ralph: "ralph",
278
299
  memory: "memory",
300
+ btw: "btw",
279
301
  milestone: "milestone",
280
302
  mcp: "mcp",
281
303
  utility: "utility",
@@ -288,4 +310,5 @@ export const PACKAGE_LABELS: Record<string, string> = {
288
310
  footer: "footer",
289
311
  updater: "updater",
290
312
  "input-shortcuts": "input-shortcuts",
313
+ cocoindex: "cocoindex",
291
314
  };