@pi-unipi/unipi 2.4.0 → 2.4.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/CHANGELOG.md +43 -0
- package/package.json +26 -22
- package/packages/ask-user/package.json +2 -2
- package/packages/autocomplete/package.json +4 -2
- package/packages/btw/package.json +2 -2
- package/packages/cocoindex/package.json +2 -2
- package/packages/cocoindex/tools.ts +0 -8
- package/packages/compactor/README.md +18 -22
- package/packages/compactor/package.json +3 -3
- package/packages/compactor/src/compaction/auto-trigger.ts +0 -0
- package/packages/compactor/src/compaction/build-sections.ts +2 -2
- package/packages/compactor/src/compaction/format.ts +1 -1
- package/packages/compactor/src/config/presets.ts +19 -30
- package/packages/compactor/src/config/schema.ts +0 -0
- package/packages/compactor/src/index.ts +39 -56
- package/packages/compactor/src/session/resume-inject.ts +27 -0
- package/packages/compactor/src/tools/ctx-batch-execute.ts +4 -2
- package/packages/compactor/src/tools/ctx-execute-file.ts +4 -2
- package/packages/compactor/src/tools/ctx-execute.ts +4 -2
- package/packages/compactor/src/tools/register.ts +22 -4
- package/packages/compactor/src/tui/settings-overlay.ts +9 -22
- package/packages/compactor/src/types.ts +24 -2
- package/packages/core/events.ts +13 -13
- package/packages/core/package.json +3 -2
- package/packages/footer/README.md +2 -2
- package/packages/footer/package.json +3 -3
- package/packages/footer/src/events.ts +0 -10
- package/packages/footer/src/segments/core.ts +0 -9
- package/packages/image/package.json +2 -2
- package/packages/info-screen/core-groups.ts +0 -14
- package/packages/info-screen/package.json +2 -2
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/kanboard/commands.ts +0 -1
- package/packages/kanboard/package.json +2 -2
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +3 -3
- package/packages/memory/storage.ts +0 -18
- package/packages/memory/tools.ts +0 -1
- package/packages/milestone/hooks.ts +9 -15
- package/packages/milestone/package.json +2 -2
- package/packages/notify/README.md +2 -0
- package/packages/notify/events.ts +23 -13
- package/packages/notify/package.json +2 -2
- package/packages/notify/tools.ts +17 -8
- package/packages/notify/types.ts +5 -1
- package/packages/ralph/index.ts +10 -5
- package/packages/ralph/package.json +3 -3
- package/packages/ralph/ralph-loop.ts +10 -0
- package/packages/subagents/README.md +3 -1
- package/packages/subagents/package.json +10 -6
- package/packages/subagents/src/agent-manager.ts +46 -11
- package/packages/subagents/src/core-compat.ts +38 -0
- package/packages/subagents/src/global.d.ts +7 -0
- package/packages/subagents/src/index.ts +6 -3
- package/packages/subagents/src/widget.ts +0 -7
- package/packages/unipi/bundled.js +371 -438
- package/packages/updater/package.json +4 -3
- package/packages/utility/README.md +1 -1
- package/packages/utility/package.json +7 -4
- package/packages/utility/src/commands.ts +2 -10
- package/packages/utility/src/diff/theme.ts +0 -3
- package/packages/utility/src/tui/name-badge.ts +0 -9
- package/packages/web-api/package.json +2 -2
- package/packages/web-api/src/engine/extract.ts +0 -26
- package/packages/web-api/src/tools.ts +0 -13
- package/packages/workflow/commands.ts +13 -1
- package/packages/workflow/index.ts +12 -1
- package/packages/workflow/lifecycle.ts +43 -0
- package/packages/workflow/package.json +3 -2
- package/packages/autocomplete/index.ts +0 -7
- package/packages/autocomplete/src/__tests__/command-registry.audit.test.ts +0 -120
- package/packages/autocomplete/src/__tests__/provider.sorting.test.ts +0 -423
- package/packages/compactor/src/compaction/recall-scope.ts +0 -32
- package/packages/compactor/src/compaction/sections.ts +0 -15
- package/packages/compactor/src/display/bash-display.ts +0 -28
- package/packages/compactor/src/display/diff-presentation.ts +0 -25
- package/packages/compactor/src/display/pending-diff-preview.ts +0 -51
- package/packages/compactor/src/display/user-message-box.ts +0 -16
- package/packages/compactor/src/session/analytics.ts +0 -205
- package/packages/image/index.ts +0 -1
- package/packages/input-shortcuts/index.ts +0 -5
- package/packages/input-shortcuts/src/status.ts +0 -35
- package/packages/kanboard/tui/kanboard-overlay.ts +0 -300
- package/packages/mcp/index.ts +0 -1
- package/packages/memory/search.ts +0 -134
- package/packages/milestone/coexist.ts +0 -114
- package/packages/notify/src/__tests__/ask-user-prompt-message.test.ts +0 -159
- package/packages/notify/src/__tests__/event-bus.test.ts +0 -119
- package/packages/notify/src/__tests__/permission-prompt-message.test.ts +0 -172
- package/packages/subagents/src/__tests__/badge-generation.test.ts +0 -315
- package/packages/subagents/src/__tests__/config.test.ts +0 -240
- package/packages/subagents/src/__tests__/esc-propagation.test.ts +0 -162
- package/packages/subagents/src/__tests__/file-lock.test.ts +0 -244
- package/packages/subagents/src/__tests__/shutdown-stale-ctx.test.ts +0 -185
- package/packages/subagents/src/__tests__/workflow-integration.test.ts +0 -334
- package/packages/subagents/src/prompts.ts +0 -39
- package/packages/utility/index.ts +0 -1
- package/packages/web-api/src/tui/progress.ts +0 -168
- package/packages/web-api/src/tui/result.ts +0 -173
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/updater",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Auto-updater, changelog browser, and readme browser for Unipi — checks npm registry, renders CHANGELOG.md and README.md files in TUI overlays",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
6
|
+
"main": "index.ts",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": "Neuron Mr White",
|
|
9
9
|
"repository": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"files": [
|
|
24
24
|
"index.ts",
|
|
25
|
+
"types.ts",
|
|
25
26
|
"src/**/*.ts",
|
|
26
27
|
"skills/**/*",
|
|
27
28
|
"README.md"
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
"access": "public"
|
|
34
35
|
},
|
|
35
36
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "2.4.
|
|
37
|
+
"@pi-unipi/core": "2.4.1"
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|
|
39
40
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -10,7 +10,7 @@ The diff rendering is the standout feature — Shiki-powered syntax-highlighted
|
|
|
10
10
|
|---------|-------------|
|
|
11
11
|
| `/unipi:env` | Show environment info (Node, Pi, OS, paths) |
|
|
12
12
|
| `/unipi:doctor` | Run diagnostics across all modules |
|
|
13
|
-
| `/unipi:status` | Show
|
|
13
|
+
| `/unipi:status` | Show immediate guidance to the live `/unipi:info` dashboard and `/unipi:doctor` diagnostics |
|
|
14
14
|
| `/unipi:cleanup` | Clean stale DBs, temp files, old sessions |
|
|
15
15
|
| `/unipi:reload` | Explain how to reload extensions |
|
|
16
16
|
| `/unipi:name-badge` | Toggle name badge overlay |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/utility",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Utility commands and tools for Pi coding agent — lifecycle, diagnostics, cache, analytics, display, batch execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -28,21 +28,24 @@
|
|
|
28
28
|
"src/types.ts",
|
|
29
29
|
"src/**/*.ts",
|
|
30
30
|
"README.md",
|
|
31
|
-
"skills/**/*.md"
|
|
31
|
+
"skills/**/*.md",
|
|
32
|
+
"!src/__tests__/**",
|
|
33
|
+
"!src/**/*.test.ts"
|
|
32
34
|
],
|
|
33
35
|
"publishConfig": {
|
|
34
36
|
"access": "public"
|
|
35
37
|
},
|
|
36
38
|
"dependencies": {
|
|
37
|
-
"@pi-unipi/core": "2.4.
|
|
39
|
+
"@pi-unipi/core": "2.4.1",
|
|
38
40
|
"diff": "^7.0.0",
|
|
39
|
-
"
|
|
41
|
+
"shiki": "^4.0.2"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"@types/diff": "^7.0.2"
|
|
43
45
|
},
|
|
44
46
|
"peerDependencies": {
|
|
45
47
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
48
|
+
"@earendil-works/pi-tui": "^0.80.0",
|
|
46
49
|
"typebox": "^1.1.38"
|
|
47
50
|
},
|
|
48
51
|
"scripts": {
|
|
@@ -234,19 +234,11 @@ export function registerUtilityCommands(pi: ExtensionAPI): void {
|
|
|
234
234
|
return;
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
// Request status from all modules — responses will be logged
|
|
238
|
-
const requestId = `status-${Date.now()}`;
|
|
239
|
-
emitEvent(pi, UNIPI_EVENTS.MODULE_STATUS_REQUEST, { requestId });
|
|
240
|
-
|
|
241
|
-
// Give modules a moment to respond, then show what we know
|
|
242
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
243
|
-
|
|
244
237
|
sendResponse(
|
|
245
238
|
pi,
|
|
246
239
|
"## 📊 Module Status\n\n" +
|
|
247
|
-
"
|
|
248
|
-
"
|
|
249
|
-
`*Request ID: \`${requestId}\`*`,
|
|
240
|
+
"Use `/unipi:info` for the live module and tool dashboard.\n" +
|
|
241
|
+
"Use `/unipi:doctor` to run configuration and runtime diagnostics.",
|
|
250
242
|
);
|
|
251
243
|
},
|
|
252
244
|
});
|
|
@@ -101,9 +101,6 @@ const PRESETS: Record<string, DiffPreset> = {
|
|
|
101
101
|
},
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
-
/** Hardcoded fallback colors (last resort) */
|
|
105
|
-
const HARDCODED_FALLBACK: DiffColors = PRESETS.default.colors;
|
|
106
|
-
|
|
107
104
|
// ─── Preset Access ──────────────────────────────────────────────────────────────
|
|
108
105
|
|
|
109
106
|
/**
|
|
@@ -13,15 +13,6 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
|
13
13
|
/** Placeholder text when no session name is set */
|
|
14
14
|
const PLACEHOLDER = "Set a name";
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* Pad content to exact visible width.
|
|
18
|
-
*/
|
|
19
|
-
function padVisible(content: string, targetWidth: number): string {
|
|
20
|
-
const vw = visibleWidth(content);
|
|
21
|
-
const pad = Math.max(0, targetWidth - vw);
|
|
22
|
-
return content + " ".repeat(pad);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
16
|
/**
|
|
26
17
|
* NameBadgeComponent — bordered box HUD overlay showing session name.
|
|
27
18
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/web-api",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Web search, read, and summarize tools with provider-based backend selection for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"README.md"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@pi-unipi/core": "2.4.
|
|
34
|
+
"@pi-unipi/core": "2.4.1",
|
|
35
35
|
"defuddle": "^0.18.1",
|
|
36
36
|
"linkedom": "^0.18.12",
|
|
37
37
|
"lodash": "^4.17.21",
|
|
@@ -35,7 +35,6 @@ import { truncateContent, formatContent } from "./format.js";
|
|
|
35
35
|
const MAX_REDIRECTS = 5;
|
|
36
36
|
|
|
37
37
|
/** Maximum alternate link fallbacks to try */
|
|
38
|
-
const MAX_ALTERNATE_LINKS = 3;
|
|
39
38
|
|
|
40
39
|
/**
|
|
41
40
|
* Validate a URL for fetching.
|
|
@@ -193,31 +192,6 @@ function findMetaRefresh(document: Document): string | null {
|
|
|
193
192
|
return match[1];
|
|
194
193
|
}
|
|
195
194
|
|
|
196
|
-
/**
|
|
197
|
-
* Check for alternate JSON content links.
|
|
198
|
-
*
|
|
199
|
-
* @param document - DOM document
|
|
200
|
-
* @returns Array of alternate URLs
|
|
201
|
-
*/
|
|
202
|
-
function findAlternateLinks(document: Document): string[] {
|
|
203
|
-
const alternates: string[] = [];
|
|
204
|
-
|
|
205
|
-
// Look for JSON feeds, oEmbed, etc.
|
|
206
|
-
const links = document.querySelectorAll(
|
|
207
|
-
'link[rel="alternate"][type="application/json"], ' +
|
|
208
|
-
'link[rel="alternate"][type="application/ld+json"]'
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
for (const link of Array.from(links)) {
|
|
212
|
-
const href = link.getAttribute("href");
|
|
213
|
-
if (href) {
|
|
214
|
-
alternates.push(href);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return alternates.slice(0, MAX_ALTERNATE_LINKS);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
195
|
/**
|
|
222
196
|
* Detect content type from response.
|
|
223
197
|
*/
|
|
@@ -62,19 +62,6 @@ function getAvailableProviders(capability: WebCapability): WebProvider[] {
|
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
/**
|
|
66
|
-
* Select provider for a capability.
|
|
67
|
-
* If sourceRank is specified, use that rank.
|
|
68
|
-
* Otherwise, use the lowest-ranked available provider.
|
|
69
|
-
*/
|
|
70
|
-
function selectProvider(
|
|
71
|
-
capability: WebCapability,
|
|
72
|
-
sourceRank?: number
|
|
73
|
-
): WebProvider {
|
|
74
|
-
const candidates = selectProviderChain(capability, sourceRank);
|
|
75
|
-
return candidates[0];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
65
|
/**
|
|
79
66
|
* Build the ordered list of providers to try for a capability.
|
|
80
67
|
*
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { readFileSync, readdirSync, existsSync, statSync } from "fs";
|
|
10
10
|
import { join, basename } from "path";
|
|
11
|
-
import { UNIPI_PREFIX, WORKFLOW_COMMANDS, getToolsForCommand, getSandboxLevel, type SandboxLevel } from "@pi-unipi/core";
|
|
11
|
+
import { UNIPI_PREFIX, WORKFLOW_COMMANDS, getToolsForCommand, getSandboxLevel, type SandboxLevel, type UnipiWorkflowEvent } from "@pi-unipi/core";
|
|
12
12
|
|
|
13
13
|
type CompletionItem = { value: string; label: string; description: string };
|
|
14
14
|
|
|
@@ -22,6 +22,8 @@ export interface WorkflowCommandOptions {
|
|
|
22
22
|
setActiveTools: (tools: string[], level: SandboxLevel) => void;
|
|
23
23
|
/** Save tools for later restore */
|
|
24
24
|
saveTools: (tools: string[]) => void;
|
|
25
|
+
/** Begin one workflow lifecycle; false means another workflow is active. */
|
|
26
|
+
startWorkflow: (event: UnipiWorkflowEvent) => boolean;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/** Command definition */
|
|
@@ -402,6 +404,16 @@ export function registerWorkflowCommands(
|
|
|
402
404
|
return null;
|
|
403
405
|
},
|
|
404
406
|
handler: async (args, ctx) => {
|
|
407
|
+
const workflowEvent: UnipiWorkflowEvent = {
|
|
408
|
+
command: cmd.name,
|
|
409
|
+
fullCommand: `/${fullCommand}`,
|
|
410
|
+
args: args?.trim() ?? "",
|
|
411
|
+
};
|
|
412
|
+
if (!options.startWorkflow(workflowEvent)) {
|
|
413
|
+
if (ctx.hasUI) ctx.ui.notify("Another UniPi workflow is still active", "warning");
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
405
417
|
// Apply sandbox — save current tools, set command's tools
|
|
406
418
|
const currentTools = options.getActiveTools();
|
|
407
419
|
options.saveTools(currentTools);
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
getBlockedToolsForLevel,
|
|
22
22
|
} from "@pi-unipi/core";
|
|
23
23
|
import { registerWorkflowCommands } from "./commands.js";
|
|
24
|
+
import { WorkflowLifecycle } from "./lifecycle.js";
|
|
24
25
|
|
|
25
26
|
/** Package version (read from package.json at load time) */
|
|
26
27
|
const VERSION = getPackageVersion(dirname(fileURLToPath(import.meta.url)));
|
|
@@ -41,6 +42,7 @@ let currentSandboxLevel: SandboxLevel | null = null;
|
|
|
41
42
|
let currentSandboxTools: string[] | null = null;
|
|
42
43
|
|
|
43
44
|
export default function (pi: ExtensionAPI) {
|
|
45
|
+
const workflowLifecycle = new WorkflowLifecycle();
|
|
44
46
|
|
|
45
47
|
// Register all workflow commands
|
|
46
48
|
registerWorkflowCommands(pi, {
|
|
@@ -55,6 +57,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
55
57
|
saveTools: (tools: string[]) => {
|
|
56
58
|
savedTools = tools;
|
|
57
59
|
},
|
|
60
|
+
startWorkflow: (event) => {
|
|
61
|
+
if (!workflowLifecycle.start(event)) return false;
|
|
62
|
+
emitEvent(pi, UNIPI_EVENTS.WORKFLOW_START, event);
|
|
63
|
+
return true;
|
|
64
|
+
},
|
|
58
65
|
});
|
|
59
66
|
|
|
60
67
|
// Block tool calls that violate sandbox
|
|
@@ -109,7 +116,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
109
116
|
});
|
|
110
117
|
|
|
111
118
|
// Restore tools when agent finishes
|
|
112
|
-
pi.on("agent_end", async (
|
|
119
|
+
pi.on("agent_end", async (event, _ctx) => {
|
|
113
120
|
if (sandboxActive && savedTools) {
|
|
114
121
|
pi.setActiveTools(savedTools);
|
|
115
122
|
savedTools = null;
|
|
@@ -117,6 +124,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
117
124
|
currentSandboxLevel = null;
|
|
118
125
|
currentSandboxTools = null;
|
|
119
126
|
}
|
|
127
|
+
|
|
128
|
+
const completedWorkflow = workflowLifecycle.complete(event.messages);
|
|
129
|
+
if (completedWorkflow) emitEvent(pi, UNIPI_EVENTS.WORKFLOW_END, completedWorkflow);
|
|
120
130
|
});
|
|
121
131
|
|
|
122
132
|
// Announce module presence on session start
|
|
@@ -163,5 +173,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
163
173
|
ralphDetected = false;
|
|
164
174
|
savedTools = null;
|
|
165
175
|
sandboxActive = false;
|
|
176
|
+
workflowLifecycle.reset();
|
|
166
177
|
});
|
|
167
178
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { UnipiWorkflowEvent } from "@pi-unipi/core";
|
|
2
|
+
|
|
3
|
+
interface WorkflowMessage {
|
|
4
|
+
role: string;
|
|
5
|
+
stopReason?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CompletedWorkflowEvent extends UnipiWorkflowEvent {
|
|
9
|
+
success: boolean;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Single-active workflow lifecycle state shared by slash handlers and agent_end. */
|
|
14
|
+
export class WorkflowLifecycle {
|
|
15
|
+
private active: (UnipiWorkflowEvent & { startedAt: number }) | null = null;
|
|
16
|
+
|
|
17
|
+
constructor(private readonly now: () => number = Date.now) {}
|
|
18
|
+
|
|
19
|
+
start(event: UnipiWorkflowEvent): boolean {
|
|
20
|
+
if (this.active) return false;
|
|
21
|
+
this.active = { ...event, startedAt: this.now() };
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
complete(messages: WorkflowMessage[]): CompletedWorkflowEvent | undefined {
|
|
26
|
+
if (!this.active) return undefined;
|
|
27
|
+
const workflow = this.active;
|
|
28
|
+
this.active = null;
|
|
29
|
+
const finalAssistant = [...messages].reverse().find((message) => message.role === "assistant");
|
|
30
|
+
const success = finalAssistant?.stopReason !== "error" && finalAssistant?.stopReason !== "aborted";
|
|
31
|
+
return {
|
|
32
|
+
command: workflow.command,
|
|
33
|
+
fullCommand: workflow.fullCommand,
|
|
34
|
+
args: workflow.args,
|
|
35
|
+
success,
|
|
36
|
+
durationMs: this.now() - workflow.startedAt,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
reset(): void {
|
|
41
|
+
this.active = null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/workflow",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Structured development workflow commands for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"files": [
|
|
21
21
|
"*.ts",
|
|
22
|
+
"!*.test.ts",
|
|
22
23
|
"skills/**/*",
|
|
23
24
|
"README.md"
|
|
24
25
|
],
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
"access": "public"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"@pi-unipi/core": "2.4.
|
|
30
|
+
"@pi-unipi/core": "2.4.1"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"@earendil-works/pi-ai": "^0.80.0",
|
|
@@ -1,120 +0,0 @@
|
|
|
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
|
-
});
|