@synergenius/flow-weaver 0.10.12 → 0.12.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,225 @@
1
+ /**
2
+ * Checkpoint serialization for crash recovery.
3
+ *
4
+ * Writes workflow state to disk after each node completes. If the process
5
+ * crashes, the checkpoint file persists and can be used to resume execution
6
+ * from the last completed node.
7
+ *
8
+ * Checkpoint files live in .fw-checkpoints/ next to the workflow file and
9
+ * are auto-deleted after successful workflow completion.
10
+ */
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import * as crypto from 'crypto';
14
+ // ---------------------------------------------------------------------------
15
+ // Serialization helpers
16
+ // ---------------------------------------------------------------------------
17
+ function isUnserializableMarker(value) {
18
+ return (typeof value === 'object' &&
19
+ value !== null &&
20
+ value.__fw_unserializable__ === true);
21
+ }
22
+ /**
23
+ * Try to serialize a value. If it contains functions, invoke them first.
24
+ * If serialization fails, return a marker.
25
+ */
26
+ function serializeValue(key, value, unsafeNodes) {
27
+ // Resolve function values (pull execution lazy evaluation)
28
+ if (typeof value === 'function') {
29
+ try {
30
+ value = value();
31
+ }
32
+ catch {
33
+ const parts = key.split(':');
34
+ unsafeNodes.add(parts[0]);
35
+ return {
36
+ __fw_unserializable__: true,
37
+ nodeId: parts[0],
38
+ portName: parts[1] || 'unknown',
39
+ reason: 'Function invocation failed',
40
+ };
41
+ }
42
+ }
43
+ // Handle Promises: can't serialize, mark as unsafe
44
+ if (value instanceof Promise) {
45
+ const parts = key.split(':');
46
+ unsafeNodes.add(parts[0]);
47
+ return {
48
+ __fw_unserializable__: true,
49
+ nodeId: parts[0],
50
+ portName: parts[1] || 'unknown',
51
+ reason: 'Promise value',
52
+ };
53
+ }
54
+ // Test serialization
55
+ try {
56
+ JSON.stringify(value);
57
+ return value;
58
+ }
59
+ catch {
60
+ const parts = key.split(':');
61
+ unsafeNodes.add(parts[0]);
62
+ return {
63
+ __fw_unserializable__: true,
64
+ nodeId: parts[0],
65
+ portName: parts[1] || 'unknown',
66
+ reason: 'Not JSON-serializable',
67
+ };
68
+ }
69
+ }
70
+ /**
71
+ * Compute SHA-256 hash of a file's contents.
72
+ */
73
+ function hashFile(filePath) {
74
+ const content = fs.readFileSync(filePath, 'utf8');
75
+ return crypto.createHash('sha256').update(content).digest('hex');
76
+ }
77
+ // ---------------------------------------------------------------------------
78
+ // CheckpointWriter
79
+ // ---------------------------------------------------------------------------
80
+ export class CheckpointWriter {
81
+ dir;
82
+ filePath;
83
+ workflowName;
84
+ runId;
85
+ params;
86
+ workflowHash;
87
+ checkpointPath;
88
+ writeLock = Promise.resolve();
89
+ constructor(workflowFilePath, workflowName, runId, params = {}) {
90
+ this.filePath = path.resolve(workflowFilePath);
91
+ this.workflowName = workflowName;
92
+ this.runId = runId;
93
+ this.params = params;
94
+ this.dir = path.join(path.dirname(this.filePath), '.fw-checkpoints');
95
+ this.checkpointPath = path.join(this.dir, `${workflowName}-${runId}.json`);
96
+ this.workflowHash = hashFile(this.filePath);
97
+ }
98
+ /**
99
+ * Write a checkpoint after a node completes. Uses a write lock so
100
+ * concurrent calls from parallel nodes are serialized.
101
+ */
102
+ async write(completedNodes, executionOrder, position, ctx) {
103
+ // Serialize under a lock to prevent concurrent writes (parallel nodes)
104
+ this.writeLock = this.writeLock.then(() => this._writeCheckpoint(completedNodes, executionOrder, position, ctx));
105
+ await this.writeLock;
106
+ }
107
+ /** Clean up checkpoint file after successful completion */
108
+ cleanup() {
109
+ try {
110
+ if (fs.existsSync(this.checkpointPath)) {
111
+ fs.unlinkSync(this.checkpointPath);
112
+ }
113
+ // Remove directory if empty
114
+ if (fs.existsSync(this.dir)) {
115
+ const remaining = fs.readdirSync(this.dir);
116
+ if (remaining.length === 0) {
117
+ fs.rmdirSync(this.dir);
118
+ }
119
+ }
120
+ }
121
+ catch {
122
+ // Best-effort cleanup
123
+ }
124
+ }
125
+ getCheckpointPath() {
126
+ return this.checkpointPath;
127
+ }
128
+ _writeCheckpoint(completedNodes, executionOrder, position, ctx) {
129
+ const serialized = ctx.serialize();
130
+ const unsafeNodes = new Set();
131
+ // Serialize variables, handling unserializable values
132
+ const variables = {};
133
+ for (const [key, value] of Object.entries(serialized.variables)) {
134
+ variables[key] = serializeValue(key, value, unsafeNodes);
135
+ }
136
+ const data = {
137
+ version: 1,
138
+ workflowHash: this.workflowHash,
139
+ workflowName: this.workflowName,
140
+ filePath: this.filePath,
141
+ params: this.params,
142
+ timestamp: new Date().toISOString(),
143
+ completedNodes: [...completedNodes],
144
+ executionOrder: [...executionOrder],
145
+ position,
146
+ variables,
147
+ executions: serialized.executions,
148
+ executionCounter: serialized.executionCounter,
149
+ nodeExecutionCounts: serialized.nodeExecutionCounts,
150
+ unsafeNodes: [...unsafeNodes],
151
+ };
152
+ // Ensure directory exists
153
+ if (!fs.existsSync(this.dir)) {
154
+ fs.mkdirSync(this.dir, { recursive: true });
155
+ }
156
+ fs.writeFileSync(this.checkpointPath, JSON.stringify(data, null, 2), 'utf8');
157
+ }
158
+ }
159
+ // ---------------------------------------------------------------------------
160
+ // Checkpoint reading and resume
161
+ // ---------------------------------------------------------------------------
162
+ /**
163
+ * Load a checkpoint file and validate it against the current workflow.
164
+ * Returns the checkpoint data and a list of nodes that need to be re-run
165
+ * (because their outputs weren't serializable).
166
+ */
167
+ export function loadCheckpoint(checkpointPath, workflowFilePath) {
168
+ const raw = fs.readFileSync(checkpointPath, 'utf8');
169
+ const data = JSON.parse(raw);
170
+ if (data.version !== 1) {
171
+ throw new Error(`Unsupported checkpoint version: ${data.version}`);
172
+ }
173
+ // Check if the workflow has changed since the checkpoint was written
174
+ let stale = false;
175
+ if (workflowFilePath) {
176
+ const currentHash = hashFile(path.resolve(workflowFilePath));
177
+ stale = currentHash !== data.workflowHash;
178
+ }
179
+ // Determine which nodes can be skipped (all outputs serialized) vs
180
+ // which need re-running (any output was unserializable)
181
+ const unsafeSet = new Set(data.unsafeNodes);
182
+ const rerunNodes = [];
183
+ const skipNodes = new Map();
184
+ // Walk execution order up to the checkpoint position.
185
+ // Once we hit an unsafe node, everything from that point forward re-runs.
186
+ let hitUnsafe = false;
187
+ for (const nodeId of data.completedNodes) {
188
+ if (hitUnsafe || unsafeSet.has(nodeId)) {
189
+ hitUnsafe = true;
190
+ rerunNodes.push(nodeId);
191
+ continue;
192
+ }
193
+ // Collect this node's outputs from the variables map
194
+ const nodeOutputs = {};
195
+ const prefix = `${nodeId}:`;
196
+ for (const [key, value] of Object.entries(data.variables)) {
197
+ if (key.startsWith(prefix) && !isUnserializableMarker(value)) {
198
+ // Store with portName:executionIndex as key
199
+ const rest = key.substring(prefix.length);
200
+ nodeOutputs[rest] = value;
201
+ }
202
+ }
203
+ skipNodes.set(nodeId, nodeOutputs);
204
+ }
205
+ return { data, stale, rerunNodes, skipNodes };
206
+ }
207
+ /**
208
+ * Find the most recent checkpoint file for a workflow.
209
+ */
210
+ export function findLatestCheckpoint(workflowFilePath, workflowName) {
211
+ const dir = path.join(path.dirname(path.resolve(workflowFilePath)), '.fw-checkpoints');
212
+ if (!fs.existsSync(dir))
213
+ return null;
214
+ const files = fs.readdirSync(dir)
215
+ .filter((f) => f.endsWith('.json'))
216
+ .filter((f) => !workflowName || f.startsWith(`${workflowName}-`));
217
+ if (files.length === 0)
218
+ return null;
219
+ // Sort by modification time, newest first
220
+ const sorted = files
221
+ .map((f) => ({ name: f, mtime: fs.statSync(path.join(dir, f)).mtimeMs }))
222
+ .sort((a, b) => b.mtime - a.mtime);
223
+ return path.join(dir, sorted[0].name);
224
+ }
225
+ //# sourceMappingURL=checkpoint.js.map
@@ -0,0 +1,110 @@
1
+ /**
2
+ * DebugController intercepts workflow execution at node boundaries,
3
+ * enabling step-through debugging and checkpoint/resume.
4
+ *
5
+ * Injected via globalThis.__fw_debug_controller__ (same pattern as
6
+ * __fw_debugger__ and __fw_agent_channel__). The generated code calls
7
+ * beforeNode/afterNode at each node boundary; the controller decides
8
+ * whether to skip, pause, checkpoint, or continue.
9
+ */
10
+ import type { GeneratedExecutionContext } from './ExecutionContext.js';
11
+ import type { CheckpointWriter } from './checkpoint.js';
12
+ export type DebugMode = 'step' | 'continue' | 'continueToBreakpoint' | 'run';
13
+ export interface DebugPauseState {
14
+ /** Node we're paused at */
15
+ currentNodeId: string;
16
+ /** Whether we paused before or after the node executed */
17
+ phase: 'before' | 'after';
18
+ /** Nodes that have finished executing */
19
+ completedNodes: string[];
20
+ /** Full topological execution order */
21
+ executionOrder: string[];
22
+ /** Current index in executionOrder */
23
+ position: number;
24
+ /** All variable values, keyed by "nodeId:portName" */
25
+ variables: Record<string, unknown>;
26
+ /** Outputs of the most recently completed node (convenience shortcut) */
27
+ currentNodeOutputs?: Record<string, unknown>;
28
+ /** Active breakpoints */
29
+ breakpoints: string[];
30
+ }
31
+ export type DebugResumeAction = {
32
+ type: 'step';
33
+ } | {
34
+ type: 'continue';
35
+ } | {
36
+ type: 'continueToBreakpoint';
37
+ } | {
38
+ type: 'abort';
39
+ };
40
+ export interface DebugControllerConfig {
41
+ /** Enable step-through debugging (pauses before first node) */
42
+ debug?: boolean;
43
+ /** Enable checkpointing to disk after each node */
44
+ checkpoint?: boolean;
45
+ /** Checkpoint writer instance (required when checkpoint=true) */
46
+ checkpointWriter?: CheckpointWriter;
47
+ /** Initial breakpoint node IDs */
48
+ breakpoints?: string[];
49
+ /** Execution order (set by executor after compilation) */
50
+ executionOrder?: string[];
51
+ /** Nodes to skip on resume (loaded from checkpoint) */
52
+ skipNodes?: Map<string, Record<string, unknown>>;
53
+ }
54
+ export declare class DebugController {
55
+ private mode;
56
+ private breakpoints;
57
+ private completedNodes;
58
+ private completedSet;
59
+ private executionOrder;
60
+ private position;
61
+ private lastCompletedNodeId;
62
+ private checkpointEnabled;
63
+ private checkpointWriter;
64
+ private skipNodes;
65
+ private _gateResolve;
66
+ private _pauseResolve;
67
+ private _pausePromise;
68
+ private pendingModifications;
69
+ constructor(config?: DebugControllerConfig);
70
+ /** Set the execution order (called by executor after compilation) */
71
+ setExecutionOrder(order: string[]): void;
72
+ /**
73
+ * Called before a node executes.
74
+ * Returns true if the node should execute, false to skip.
75
+ */
76
+ beforeNode(nodeId: string, ctx: GeneratedExecutionContext): Promise<boolean>;
77
+ /**
78
+ * Called after a node completes successfully.
79
+ */
80
+ afterNode(nodeId: string, ctx: GeneratedExecutionContext): Promise<void>;
81
+ /**
82
+ * Awaited by the executor to detect when the controller pauses.
83
+ * Resolves with the current debug state.
84
+ */
85
+ onPause(): Promise<DebugPauseState>;
86
+ /**
87
+ * Called by MCP tools or CLI to resume execution.
88
+ */
89
+ resume(action: DebugResumeAction): void;
90
+ /**
91
+ * Queue a variable modification. Applied before the next node runs.
92
+ * Key format: "nodeId:portName:executionIndex"
93
+ */
94
+ setVariable(key: string, value: unknown): void;
95
+ addBreakpoint(nodeId: string): void;
96
+ removeBreakpoint(nodeId: string): void;
97
+ getBreakpoints(): string[];
98
+ /** Build the current debug state for external consumers */
99
+ buildState(nodeId: string, phase: 'before' | 'after', ctx: GeneratedExecutionContext): DebugPauseState;
100
+ /** Get completed nodes list */
101
+ getCompletedNodes(): string[];
102
+ private pause;
103
+ private applyAction;
104
+ private applyPendingModifications;
105
+ private restoreNodeOutputs;
106
+ private extractVariables;
107
+ private extractNodeOutputs;
108
+ private _createPausePromise;
109
+ }
110
+ //# sourceMappingURL=debug-controller.d.ts.map
@@ -0,0 +1,247 @@
1
+ /**
2
+ * DebugController intercepts workflow execution at node boundaries,
3
+ * enabling step-through debugging and checkpoint/resume.
4
+ *
5
+ * Injected via globalThis.__fw_debug_controller__ (same pattern as
6
+ * __fw_debugger__ and __fw_agent_channel__). The generated code calls
7
+ * beforeNode/afterNode at each node boundary; the controller decides
8
+ * whether to skip, pause, checkpoint, or continue.
9
+ */
10
+ // ---------------------------------------------------------------------------
11
+ // DebugController
12
+ // ---------------------------------------------------------------------------
13
+ export class DebugController {
14
+ mode;
15
+ breakpoints;
16
+ completedNodes = [];
17
+ completedSet = new Set();
18
+ executionOrder = [];
19
+ position = 0;
20
+ lastCompletedNodeId = null;
21
+ // Checkpoint
22
+ checkpointEnabled;
23
+ checkpointWriter;
24
+ // Skip nodes (for resume from checkpoint)
25
+ skipNodes;
26
+ // Pause/resume channel (mirrors AgentChannel pattern)
27
+ _gateResolve = null;
28
+ _pauseResolve = null;
29
+ _pausePromise;
30
+ // Variable modification buffer: applied before next node runs
31
+ pendingModifications = new Map();
32
+ constructor(config = {}) {
33
+ this.mode = config.debug ? 'step' : 'run';
34
+ this.breakpoints = new Set(config.breakpoints ?? []);
35
+ this.checkpointEnabled = config.checkpoint ?? false;
36
+ this.checkpointWriter = config.checkpointWriter ?? null;
37
+ this.executionOrder = config.executionOrder ?? [];
38
+ this.skipNodes = config.skipNodes ?? new Map();
39
+ this._pausePromise = this._createPausePromise();
40
+ }
41
+ /** Set the execution order (called by executor after compilation) */
42
+ setExecutionOrder(order) {
43
+ this.executionOrder = order;
44
+ }
45
+ // -----------------------------------------------------------------------
46
+ // Node boundary hooks (called by generated code)
47
+ // -----------------------------------------------------------------------
48
+ /**
49
+ * Called before a node executes.
50
+ * Returns true if the node should execute, false to skip.
51
+ */
52
+ async beforeNode(nodeId, ctx) {
53
+ // Apply any pending variable modifications
54
+ this.applyPendingModifications(ctx);
55
+ // If this node should be skipped (resume from checkpoint), restore its
56
+ // outputs into the context and return false
57
+ if (this.skipNodes.has(nodeId)) {
58
+ const savedOutputs = this.skipNodes.get(nodeId);
59
+ this.restoreNodeOutputs(nodeId, savedOutputs, ctx);
60
+ this.completedNodes.push(nodeId);
61
+ this.completedSet.add(nodeId);
62
+ this.lastCompletedNodeId = nodeId;
63
+ this.position++;
64
+ return false;
65
+ }
66
+ // Check if we should pause here
67
+ const shouldPause = this.mode === 'step' ||
68
+ (this.mode === 'continueToBreakpoint' && this.breakpoints.has(nodeId));
69
+ if (shouldPause) {
70
+ const action = await this.pause(nodeId, 'before', ctx);
71
+ if (action.type === 'abort') {
72
+ throw new Error(`Debug session aborted at node "${nodeId}"`);
73
+ }
74
+ // Action may change mode for subsequent nodes
75
+ this.applyAction(action);
76
+ }
77
+ return true;
78
+ }
79
+ /**
80
+ * Called after a node completes successfully.
81
+ */
82
+ async afterNode(nodeId, ctx) {
83
+ this.completedNodes.push(nodeId);
84
+ this.completedSet.add(nodeId);
85
+ this.lastCompletedNodeId = nodeId;
86
+ this.position++;
87
+ // Write checkpoint to disk
88
+ if (this.checkpointEnabled && this.checkpointWriter) {
89
+ await this.checkpointWriter.write(this.completedNodes, this.executionOrder, this.position, ctx);
90
+ }
91
+ // Pause after node in step mode
92
+ if (this.mode === 'step') {
93
+ const action = await this.pause(nodeId, 'after', ctx);
94
+ if (action.type === 'abort') {
95
+ throw new Error(`Debug session aborted after node "${nodeId}"`);
96
+ }
97
+ this.applyAction(action);
98
+ }
99
+ }
100
+ // -----------------------------------------------------------------------
101
+ // Pause/resume channel
102
+ // -----------------------------------------------------------------------
103
+ /**
104
+ * Awaited by the executor to detect when the controller pauses.
105
+ * Resolves with the current debug state.
106
+ */
107
+ onPause() {
108
+ return this._pausePromise;
109
+ }
110
+ /**
111
+ * Called by MCP tools or CLI to resume execution.
112
+ */
113
+ resume(action) {
114
+ if (action.type !== 'abort') {
115
+ this.applyAction(action);
116
+ }
117
+ this._gateResolve?.(action);
118
+ this._gateResolve = null;
119
+ this._pausePromise = this._createPausePromise();
120
+ }
121
+ // -----------------------------------------------------------------------
122
+ // Variable modification
123
+ // -----------------------------------------------------------------------
124
+ /**
125
+ * Queue a variable modification. Applied before the next node runs.
126
+ * Key format: "nodeId:portName:executionIndex"
127
+ */
128
+ setVariable(key, value) {
129
+ this.pendingModifications.set(key, value);
130
+ }
131
+ // -----------------------------------------------------------------------
132
+ // Breakpoints
133
+ // -----------------------------------------------------------------------
134
+ addBreakpoint(nodeId) {
135
+ this.breakpoints.add(nodeId);
136
+ }
137
+ removeBreakpoint(nodeId) {
138
+ this.breakpoints.delete(nodeId);
139
+ }
140
+ getBreakpoints() {
141
+ return [...this.breakpoints];
142
+ }
143
+ // -----------------------------------------------------------------------
144
+ // State inspection
145
+ // -----------------------------------------------------------------------
146
+ /** Build the current debug state for external consumers */
147
+ buildState(nodeId, phase, ctx) {
148
+ const variables = this.extractVariables(ctx);
149
+ const currentNodeOutputs = this.lastCompletedNodeId
150
+ ? this.extractNodeOutputs(this.lastCompletedNodeId, variables)
151
+ : undefined;
152
+ return {
153
+ currentNodeId: nodeId,
154
+ phase,
155
+ completedNodes: [...this.completedNodes],
156
+ executionOrder: [...this.executionOrder],
157
+ position: this.position,
158
+ variables,
159
+ currentNodeOutputs,
160
+ breakpoints: [...this.breakpoints],
161
+ };
162
+ }
163
+ /** Get completed nodes list */
164
+ getCompletedNodes() {
165
+ return [...this.completedNodes];
166
+ }
167
+ // -----------------------------------------------------------------------
168
+ // Internal helpers
169
+ // -----------------------------------------------------------------------
170
+ async pause(nodeId, phase, ctx) {
171
+ const state = this.buildState(nodeId, phase, ctx);
172
+ // Signal the executor that we're paused
173
+ this._pauseResolve?.(state);
174
+ // Suspend on a gate Promise until resume() is called
175
+ return new Promise((resolve) => {
176
+ this._gateResolve = (action) => resolve(action);
177
+ });
178
+ }
179
+ applyAction(action) {
180
+ switch (action.type) {
181
+ case 'step':
182
+ this.mode = 'step';
183
+ break;
184
+ case 'continue':
185
+ this.mode = 'continue';
186
+ break;
187
+ case 'continueToBreakpoint':
188
+ this.mode = 'continueToBreakpoint';
189
+ break;
190
+ // 'abort' is handled by the caller (throws)
191
+ }
192
+ }
193
+ applyPendingModifications(ctx) {
194
+ if (this.pendingModifications.size === 0)
195
+ return;
196
+ for (const [key, value] of this.pendingModifications) {
197
+ // Key format: "nodeId:portName:executionIndex"
198
+ const parts = key.split(':');
199
+ if (parts.length >= 3) {
200
+ const address = {
201
+ id: parts[0],
202
+ portName: parts[1],
203
+ executionIndex: parseInt(parts[2], 10),
204
+ };
205
+ ctx.setVariable(address, value);
206
+ }
207
+ }
208
+ this.pendingModifications.clear();
209
+ }
210
+ restoreNodeOutputs(nodeId, outputs, ctx) {
211
+ // outputs is keyed by "portName:executionIndex" -> value
212
+ // Don't call ctx.addExecution here: the generated code's else block handles
213
+ // execution registration so local variables (e.g. dIdx) are set correctly.
214
+ for (const [portKey, value] of Object.entries(outputs)) {
215
+ const colonIdx = portKey.lastIndexOf(':');
216
+ if (colonIdx === -1)
217
+ continue;
218
+ const portName = portKey.substring(0, colonIdx);
219
+ const executionIndex = parseInt(portKey.substring(colonIdx + 1), 10);
220
+ ctx.setVariable({ id: nodeId, portName, executionIndex }, value);
221
+ }
222
+ }
223
+ extractVariables(ctx) {
224
+ const serialized = ctx.serialize();
225
+ return serialized.variables;
226
+ }
227
+ extractNodeOutputs(nodeId, allVariables) {
228
+ const outputs = {};
229
+ const prefix = `${nodeId}:`;
230
+ for (const [key, value] of Object.entries(allVariables)) {
231
+ if (key.startsWith(prefix)) {
232
+ // Extract portName from key "nodeId:portName:executionIndex"
233
+ const rest = key.substring(prefix.length);
234
+ const colonIdx = rest.lastIndexOf(':');
235
+ const portName = colonIdx >= 0 ? rest.substring(0, colonIdx) : rest;
236
+ outputs[portName] = value;
237
+ }
238
+ }
239
+ return outputs;
240
+ }
241
+ _createPausePromise() {
242
+ return new Promise((resolve) => {
243
+ this._pauseResolve = resolve;
244
+ });
245
+ }
246
+ }
247
+ //# sourceMappingURL=debug-controller.js.map
@@ -1,5 +1,9 @@
1
1
  export { GeneratedExecutionContext } from "./ExecutionContext.js";
2
2
  export { CancellationError } from "./CancellationError.js";
3
+ export { DebugController } from "./debug-controller.js";
4
+ export type { DebugMode, DebugPauseState, DebugResumeAction, DebugControllerConfig } from "./debug-controller.js";
5
+ export { CheckpointWriter, loadCheckpoint, findLatestCheckpoint } from "./checkpoint.js";
6
+ export type { CheckpointData } from "./checkpoint.js";
3
7
  export * from "./events.js";
4
8
  export * from "./function-registry.js";
5
9
  export * from "./parameter-resolver.js";
@@ -1,5 +1,7 @@
1
1
  export { GeneratedExecutionContext } from "./ExecutionContext.js";
2
2
  export { CancellationError } from "./CancellationError.js";
3
+ export { DebugController } from "./debug-controller.js";
4
+ export { CheckpointWriter, loadCheckpoint, findLatestCheckpoint } from "./checkpoint.js";
3
5
  export * from "./events.js";
4
6
  export * from "./function-registry.js";
5
7
  export * from "./parameter-resolver.js";
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: CLI Reference
3
3
  description: Complete reference for all Flow Weaver CLI commands, flags, and options
4
- keywords: [cli, commands, compile, validate, strip, run, watch, dev, serve, export, diagram, diff, doctor, init, migrate, marketplace, plugin, grammar, changelog, openapi, pattern, create, templates]
4
+ keywords: [cli, commands, compile, validate, strip, run, watch, dev, serve, export, diagram, diff, doctor, init, migrate, marketplace, plugin, grammar, changelog, openapi, pattern, create, templates, context]
5
5
  ---
6
6
 
7
7
  # CLI Reference
@@ -34,6 +34,7 @@ Complete reference for all `flow-weaver` CLI commands.
34
34
  | `changelog` | Generate changelog from git |
35
35
  | `market` | Marketplace packages |
36
36
  | `plugin` | External plugins |
37
+ | `context` | Generate LLM context bundle |
37
38
  | `docs` | Browse reference documentation |
38
39
  | `ui` | Send commands to the editor |
39
40
  | `listen` | Stream editor events |
@@ -176,6 +177,10 @@ flow-weaver run <input> [options]
176
177
  | `--timeout <ms>` | Execution timeout in milliseconds | — |
177
178
  | `--mocks <json>` | Mock config for built-in nodes as JSON | — |
178
179
  | `--mocks-file <path>` | Path to JSON file with mock config | — |
180
+ | `-d, --debug` | Start in step-through debug mode | `false` |
181
+ | `--checkpoint` | Enable checkpointing to disk after each node | `false` |
182
+ | `--resume [file]` | Resume from a checkpoint file (auto-detects latest if no file given) | — |
183
+ | `-b, --breakpoint <nodeIds...>` | Set initial breakpoints (repeatable) | — |
179
184
 
180
185
  **Examples:**
181
186
  ```bash
@@ -183,9 +188,13 @@ flow-weaver run workflow.ts --params '{"amount": 500}'
183
188
  flow-weaver run workflow.ts --params-file input.json --trace
184
189
  flow-weaver run workflow.ts --mocks '{"fast": true, "events": {"app/approved": {"status": "ok"}}}'
185
190
  flow-weaver run workflow.ts --timeout 30000 --json
191
+ flow-weaver run workflow.ts --debug
192
+ flow-weaver run workflow.ts --checkpoint
193
+ flow-weaver run workflow.ts --resume
194
+ flow-weaver run workflow.ts --debug --breakpoint processData --breakpoint validate
186
195
  ```
187
196
 
188
- > See also: [Built-in Nodes](built-in-nodes) for mock configuration details.
197
+ > See also: [Built-in Nodes](built-in-nodes) for mock configuration details, [Debugging](debugging) for debug REPL commands and checkpoint details.
189
198
 
190
199
  ---
191
200
 
@@ -797,6 +806,37 @@ flow-weaver docs search "missing workflow"
797
806
 
798
807
  ---
799
808
 
809
+ ### context
810
+
811
+ Generate a self-contained LLM context bundle from documentation and annotation grammar. Two profiles control the output format: `standalone` produces a complete reference for pasting into any LLM, `assistant` produces a leaner version that assumes MCP tools are available.
812
+
813
+ ```bash
814
+ flow-weaver context [preset] [options]
815
+ ```
816
+
817
+ | Flag | Description | Default |
818
+ |------|-------------|---------|
819
+ | `--profile <profile>` | `standalone` or `assistant` | `standalone` |
820
+ | `--topics <slugs>` | Comma-separated topic slugs (overrides preset) | — |
821
+ | `--add <slugs>` | Extra topic slugs on top of preset | — |
822
+ | `--no-grammar` | Omit EBNF grammar section | grammar included |
823
+ | `-o, --output <path>` | Write to file instead of stdout | stdout |
824
+ | `--list` | List available presets and exit | — |
825
+
826
+ Built-in presets: `core` (concepts, grammar, tutorial), `authoring` (concepts, grammar, annotations, built-in nodes, scaffold, node-conversion, patterns), `ops` (CLI, compilation, deployment, export, debugging, error-codes), `full` (all 16 topics).
827
+
828
+ **Examples:**
829
+ ```bash
830
+ flow-weaver context core | pbcopy
831
+ flow-weaver context full -o .flow-weaver-context.md
832
+ flow-weaver context authoring --profile assistant
833
+ flow-weaver context --topics concepts,jsdoc-grammar,error-codes
834
+ flow-weaver context core --add error-codes
835
+ flow-weaver context --list
836
+ ```
837
+
838
+ ---
839
+
800
840
  ## Editor Integration
801
841
 
802
842
  ### ui focus-node