@harms-haus/pi-workflows 1.0.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/LICENSE +21 -0
- package/README.md +113 -0
- package/docs/architecture.md +318 -0
- package/docs/configuration-reference.md +427 -0
- package/docs/contributing.md +132 -0
- package/docs/examples.md +1242 -0
- package/docs/hook-lifecycle.md +380 -0
- package/docs/state-management.md +534 -0
- package/docs/subworkflows.md +428 -0
- package/docs/template-variables.md +383 -0
- package/docs/testing.md +479 -0
- package/package.json +69 -0
- package/skills/workflow-generation/SKILL.md +272 -0
- package/src/TimerManager.ts +67 -0
- package/src/command.ts +199 -0
- package/src/config/index.ts +11 -0
- package/src/config/loading-parse.ts +205 -0
- package/src/config/loading-phases.ts +78 -0
- package/src/config/loading-resolve.ts +82 -0
- package/src/config/loading.ts +202 -0
- package/src/config/templates.ts +25 -0
- package/src/config/validation.ts +258 -0
- package/src/hooks.ts +265 -0
- package/src/index.ts +98 -0
- package/src/prompts.ts +141 -0
- package/src/renderers.ts +46 -0
- package/src/state.ts +426 -0
- package/src/tool.ts +364 -0
- package/src/types.ts +211 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# Configuration Reference
|
|
2
|
+
|
|
3
|
+
Complete reference for every field in `workflow.yaml` and phase `.md` files used by pi-workflows.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
pi-workflows uses a **two-tier file-based discovery model** for loading workflow definitions:
|
|
10
|
+
|
|
11
|
+
| Tier | Location | Notes |
|
|
12
|
+
| ----------- | --------------------------------------------------------------- | -------------------------------------------------------- |
|
|
13
|
+
| **Global** | `~/.pi/agent/workflows/` (or `$PI_CODING_AGENT_DIR/workflows/`) | Shared across all projects. |
|
|
14
|
+
| **Project** | `.pi/workflows/` (relative to project root / `cwd`) | Overrides global workflows with the same directory name. |
|
|
15
|
+
|
|
16
|
+
Each workflow lives in its own **directory**. The directory name becomes the workflow key. A `workflow.yaml` file serves as the entry point, and individual phase `.md` files define each phase's instructions and metadata.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
workflows/
|
|
20
|
+
├── my-workflow/ # key = "my-workflow"
|
|
21
|
+
│ ├── workflow.yaml # entry point
|
|
22
|
+
│ ├── research.md # phase file
|
|
23
|
+
│ ├── planning.md # phase file
|
|
24
|
+
│ └── implementing.md # phase file
|
|
25
|
+
└── code-review/ # key = "code-review"
|
|
26
|
+
├── workflow.yaml
|
|
27
|
+
└── ...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
When both tiers define a workflow with the same directory name, the **project** version wins. Project definitions are merged over global ones with a simple object spread.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Directory Structure
|
|
35
|
+
|
|
36
|
+
A complete example showing both tiers and various configuration options:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
~/.pi/agent/workflows/ # Global workflows root
|
|
40
|
+
├── rpir/
|
|
41
|
+
│ ├── workflow.yaml
|
|
42
|
+
│ ├── research.md
|
|
43
|
+
│ ├── planning.md
|
|
44
|
+
│ ├── implementing.md
|
|
45
|
+
│ └── reviewing.md
|
|
46
|
+
└── shared-utilities/ # Internal workflow (show: workflows)
|
|
47
|
+
├── workflow.yaml
|
|
48
|
+
└── refactor.md
|
|
49
|
+
|
|
50
|
+
my-project/.pi/workflows/ # Project workflows root
|
|
51
|
+
├── rpir/ # Overrides global "rpir"
|
|
52
|
+
│ ├── workflow.yaml
|
|
53
|
+
│ ├── research.md
|
|
54
|
+
│ ├── custom-phase.md
|
|
55
|
+
│ └── deploy.md
|
|
56
|
+
└── bugfix/ # Project-only workflow
|
|
57
|
+
├── workflow.yaml
|
|
58
|
+
├── reproduce.md
|
|
59
|
+
├── fix.md
|
|
60
|
+
└── verify.md
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `workflow.yaml` Entry Point
|
|
64
|
+
|
|
65
|
+
The YAML file at the root of each workflow directory defines the workflow metadata and lists its phases (as filenames) or subworkflow references.
|
|
66
|
+
|
|
67
|
+
### Phase `.md` Files
|
|
68
|
+
|
|
69
|
+
Each phase is a Markdown file with **YAML frontmatter** for metadata and a **Markdown body** for the agent instructions. The filename is referenced from `workflow.yaml`'s `phases` array.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## `workflow.yaml` Field Reference
|
|
74
|
+
|
|
75
|
+
| Field | Type | Required | Default | Description |
|
|
76
|
+
| ---------------------- | ----------------------- | ----------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
77
|
+
| `name` | `string` | **Yes** | — | Human-readable workflow name displayed in the status bar, messages, and the `/workflow` listing. Must be a non-empty string. |
|
|
78
|
+
| `commandName` | `string` | Yes (if `show: "user"`) | `""` | Slash-command identifier used as `/workflow {commandName} {description}`. Must match `^[a-zA-Z0-9_-]+$`. Ignored for internal workflows (`show: "workflows"`). |
|
|
79
|
+
| `initialMessage` | `string` | Yes (if `show: "user"`) | `""` | Template string sent to the agent when the workflow starts. Supports template variables: `{workflowName}`, `{description}`, `{workflowKey}`, `{firstPhaseId}`, `{firstPhaseName}`, `{firstPhaseEmoji}`, `{firstPhaseProfiles}`. |
|
|
80
|
+
| `phases` | `array` | **Yes** | — | Ordered list of phase entries. Each entry is either a **string** (filename of a `.md` phase file) or an **object** with `{ subworkflow: "workflow-key" }`. Must contain at least 1 entry. |
|
|
81
|
+
| `show` | `"user" \| "workflows"` | No | `"user"` | Controls visibility. `"user"` = listed in `/workflow` command. `"workflows"` = hidden from direct invocation; only usable as a subworkflow phase in another workflow. |
|
|
82
|
+
| `loopable` | `boolean` | No | `true` | Whether the workflow can be restarted from phase 0 via the `loop` action on `workflow_step`. When `false`, the loop action returns an error. |
|
|
83
|
+
| `sessionNamePrefix` | `string` | No | `"Workflow: "` | Prefix prepended to the task description when setting the session name. |
|
|
84
|
+
| `sessionNameMaxLength` | `number` | No | `50` | Maximum character count for the session name (after the prefix). The description is truncated to this length with a trailing `…` if it exceeds it. |
|
|
85
|
+
| `roleInstruction` | `string` | No | _(built-in default)_ | Template prepended to every context injection. All [Phase Instructions variables](template-variables.md#phase-instructions) are available. If omitted, a default instructing the agent to act as orchestrator and delegate to subagents is used. |
|
|
86
|
+
| `advanceReminder` | `string` | No | _(built-in default)_ | Template appended at the end of every context injection reminding the agent to advance. All [Phase Instructions variables](template-variables.md#phase-instructions) are available. |
|
|
87
|
+
| `blockReasonTemplate` | `string` | No | _(built-in default)_ | Template for the reason shown when a tool call is blocked. Variables: `{workflowName}`, `{phaseName}`, `{toolName}`, `{allowedTools}`. |
|
|
88
|
+
| `completionMessage` | `string` | No | _(built-in default)_ | Template sent when the workflow reaches the DONE state. Variables: `{workflowName}`, `{taskDescription}`, `{taskId}`, `{phaseCount}`. |
|
|
89
|
+
| `notDoneReminder` | `string` | No | _(built-in default)_ | Template injected when the agent tries to finish but the workflow is still active. Variables: `{workflowName}`, `{phaseName}`, `{phaseEmoji}`, `{phaseInstructions}`, `{taskDescription}`, `{taskId}`, `{workflowKey}`. |
|
|
90
|
+
|
|
91
|
+
### Example `workflow.yaml`
|
|
92
|
+
|
|
93
|
+
```yaml
|
|
94
|
+
name: "RPIR Development Workflow"
|
|
95
|
+
commandName: "rpir"
|
|
96
|
+
initialMessage: |
|
|
97
|
+
Start the {workflowName} for: "{description}"
|
|
98
|
+
|
|
99
|
+
Begin with Phase 1 ({firstPhaseName}).
|
|
100
|
+
sessionNamePrefix: "RPIR: "
|
|
101
|
+
sessionNameMaxLength: 60
|
|
102
|
+
loopable: false
|
|
103
|
+
roleInstruction: "You are the orchestrator for {workflowName}. Delegate all work to subagents."
|
|
104
|
+
advanceReminder: "When done with {phaseName}, call {toolName} to advance to {nextPhaseName}."
|
|
105
|
+
completionMessage: "✅ {workflowName} complete! Task: {taskDescription} (ID: {taskId}, {phaseCount} phases)"
|
|
106
|
+
phases:
|
|
107
|
+
- research.md
|
|
108
|
+
- planning.md
|
|
109
|
+
- implementing.md
|
|
110
|
+
- reviewing.md
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Subworkflow Reference in `phases`
|
|
114
|
+
|
|
115
|
+
Instead of a filename string, a phase entry can be an object referencing another workflow:
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
phases:
|
|
119
|
+
- research.md
|
|
120
|
+
- subworkflow: shared-utilities # delegates to the "shared-utilities" workflow
|
|
121
|
+
- final-review.md
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
See [docs/subworkflows.md](subworkflows.md) for full subworkflow mechanics including cycle detection, resolution, and stack-based navigation.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Phase `.md` File Reference
|
|
129
|
+
|
|
130
|
+
Each phase file is a Markdown document with YAML frontmatter:
|
|
131
|
+
|
|
132
|
+
```markdown
|
|
133
|
+
---
|
|
134
|
+
id: research
|
|
135
|
+
name: Research
|
|
136
|
+
emoji: "🔍"
|
|
137
|
+
tools:
|
|
138
|
+
blacklist:
|
|
139
|
+
- edit
|
|
140
|
+
- write
|
|
141
|
+
availableProfiles:
|
|
142
|
+
- vertical-researcher
|
|
143
|
+
- horizontal-researcher
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Research Phase Instructions
|
|
147
|
+
|
|
148
|
+
Spawn parallel research subagents using `delegate_to_subagents` with the
|
|
149
|
+
vertical-researcher and horizontal-researcher profiles.
|
|
150
|
+
|
|
151
|
+
Focus on understanding the codebase and gathering all information needed
|
|
152
|
+
for the planning phase.
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Frontmatter Fields
|
|
156
|
+
|
|
157
|
+
| Field | Type | Required | Default | Description |
|
|
158
|
+
| ------------------- | ---------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
159
|
+
| `id` | `string` | **Yes** | — | Machine-readable phase identifier. Must be **unique within the workflow** (duplicate IDs are a validation error). |
|
|
160
|
+
| `name` | `string` | **Yes** | — | Human-readable phase name shown in the status bar and context injection. |
|
|
161
|
+
| `emoji` | `string` | **Yes** | — | Emoji string displayed in the status bar, messages, and breadcrumb. Must be a non-empty string. |
|
|
162
|
+
| `tools` | `object` | No | — | Tool restriction configuration. See [Tool Configuration Details](#tool-configuration-details). |
|
|
163
|
+
| `tools.blacklist` | `string[]` | No | — | List of tool names to **block** during this phase. Mutually exclusive with `whitelist`. |
|
|
164
|
+
| `tools.whitelist` | `string[]` | No | — | List of tool names to **allow** during this phase. All other tools are blocked. Mutually exclusive with `blacklist`. |
|
|
165
|
+
| `availableProfiles` | `string[]` | No | — | Subagent profiles listed in the context injection as available for this phase. Informational only — not enforced by the extension. |
|
|
166
|
+
|
|
167
|
+
### Markdown Body
|
|
168
|
+
|
|
169
|
+
Everything after the YAML frontmatter delimiter (`---`) is treated as the **phase instructions**. This string is injected into the agent's context on every turn and supports the full set of template variables. Leading/trailing whitespace is trimmed.
|
|
170
|
+
|
|
171
|
+
See [docs/template-variables.md](template-variables.md) for the complete list of template variables available in phase instructions.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Tool Configuration Details
|
|
176
|
+
|
|
177
|
+
Each phase can optionally restrict which tools the agent may use. Tool control is configured via the `tools` frontmatter field with either a **blacklist** or a **whitelist** — never both.
|
|
178
|
+
|
|
179
|
+
### Blacklist Mode
|
|
180
|
+
|
|
181
|
+
```yaml
|
|
182
|
+
tools:
|
|
183
|
+
blacklist:
|
|
184
|
+
- edit
|
|
185
|
+
- write
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Semantics:** The listed tools are **blocked**. All other tools (including any not explicitly named) are **allowed**.
|
|
189
|
+
|
|
190
|
+
Use when a phase should have broad tool access except for a few specific dangerous tools.
|
|
191
|
+
|
|
192
|
+
### Whitelist Mode
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
tools:
|
|
196
|
+
whitelist:
|
|
197
|
+
- read
|
|
198
|
+
- search
|
|
199
|
+
- delegate_to_subagents
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Semantics:** **Only** the listed tools are **allowed**. Every other tool is blocked.
|
|
203
|
+
|
|
204
|
+
Use when a phase should have minimal, tightly scoped tool access.
|
|
205
|
+
|
|
206
|
+
### Mutual Exclusivity
|
|
207
|
+
|
|
208
|
+
Setting both `blacklist` and `whitelist` on the same phase is a **validation error**. The workflow will be skipped during loading with a warning:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
Workflow "my-workflow", phase "planning": cannot set both blacklist and whitelist.
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `workflow_step` Is Always Exempt
|
|
215
|
+
|
|
216
|
+
The `workflow_step` tool (used to advance, check status, loop, or cancel) is **always allowed** regardless of blacklist or whitelist configuration. It cannot be blocked.
|
|
217
|
+
|
|
218
|
+
### No `tools` Field
|
|
219
|
+
|
|
220
|
+
If a phase has no `tools` frontmatter field, **all tools are allowed** — no restrictions are applied.
|
|
221
|
+
|
|
222
|
+
### Block Reason Message
|
|
223
|
+
|
|
224
|
+
When a tool call is blocked, the agent receives a reason message. The default is:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
[workflow] The tool "{toolName}" is blocked during the {phaseName} phase.
|
|
228
|
+
Refer to the current phase instructions for allowed tools and approaches.
|
|
229
|
+
When finished, call workflow_step to advance to the next phase.
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Override this by setting `blockReasonTemplate` in `workflow.yaml`. Available variables: `{workflowName}`, `{phaseName}`, `{toolName}`, `{allowedTools}`.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Validation Rules
|
|
237
|
+
|
|
238
|
+
All validation is performed by `validateWorkflowDefinition()`. Workflows that fail validation are **skipped** (not loaded) with a console warning.
|
|
239
|
+
|
|
240
|
+
### Workflow-Level Validation
|
|
241
|
+
|
|
242
|
+
| Rule | Applies To | Error Condition |
|
|
243
|
+
| ------------------------- | -------------- | ------------------------------------------------ |
|
|
244
|
+
| `name` required | All | Missing, empty, or not a string |
|
|
245
|
+
| `commandName` required | `show: "user"` | Missing, empty, or not a string |
|
|
246
|
+
| `commandName` format | `show: "user"` | Does not match `^[a-zA-Z0-9_-]+$` |
|
|
247
|
+
| `initialMessage` required | `show: "user"` | Missing, empty, or not a string |
|
|
248
|
+
| `phases` required | All | Missing, not an array, or has fewer than 1 entry |
|
|
249
|
+
| `loopable` type | All | Present but not a boolean |
|
|
250
|
+
| `show` value | All | Present but not `"user"` or `"workflows"` |
|
|
251
|
+
|
|
252
|
+
For `show: "workflows"` workflows, `commandName` and `initialMessage` are **not required**. They may be omitted or empty.
|
|
253
|
+
|
|
254
|
+
### Phase-Level Validation (Concrete Phases)
|
|
255
|
+
|
|
256
|
+
| Rule | Error Condition |
|
|
257
|
+
| -------------------------------------- | ------------------------------------------------- |
|
|
258
|
+
| `id` required | Missing, empty, or not a string |
|
|
259
|
+
| `id` unique | Duplicate `id` within the same workflow |
|
|
260
|
+
| `name` required | Missing, empty, or not a string |
|
|
261
|
+
| `emoji` required | Missing, empty, or not a string |
|
|
262
|
+
| `instructions` required | Missing, empty, or not a string (from `.md` body) |
|
|
263
|
+
| `tools.blacklist` type | Present but not an array |
|
|
264
|
+
| `tools.whitelist` type | Present but not an array |
|
|
265
|
+
| blacklist/whitelist mutual exclusivity | Both set on the same phase |
|
|
266
|
+
|
|
267
|
+
### Subworkflow Reference Validation
|
|
268
|
+
|
|
269
|
+
| Rule | Error Condition |
|
|
270
|
+
| ---------------------- | ------------------------------- |
|
|
271
|
+
| `workflowKey` required | Missing, empty, or not a string |
|
|
272
|
+
|
|
273
|
+
Subworkflow references skip `id`/`name`/`emoji`/`instructions` validation — those fields live on the resolved target workflow.
|
|
274
|
+
|
|
275
|
+
### Cycle Detection
|
|
276
|
+
|
|
277
|
+
After validation, the loaded definitions undergo **cycle detection** using iterative DFS with 3-state coloring (WHITE/GRAY/BLACK). If a cycle is found:
|
|
278
|
+
|
|
279
|
+
1. A warning is logged: `Cycle detected: A → B → C → A. Skipping workflow "A".`
|
|
280
|
+
2. **All workflows involved in the cycle** are removed from the loaded set.
|
|
281
|
+
|
|
282
|
+
### Broken Subworkflow References
|
|
283
|
+
|
|
284
|
+
During resolution, if a workflow references a `workflowKey` that doesn't exist in the valid definitions:
|
|
285
|
+
|
|
286
|
+
1. A warning is logged: `Workflow "X" references non-existent subworkflow "Y". Skipping.`
|
|
287
|
+
2. The referencing workflow is removed.
|
|
288
|
+
3. This cascades — any other workflows referencing the removed workflow are also removed. The process repeats until no more deletions occur.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Path Safety
|
|
293
|
+
|
|
294
|
+
Phase file paths listed in `workflow.yaml`'s `phases` array are subject to **path traversal protection**:
|
|
295
|
+
|
|
296
|
+
1. The **canonical root** is computed via `realpathSync` on the resolved workflows root directory (either global or project).
|
|
297
|
+
2. Each phase file path is **canonicalized** via `realpathSync` on the resolved absolute path of `join(dirPath, phaseEntry)`.
|
|
298
|
+
3. The canonical phase path must start with `canonicalRoot + sep` (the root path plus the OS path separator).
|
|
299
|
+
4. If the file doesn't exist on disk yet, a deterministic prefix check (`resolve` without `realpathSync`) is performed instead.
|
|
300
|
+
|
|
301
|
+
**Effect:** Symlinks or relative segments like `../../etc/passwd` are resolved to their real path and rejected if they escape the workflows root. The workflow is skipped with a warning:
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
[pi-workflows] Phase file path escapes workflows root: ../../etc/passwd in /path/to/workflow.yaml
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Duplicate `commandName` Handling
|
|
310
|
+
|
|
311
|
+
After all validation, cycle removal, and broken-reference cascading, the loader checks for **duplicate `commandName` values** across the surviving workflow definitions:
|
|
312
|
+
|
|
313
|
+
1. A `Map<string, string>` tracks the first `commandName → workflowKey` mapping.
|
|
314
|
+
2. If a second workflow claims the same `commandName`, a warning is logged:
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
[pi-workflows] Duplicate commandName "rpir" in workflows "old-rpir" and "new-rpir". The first one found will be used.
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
3. **The first workflow encountered wins.** Since project definitions are spread over global ones (`{ ...globalDefs, ...projectDefs }`), project definitions are iterated last — the iteration order of `Object.entries` determines which "first" means. In practice, if a global and project workflow share a `commandName`, the project one overrides the global one's _definition_ (due to the spread), but the duplicate detection logs a warning and the first-encountered one wins.
|
|
321
|
+
|
|
322
|
+
4. Workflows with `show: "workflows"` that have no `commandName` are skipped during this check.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Template Variables
|
|
327
|
+
|
|
328
|
+
All `workflow.yaml` template fields and phase instruction bodies support `{varName}` placeholder substitution. Unknown variables are left as-is (e.g., `{unknown}` stays `{unknown}`).
|
|
329
|
+
|
|
330
|
+
See [docs/template-variables.md](template-variables.md) for the complete variable reference organized by context.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Complete Example
|
|
335
|
+
|
|
336
|
+
### `workflow.yaml`
|
|
337
|
+
|
|
338
|
+
```yaml
|
|
339
|
+
name: "Bug Fix Workflow"
|
|
340
|
+
commandName: "bugfix"
|
|
341
|
+
initialMessage: |
|
|
342
|
+
Starting {workflowName} for: "{description}"
|
|
343
|
+
Phase 1: {firstPhaseName} {firstPhaseEmoji}
|
|
344
|
+
Available profiles: {firstPhaseProfiles}
|
|
345
|
+
sessionNamePrefix: "Bugfix: "
|
|
346
|
+
sessionNameMaxLength: 40
|
|
347
|
+
loopable: false
|
|
348
|
+
show: "user"
|
|
349
|
+
roleInstruction: "You are the orchestrator for {workflowName}. Blocked tools: {blockedToolsList}."
|
|
350
|
+
advanceReminder: "Phase complete. Use {toolName} to advance to {nextPhaseName}."
|
|
351
|
+
blockReasonTemplate: "Tool '{toolName}' is blocked during {phaseName}. Allowed: {allowedTools}."
|
|
352
|
+
completionMessage: |
|
|
353
|
+
✅ {workflowName} complete!
|
|
354
|
+
Task: {taskDescription}
|
|
355
|
+
ID: {taskId}
|
|
356
|
+
Phases: {phaseCount}
|
|
357
|
+
notDoneReminder: |
|
|
358
|
+
⚠️ {workflowName} is still active (phase: {phaseEmoji} {phaseName}).
|
|
359
|
+
Instructions: {phaseInstructions}
|
|
360
|
+
phases:
|
|
361
|
+
- reproduce.md
|
|
362
|
+
- fix.md
|
|
363
|
+
- verify.md
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### `reproduce.md`
|
|
367
|
+
|
|
368
|
+
```markdown
|
|
369
|
+
---
|
|
370
|
+
id: reproduce
|
|
371
|
+
name: Reproduce
|
|
372
|
+
emoji: "🐛"
|
|
373
|
+
tools:
|
|
374
|
+
whitelist:
|
|
375
|
+
- read
|
|
376
|
+
- search
|
|
377
|
+
- delegate_to_subagents
|
|
378
|
+
availableProfiles:
|
|
379
|
+
- bug-reproducer
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Reproduce the Bug
|
|
383
|
+
|
|
384
|
+
Read the user's description and reproduce the issue in the codebase.
|
|
385
|
+
|
|
386
|
+
1. Search for relevant code paths
|
|
387
|
+
2. Identify the root cause
|
|
388
|
+
3. Document findings for the fix phase
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### `fix.md`
|
|
392
|
+
|
|
393
|
+
```markdown
|
|
394
|
+
---
|
|
395
|
+
id: fix
|
|
396
|
+
name: Fix
|
|
397
|
+
emoji: "🔧"
|
|
398
|
+
tools:
|
|
399
|
+
blacklist:
|
|
400
|
+
- bash
|
|
401
|
+
availableProfiles:
|
|
402
|
+
- task-coder
|
|
403
|
+
- task-reviewer
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Implement the Fix
|
|
407
|
+
|
|
408
|
+
Based on the reproduction findings, implement the fix.
|
|
409
|
+
|
|
410
|
+
Delegate implementation to the task-coder profile, then have the
|
|
411
|
+
task-reviewer profile verify the changes.
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### `verify.md`
|
|
415
|
+
|
|
416
|
+
```markdown
|
|
417
|
+
---
|
|
418
|
+
id: verify
|
|
419
|
+
name: Verify
|
|
420
|
+
emoji: "✅"
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Verify the Fix
|
|
424
|
+
|
|
425
|
+
Confirm the fix resolves the original issue. Run tests and check
|
|
426
|
+
for regressions. No tool restrictions — full access allowed.
|
|
427
|
+
```
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Contributing to pi-workflows
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing. This guide covers everything you need to set up a development environment, understand the codebase, and submit changes.
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
**Prerequisites:** [Node.js](https://nodejs.org/) (v20+ recommended) and npm.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone <repo-url>
|
|
11
|
+
cd pi-workflows
|
|
12
|
+
npm install
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
There is **no build step**. The pi framework loads TypeScript source files directly via `src/index.ts` (declared as `"main"` in `package.json`). TypeScript is used for type-checking only (`noEmit: true` in `tsconfig.json`).
|
|
16
|
+
|
|
17
|
+
**Run tests:**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm test
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This runs `vitest run` — tests live in `src/__tests__/` and are matched by the pattern `src/__tests__/**/*.test.ts`.
|
|
24
|
+
|
|
25
|
+
## Project Structure
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
pi-workflows/
|
|
29
|
+
├── src/
|
|
30
|
+
│ ├── index.ts # Extension entry point & event wiring
|
|
31
|
+
│ ├── types.ts # Type definitions, interfaces, type guards
|
|
32
|
+
│ ├── config/ # Workflow loading, validation, template resolution
|
|
33
|
+
│ │ ├── index.ts # Re-exports from sub-modules
|
|
34
|
+
│ │ ├── loading.ts # Workflow directory scanning & two-pass loading
|
|
35
|
+
│ │ ├── loading-parse.ts # Definition file parsing
|
|
36
|
+
│ │ ├── loading-phases.ts # Phase extraction & ordering
|
|
37
|
+
│ │ ├── loading-resolve.ts# Subworkflow & reference resolution
|
|
38
|
+
│ │ ├── validation.ts # Definition validation & cycle detection
|
|
39
|
+
│ │ └── templates.ts # Template variable resolution
|
|
40
|
+
│ ├── state.ts # State creation, advancement, persistence, reconstruction
|
|
41
|
+
│ ├── tool.ts # workflow_step tool registration & execution
|
|
42
|
+
│ ├── command.ts # /workflow and /cancel-workflow slash commands
|
|
43
|
+
│ ├── hooks.ts # Lifecycle hooks (tool_call, before_agent_start, agent_end, turn_end)
|
|
44
|
+
│ ├── prompts.ts # Context injection prompt builder & default templates
|
|
45
|
+
│ ├── renderers.ts # TUI message renderers for workflow events
|
|
46
|
+
│ ├── TimerManager.ts # Timer lifecycle management for workflow timeouts
|
|
47
|
+
│ └── __tests__/
|
|
48
|
+
│ ├── command.test.ts
|
|
49
|
+
│ ├── config.test.ts
|
|
50
|
+
│ ├── hooks.test.ts
|
|
51
|
+
│ ├── index.test.ts
|
|
52
|
+
│ ├── prompts.test.ts
|
|
53
|
+
│ ├── renderers.test.ts
|
|
54
|
+
│ ├── setup.ts
|
|
55
|
+
│ ├── state.test.ts
|
|
56
|
+
│ ├── tool.test.ts
|
|
57
|
+
│ └── helpers/
|
|
58
|
+
│ ├── fixtures.ts
|
|
59
|
+
│ └── mocks.ts
|
|
60
|
+
├── skills/
|
|
61
|
+
│ └── workflow-generation/
|
|
62
|
+
│ └── SKILL.md # Agent skill for generating workflow definitions
|
|
63
|
+
├── docs/ # Documentation
|
|
64
|
+
│ ├── architecture.md
|
|
65
|
+
│ ├── configuration-reference.md
|
|
66
|
+
│ ├── contributing.md
|
|
67
|
+
│ ├── examples.md
|
|
68
|
+
│ ├── state-management.md
|
|
69
|
+
│ ├── subworkflows.md
|
|
70
|
+
│ ├── template-variables.md
|
|
71
|
+
│ └── testing.md
|
|
72
|
+
├── package.json
|
|
73
|
+
├── tsconfig.json
|
|
74
|
+
├── vitest.config.ts
|
|
75
|
+
└── README.md
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
For a detailed explanation of how these modules interact, see [architecture.md](architecture.md).
|
|
79
|
+
|
|
80
|
+
## Code Style
|
|
81
|
+
|
|
82
|
+
- **TypeScript with ESM modules** — `"type": "module"` in `package.json`, `module: "ESNext"` in `tsconfig.json`.
|
|
83
|
+
- **Explicit return types** on all exported functions.
|
|
84
|
+
- **JSDoc comments** on public/exported functions describing purpose and parameters.
|
|
85
|
+
- **`const`** for bindings that are never reassigned; `let` only when reassignment is required.
|
|
86
|
+
- **No runtime build** — the project relies on `"noEmit": true` and the host framework's TypeScript loader.
|
|
87
|
+
|
|
88
|
+
Example of expected style:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
/**
|
|
92
|
+
* Validate a workflow definition and return an error message if invalid.
|
|
93
|
+
*/
|
|
94
|
+
export function validateWorkflowDefinition(key: string, def: WorkflowDefinition): string | null {
|
|
95
|
+
// ...
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Adding a New Feature
|
|
100
|
+
|
|
101
|
+
Follow these steps in order:
|
|
102
|
+
|
|
103
|
+
1. **Define types in `types.ts`** — Add interfaces, type aliases, and type guards. All runtime types and Zod/typebox schemas live alongside or reference these definitions.
|
|
104
|
+
|
|
105
|
+
2. **Implement in the appropriate module** — Place logic in the module that owns that domain:
|
|
106
|
+
- Workflow loading/validation → `config/`
|
|
107
|
+
- State manipulation → `state.ts`
|
|
108
|
+
- Tool behavior → `tool.ts`
|
|
109
|
+
- Slash commands → `command.ts`
|
|
110
|
+
- Hook handlers → `hooks.ts`
|
|
111
|
+
- Prompt text → `prompts.ts`
|
|
112
|
+
- TUI rendering → `renderers.ts`
|
|
113
|
+
|
|
114
|
+
3. **Wire into `index.ts`** — The default export function receives the `ExtensionAPI` and registers everything. Import your new function and call it from the entry point, or extend an existing registration call.
|
|
115
|
+
|
|
116
|
+
4. **Add tests in `src/__tests__/`** — Create a `<module>.test.ts` file alongside the existing test files. Use vitest (`describe`, `it`, `expect`).
|
|
117
|
+
|
|
118
|
+
5. **Update documentation in `docs/`** — Add or update relevant docs to reflect the new behavior.
|
|
119
|
+
|
|
120
|
+
6. **Update `skills/workflow-generation/SKILL.md`** — If your change affects the workflow definition schema (e.g., new fields in `WorkflowDefinition`, new phase frontmatter keys, or changed directory conventions), update the skill so the agent can generate definitions that use the new features.
|
|
121
|
+
|
|
122
|
+
## PR Guidelines
|
|
123
|
+
|
|
124
|
+
- **Focused PRs** — One concern per pull request. Avoid mixing refactors, features, and documentation updates in a single PR unless tightly coupled.
|
|
125
|
+
- **All tests pass** — Run `npm test` before pushing. CI will validate this.
|
|
126
|
+
- **New features include tests** — Every new exported function or behavior change should have corresponding test coverage.
|
|
127
|
+
- **Docs in the same PR** — Documentation updates should accompany the code they describe, not follow in a separate PR.
|
|
128
|
+
- **Schema changes update SKILL.md** — If the workflow definition schema changes, the agent skill must be updated in the same PR.
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
By contributing, you agree that your contributions will be licensed under the [MIT License](https://opensource.org/licenses/MIT), consistent with the project's `"license": "MIT"` in `package.json`.
|