@pi-unipi/workflow 0.1.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 ADDED
@@ -0,0 +1,130 @@
1
+ # @unipi/workflow
2
+
3
+ Structured development workflow commands for Pi coding agent.
4
+
5
+ ## Overview
6
+
7
+ 13 slash commands that guide work from idea to completion. Each command maps to a skill that instructs the agent on what to do.
8
+
9
+ ## Commands
10
+
11
+ | Command | Description | Format |
12
+ |---------|-------------|--------|
13
+ | `/unipi:brainstorm` | Collaborative discovery, write design spec | `<string>` |
14
+ | `/unipi:plan` | Create implementation plan from specs | `specs:<path> <string>` |
15
+ | `/unipi:work` | Execute plan in worktree | `worktree:<branch> specs:<path> <string>` |
16
+ | `/unipi:review-work` | Review work, run checks, mark remarks | `plan:<path> <string>` |
17
+ | `/unipi:consolidate` | Save learnings, craft skills | `<string>` |
18
+ | `/unipi:worktree-create` | Create git worktree | `<string>` |
19
+ | `/unipi:worktree-list` | List all unipi worktrees | — |
20
+ | `/unipi:worktree-merge` | Merge worktrees to main | `<branch> <string>` |
21
+ | `/unipi:consultant` | Expert advisory | `<string>` |
22
+ | `/unipi:quick-work` | Fast single-task execution | `<string>` |
23
+ | `/unipi:gather-context` | Research codebase, prepare for brainstorm | `<string>` |
24
+ | `/unipi:document` | Generate documentation | `<string>` |
25
+ | `/unipi:scan-issues` | Find bugs, anti-patterns, security issues | `<string>` |
26
+
27
+ ## Workflow
28
+
29
+ ```
30
+ brainstorm → plan → work → review-work → consolidate
31
+ ↑ │
32
+ └────────────────────────────────────────┘
33
+ (loop)
34
+ ```
35
+
36
+ ### Typical Flow
37
+
38
+ ```bash
39
+ # 1. Brainstorm an idea
40
+ /unipi:brainstorm redesign auth system
41
+
42
+ # 2. Create implementation plan
43
+ /unipi:plan specs:2026-04-26-auth-redesign-design
44
+
45
+ # 3. Execute plan in worktree
46
+ /unipi:work worktree:feat/auth specs:2026-04-26-auth-redesign-plan
47
+
48
+ # 4. Review what was built
49
+ /unipi:review-work plan:2026-04-26-auth-redesign-plan
50
+
51
+ # 5. Consolidate learnings
52
+ /unipi:consolidate
53
+ ```
54
+
55
+ ### Quick Tasks
56
+
57
+ ```bash
58
+ # For small tasks that don't need full flow
59
+ /unipi:quick-work fix typo in README
60
+ ```
61
+
62
+ ### Research & Advisory
63
+
64
+ ```bash
65
+ # Gather context before brainstorming
66
+ /unipi:gather-context how we handle errors
67
+
68
+ # Get expert advice
69
+ /unipi:consultant should we use GraphQL or REST?
70
+
71
+ # Generate documentation
72
+ /unipi:document the auth module
73
+
74
+ # Find issues
75
+ /unipi:scan-issues focus on security
76
+ ```
77
+
78
+ ### Worktree Management
79
+
80
+ ```bash
81
+ # Create worktree
82
+ /unipi:worktree-create feat/new-feature
83
+
84
+ # List worktrees
85
+ /unipi:worktree-list
86
+
87
+ # Merge back to main
88
+ /ununi:worktree-merge feat/new-feature
89
+ ```
90
+
91
+ ## Directory Structure
92
+
93
+ ```
94
+ .unipi/
95
+ ├── docs/
96
+ │ ├── specs/ ← brainstorm output (design specs)
97
+ │ ├── plans/ ← plan output (implementation plans)
98
+ │ ├── generated/ ← document output (docs, guides)
99
+ │ └── reviews/ ← review remarks (in plan docs)
100
+ ├── memory/ ← consolidate memory
101
+ ├── quick-work/ ← quick-work summaries
102
+ └── worktrees/ ← git worktrees
103
+ ├── feat/auth/
104
+ └── fix/login-bug/
105
+ ```
106
+
107
+ ## Integration
108
+
109
+ - **@unipi/core** — shared constants, events, utilities
110
+ - **@unipi/memory** — memory hooks for consolidate (optional)
111
+ - **@unipi/registry** — skill crafting for consolidate (optional)
112
+ - **@unipi/subagents** — parallel research for gather-context, scan-issues (optional)
113
+ - **@unipi/ralph** — loop integration for long-running tasks (optional)
114
+
115
+ ## Installation
116
+
117
+ ```bash
118
+ npm install @unipi/workflow
119
+ ```
120
+
121
+ Add to pi settings:
122
+ ```json
123
+ {
124
+ "extensions": ["@unipi/workflow"]
125
+ }
126
+ ```
127
+
128
+ ## License
129
+
130
+ MIT
package/commands.ts ADDED
@@ -0,0 +1,281 @@
1
+ /**
2
+ * @unipi/workflow — Command registration and dispatch
3
+ *
4
+ * Each workflow command maps to a skill. The extension registers
5
+ * slash commands that load and invoke the appropriate skill.
6
+ */
7
+
8
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
9
+ import { readFileSync, readdirSync, existsSync } from "fs";
10
+ import { join, basename } from "path";
11
+ import { UNIPI_PREFIX, WORKFLOW_COMMANDS, getToolsForCommand, getSandboxLevel, type SandboxLevel } from "@unipi/core";
12
+
13
+ /** Options for command registration */
14
+ export interface WorkflowCommandOptions {
15
+ /** Check if ralph module is detected */
16
+ isRalphDetected: () => boolean;
17
+ /** Get current active tool names */
18
+ getActiveTools: () => string[];
19
+ /** Set active tools with sandbox level */
20
+ setActiveTools: (tools: string[], level: SandboxLevel) => void;
21
+ /** Save tools for later restore */
22
+ saveTools: (tools: string[]) => void;
23
+ }
24
+
25
+ /** Command definition */
26
+ interface WorkflowCommand {
27
+ name: string;
28
+ description: string;
29
+ skillName: string;
30
+ /** Argument hint shown in autocomplete */
31
+ argumentHint?: string;
32
+ /** Extra context to inject when ralph is available */
33
+ ralphHint?: string;
34
+ }
35
+
36
+ /**
37
+ * Suggest spec files from .unipi/docs/specs/ for plan command.
38
+ */
39
+ function suggestSpecFiles(prefix: string): { value: string; label: string; description: string }[] {
40
+ const specsDir = join(process.cwd(), ".unipi", "docs", "specs");
41
+ if (!existsSync(specsDir)) return [];
42
+
43
+ try {
44
+ const files = readdirSync(specsDir).filter((f) => f.endsWith(".md"));
45
+ return files
46
+ .filter((f) => !prefix || f.includes(prefix))
47
+ .map((f) => ({
48
+ value: `specs:${f}`,
49
+ label: basename(f, ".md"),
50
+ description: `Spec: ${f}`,
51
+ }));
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Suggest existing worktree names for merge/list commands.
59
+ */
60
+ function suggestWorktrees(): { value: string; label: string; description: string }[] {
61
+ const worktreesDir = join(process.cwd(), ".unipi", "worktrees");
62
+ if (!existsSync(worktreesDir)) return [];
63
+
64
+ try {
65
+ const dirs = readdirSync(worktreesDir, { withFileTypes: true })
66
+ .filter((d) => d.isDirectory())
67
+ .map((d) => d.name);
68
+ return dirs.map((d) => ({
69
+ value: d,
70
+ label: d,
71
+ description: `Worktree: ${d}`,
72
+ }));
73
+ } catch {
74
+ return [];
75
+ }
76
+ }
77
+
78
+ /** All workflow commands with their skill mappings */
79
+ const COMMANDS: WorkflowCommand[] = [
80
+ {
81
+ name: WORKFLOW_COMMANDS.BRAINSTORM,
82
+ description:
83
+ "Collaborative discovery — explore problem space, evaluate approaches, write design spec",
84
+ skillName: "brainstorm",
85
+ argumentHint: "<topic>",
86
+ },
87
+ {
88
+ name: WORKFLOW_COMMANDS.PLAN,
89
+ description:
90
+ "Strategic planning — tasks, dependencies, acceptance criteria from specs",
91
+ skillName: "plan",
92
+ argumentHint: "specs:<file> <description>",
93
+ },
94
+ {
95
+ name: WORKFLOW_COMMANDS.WORK,
96
+ description:
97
+ "Execute plan — implement in worktree, test, commit on done",
98
+ skillName: "work",
99
+ argumentHint: "<task description>",
100
+ ralphHint: "Ralph detected. Use /unipi:ralph-start for long-running tasks.",
101
+ },
102
+ {
103
+ name: WORKFLOW_COMMANDS.REVIEW_WORK,
104
+ description:
105
+ "Review work — check task completion, run lint/build, mark reviewer remarks",
106
+ skillName: "review-work",
107
+ argumentHint: "<worktree>",
108
+ },
109
+ {
110
+ name: WORKFLOW_COMMANDS.CONSOLIDATE,
111
+ description:
112
+ "Consolidate — save learnings to memory, craft skills if reusable",
113
+ skillName: "consolidate",
114
+ argumentHint: "<what to consolidate>",
115
+ },
116
+ {
117
+ name: WORKFLOW_COMMANDS.WORKTREE_CREATE,
118
+ description: "Create git worktree for parallel work",
119
+ skillName: "worktree-create",
120
+ argumentHint: "<branch-name>",
121
+ },
122
+ {
123
+ name: WORKFLOW_COMMANDS.WORKTREE_LIST,
124
+ description: "List all unipi worktrees",
125
+ skillName: "worktree-list",
126
+ },
127
+ {
128
+ name: WORKFLOW_COMMANDS.WORKTREE_MERGE,
129
+ description: "Merge worktrees back to main branch",
130
+ skillName: "worktree-merge",
131
+ argumentHint: "<worktree>",
132
+ },
133
+ {
134
+ name: WORKFLOW_COMMANDS.CONSULTANT,
135
+ description:
136
+ "Expert consultation — advisory with framework-based analysis",
137
+ skillName: "consultant",
138
+ argumentHint: "<question>",
139
+ },
140
+ {
141
+ name: WORKFLOW_COMMANDS.QUICK_WORK,
142
+ description: "Fast single-task execution — one shot, summary recorded",
143
+ skillName: "quick-work",
144
+ argumentHint: "<task>",
145
+ },
146
+ {
147
+ name: WORKFLOW_COMMANDS.GATHER_CONTEXT,
148
+ description:
149
+ "Research codebase — surface patterns, find prior art, prepare for brainstorm",
150
+ skillName: "gather-context",
151
+ argumentHint: "<topic>",
152
+ },
153
+ {
154
+ name: WORKFLOW_COMMANDS.DOCUMENT,
155
+ description: "Generate documentation — README, API docs, guides",
156
+ skillName: "document",
157
+ argumentHint: "<target>",
158
+ },
159
+ {
160
+ name: WORKFLOW_COMMANDS.SCAN_ISSUES,
161
+ description:
162
+ "Deep investigation — find bugs, anti-patterns, security issues",
163
+ skillName: "scan-issues",
164
+ argumentHint: "<scope>",
165
+ },
166
+ ];
167
+
168
+ /**
169
+ * Register all workflow commands with pi.
170
+ */
171
+ export function registerWorkflowCommands(
172
+ pi: ExtensionAPI,
173
+ options: WorkflowCommandOptions,
174
+ ): void {
175
+ for (const cmd of COMMANDS) {
176
+ const fullCommand = `${UNIPI_PREFIX}${cmd.name}`;
177
+
178
+ pi.registerCommand(fullCommand, {
179
+ description: cmd.description,
180
+ getArgumentCompletions: (prefix: string) => {
181
+ // Plan command: suggest spec files
182
+ if (cmd.name === WORKFLOW_COMMANDS.PLAN) {
183
+ return suggestSpecFiles(prefix);
184
+ }
185
+
186
+ // Worktree merge: suggest existing worktrees
187
+ if (cmd.name === WORKFLOW_COMMANDS.WORKTREE_MERGE) {
188
+ return suggestWorktrees();
189
+ }
190
+
191
+ // Worktree create: suggest existing worktrees as reference
192
+ if (cmd.name === WORKFLOW_COMMANDS.WORKTREE_CREATE) {
193
+ return suggestWorktrees();
194
+ }
195
+
196
+ // Other commands: no dynamic completions (free-text args)
197
+ return null;
198
+ },
199
+ handler: async (args, ctx) => {
200
+ // Apply sandbox — save current tools, set command's tools
201
+ const currentTools = options.getActiveTools();
202
+ options.saveTools(currentTools);
203
+ const sandboxTools = getToolsForCommand(cmd.name);
204
+ const sandboxLevel = getSandboxLevel(cmd.name);
205
+ options.setActiveTools([...sandboxTools], sandboxLevel);
206
+
207
+ // Load skill content from SKILL.md
208
+ let skillContent = "";
209
+ try {
210
+ const skillPath = join(
211
+ new URL("./skills", import.meta.url).pathname,
212
+ cmd.skillName,
213
+ "SKILL.md",
214
+ );
215
+ skillContent = readFileSync(skillPath, "utf-8");
216
+ } catch {
217
+ // Skill file not found — continue without it
218
+ }
219
+
220
+ // Build skill invocation message
221
+ let message = `Execute the ${cmd.skillName} workflow.`;
222
+
223
+ // Add args if provided
224
+ if (args?.trim()) {
225
+ message += `\n\nArguments: ${args.trim()}`;
226
+ }
227
+
228
+ // Add ralph hint if applicable
229
+ if (cmd.ralphHint && options.isRalphDetected()) {
230
+ message += `\n\n💡 ${cmd.ralphHint}`;
231
+ }
232
+
233
+ // Inject skill content as context
234
+ if (skillContent) {
235
+ message += `\n\n<skill_content>\n${skillContent}\n</skill_content>`;
236
+ }
237
+
238
+ // Send as user message to trigger skill processing
239
+ pi.sendUserMessage(message, { deliverAs: "followUp" });
240
+
241
+ if (ctx.hasUI) {
242
+ ctx.ui.notify(`Running /${fullCommand}`, "info");
243
+ }
244
+ },
245
+ });
246
+ }
247
+
248
+ // Register the ralph integration command if ralph is detected
249
+ pi.registerCommand(`${UNIPI_PREFIX}ralph-start`, {
250
+ description: "Start a ralph loop for the current task",
251
+ handler: async (args, ctx) => {
252
+ if (!options.isRalphDetected()) {
253
+ if (ctx.hasUI) {
254
+ ctx.ui.notify(
255
+ "Ralph module not detected. Install @unipi/ralph first.",
256
+ "warning",
257
+ );
258
+ }
259
+ return;
260
+ }
261
+
262
+ // Delegate to ralph's start command
263
+ const taskContent = args?.trim() || "Continue current task in a ralph loop.";
264
+ pi.sendUserMessage(
265
+ `Start a ralph loop with this task:\n\n${taskContent}`,
266
+ { deliverAs: "followUp" },
267
+ );
268
+
269
+ if (ctx.hasUI) {
270
+ ctx.ui.notify("Starting ralph loop...", "info");
271
+ }
272
+ },
273
+ });
274
+ }
275
+
276
+ /**
277
+ * Get list of all registered workflow command names.
278
+ */
279
+ export function getWorkflowCommandNames(): string[] {
280
+ return COMMANDS.map((c) => `${UNIPI_PREFIX}${c.name}`);
281
+ }
package/index.ts ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @unipi/workflow — Structured development workflow commands
3
+ *
4
+ * Registers 13 commands that dispatch to skills for LLM instruction.
5
+ * Emits MODULE_READY event for inter-module discovery.
6
+ * Detects @unipi/ralph presence for loop integration.
7
+ * Applies sandbox (tool filtering) per command.
8
+ */
9
+
10
+ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
11
+ import {
12
+ UNIPI_EVENTS,
13
+ MODULES,
14
+ WORKFLOW_COMMANDS,
15
+ emitEvent,
16
+ getPackageVersion,
17
+ initUnipiDirs,
18
+ type SandboxLevel,
19
+ getToolsForLevel,
20
+ } from "@unipi/core";
21
+ import { registerWorkflowCommands } from "./commands.js";
22
+
23
+ /** Package version (read from package.json at load time) */
24
+ const VERSION = getPackageVersion(new URL(".", import.meta.url).pathname);
25
+
26
+ /** Whether ralph module is detected */
27
+ let ralphDetected = false;
28
+
29
+ /** Saved tools before sandbox was applied (for restore) */
30
+ let savedTools: string[] | null = null;
31
+
32
+ /** Whether sandbox is currently active */
33
+ let sandboxActive = false;
34
+
35
+ /** Current sandbox level (null = no sandbox) */
36
+ let currentSandboxLevel: SandboxLevel | null = null;
37
+
38
+ export default function (pi: ExtensionAPI) {
39
+ // Register skills directory with pi's resource loader
40
+ const skillsDir = new URL("./skills", import.meta.url).pathname;
41
+ pi.on("resources_discover", async (_event, _ctx) => {
42
+ return {
43
+ skillPaths: [skillsDir],
44
+ };
45
+ });
46
+
47
+ // Register all workflow commands
48
+ registerWorkflowCommands(pi, {
49
+ isRalphDetected: () => ralphDetected,
50
+ getActiveTools: () => pi.getActiveTools(),
51
+ setActiveTools: (tools: string[], level: SandboxLevel) => {
52
+ pi.setActiveTools(tools);
53
+ sandboxActive = true;
54
+ currentSandboxLevel = level;
55
+ },
56
+ saveTools: (tools: string[]) => {
57
+ savedTools = tools;
58
+ },
59
+ });
60
+
61
+ // Block tool calls that violate sandbox
62
+ pi.on("tool_call", async (event, _ctx) => {
63
+ if (!sandboxActive || !currentSandboxLevel) return;
64
+
65
+ const allowed = getToolsForLevel(currentSandboxLevel);
66
+ if (!allowed.includes(event.toolName)) {
67
+ return {
68
+ block: true,
69
+ reason: `Tool "${event.toolName}" is not allowed in ${currentSandboxLevel} sandbox. Allowed: ${allowed.join(", ")}`,
70
+ };
71
+ }
72
+ });
73
+
74
+ // Inject sandbox constraints into system prompt so LLM knows its limits
75
+ pi.on("before_agent_start", async (event, _ctx) => {
76
+ if (!sandboxActive || !currentSandboxLevel) return;
77
+
78
+ const allowed = getToolsForLevel(currentSandboxLevel);
79
+ const blocked = ["read", "write", "edit", "bash", "grep", "find", "ls"]
80
+ .filter((t) => !allowed.includes(t));
81
+
82
+ const base = `\n\n<sandbox>\nSandbox mode: ${currentSandboxLevel}.\nAvailable tools: ${allowed.join(", ")}.\nBlocked tools: ${blocked.join(", ")} — removed from your tool list.`;
83
+
84
+ if (currentSandboxLevel === "brainstorm") {
85
+ return {
86
+ systemPrompt:
87
+ event.systemPrompt +
88
+ base +
89
+ `\nThe write tool is available but restricted to .unipi/docs/specs/ only.\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user requests an action that requires a blocked tool, respond that you do not have access.\n</sandbox>`,
90
+ };
91
+ }
92
+
93
+ return {
94
+ systemPrompt:
95
+ event.systemPrompt +
96
+ base +
97
+ `\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user requests an action that requires a blocked tool, respond that you do not have access.\n</sandbox>`,
98
+ };
99
+ });
100
+
101
+ // Restore tools when agent finishes
102
+ pi.on("agent_end", async (_event, _ctx) => {
103
+ if (sandboxActive && savedTools) {
104
+ pi.setActiveTools(savedTools);
105
+ savedTools = null;
106
+ sandboxActive = false;
107
+ currentSandboxLevel = null;
108
+ }
109
+ });
110
+
111
+ // Announce module presence on session start
112
+ pi.on("session_start", async (_event, ctx) => {
113
+ // Initialize .unipi directory structure
114
+ initUnipiDirs();
115
+
116
+ // Emit MODULE_READY
117
+ emitEvent(pi, UNIPI_EVENTS.MODULE_READY, {
118
+ name: MODULES.WORKFLOW,
119
+ version: VERSION,
120
+ commands: Object.values(WORKFLOW_COMMANDS),
121
+ tools: [],
122
+ });
123
+
124
+ // Listen for ralph module
125
+ if (!ralphDetected) {
126
+ try {
127
+ // Check if ralph tools exist (indicates @unipi/ralph is loaded)
128
+ const allTools = pi.getAllTools();
129
+ ralphDetected = allTools.some((t) => t.name === "ralph_start");
130
+ } catch {
131
+ // Ignore — ralph not present
132
+ }
133
+ }
134
+
135
+ // Show workflow status in UI
136
+ if (ctx.hasUI) {
137
+ const ralphStatus = ralphDetected ? "✓ ralph" : "○ ralph";
138
+ ctx.ui.setStatus("unipi-workflow", `⚡ workflow ${ralphStatus}`);
139
+ }
140
+ });
141
+
142
+ // Listen for ralph module ready event
143
+ pi.on(UNIPI_EVENTS.MODULE_READY as any, (event: any) => {
144
+ if (event?.name === MODULES.RALPH) {
145
+ ralphDetected = true;
146
+ }
147
+ });
148
+
149
+ // Clean up on shutdown
150
+ pi.on("session_shutdown", async () => {
151
+ ralphDetected = false;
152
+ savedTools = null;
153
+ sandboxActive = false;
154
+ });
155
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@pi-unipi/workflow",
3
+ "version": "0.1.0",
4
+ "description": "Structured development workflow commands for Pi coding agent",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Neuron Mr White",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Neuron-Mr-White/unipi.git",
11
+ "directory": "packages/workflow"
12
+ },
13
+ "keywords": [
14
+ "pi-package",
15
+ "pi-extension",
16
+ "unipi",
17
+ "workflow"
18
+ ],
19
+ "files": [
20
+ "*.ts",
21
+ "skills/**/*",
22
+ "README.md"
23
+ ],
24
+ "pi": {
25
+ "extensions": [
26
+ "./index.ts"
27
+ ],
28
+ "skills": [
29
+ "./skills"
30
+ ]
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "dependencies": {
36
+ "@pi-unipi/core": "^0.1.0"
37
+ },
38
+ "peerDependencies": {
39
+ "@mariozechner/pi-ai": "*",
40
+ "@mariozechner/pi-coding-agent": "*",
41
+ "@mariozechner/pi-tui": "*",
42
+ "@sinclair/typebox": "*"
43
+ }
44
+ }