@synergenius/flow-weaver 0.5.1 → 0.7.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.
Files changed (45) hide show
  1. package/README.md +2 -1
  2. package/dist/annotation-generator.js +29 -4
  3. package/dist/api/generate-in-place.js +3 -0
  4. package/dist/ast/types.d.ts +22 -2
  5. package/dist/built-in-nodes/coercion-types.d.ts +15 -0
  6. package/dist/built-in-nodes/coercion-types.js +61 -0
  7. package/dist/built-in-nodes/invoke-workflow.js +3 -3
  8. package/dist/built-in-nodes/mock-types.d.ts +20 -0
  9. package/dist/built-in-nodes/mock-types.js +30 -0
  10. package/dist/built-in-nodes/wait-for-agent.js +5 -4
  11. package/dist/built-in-nodes/wait-for-event.js +3 -3
  12. package/dist/chevrotain-parser/coerce-parser.d.ts +33 -0
  13. package/dist/chevrotain-parser/coerce-parser.js +103 -0
  14. package/dist/chevrotain-parser/index.d.ts +3 -1
  15. package/dist/chevrotain-parser/index.js +3 -1
  16. package/dist/chevrotain-parser/port-parser.js +2 -1
  17. package/dist/chevrotain-parser/tokens.d.ts +2 -0
  18. package/dist/chevrotain-parser/tokens.js +10 -0
  19. package/dist/cli/commands/compile.js +6 -0
  20. package/dist/cli/commands/init.js +2 -0
  21. package/dist/cli/commands/run.d.ts +4 -0
  22. package/dist/cli/commands/run.js +68 -1
  23. package/dist/cli/commands/validate.js +12 -0
  24. package/dist/cli/flow-weaver.mjs +764 -65
  25. package/dist/cli/index.js +1 -0
  26. package/dist/doc-metadata/extractors/annotations.js +17 -0
  27. package/dist/doc-metadata/extractors/error-codes.js +50 -0
  28. package/dist/friendly-errors.js +131 -5
  29. package/dist/generator/inngest.js +27 -0
  30. package/dist/generator/unified.d.ts +7 -2
  31. package/dist/generator/unified.js +31 -3
  32. package/dist/jsdoc-parser.d.ts +14 -0
  33. package/dist/jsdoc-parser.js +19 -1
  34. package/dist/mcp/tools-editor.js +20 -1
  35. package/dist/mcp/workflow-executor.d.ts +1 -0
  36. package/dist/mcp/workflow-executor.js +11 -4
  37. package/dist/parser.d.ts +4 -0
  38. package/dist/parser.js +69 -0
  39. package/dist/validator.d.ts +5 -0
  40. package/dist/validator.js +207 -14
  41. package/docs/reference/advanced-annotations.md +71 -2
  42. package/docs/reference/concepts.md +44 -4
  43. package/docs/reference/debugging.md +33 -0
  44. package/docs/reference/error-codes.md +7 -0
  45. package/package.json +1 -1
package/README.md CHANGED
@@ -218,7 +218,7 @@ flow-weaver export workflow.ts --target cloudflare --output deploy/
218
218
  flow-weaver serve ./workflows --port 3000 --swagger
219
219
  ```
220
220
 
221
- Inngest compilation wraps each node in `step.run()` for individual durability, parallelizes independent nodes with `Promise.all()`, and generates typed Zod event schemas.
221
+ Both the default TypeScript target and Inngest target parallelize independent nodes with `Promise.all()`. Inngest additionally wraps each node in `step.run()` for individual durability and generates typed Zod event schemas.
222
222
 
223
223
  ## Visual Human-in-the-Loop
224
224
 
@@ -319,6 +319,7 @@ flow-weaver listen # Stream editor events
319
319
  |------|---------|
320
320
  | `delay` | Sleep for a duration (ms, s, m, h, d). Mockable for fast testing. |
321
321
  | `waitForEvent` | Wait for an external event with optional field matching and timeout. Maps to Inngest `step.waitForEvent()` for zero-cost durable pauses. |
322
+ | `waitForAgent` | Pause execution and wait for an external agent to provide a result. Supports multi-step human-in-the-loop and agent delegation patterns. |
322
323
  | `invokeWorkflow` | Invoke another workflow by ID with payload and timeout. Maps to Inngest `step.invoke()`. |
323
324
 
324
325
  ## STEP Port Architecture
@@ -20,6 +20,10 @@ export class AnnotationGenerator {
20
20
  if (nodeType.variant === 'MAP_ITERATOR') {
21
21
  return;
22
22
  }
23
+ // Skip synthetic COERCION node types — they're generated from @coerce macros
24
+ if (nodeType.variant === 'COERCION') {
25
+ return;
26
+ }
23
27
  lines.push(...this.generateNodeTypeAnnotation(nodeType, indent, includeComments, includeMetadata));
24
28
  lines.push("");
25
29
  });
@@ -36,9 +40,11 @@ export class AnnotationGenerator {
36
40
  }
37
41
  // Generate JSDoc comment block (only when no functionText)
38
42
  lines.push("/**");
39
- // Add description if present
43
+ // Add description if present (handle multi-line descriptions)
40
44
  if (includeComments && nodeType.description) {
41
- lines.push(` * ${nodeType.description}`);
45
+ for (const descLine of nodeType.description.split('\n')) {
46
+ lines.push(` * ${descLine}`);
47
+ }
42
48
  lines.push(` *`);
43
49
  }
44
50
  // @flowWeaver nodeType marker
@@ -136,10 +142,11 @@ export class AnnotationGenerator {
136
142
  }
137
143
  generateWorkflowAnnotation(workflow, indent, includeComments, skipParamReturns = false) {
138
144
  const lines = [];
139
- // Build macro coverage sets for filtering (@map-specific)
145
+ // Build macro coverage sets for filtering (@map and @coerce)
140
146
  const macroInstanceIds = new Set();
141
147
  const macroChildIds = new Set();
142
148
  const macroScopeNames = new Set();
149
+ const coerceInstanceIds = new Set();
143
150
  if (workflow.macros && workflow.macros.length > 0) {
144
151
  for (const macro of workflow.macros) {
145
152
  if (macro.type === 'map') {
@@ -147,6 +154,9 @@ export class AnnotationGenerator {
147
154
  macroChildIds.add(macro.childId);
148
155
  macroScopeNames.add(`${macro.instanceId}.iterate`);
149
156
  }
157
+ else if (macro.type === 'coerce') {
158
+ coerceInstanceIds.add(macro.instanceId);
159
+ }
150
160
  }
151
161
  }
152
162
  // Generate JSDoc comment block
@@ -211,10 +221,12 @@ export class AnnotationGenerator {
211
221
  if (workflow.description && includeComments) {
212
222
  lines.push(` * @description ${workflow.description}`);
213
223
  }
214
- // Add node instances — skip synthetic MAP_ITERATOR instances, strip parent from macro children
224
+ // Add node instances — skip synthetic MAP_ITERATOR/COERCION instances, strip parent from macro children
215
225
  workflow.instances.forEach((instance) => {
216
226
  if (macroInstanceIds.has(instance.id))
217
227
  return;
228
+ if (coerceInstanceIds.has(instance.id))
229
+ return;
218
230
  if (macroChildIds.has(instance.id) && instance.parent) {
219
231
  const stripped = { ...instance, parent: undefined };
220
232
  lines.push(generateNodeInstanceTag(stripped));
@@ -257,6 +269,11 @@ export class AnnotationGenerator {
257
269
  const tgt = `${macro.target.node}.${macro.target.port}`;
258
270
  lines.push(` * @fanIn ${srcs} -> ${tgt}`);
259
271
  }
272
+ else if (macro.type === 'coerce') {
273
+ const src = `${macro.source.node}.${macro.source.port}`;
274
+ const tgt = `${macro.target.node}.${macro.target.port}`;
275
+ lines.push(` * @coerce ${macro.instanceId} ${src} -> ${tgt} as ${macro.targetType}`);
276
+ }
260
277
  }
261
278
  }
262
279
  // Add node positions (if they exist)
@@ -492,6 +509,14 @@ function isConnectionCoveredByMacroStatic(conn, macros) {
492
509
  }
493
510
  }
494
511
  }
512
+ else if (macro.type === 'coerce') {
513
+ if (conn.from.scope || conn.to.scope)
514
+ continue;
515
+ // Connections to/from the synthetic coercion instance are covered
516
+ if (conn.to.node === macro.instanceId || conn.from.node === macro.instanceId) {
517
+ return true;
518
+ }
519
+ }
495
520
  }
496
521
  return false;
497
522
  }
@@ -408,6 +408,9 @@ function replaceWorkflowFunctionBody(source, functionName, newBody) {
408
408
  }
409
409
  // Find the closing brace
410
410
  const closeBraceIdx = functionNode.body.end - 1;
411
+ if (closeBraceIdx <= openBraceIdx) {
412
+ return { code: source, changed: false };
413
+ }
411
414
  const before = source.slice(0, openBraceIdx + 1);
412
415
  const after = source.slice(closeBraceIdx);
413
416
  const newBodyWithMarkers = [
@@ -111,7 +111,7 @@ export type TWorkflowAST = {
111
111
  * A sugar macro that expands to a full scope pattern or connection set.
112
112
  * Stored on the workflow AST for round-trip preservation in generateInPlace.
113
113
  */
114
- export type TWorkflowMacro = TMapMacro | TPathMacro | TFanOutMacro | TFanInMacro;
114
+ export type TWorkflowMacro = TMapMacro | TPathMacro | TFanOutMacro | TFanInMacro | TCoerceMacro;
115
115
  export type TMapMacro = {
116
116
  type: 'map';
117
117
  /** Instance ID for the synthetic iterator node (e.g., "loop") */
@@ -159,6 +159,24 @@ export type TFanInMacro = {
159
159
  port: string;
160
160
  };
161
161
  };
162
+ export type TCoerceTargetType = 'string' | 'number' | 'boolean' | 'json' | 'object';
163
+ export type TCoerceMacro = {
164
+ type: 'coerce';
165
+ /** Instance ID for the synthetic coercion node */
166
+ instanceId: string;
167
+ /** Source port in "node.port" format */
168
+ source: {
169
+ node: string;
170
+ port: string;
171
+ };
172
+ /** Target port in "node.port" format */
173
+ target: {
174
+ node: string;
175
+ port: string;
176
+ };
177
+ /** Coercion target type */
178
+ targetType: TCoerceTargetType;
179
+ };
162
180
  /**
163
181
  * Default configuration for a node type that can be overridden per instance.
164
182
  */
@@ -222,7 +240,7 @@ export type TNodeTypeAST = {
222
240
  /** Multiple scopes this node creates */
223
241
  scopes?: string[];
224
242
  /** Variant identifier (set by app layer) */
225
- variant?: 'FUNCTION' | 'WORKFLOW' | 'IMPORTED_WORKFLOW' | 'MAP_ITERATOR';
243
+ variant?: 'FUNCTION' | 'WORKFLOW' | 'IMPORTED_WORKFLOW' | 'MAP_ITERATOR' | 'COERCION';
226
244
  /** File path for external node types */
227
245
  path?: string;
228
246
  /** Function reference for function-based node types */
@@ -619,6 +637,8 @@ export type TValidationError = {
619
637
  node?: string;
620
638
  connection?: TConnectionAST;
621
639
  location?: TSourceLocation;
640
+ /** Reference to documentation explaining this error and how to fix it. */
641
+ docUrl?: string;
622
642
  };
623
643
  export type TAnalysisResult = {
624
644
  controlFlowGraph: TControlFlowGraph;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Synthetic node type definitions for explicit type coercion.
3
+ *
4
+ * These are compiler-internal node types created by @coerce macros.
5
+ * They generate inline JavaScript coercion expressions instead of function calls.
6
+ */
7
+ import type { TNodeTypeAST, TCoerceTargetType } from '../ast/types.js';
8
+ /** Maps coercion target type keyword to the synthetic node type name */
9
+ export declare const COERCE_TYPE_MAP: Record<TCoerceTargetType, string>;
10
+ /** Maps synthetic node type name to the inline JS expression */
11
+ export declare const COERCE_EXPRESSIONS: Record<string, string>;
12
+ export declare const COERCION_NODE_TYPES: Record<string, TNodeTypeAST>;
13
+ /** Check if a function name is a coercion node type */
14
+ export declare function isCoercionNode(functionName: string): boolean;
15
+ //# sourceMappingURL=coercion-types.d.ts.map
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Synthetic node type definitions for explicit type coercion.
3
+ *
4
+ * These are compiler-internal node types created by @coerce macros.
5
+ * They generate inline JavaScript coercion expressions instead of function calls.
6
+ */
7
+ /** Maps coercion target type keyword to the synthetic node type name */
8
+ export const COERCE_TYPE_MAP = {
9
+ string: '__fw_toString',
10
+ number: '__fw_toNumber',
11
+ boolean: '__fw_toBoolean',
12
+ json: '__fw_toJSON',
13
+ object: '__fw_parseJSON',
14
+ };
15
+ /** Maps synthetic node type name to the inline JS expression */
16
+ export const COERCE_EXPRESSIONS = {
17
+ __fw_toString: 'String',
18
+ __fw_toNumber: 'Number',
19
+ __fw_toBoolean: 'Boolean',
20
+ __fw_toJSON: 'JSON.stringify',
21
+ __fw_parseJSON: 'JSON.parse',
22
+ };
23
+ function makeCoercionNodeType(name, outputType, outputTsType) {
24
+ return {
25
+ type: 'NodeType',
26
+ name,
27
+ functionName: name,
28
+ expression: true,
29
+ isAsync: false,
30
+ hasSuccessPort: true,
31
+ hasFailurePort: true,
32
+ executeWhen: 'CONJUNCTION',
33
+ variant: 'COERCION',
34
+ inputs: {
35
+ execute: { dataType: 'STEP', label: 'Execute' },
36
+ value: { dataType: 'ANY', label: 'Value', tsType: 'unknown' },
37
+ },
38
+ outputs: {
39
+ onSuccess: { dataType: 'STEP', label: 'On Success', isControlFlow: true },
40
+ onFailure: { dataType: 'STEP', label: 'On Failure', failure: true, isControlFlow: true },
41
+ result: { dataType: outputType, label: 'Result', tsType: outputTsType },
42
+ },
43
+ visuals: {
44
+ color: '#0d9488',
45
+ icon: 'transform',
46
+ tags: [{ label: 'coerce' }],
47
+ },
48
+ };
49
+ }
50
+ export const COERCION_NODE_TYPES = {
51
+ __fw_toString: makeCoercionNodeType('__fw_toString', 'STRING', 'string'),
52
+ __fw_toNumber: makeCoercionNodeType('__fw_toNumber', 'NUMBER', 'number'),
53
+ __fw_toBoolean: makeCoercionNodeType('__fw_toBoolean', 'BOOLEAN', 'boolean'),
54
+ __fw_toJSON: makeCoercionNodeType('__fw_toJSON', 'STRING', 'string'),
55
+ __fw_parseJSON: makeCoercionNodeType('__fw_parseJSON', 'OBJECT', 'object'),
56
+ };
57
+ /** Check if a function name is a coercion node type */
58
+ export function isCoercionNode(functionName) {
59
+ return functionName in COERCION_NODE_TYPES;
60
+ }
61
+ //# sourceMappingURL=coercion-types.js.map
@@ -1,4 +1,4 @@
1
- import { getMockConfig } from './mock-types.js';
1
+ import { getMockConfig, lookupMock } from './mock-types.js';
2
2
  /**
3
3
  * @flowWeaver nodeType
4
4
  * @input functionId - Inngest function ID (e.g. "my-service/sub-workflow")
@@ -11,8 +11,8 @@ export async function invokeWorkflow(execute, functionId, payload, timeout) {
11
11
  return { onSuccess: false, onFailure: false, result: {} };
12
12
  const mocks = getMockConfig();
13
13
  if (mocks) {
14
- // Mock mode active — look up result by functionId
15
- const mockResult = mocks.invocations?.[functionId];
14
+ // Mock mode — look up result by functionId (supports instance-qualified keys)
15
+ const mockResult = lookupMock(mocks.invocations, functionId);
16
16
  if (mockResult !== undefined) {
17
17
  return { onSuccess: true, onFailure: false, result: mockResult };
18
18
  }
@@ -17,4 +17,24 @@ export interface FwMockConfig {
17
17
  * Read the mock config from globalThis, returning undefined if not set.
18
18
  */
19
19
  export declare function getMockConfig(): FwMockConfig | undefined;
20
+ /**
21
+ * Look up a mock value from a section, supporting instance-qualified keys.
22
+ *
23
+ * Checks "instanceId:key" first (for per-node targeting), then falls back
24
+ * to plain "key". The instance ID comes from __fw_current_node_id__ which
25
+ * the generated code sets before each node invocation.
26
+ *
27
+ * @example
28
+ * ```json
29
+ * {
30
+ * "invocations": {
31
+ * "retryCall:api/process": { "status": "ok" },
32
+ * "api/process": { "status": "default" }
33
+ * }
34
+ * }
35
+ * ```
36
+ * When the node "retryCall" invokes "api/process", it gets `{ status: "ok" }`.
37
+ * Any other node invoking "api/process" gets `{ status: "default" }`.
38
+ */
39
+ export declare function lookupMock<T>(section: Record<string, T> | undefined, key: string): T | undefined;
20
40
  //# sourceMappingURL=mock-types.d.ts.map
@@ -9,4 +9,34 @@
9
9
  export function getMockConfig() {
10
10
  return globalThis.__fw_mocks__;
11
11
  }
12
+ /**
13
+ * Look up a mock value from a section, supporting instance-qualified keys.
14
+ *
15
+ * Checks "instanceId:key" first (for per-node targeting), then falls back
16
+ * to plain "key". The instance ID comes from __fw_current_node_id__ which
17
+ * the generated code sets before each node invocation.
18
+ *
19
+ * @example
20
+ * ```json
21
+ * {
22
+ * "invocations": {
23
+ * "retryCall:api/process": { "status": "ok" },
24
+ * "api/process": { "status": "default" }
25
+ * }
26
+ * }
27
+ * ```
28
+ * When the node "retryCall" invokes "api/process", it gets `{ status: "ok" }`.
29
+ * Any other node invoking "api/process" gets `{ status: "default" }`.
30
+ */
31
+ export function lookupMock(section, key) {
32
+ if (!section)
33
+ return undefined;
34
+ const nodeId = globalThis.__fw_current_node_id__;
35
+ if (nodeId) {
36
+ const qualified = section[`${nodeId}:${key}`];
37
+ if (qualified !== undefined)
38
+ return qualified;
39
+ }
40
+ return section[key];
41
+ }
12
42
  //# sourceMappingURL=mock-types.js.map
@@ -1,4 +1,4 @@
1
- import { getMockConfig } from './mock-types.js';
1
+ import { getMockConfig, lookupMock } from './mock-types.js';
2
2
  /**
3
3
  * @flowWeaver nodeType
4
4
  * @input agentId - Agent/task identifier
@@ -9,10 +9,11 @@ import { getMockConfig } from './mock-types.js';
9
9
  export async function waitForAgent(execute, agentId, context, prompt) {
10
10
  if (!execute)
11
11
  return { onSuccess: false, onFailure: false, agentResult: {} };
12
- // 1. Check mocks first
12
+ // 1. Check mocks first (supports instance-qualified keys)
13
13
  const mocks = getMockConfig();
14
- if (mocks?.agents?.[agentId]) {
15
- return { onSuccess: true, onFailure: false, agentResult: mocks.agents[agentId] };
14
+ const mockResult = lookupMock(mocks?.agents, agentId);
15
+ if (mockResult !== undefined) {
16
+ return { onSuccess: true, onFailure: false, agentResult: mockResult };
16
17
  }
17
18
  // 2. Check agent channel (set by executor for pause/resume)
18
19
  const channel = globalThis.__fw_agent_channel__;
@@ -1,4 +1,4 @@
1
- import { getMockConfig } from './mock-types.js';
1
+ import { getMockConfig, lookupMock } from './mock-types.js';
2
2
  /**
3
3
  * @flowWeaver nodeType
4
4
  * @input eventName - Event name to wait for (e.g. "app/approval.received")
@@ -11,8 +11,8 @@ export async function waitForEvent(execute, eventName, match, timeout) {
11
11
  return { onSuccess: false, onFailure: false, eventData: {} };
12
12
  const mocks = getMockConfig();
13
13
  if (mocks) {
14
- // Mock mode active — look up event data by name
15
- const mockData = mocks.events?.[eventName];
14
+ // Mock mode — look up event data by name (supports instance-qualified keys)
15
+ const mockData = lookupMock(mocks.events, eventName);
16
16
  if (mockData !== undefined) {
17
17
  return { onSuccess: true, onFailure: false, eventData: mockData };
18
18
  }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @module chevrotain-parser/coerce-parser
3
+ *
4
+ * Parser for @coerce sugar annotation using Chevrotain.
5
+ *
6
+ * Syntax:
7
+ * @coerce instanceId sourceNode.port -> targetNode.port as targetType
8
+ *
9
+ * Where targetType is one of: string, number, boolean, json, object
10
+ */
11
+ import type { TCoerceTargetType } from '../ast/types.js';
12
+ export interface CoerceParseResult {
13
+ instanceId: string;
14
+ source: {
15
+ node: string;
16
+ port: string;
17
+ };
18
+ target: {
19
+ node: string;
20
+ port: string;
21
+ };
22
+ targetType: TCoerceTargetType;
23
+ }
24
+ /**
25
+ * Parse a @coerce line and return structured result.
26
+ * Returns null if the line is not a valid @coerce declaration.
27
+ */
28
+ export declare function parseCoerceLine(input: string, warnings: string[]): CoerceParseResult | null;
29
+ /**
30
+ * Get serialized grammar for documentation/diagram generation.
31
+ */
32
+ export declare function getCoerceGrammar(): import("chevrotain").ISerializedGast[];
33
+ //# sourceMappingURL=coerce-parser.d.ts.map
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @module chevrotain-parser/coerce-parser
3
+ *
4
+ * Parser for @coerce sugar annotation using Chevrotain.
5
+ *
6
+ * Syntax:
7
+ * @coerce instanceId sourceNode.port -> targetNode.port as targetType
8
+ *
9
+ * Where targetType is one of: string, number, boolean, json, object
10
+ */
11
+ import { CstParser } from 'chevrotain';
12
+ import { JSDocLexer, CoerceTag, Identifier, Arrow, Dot, AsKeyword, allTokens, } from './tokens.js';
13
+ const VALID_COERCE_TYPES = new Set(['string', 'number', 'boolean', 'json', 'object']);
14
+ // =============================================================================
15
+ // Parser Definition
16
+ // =============================================================================
17
+ class CoerceParser extends CstParser {
18
+ constructor() {
19
+ super(allTokens);
20
+ this.performSelfAnalysis();
21
+ }
22
+ // @coerce Identifier portRef Arrow portRef AsKeyword Identifier
23
+ coerceLine = this.RULE('coerceLine', () => {
24
+ this.CONSUME(CoerceTag);
25
+ this.CONSUME(Identifier, { LABEL: 'instanceId' });
26
+ this.SUBRULE(this.portRef, { LABEL: 'source' });
27
+ this.CONSUME(Arrow);
28
+ this.SUBRULE2(this.portRef, { LABEL: 'target' });
29
+ this.CONSUME(AsKeyword);
30
+ this.CONSUME2(Identifier, { LABEL: 'typeName' });
31
+ });
32
+ // portRef: Identifier Dot Identifier
33
+ portRef = this.RULE('portRef', () => {
34
+ this.CONSUME(Identifier, { LABEL: 'nodeName' });
35
+ this.CONSUME(Dot);
36
+ this.CONSUME2(Identifier, { LABEL: 'portName' });
37
+ });
38
+ }
39
+ // =============================================================================
40
+ // Parser Instance (singleton)
41
+ // =============================================================================
42
+ const parserInstance = new CoerceParser();
43
+ // =============================================================================
44
+ // CST Visitor
45
+ // =============================================================================
46
+ const BaseVisitor = parserInstance.getBaseCstVisitorConstructor();
47
+ class CoerceVisitor extends BaseVisitor {
48
+ constructor() {
49
+ super();
50
+ this.validateVisitor();
51
+ }
52
+ coerceLine(ctx) {
53
+ const instanceId = ctx.instanceId[0].image;
54
+ const source = this.portRef(ctx.source[0].children);
55
+ const target = this.portRef(ctx.target[0].children);
56
+ const targetType = ctx.typeName[0].image;
57
+ return { instanceId, source, target, targetType };
58
+ }
59
+ portRef(ctx) {
60
+ return {
61
+ node: ctx.nodeName[0].image,
62
+ port: ctx.portName[0].image,
63
+ };
64
+ }
65
+ }
66
+ const visitorInstance = new CoerceVisitor();
67
+ // =============================================================================
68
+ // Public API
69
+ // =============================================================================
70
+ /**
71
+ * Parse a @coerce line and return structured result.
72
+ * Returns null if the line is not a valid @coerce declaration.
73
+ */
74
+ export function parseCoerceLine(input, warnings) {
75
+ const lexResult = JSDocLexer.tokenize(input);
76
+ if (lexResult.errors.length > 0 || lexResult.tokens.length === 0)
77
+ return null;
78
+ if (lexResult.tokens[0].tokenType !== CoerceTag)
79
+ return null;
80
+ parserInstance.input = lexResult.tokens;
81
+ const cst = parserInstance.coerceLine();
82
+ if (parserInstance.errors.length > 0) {
83
+ const truncatedInput = input.length > 80 ? input.substring(0, 80) + '...' : input;
84
+ warnings.push(`Failed to parse @coerce line: "${truncatedInput}"\n` +
85
+ ` Error: ${parserInstance.errors[0].message}\n` +
86
+ ` Expected format: @coerce instanceId source.port -> target.port as type`);
87
+ return null;
88
+ }
89
+ const result = visitorInstance.visit(cst);
90
+ // Validate the target type
91
+ if (!VALID_COERCE_TYPES.has(result.targetType)) {
92
+ warnings.push(`@coerce: invalid target type "${result.targetType}". Valid types: ${[...VALID_COERCE_TYPES].join(', ')}`);
93
+ return null;
94
+ }
95
+ return result;
96
+ }
97
+ /**
98
+ * Get serialized grammar for documentation/diagram generation.
99
+ */
100
+ export function getCoerceGrammar() {
101
+ return parserInstance.getSerializedGastProductions();
102
+ }
103
+ //# sourceMappingURL=coerce-parser.js.map
@@ -4,7 +4,7 @@
4
4
  * Chevrotain-based parsers for JSDoc annotations.
5
5
  * Provides structured parsing for @input, @output, @node, @connect, and @position.
6
6
  */
7
- export { JSDocLexer, allTokens, InputTag, OutputTag, NodeTag, ConnectTag, PositionTag, ScopeTag, MapTag, PathTag, FanOutTag, FanInTag, TriggerTag, CancelOnTag, RetriesTag, TimeoutTag, ThrottleTag, } from './tokens.js';
7
+ export { JSDocLexer, allTokens, InputTag, OutputTag, NodeTag, ConnectTag, PositionTag, ScopeTag, MapTag, PathTag, FanOutTag, FanInTag, CoerceTag, TriggerTag, CancelOnTag, RetriesTag, TimeoutTag, ThrottleTag, } from './tokens.js';
8
8
  export { parsePortLine, getPortGrammar } from './port-parser.js';
9
9
  export type { PortParseResult } from './port-parser.js';
10
10
  export { parseNodeLine, getNodeGrammar } from './node-parser.js';
@@ -21,6 +21,8 @@ export { parsePathLine, getPathGrammar } from './path-parser.js';
21
21
  export type { PathParseResult, PathStep } from './path-parser.js';
22
22
  export { parseFanOutLine, parseFanInLine } from './fan-parser.js';
23
23
  export type { FanOutParseResult, FanInParseResult, PortRef } from './fan-parser.js';
24
+ export { parseCoerceLine } from './coerce-parser.js';
25
+ export type { CoerceParseResult } from './coerce-parser.js';
24
26
  export { parseTriggerLine, parseCancelOnLine, parseRetriesLine, parseTimeoutLine, parseThrottleLine } from './trigger-cancel-parser.js';
25
27
  export type { TriggerParseResult, CancelOnParseResult, ThrottleParseResult } from './trigger-cancel-parser.js';
26
28
  export { generateGrammarDiagrams, getAllGrammars, serializedToEBNF } from './grammar-diagrams.js';
@@ -5,7 +5,7 @@
5
5
  * Provides structured parsing for @input, @output, @node, @connect, and @position.
6
6
  */
7
7
  // Token exports (for advanced use cases)
8
- export { JSDocLexer, allTokens, InputTag, OutputTag, NodeTag, ConnectTag, PositionTag, ScopeTag, MapTag, PathTag, FanOutTag, FanInTag, TriggerTag, CancelOnTag, RetriesTag, TimeoutTag, ThrottleTag, } from './tokens.js';
8
+ export { JSDocLexer, allTokens, InputTag, OutputTag, NodeTag, ConnectTag, PositionTag, ScopeTag, MapTag, PathTag, FanOutTag, FanInTag, CoerceTag, TriggerTag, CancelOnTag, RetriesTag, TimeoutTag, ThrottleTag, } from './tokens.js';
9
9
  // Port parser
10
10
  export { parsePortLine, getPortGrammar } from './port-parser.js';
11
11
  // Node parser
@@ -22,6 +22,8 @@ export { parseMapLine, getMapGrammar } from './map-parser.js';
22
22
  export { parsePathLine, getPathGrammar } from './path-parser.js';
23
23
  // Fan-out / Fan-in parser
24
24
  export { parseFanOutLine, parseFanInLine } from './fan-parser.js';
25
+ // Coerce parser
26
+ export { parseCoerceLine } from './coerce-parser.js';
25
27
  // Trigger/cancel/retries/timeout/throttle parser
26
28
  export { parseTriggerLine, parseCancelOnLine, parseRetriesLine, parseTimeoutLine, parseThrottleLine } from './trigger-cancel-parser.js';
27
29
  // Grammar diagram generation
@@ -4,7 +4,7 @@
4
4
  * Parser for @input/@output/@step port declarations using Chevrotain.
5
5
  */
6
6
  import { CstParser } from 'chevrotain';
7
- import { JSDocLexer, InputTag, OutputTag, StepTag, Identifier, ScopePrefix, OrderPrefix, PlacementPrefix, TypePrefix, MergeStrategyPrefix, TopKeyword, BottomKeyword, TrueKeyword, FalseKeyword, OverKeyword, MinimizedKeyword, Integer, StringLiteral, DescriptionText, LBracket, RBracket, LParen, RParen, Comma, Colon, Dot, Dash, Equals, GreaterThan, LessThan, Pipe, Ampersand, LBrace, RBrace, Asterisk, allTokens, } from './tokens.js';
7
+ import { JSDocLexer, InputTag, OutputTag, StepTag, Identifier, ScopePrefix, OrderPrefix, PlacementPrefix, TypePrefix, MergeStrategyPrefix, TopKeyword, BottomKeyword, TrueKeyword, FalseKeyword, OverKeyword, AsKeyword, MinimizedKeyword, Integer, StringLiteral, DescriptionText, LBracket, RBracket, LParen, RParen, Comma, Colon, Dot, Dash, Equals, GreaterThan, LessThan, Pipe, Ampersand, LBrace, RBrace, Asterisk, allTokens, } from './tokens.js';
8
8
  // =============================================================================
9
9
  // Parser Definition
10
10
  // =============================================================================
@@ -160,6 +160,7 @@ class PortParser extends CstParser {
160
160
  { ALT: () => this.CONSUME(Asterisk) },
161
161
  // Keywords that can appear in description text
162
162
  { ALT: () => this.CONSUME(OverKeyword) },
163
+ { ALT: () => this.CONSUME(AsKeyword) },
163
164
  { ALT: () => this.CONSUME(TopKeyword) },
164
165
  { ALT: () => this.CONSUME(BottomKeyword) },
165
166
  { ALT: () => this.CONSUME(TrueKeyword) },
@@ -19,12 +19,14 @@ export declare const MapTag: import("chevrotain").TokenType;
19
19
  export declare const PathTag: import("chevrotain").TokenType;
20
20
  export declare const FanOutTag: import("chevrotain").TokenType;
21
21
  export declare const FanInTag: import("chevrotain").TokenType;
22
+ export declare const CoerceTag: import("chevrotain").TokenType;
22
23
  export declare const TriggerTag: import("chevrotain").TokenType;
23
24
  export declare const CancelOnTag: import("chevrotain").TokenType;
24
25
  export declare const RetriesTag: import("chevrotain").TokenType;
25
26
  export declare const TimeoutTag: import("chevrotain").TokenType;
26
27
  export declare const ThrottleTag: import("chevrotain").TokenType;
27
28
  export declare const OverKeyword: import("chevrotain").TokenType;
29
+ export declare const AsKeyword: import("chevrotain").TokenType;
28
30
  export declare const ParamTag: import("chevrotain").TokenType;
29
31
  export declare const ReturnsTag: import("chevrotain").TokenType;
30
32
  export declare const FlowWeaverTag: import("chevrotain").TokenType;
@@ -69,6 +69,10 @@ export const FanInTag = createToken({
69
69
  name: 'FanInTag',
70
70
  pattern: /@fanIn\b/,
71
71
  });
72
+ export const CoerceTag = createToken({
73
+ name: 'CoerceTag',
74
+ pattern: /@coerce\b/,
75
+ });
72
76
  export const TriggerTag = createToken({
73
77
  name: 'TriggerTag',
74
78
  pattern: /@trigger\b/,
@@ -93,6 +97,10 @@ export const OverKeyword = createToken({
93
97
  name: 'OverKeyword',
94
98
  pattern: /over\b/,
95
99
  });
100
+ export const AsKeyword = createToken({
101
+ name: 'AsKeyword',
102
+ pattern: /as\b/,
103
+ });
96
104
  export const ParamTag = createToken({
97
105
  name: 'ParamTag',
98
106
  pattern: /@param\b/,
@@ -325,6 +333,7 @@ export const allTokens = [
325
333
  PathTag,
326
334
  FanOutTag,
327
335
  FanInTag,
336
+ CoerceTag,
328
337
  TriggerTag,
329
338
  CancelOnTag,
330
339
  RetriesTag,
@@ -358,6 +367,7 @@ export const allTokens = [
358
367
  PeriodEq,
359
368
  // Keywords (before Identifier)
360
369
  OverKeyword,
370
+ AsKeyword,
361
371
  MinimizedKeyword,
362
372
  TopKeyword,
363
373
  BottomKeyword,
@@ -116,6 +116,9 @@ export async function compileCommand(input, options = {}) {
116
116
  const loc = err.location ? `[line ${err.location.line}] ` : '';
117
117
  logger.error(` ${loc}${friendly.title}: ${friendly.explanation}`);
118
118
  logger.info(` How to fix: ${friendly.fix}`);
119
+ if (err.docUrl) {
120
+ logger.info(` See: ${err.docUrl}`);
121
+ }
119
122
  }
120
123
  else {
121
124
  let msg = ` - ${err.message}`;
@@ -123,6 +126,9 @@ export async function compileCommand(input, options = {}) {
123
126
  msg += ` (node: ${err.node})`;
124
127
  }
125
128
  logger.error(msg);
129
+ if (err.docUrl) {
130
+ logger.info(` See: ${err.docUrl}`);
131
+ }
126
132
  }
127
133
  });
128
134
  errorCount++;
@@ -273,12 +273,14 @@ export function generateProjectFiles(projectName, template, format = 'esm') {
273
273
  ].join('\n');
274
274
  }
275
275
  const gitignore = `node_modules/\ndist/\n.tsbuildinfo\n`;
276
+ const configYaml = `defaultFileType: ts\n`;
276
277
  return {
277
278
  'package.json': packageJson,
278
279
  'tsconfig.json': tsconfigJson,
279
280
  [`src/${workflowFile}`]: workflowCode,
280
281
  'src/main.ts': mainTs,
281
282
  '.gitignore': gitignore,
283
+ '.flowweaver/config.yaml': configYaml,
282
284
  };
283
285
  }
284
286
  // ── Filesystem writer ────────────────────────────────────────────────────────