@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.
@@ -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`.