@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 +130 -0
- package/commands.ts +281 -0
- package/index.ts +155 -0
- package/package.json +44 -0
- package/skills/brainstorm/SKILL.md +202 -0
- package/skills/consolidate/SKILL.md +142 -0
- package/skills/consultant/SKILL.md +97 -0
- package/skills/document/SKILL.md +120 -0
- package/skills/gather-context/SKILL.md +122 -0
- package/skills/plan/SKILL.md +169 -0
- package/skills/quick-work/SKILL.md +110 -0
- package/skills/review-work/SKILL.md +131 -0
- package/skills/scan-issues/SKILL.md +183 -0
- package/skills/work/SKILL.md +144 -0
- package/skills/worktree-create/SKILL.md +69 -0
- package/skills/worktree-list/SKILL.md +67 -0
- package/skills/worktree-merge/SKILL.md +98 -0
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
|
+
}
|