lilflow 0.1.1 → 0.2.1

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,7 @@
1
+ {
2
+ "name": "lilflow",
3
+ "version": "0.2.1",
4
+ "description": "Drive lilflow workflows from inside a Claude Code session. Exposes the flow session-bridge CLI as a skill so the agent can request the next step, update workflow state, evaluate gates, and converse with the user.",
5
+ "homepage": "https://github.com/iVintik/lilflow",
6
+ "license": "MIT"
7
+ }
package/README.md CHANGED
@@ -1,112 +1,254 @@
1
+ <!-- codeharness:readme -->
1
2
  # lilflow
2
3
 
3
- Repo-native workflow engine CLI. Step-level resumable YAML workflows with state stored under `.flow/` in your repo.
4
+ Repo-native workflow engine CLI. Define multi-step workflows in YAML, execute them with step-level persistence, and resume from failure -- all stored under `.flow/` in your repo. No external services, no databases, no daemon.
4
5
 
5
- ## Install
6
+ ## What it does
7
+
8
+ lilflow turns YAML workflow definitions into executable, resumable pipelines that live inside your repository. It persists every step's start, completion, and failure as append-only JSONL events, so you can resume a failed run from exactly where it broke. It has first-class support for LLM coding agents (Claude Code and OpenCode) as step types, with session management across steps and two execution modes: classic (one agent process per step) and session (the entire workflow runs inside a single long-lived agent session for prompt-cache efficiency). It's for developers who want CI-like orchestration without leaving the repo.
9
+
10
+ ## Key features
11
+
12
+ - **Step-level resumability** -- append-only JSONL event logs let you resume from the exact point of failure
13
+ - **LLM agent steps** -- run Claude Code or OpenCode as workflow steps with session continuation and cost tracking
14
+ - **Session mode** -- run the whole workflow inside one agent session; agent calls `flow session-bridge` CLI to advance steps. Keeps the prompt cache warm for the entire run
15
+ - **Conversational agent mode** -- `agent.mode: conversational` lets the agent iterate with the user until the step is complete, then advance
16
+ - **Structured JSON output** -- `output_file` + `output_format: json` on agent steps extracts and validates JSON from the agent's stdout, no parsing scripts required
17
+ - **Parallel execution** -- mark steps with `parallel: true` or group them with `parallel_group` for concurrent batches
18
+ - **For-each loops** -- expand a step across an array of items with `{{item}}` templating
19
+ - **Quality gates** -- conditional checkpoints that halt or warn based on expressions
20
+ - **Subflow composition** -- call child workflows with parameter passing
21
+ - **Wait triggers** -- block on file existence or external signals with `flow signal`
22
+ - **Hierarchical config** -- global, project, workflow-local, env vars, and CLI flags merge in order
23
+
24
+ ## Tech stack
25
+
26
+ - **Runtime:** Node.js >= 22
27
+ - **Language:** JavaScript (ES modules)
28
+ - **Dependencies:** js-yaml (single runtime dependency)
29
+ - **Test runner:** node:test (built-in)
30
+ - **Coverage:** c8
31
+
32
+ ## Getting started
33
+
34
+ ### Install
6
35
 
7
36
  ```bash
8
37
  npm install -g lilflow
9
38
  ```
10
39
 
11
- Binary is exposed as both `lilflow` and `flow`.
12
-
13
- ## Quick Start
40
+ ### Run the simplest possible example
14
41
 
15
42
  ```bash
16
- flow init # scaffold workflow.yaml + .flow/ state dir
17
- flow run # execute the workflow
18
- flow status # inspect run state
19
- flow resume # resume a failed run from the last completed step
43
+ flow init && flow run
20
44
  ```
21
45
 
22
- ## Parallel Steps
46
+ Expected output:
47
+ ```
48
+ Initialized .flow/ and workflow.yaml
49
+ Running workflow 'hello-world'...
50
+ [setup] echo "Hello from flow"
51
+ Hello from flow
52
+ Workflow completed.
53
+ ```
54
+
55
+ ### Next step
56
+
57
+ See [Usage](#usage) for common workflows or [docs/index.md](./docs/index.md) for full docs.
23
58
 
24
- The runner supports contiguous parallel batches inside `workflow.yaml`.
59
+ ## Usage
60
+
61
+ ### Define and run a multi-step workflow
25
62
 
26
63
  ```yaml
27
- name: ci
64
+ name: build-and-test
28
65
  steps:
66
+ - name: install
67
+ run: npm ci
29
68
  - name: lint
30
69
  run: npm run lint
31
- parallel: true
70
+ - name: test
71
+ run: npm test
72
+ ```
73
+
74
+ ```bash
75
+ flow run
76
+ ```
77
+
78
+ ### Run steps in parallel
32
79
 
80
+ ```yaml
81
+ steps:
82
+ - name: lint
83
+ run: npm run lint
84
+ parallel: true
33
85
  - name: test
34
86
  run: npm test
35
87
  parallel: true
36
-
37
88
  - name: build
38
89
  run: npm run build
39
90
  ```
40
91
 
41
- Parallel step output is streamed in real time with step-name prefixes such as `[lint] ...`, and `flow status <run-id>` shows each started step with its own `running`, `completed`, or `failed` state.
92
+ Parallel step output streams in real time with `[lint] ...` / `[test] ...` prefixes.
93
+
94
+ ### Resume a failed run
95
+
96
+ ```bash
97
+ flow status <run-id> # see what failed
98
+ flow resume <run-id> # pick up from the failed step
99
+ ```
42
100
 
43
- Parallel batch concurrency is controlled by the resolved `parallelism` config value. The default is `4`, and you can override it in `.flow/config.yaml`, `~/.flow/config.yaml`, or with `FLOW_PARALLELISM`.
101
+ ### Use LLM agents as steps
44
102
 
45
103
  ```yaml
46
- parallelism: 2
104
+ steps:
105
+ - name: implement
106
+ agent:
107
+ provider: claude-code
108
+ prompt: "Implement the dashboard component"
109
+ - name: review
110
+ agent:
111
+ provider: claude-code
112
+ prompt: "Review the implementation"
113
+ session: continue
47
114
  ```
48
115
 
49
- ## Retry Logic
116
+ ### Agent interaction modes
50
117
 
51
- Steps can retry transient failures with exponential backoff.
118
+ Agent steps take an optional `mode` field:
119
+
120
+ - `autonomous` (default) -- agent works alone, runs headless, reports when done
121
+ - `conversational` -- agent works with the user iteratively in a TTY, advances only after user approval
52
122
 
53
123
  ```yaml
54
- name: deploy
55
124
  steps:
56
- - name: flaky-check
57
- run: npm run flaky-check
58
- retry: 3
59
- retry_delay: 5s
125
+ - name: build
126
+ agent:
127
+ provider: claude-code
128
+ prompt: "Build the feature described in spec.md"
129
+ mode: conversational
60
130
  ```
61
131
 
62
- `retry` is the number of retries after the initial attempt, so `retry: 3` allows up to 4 total attempts. `retry_delay` is the base delay and doubles on each retry. The runner prints numbered attempt labels such as `[attempt 2/4]`, shows the failure reason for each retryable attempt, and prints wait lines such as `Waiting 10s before retry 2/3...`.
132
+ ### Extract JSON output from an agent step
133
+
134
+ ```yaml
135
+ steps:
136
+ - name: analyze
137
+ agent:
138
+ provider: claude-code
139
+ prompt: ./analyze.md
140
+ output_file: out/analysis.json
141
+ output_format: json
142
+ ```
63
143
 
64
- ## For-Each Loops
144
+ lilflow strips markdown fences, finds the first balanced JSON object/array in stdout, validates it parses, and writes the cleaned JSON to the file. The step fails with a structured error code if the output isn't valid JSON.
65
145
 
66
- Steps can expand an inline array into concrete iterations with `for_each`.
146
+ ### Run the entire workflow in one agent session
67
147
 
68
148
  ```yaml
69
- name: deploy
149
+ name: iterative-dev
150
+ mode: session
151
+ session:
152
+ provider: claude-code # or: opencode (via oh-my-opencode)
153
+ model: claude-opus-4-6
154
+ allow_tools: [Bash, Read, Write, Edit, Grep, Glob]
155
+ plugins: [lilflow]
70
156
  steps:
71
- - name: deploy-target
72
- run: echo "Deploying to {{item}} ({{item_number}}/{{item_count}})"
73
- for_each: [dev, staging, prod]
157
+ - name: scaffold
158
+ run: mkdir -p src/components
159
+ - name: implement
160
+ agent:
161
+ provider: claude-code
162
+ prompt: "Build dashboard components"
163
+ mode: conversational
164
+ - name: test
165
+ run: npm test
74
166
  ```
75
167
 
76
- Each iteration becomes a concrete runtime step with a stable label such as `deploy-target [item-prod]`. The templating variables `{{item}}`, `{{item_index}}`, `{{item_number}}`, `{{item_count}}`, `{{item_first}}`, and `{{item_last}}` are available in string step fields. If one iteration fails, the remaining iterations still run before the workflow stops.
168
+ `flow run` spawns one `claude` (or `opencode`) process, injects a workflow-aware system prompt, and exposes the `flow session-bridge` CLI through the bundled `lilflow` skill. The agent drives the workflow (`flow session-bridge next` -> execute -> `flow session-bridge update`) without per-step process spawns, keeping the prompt cache warm for the entire run.
77
169
 
78
- ## Workflow Templates
170
+ OpenCode sessions work through [oh-my-opencode](https://github.com/opensoft/oh-my-opencode)'s Claude Code compatibility layer -- the same bundled plugin loads unmodified.
79
171
 
80
- `flow init` can scaffold `workflow.yaml` from reusable local templates stored under `.flow/templates/<name>/workflow.yaml`.
172
+ ### Iterate with for-each
81
173
 
82
- ```bash
83
- flow init --list-templates
84
- flow init --template ci-pipeline
174
+ ```yaml
175
+ steps:
176
+ - name: deploy
177
+ run: ./deploy.sh {{item}}
178
+ for_each: [dev, staging, prod]
85
179
  ```
86
180
 
87
- Template files can mix prompt-backed placeholders and config-backed placeholders:
181
+ ### Wait for external input
88
182
 
89
183
  ```yaml
90
- name: {{template.workflow_name|ci-pipeline}}
91
- parallelism: {{config.parallelism}}
92
184
  steps:
93
- - name: test
94
- run: npm test
185
+ - name: await-approval
186
+ wait:
187
+ trigger: signal
188
+ timeout: 1h
189
+ ```
190
+
191
+ Then from another terminal: `flow signal <run-id> await-approval --data '{"approved": true}'`
192
+
193
+ ## CLI
194
+
95
195
  ```
196
+ flow init [--template <name>]
197
+ flow config
198
+ flow run [<workflow>]
199
+ flow resume <run-id>
200
+ flow set-step <run-id> <step-index>
201
+ flow signal <run-id> <step-name> [--data '{}']
202
+ flow status <run-id>
203
+ flow list
204
+ flow logs <run-id> [--step <step>]
205
+ flow session-bridge <subcommand> # agent-facing bridge for session-mode workflows
206
+ ```
207
+
208
+ ## Claude Code plugin
96
209
 
97
- - `{{template.name}}` prompts for a value
98
- - `{{template.name|default}}` prompts and uses the default when you press enter
99
- - `{{config.parallelism}}` reads from the resolved lilflow config
210
+ This repo is also a Claude Code plugin. `.claude-plugin/plugin.json` is the marker file; `skills/` holds the `lilflow-workflow-driver` skill used by session mode. The plugin version is kept in lock-step with the npm package version via the `npm version` lifecycle hook, so a single `v{version}` tag releases both.
100
211
 
101
- Any directory matching `.flow/templates/<name>/workflow.yaml` is a usable custom template.
212
+ Install as a plugin from a Claude Code session:
213
+ ```bash
214
+ claude plugin install github:iVintik/lilflow
215
+ ```
216
+
217
+ OpenCode users install the same plugin via [oh-my-opencode](https://github.com/opensoft/oh-my-opencode)'s Claude Code compatibility layer.
218
+
219
+ ## Project structure
220
+
221
+ ```
222
+ lilflow/
223
+ ├── src/ # Application source (ES modules)
224
+ │ ├── cli.js # Entry point, command router
225
+ │ ├── run-workflow.js # Core workflow engine
226
+ │ ├── config.js # Hierarchical config system
227
+ │ ├── init-project.js # Project scaffolding
228
+ │ ├── session-bridge.js # Agent-facing CLI for session mode
229
+ │ ├── session-runner.js # Single-process execution path
230
+ │ ├── session-prompt.js # System prompt generator
231
+ │ └── agents/ # LLM agent provider adapters + output extraction
232
+ ├── .claude-plugin/ # Claude Code plugin manifest
233
+ │ └── plugin.json
234
+ ├── skills/ # Claude Code skills bundled with the plugin
235
+ │ └── lilflow-workflow-driver/SKILL.md
236
+ ├── scripts/ # Release helpers (sync-plugin-version.js)
237
+ ├── tests/ # Test suite (node:test)
238
+ ├── docs/ # Generated documentation + requirements specs
239
+ └── .github/workflows/ # CI/CD (lint + test + npm publish)
240
+ ```
102
241
 
103
- ## Configuration
242
+ ## Documentation
104
243
 
105
- - Project config: `.flow/config.yaml`
106
- - Global config: `~/.flow/config.yaml`
107
- - Env var override: `FLOW_*`
108
- - Override order: defaults → global → project → env → flags
244
+ - [Documentation Index](./docs/index.md)
245
+ - [Architecture](./docs/architecture.md)
246
+ - [Component Inventory](./docs/component-inventory.md)
247
+ - [Source Tree Analysis](./docs/source-tree-analysis.md)
248
+ - [Development Guide](./docs/development-guide.md)
249
+ - [Session Mode Spec](./docs/requirements/session-mode.md)
109
250
 
110
251
  ## License
111
252
 
112
253
  MIT
254
+ <!-- /codeharness:readme -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lilflow",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Repo-native workflow engine CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,8 @@
9
9
  },
10
10
  "files": [
11
11
  "src",
12
+ ".claude-plugin",
13
+ "skills",
12
14
  "README.md",
13
15
  "AGENTS.md"
14
16
  ],
@@ -33,7 +35,9 @@
33
35
  "scripts": {
34
36
  "test": "c8 --reporter=text --reporter=lcov --reporter=json-summary node --test",
35
37
  "coverage": "npm test",
36
- "lint": "eslint src/"
38
+ "lint": "eslint src/",
39
+ "sync-plugin-version": "node scripts/sync-plugin-version.js",
40
+ "version": "npm run sync-plugin-version && git add .claude-plugin/plugin.json"
37
41
  },
38
42
  "engines": {
39
43
  "node": ">=22"
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: lilflow-workflow-driver
3
+ description: Drive a lilflow session-mode workflow from inside this agent session (Claude Code natively, or OpenCode via the oh-my-opencode Claude Code compatibility layer). Use this skill whenever the FLOW_RUN_ID environment variable is set — it means lilflow has started a workflow and expects you to advance it via the `flow session-bridge` CLI. Call `flow session-bridge next` to get the next step, execute it, then call `flow session-bridge update <step> completed|failed` to advance. For conversational agent steps, work with the user iteratively before marking complete.
4
+ ---
5
+
6
+ # lilflow Workflow Driver
7
+
8
+ You are driving a **lilflow** workflow. Each step in the workflow must be fetched, executed, and reported back through the `flow session-bridge` CLI. Do not try to guess the workflow — always ask the bridge what's next.
9
+
10
+ This skill works identically in Claude Code and in OpenCode (via [oh-my-opencode](https://github.com/opensoft/oh-my-opencode)'s Claude Code compatibility layer). The bridge CLI is the same in both environments.
11
+
12
+ ## When this skill activates
13
+
14
+ You can tell this skill applies when:
15
+
16
+ - The environment variable `FLOW_RUN_ID` is set (always check `echo $FLOW_RUN_ID` first).
17
+ - The user's initial prompt references a workflow, a "run", or explicitly mentions lilflow.
18
+
19
+ If `FLOW_RUN_ID` is not set, stop and tell the user — the workflow runner did not start this session.
20
+
21
+ ## Core loop
22
+
23
+ Follow this loop until `flow session-bridge next` reports `workflow_status: "completed"`:
24
+
25
+ 1. **Get the next step:**
26
+ ```bash
27
+ flow session-bridge next
28
+ ```
29
+ Returns JSON: `{ step, steps, remaining, workflow_status }`. `step` is non-null for a single eligible step; `steps` is a list for a parallel batch.
30
+
31
+ 2. **Execute the step** based on its `type`:
32
+
33
+ - **`run`** — execute `step.command` via the `Bash` tool. Capture stdout/exit code.
34
+ - **`agent`** with `mode: autonomous` — execute `step.agent.prompt` yourself. You are the agent.
35
+ - **`agent`** with `mode: conversational` — see [Conversational steps](#conversational-steps) below.
36
+ - **`agent`** with a different provider (e.g. `opencode`) — mark the step `deferred`; the runner handles foreign providers.
37
+ - **`gate`** — call `flow session-bridge gate <name>`. It evaluates the condition and returns `{ passed, workflow_should_stop }`.
38
+ - **`interactive`** — call `flow session-bridge ask <prompt>` to get user input, then run the command with the input.
39
+ - **`wait`** — call `flow session-bridge wait <name>`. Blocks until the trigger fires.
40
+ - **`subflow`** — not yet supported in session mode. Mark the step `deferred`.
41
+
42
+ 3. **Report the result:**
43
+ ```bash
44
+ flow session-bridge update <step-name> completed --output "..." --exit-code 0
45
+ flow session-bridge update <step-name> failed --reason "..."
46
+ ```
47
+
48
+ 4. **On failure:** call `flow session-bridge update <name> failed` and stop the loop unless the user instructs otherwise.
49
+
50
+ 5. **Repeat** until `workflow_status === "completed"`.
51
+
52
+ ## Conversational steps
53
+
54
+ When `step.agent.mode === "conversational"`:
55
+
56
+ 1. Present the step's prompt and your proposed approach to the user.
57
+ 2. Work iteratively — implement, show results, gather feedback, adjust.
58
+ 3. Do NOT call `flow session-bridge update` until the user has confirmed satisfaction OR you have completed the task to its written specification.
59
+ 4. Explicitly tell the user when you consider the step complete before advancing.
60
+ 5. If the user says "skip", "abort", or similar, call `flow session-bridge update <name> failed --reason "user aborted"`.
61
+
62
+ Example:
63
+ ```
64
+ User: (workflow starts)
65
+ Agent: The workflow says: "Create React components for the dashboard.
66
+ Iterate with the user until they approve." Here's my plan: ...
67
+ User: Add a sidebar too.
68
+ Agent: [revises, implements, shows output]. Anything else?
69
+ User: Looks good.
70
+ Agent: Marking "implement" complete, advancing to the next step.
71
+ [runs: flow session-bridge update implement completed]
72
+ ```
73
+
74
+ ## Parallel batches
75
+
76
+ If `flow session-bridge next` returns multiple `steps`, they are eligible in parallel. You may execute them sequentially, or use sub-agents (the `Agent` tool) to run them concurrently. Call `flow session-bridge update` once per step.
77
+
78
+ ## User-initiated questions
79
+
80
+ If you need user input in the middle of an autonomous step (an ambiguity the workflow didn't cover), call `flow session-bridge ask "<question>"` — the bridge prompts the user and returns their response on stdout.
81
+
82
+ ## Context management
83
+
84
+ After long workflows, your context may accumulate noise from completed steps. Call:
85
+ ```bash
86
+ flow session-bridge compact
87
+ ```
88
+ This returns a condensed summary of completed steps. Use the summary to anchor your memory and treat earlier tool results as droppable.
89
+
90
+ ## Reporting notes and decisions
91
+
92
+ To record observations that don't change workflow state:
93
+ ```bash
94
+ flow session-bridge log "observation or decision"
95
+ ```
96
+
97
+ ## Inspecting state
98
+
99
+ To see the full workflow state (what's done, what's pending):
100
+ ```bash
101
+ flow session-bridge status
102
+ ```
103
+
104
+ ## Rules
105
+
106
+ 1. **Always** call `flow session-bridge next` before executing anything. Do not infer the workflow.
107
+ 2. **Always** call `flow session-bridge update` after finishing a step, even on failure. The bridge is the source of truth.
108
+ 3. **Never** invent step names or pretend a step succeeded. The event log is authoritative.
109
+ 4. When a gate fails with `workflow_should_stop: true`, stop the loop and report the failure to the user.
110
+ 5. Respect `mode: conversational` — do not advance these steps without user interaction.
@@ -5,6 +5,7 @@ import { runOpencode } from "./opencode.js";
5
5
  import { runClaudeCode } from "./claude-code.js";
6
6
  import { resolvePromptInput } from "./prompt.js";
7
7
  import { getSessionId, loadSessionStore, saveSessionId } from "./session-store.js";
8
+ import { AgentOutputError, writeAgentOutput } from "./output-file.js";
8
9
 
9
10
  const PROVIDERS = {
10
11
  opencode: {
@@ -101,6 +102,18 @@ export async function executeAgentStep(options) {
101
102
  const templatedAppendSystemPrompt = agentSpec.append_system_prompt === undefined
102
103
  ? undefined
103
104
  : templateFn(agentSpec.append_system_prompt);
105
+
106
+ if (agentSpec.interactive !== undefined && typeof warn === "function") {
107
+ warn(
108
+ `Agent step '${step.name}' uses deprecated agent.interactive; prefer agent.mode: ${agentSpec.interactive === true ? "conversational" : "autonomous"}.`
109
+ );
110
+ }
111
+
112
+ // mode: conversational maps to TTY passthrough. Legacy agent.interactive
113
+ // is respected for back-compat until removal.
114
+ const resolvedInteractive = agentSpec.mode === "conversational"
115
+ || agentSpec.interactive === true;
116
+
104
117
  const result = await provider.run({
105
118
  bin: binary,
106
119
  prompt: templatedPrompt,
@@ -109,7 +122,7 @@ export async function executeAgentStep(options) {
109
122
  timeoutMs,
110
123
  cwd,
111
124
  env,
112
- interactive: agentSpec.interactive === true,
125
+ interactive: resolvedInteractive,
113
126
  plugins: agentSpec.plugins ?? [],
114
127
  allowTools: agentSpec.allow_tools ?? [],
115
128
  appendSystemPrompt: templatedAppendSystemPrompt,
@@ -128,6 +141,33 @@ export async function executeAgentStep(options) {
128
141
  }
129
142
  }
130
143
 
144
+ if (agentSpec.output_file !== undefined && result.exitCode === 0) {
145
+ try {
146
+ const writeResult = await writeAgentOutput({
147
+ outputFile: agentSpec.output_file,
148
+ format: agentSpec.output_format ?? "text",
149
+ stdout: result.stdout,
150
+ cwd,
151
+ stepName: step.name
152
+ });
153
+
154
+ if (typeof warn === "function") {
155
+ warn(`Agent step '${step.name}' wrote ${writeResult.bytesWritten} bytes to ${agentSpec.output_file}.`);
156
+ }
157
+ } catch (error) {
158
+ if (error instanceof AgentOutputError) {
159
+ // Surface as step failure without corrupting the agent result payload.
160
+ return {
161
+ ...result,
162
+ exitCode: result.exitCode === 0 ? 1 : result.exitCode,
163
+ stderr: `${result.stderr ?? ""}\n${error.message}`.trim(),
164
+ outputError: { code: error.code, message: error.message }
165
+ };
166
+ }
167
+ throw error;
168
+ }
169
+ }
170
+
131
171
  return result;
132
172
  }
133
173