@synergenius/flow-weaver 0.20.1 → 0.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/annotation-generator.js +7 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/modify-operation.d.ts +15 -0
- package/dist/api/modify-operation.js +177 -0
- package/dist/ast/types.d.ts +2 -0
- package/dist/chevrotain-parser/node-parser.d.ts +2 -0
- package/dist/chevrotain-parser/node-parser.js +27 -1
- package/dist/chevrotain-parser/tokens.d.ts +1 -0
- package/dist/chevrotain-parser/tokens.js +5 -0
- package/dist/cli/commands/modify.d.ts +29 -0
- package/dist/cli/commands/modify.js +57 -0
- package/dist/cli/flow-weaver.mjs +45246 -45018
- package/dist/cli/index.js +73 -2
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/dist/jsdoc-parser.d.ts +1 -0
- package/dist/jsdoc-parser.js +2 -1
- package/dist/mcp/tools-pattern.js +1 -180
- package/dist/parser.js +1 -0
- package/dist/validator.js +15 -0
- package/docs/reference/advanced-annotations.md +11 -0
- package/docs/reference/cli-reference.md +118 -1
- package/docs/reference/error-codes.md +1 -1
- package/docs/reference/jsdoc-grammar.md +4 -2
- package/docs/reference/marketplace.md +74 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -39,6 +39,7 @@ import { docsListCommand, docsReadCommand, docsSearchCommand } from './commands/
|
|
|
39
39
|
import { contextCommand } from './commands/context.js';
|
|
40
40
|
import { statusCommand } from './commands/status.js';
|
|
41
41
|
import { implementCommand } from './commands/implement.js';
|
|
42
|
+
import { modifyAddNodeCommand, modifyRemoveNodeCommand, modifyAddConnectionCommand, modifyRemoveConnectionCommand, modifyRenameNodeCommand, modifySetPositionCommand, modifySetLabelCommand, } from './commands/modify.js';
|
|
42
43
|
import { marketInitCommand, marketPackCommand, marketPublishCommand, marketInstallCommand, marketSearchCommand, marketListCommand, } from './commands/market.js';
|
|
43
44
|
import { mcpSetupCommand } from './commands/mcp-setup.js';
|
|
44
45
|
import { logger } from './utils/logger.js';
|
|
@@ -352,6 +353,71 @@ createCmd
|
|
|
352
353
|
.action(wrapAction(async (name, file, options) => {
|
|
353
354
|
await createNodeCommand(name, file, options);
|
|
354
355
|
}));
|
|
356
|
+
// Modify command (with subcommands)
|
|
357
|
+
const modifyCmd = program.command('modify').description('Modify workflow structure');
|
|
358
|
+
modifyCmd
|
|
359
|
+
.command('addNode')
|
|
360
|
+
.description('Add a node instance to a workflow')
|
|
361
|
+
.requiredOption('--file <path>', 'Workflow file')
|
|
362
|
+
.requiredOption('--nodeId <id>', 'Node instance ID')
|
|
363
|
+
.requiredOption('--nodeType <type>', 'Node type name')
|
|
364
|
+
.action(wrapAction(async (options) => {
|
|
365
|
+
await modifyAddNodeCommand(options.file, options);
|
|
366
|
+
}));
|
|
367
|
+
modifyCmd
|
|
368
|
+
.command('removeNode')
|
|
369
|
+
.description('Remove a node instance from a workflow')
|
|
370
|
+
.requiredOption('--file <path>', 'Workflow file')
|
|
371
|
+
.requiredOption('--nodeId <id>', 'Node instance ID')
|
|
372
|
+
.action(wrapAction(async (options) => {
|
|
373
|
+
await modifyRemoveNodeCommand(options.file, options);
|
|
374
|
+
}));
|
|
375
|
+
modifyCmd
|
|
376
|
+
.command('addConnection')
|
|
377
|
+
.description('Add a connection between nodes')
|
|
378
|
+
.requiredOption('--file <path>', 'Workflow file')
|
|
379
|
+
.requiredOption('--from <node.port>', 'Source (e.g. nodeA.output)')
|
|
380
|
+
.requiredOption('--to <node.port>', 'Target (e.g. nodeB.input)')
|
|
381
|
+
.action(wrapAction(async (options) => {
|
|
382
|
+
await modifyAddConnectionCommand(options.file, options);
|
|
383
|
+
}));
|
|
384
|
+
modifyCmd
|
|
385
|
+
.command('removeConnection')
|
|
386
|
+
.description('Remove a connection between nodes')
|
|
387
|
+
.requiredOption('--file <path>', 'Workflow file')
|
|
388
|
+
.requiredOption('--from <node.port>', 'Source (e.g. nodeA.output)')
|
|
389
|
+
.requiredOption('--to <node.port>', 'Target (e.g. nodeB.input)')
|
|
390
|
+
.action(wrapAction(async (options) => {
|
|
391
|
+
await modifyRemoveConnectionCommand(options.file, options);
|
|
392
|
+
}));
|
|
393
|
+
modifyCmd
|
|
394
|
+
.command('renameNode')
|
|
395
|
+
.description('Rename a node instance (updates all connections)')
|
|
396
|
+
.requiredOption('--file <path>', 'Workflow file')
|
|
397
|
+
.requiredOption('--oldId <id>', 'Current node ID')
|
|
398
|
+
.requiredOption('--newId <id>', 'New node ID')
|
|
399
|
+
.action(wrapAction(async (options) => {
|
|
400
|
+
await modifyRenameNodeCommand(options.file, options);
|
|
401
|
+
}));
|
|
402
|
+
modifyCmd
|
|
403
|
+
.command('setPosition')
|
|
404
|
+
.description('Set position of a node instance')
|
|
405
|
+
.requiredOption('--file <path>', 'Workflow file')
|
|
406
|
+
.requiredOption('--nodeId <id>', 'Node instance ID')
|
|
407
|
+
.requiredOption('--x <number>', 'X coordinate')
|
|
408
|
+
.requiredOption('--y <number>', 'Y coordinate')
|
|
409
|
+
.action(wrapAction(async (options) => {
|
|
410
|
+
await modifySetPositionCommand(options.file, options);
|
|
411
|
+
}));
|
|
412
|
+
modifyCmd
|
|
413
|
+
.command('setLabel')
|
|
414
|
+
.description('Set display label for a node instance')
|
|
415
|
+
.requiredOption('--file <path>', 'Workflow file')
|
|
416
|
+
.requiredOption('--nodeId <id>', 'Node instance ID')
|
|
417
|
+
.requiredOption('--label <text>', 'Display label')
|
|
418
|
+
.action(wrapAction(async (options) => {
|
|
419
|
+
await modifySetLabelCommand(options.file, options);
|
|
420
|
+
}));
|
|
355
421
|
// Templates command
|
|
356
422
|
program
|
|
357
423
|
.command('templates')
|
|
@@ -503,14 +569,19 @@ program
|
|
|
503
569
|
}));
|
|
504
570
|
// Implement command
|
|
505
571
|
program
|
|
506
|
-
.command('implement <input>
|
|
572
|
+
.command('implement <input> [node]')
|
|
507
573
|
.description('Replace a stub node with a real function skeleton')
|
|
508
574
|
.option('-w, --workflow <name>', 'Specific workflow name')
|
|
575
|
+
.option('--nodeId <id>', 'Node to implement (alternative to positional arg)')
|
|
509
576
|
.option('-p, --preview', 'Preview the generated code without writing', false)
|
|
510
577
|
.action(wrapAction(async (input, node, options) => {
|
|
578
|
+
const nodeName = node ?? options.nodeId;
|
|
579
|
+
if (!nodeName) {
|
|
580
|
+
throw new Error('Node name is required (as positional arg or --nodeId flag)');
|
|
581
|
+
}
|
|
511
582
|
if (options.workflow)
|
|
512
583
|
options.workflowName = options.workflow;
|
|
513
|
-
await implementCommand(input,
|
|
584
|
+
await implementCommand(input, nodeName, options);
|
|
514
585
|
}));
|
|
515
586
|
// Changelog command
|
|
516
587
|
program
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.20.
|
|
1
|
+
export declare const VERSION = "0.20.2";
|
|
2
2
|
//# sourceMappingURL=generated-version.d.ts.map
|
package/dist/jsdoc-parser.d.ts
CHANGED
package/dist/jsdoc-parser.js
CHANGED
|
@@ -850,7 +850,7 @@ export class JSDocParser {
|
|
|
850
850
|
if (!result) {
|
|
851
851
|
return;
|
|
852
852
|
}
|
|
853
|
-
const { instanceId, nodeType, parentScope, label, expressions, portOrder, portLabel, minimized, pullExecution, size, position, color, icon, tags, job, environment, } = result;
|
|
853
|
+
const { instanceId, nodeType, parentScope, label, expressions, portOrder, portLabel, minimized, pullExecution, size, position, color, icon, tags, job, environment, suppress, } = result;
|
|
854
854
|
// Capture source location from tag
|
|
855
855
|
const line = tag.getStartLineNumber();
|
|
856
856
|
// Build portConfigs from portOrder, portLabel, and expressions
|
|
@@ -900,6 +900,7 @@ export class JSDocParser {
|
|
|
900
900
|
...(position && { x: position.x, y: position.y }),
|
|
901
901
|
...(job && { job }),
|
|
902
902
|
...(environment && { environment }),
|
|
903
|
+
...(suppress && suppress.length > 0 && { suppressWarnings: suppress }),
|
|
903
904
|
sourceLocation: { line, column: 0 },
|
|
904
905
|
});
|
|
905
906
|
}
|
|
@@ -7,191 +7,12 @@ import { listPatterns, applyPattern, findWorkflows, extractPattern } from '../ap
|
|
|
7
7
|
import { generateInPlace } from '../api/generate-in-place.js';
|
|
8
8
|
import { applyMigrations, getRegisteredMigrations } from '../migration/registry.js';
|
|
9
9
|
import { describeWorkflow, formatDescribeOutput } from '../cli/commands/describe.js';
|
|
10
|
+
import { applyModifyOperation, validateModifyParams } from '../api/modify-operation.js';
|
|
10
11
|
import { addNode as manipAddNode, removeNode as manipRemoveNode, renameNode as manipRenameNode, addConnection as manipAddConnection, removeConnection as manipRemoveConnection, setNodePosition as manipSetNodePosition, setNodeLabel as manipSetNodeLabel, } from '../api/manipulation/index.js';
|
|
11
12
|
import { findIsolatedNodes } from '../api/query.js';
|
|
12
13
|
import { AnnotationParser } from '../parser.js';
|
|
13
14
|
import { makeToolResult, makeErrorResult, addHintsToItems } from './response-utils.js';
|
|
14
15
|
import { getFriendlyError } from '../friendly-errors.js';
|
|
15
|
-
// Runtime validation schemas for fw_modify operations
|
|
16
|
-
const modifyParamsSchemas = {
|
|
17
|
-
addNode: z.object({
|
|
18
|
-
nodeId: z.string({ required_error: 'nodeId is required' }),
|
|
19
|
-
nodeType: z.string({ required_error: 'nodeType is required' }),
|
|
20
|
-
x: z.number().optional(),
|
|
21
|
-
y: z.number().optional(),
|
|
22
|
-
}),
|
|
23
|
-
removeNode: z.object({
|
|
24
|
-
nodeId: z.string({ required_error: 'nodeId is required' }),
|
|
25
|
-
}),
|
|
26
|
-
renameNode: z.object({
|
|
27
|
-
oldId: z.string({ required_error: 'oldId is required' }),
|
|
28
|
-
newId: z.string({ required_error: 'newId is required' }),
|
|
29
|
-
}),
|
|
30
|
-
addConnection: z.object({
|
|
31
|
-
from: z.string({ required_error: 'from is required (format: "node.port")' }),
|
|
32
|
-
to: z.string({ required_error: 'to is required (format: "node.port")' }),
|
|
33
|
-
}),
|
|
34
|
-
removeConnection: z.object({
|
|
35
|
-
from: z.string({ required_error: 'from is required (format: "node.port")' }),
|
|
36
|
-
to: z.string({ required_error: 'to is required (format: "node.port")' }),
|
|
37
|
-
}),
|
|
38
|
-
setNodePosition: z.object({
|
|
39
|
-
nodeId: z.string({ required_error: 'nodeId is required' }),
|
|
40
|
-
x: z.number({ required_error: 'x is required', invalid_type_error: 'x must be a number' }),
|
|
41
|
-
y: z.number({ required_error: 'y is required', invalid_type_error: 'y must be a number' }),
|
|
42
|
-
}),
|
|
43
|
-
setNodeLabel: z.object({
|
|
44
|
-
nodeId: z.string({ required_error: 'nodeId is required' }),
|
|
45
|
-
label: z.string({ required_error: 'label is required' }),
|
|
46
|
-
}),
|
|
47
|
-
};
|
|
48
|
-
function validateModifyParams(operation, params) {
|
|
49
|
-
const schema = modifyParamsSchemas[operation];
|
|
50
|
-
if (!schema) {
|
|
51
|
-
return { success: false, error: `Unknown operation: ${operation}` };
|
|
52
|
-
}
|
|
53
|
-
const result = schema.safeParse(params);
|
|
54
|
-
if (!result.success) {
|
|
55
|
-
const messages = result.error.issues.map((i) => i.message).join('; ');
|
|
56
|
-
return { success: false, error: `${operation} params invalid: ${messages}` };
|
|
57
|
-
}
|
|
58
|
-
return { success: true };
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Apply a single modify operation to an AST.
|
|
62
|
-
* Shared by fw_modify and fw_modify_batch.
|
|
63
|
-
*/
|
|
64
|
-
function applyModifyOperation(ast, operation, params) {
|
|
65
|
-
const p = params;
|
|
66
|
-
const warnings = [];
|
|
67
|
-
const extraData = {};
|
|
68
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- AST manipulation functions use loose typing
|
|
69
|
-
let modifiedAST = ast;
|
|
70
|
-
switch (operation) {
|
|
71
|
-
case 'addNode': {
|
|
72
|
-
const nodeId = p.nodeId;
|
|
73
|
-
const nodeType = p.nodeType;
|
|
74
|
-
const nodeTypeExists = modifiedAST.nodeTypes.some((nt) => nt.name === nodeType || nt.functionName === nodeType);
|
|
75
|
-
if (!nodeTypeExists) {
|
|
76
|
-
warnings.push(`Node type "${nodeType}" is not defined in the file. ` +
|
|
77
|
-
`The node will be added but may not render until the type is defined.`);
|
|
78
|
-
}
|
|
79
|
-
let autoX = typeof p.x === 'number' ? p.x : undefined;
|
|
80
|
-
let autoY = typeof p.y === 'number' ? p.y : undefined;
|
|
81
|
-
if (autoX === undefined || autoY === undefined) {
|
|
82
|
-
const positions = modifiedAST.instances
|
|
83
|
-
.map((inst) => inst.config)
|
|
84
|
-
.filter((c) => c !== undefined &&
|
|
85
|
-
c !== null &&
|
|
86
|
-
typeof c.x === 'number' &&
|
|
87
|
-
typeof c.y === 'number');
|
|
88
|
-
if (positions.length > 0) {
|
|
89
|
-
const maxX = Math.max(...positions.map((pos) => pos.x));
|
|
90
|
-
if (autoX === undefined)
|
|
91
|
-
autoX = maxX + 180;
|
|
92
|
-
if (autoY === undefined)
|
|
93
|
-
autoY = 0;
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
if (autoX === undefined)
|
|
97
|
-
autoX = 0;
|
|
98
|
-
if (autoY === undefined)
|
|
99
|
-
autoY = 0;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
modifiedAST = manipAddNode(modifiedAST, {
|
|
103
|
-
type: 'NodeInstance',
|
|
104
|
-
id: nodeId,
|
|
105
|
-
nodeType,
|
|
106
|
-
config: { x: autoX, y: autoY },
|
|
107
|
-
});
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
case 'removeNode': {
|
|
111
|
-
const nodeId = p.nodeId;
|
|
112
|
-
const removedConnections = modifiedAST.connections
|
|
113
|
-
.filter((c) => c.from.node === nodeId || c.to.node === nodeId)
|
|
114
|
-
.map((c) => ({
|
|
115
|
-
from: `${c.from.node}.${c.from.port}`,
|
|
116
|
-
to: `${c.to.node}.${c.to.port}`,
|
|
117
|
-
}));
|
|
118
|
-
modifiedAST = manipRemoveNode(modifiedAST, nodeId);
|
|
119
|
-
if (removedConnections.length > 0) {
|
|
120
|
-
extraData.removedConnections = removedConnections;
|
|
121
|
-
}
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
case 'renameNode': {
|
|
125
|
-
modifiedAST = manipRenameNode(modifiedAST, p.oldId, p.newId);
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
case 'addConnection': {
|
|
129
|
-
const from = p.from;
|
|
130
|
-
const to = p.to;
|
|
131
|
-
const [fromNode, fromPort] = from.split('.');
|
|
132
|
-
const [toNode, toPort] = to.split('.');
|
|
133
|
-
if (!fromPort || !toPort) {
|
|
134
|
-
throw new Error('Connection format must be "node.port" (e.g., "Start.execute")');
|
|
135
|
-
}
|
|
136
|
-
const validNodes = [
|
|
137
|
-
'Start',
|
|
138
|
-
'Exit',
|
|
139
|
-
...modifiedAST.instances.map((i) => i.id),
|
|
140
|
-
];
|
|
141
|
-
if (!validNodes.includes(fromNode)) {
|
|
142
|
-
throw new Error(`Source node "${fromNode}" not found. Available: ${validNodes.join(', ')}`);
|
|
143
|
-
}
|
|
144
|
-
if (!validNodes.includes(toNode)) {
|
|
145
|
-
throw new Error(`Target node "${toNode}" not found. Available: ${validNodes.join(', ')}`);
|
|
146
|
-
}
|
|
147
|
-
if (fromNode !== 'Start' && fromNode !== 'Exit') {
|
|
148
|
-
const inst = modifiedAST.instances.find((i) => i.id === fromNode);
|
|
149
|
-
const nt = modifiedAST.nodeTypes.find((t) => t.name === inst?.nodeType);
|
|
150
|
-
if (nt && !nt.outputs[fromPort]) {
|
|
151
|
-
throw new Error(`Node "${fromNode}" has no output "${fromPort}". Available: ${Object.keys(nt.outputs).join(', ')}`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
if (toNode !== 'Start' && toNode !== 'Exit') {
|
|
155
|
-
const inst = modifiedAST.instances.find((i) => i.id === toNode);
|
|
156
|
-
const nt = modifiedAST.nodeTypes.find((t) => t.name === inst?.nodeType);
|
|
157
|
-
if (nt && !nt.inputs[toPort]) {
|
|
158
|
-
throw new Error(`Node "${toNode}" has no input "${toPort}". Available: ${Object.keys(nt.inputs).join(', ')}`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
modifiedAST = manipAddConnection(modifiedAST, from, to);
|
|
162
|
-
// Transition from autoConnect to explicit mode when connections are manually modified
|
|
163
|
-
if (modifiedAST.options?.autoConnect) {
|
|
164
|
-
modifiedAST = { ...modifiedAST, options: { ...modifiedAST.options, autoConnect: undefined } };
|
|
165
|
-
warnings.push('autoConnect was disabled because connections were manually modified');
|
|
166
|
-
}
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
case 'removeConnection': {
|
|
170
|
-
modifiedAST = manipRemoveConnection(modifiedAST, p.from, p.to);
|
|
171
|
-
// Transition from autoConnect to explicit mode when connections are manually modified
|
|
172
|
-
if (modifiedAST.options?.autoConnect) {
|
|
173
|
-
modifiedAST = { ...modifiedAST, options: { ...modifiedAST.options, autoConnect: undefined } };
|
|
174
|
-
warnings.push('autoConnect was disabled because connections were manually modified');
|
|
175
|
-
}
|
|
176
|
-
const newlyIsolated = findIsolatedNodes(modifiedAST);
|
|
177
|
-
if (newlyIsolated.length > 0) {
|
|
178
|
-
extraData.newlyIsolatedNodes = newlyIsolated;
|
|
179
|
-
}
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
case 'setNodePosition': {
|
|
183
|
-
modifiedAST = manipSetNodePosition(modifiedAST, p.nodeId, p.x, p.y);
|
|
184
|
-
break;
|
|
185
|
-
}
|
|
186
|
-
case 'setNodeLabel': {
|
|
187
|
-
modifiedAST = manipSetNodeLabel(modifiedAST, p.nodeId, p.label);
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
-
default:
|
|
191
|
-
throw new Error(`Unknown operation: ${operation}`);
|
|
192
|
-
}
|
|
193
|
-
return { ast: modifiedAST, warnings, extraData };
|
|
194
|
-
}
|
|
195
16
|
export function registerPatternTools(mcp) {
|
|
196
17
|
mcp.tool('fw_list_patterns', 'List reusable patterns defined in a file.', {
|
|
197
18
|
filePath: z.string().describe('Path to file containing patterns'),
|
package/dist/parser.js
CHANGED
|
@@ -970,6 +970,7 @@ export class AnnotationParser {
|
|
|
970
970
|
...(inst.tags && inst.tags.length > 0 && { tags: inst.tags }),
|
|
971
971
|
...(inst.width && { width: inst.width }),
|
|
972
972
|
...(inst.height && { height: inst.height }),
|
|
973
|
+
...(inst.suppressWarnings?.length && { suppressWarnings: inst.suppressWarnings }),
|
|
973
974
|
},
|
|
974
975
|
...(inst.sourceLocation && {
|
|
975
976
|
sourceLocation: { file: filePath, ...inst.sourceLocation },
|
package/dist/validator.js
CHANGED
|
@@ -228,6 +228,21 @@ export class WorkflowValidator {
|
|
|
228
228
|
});
|
|
229
229
|
this.warnings.push(...promoted);
|
|
230
230
|
}
|
|
231
|
+
// Filter out warnings suppressed by per-instance [suppress: "CODE"] annotations
|
|
232
|
+
const suppressMap = new Map();
|
|
233
|
+
for (const inst of workflow.instances) {
|
|
234
|
+
if (inst.config?.suppressWarnings?.length) {
|
|
235
|
+
suppressMap.set(inst.id, new Set(inst.config.suppressWarnings));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (suppressMap.size > 0) {
|
|
239
|
+
this.warnings = this.warnings.filter((w) => {
|
|
240
|
+
if (!w.node)
|
|
241
|
+
return true;
|
|
242
|
+
const codes = suppressMap.get(w.node);
|
|
243
|
+
return !codes || !codes.has(w.code);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
231
246
|
// Attach doc URLs to diagnostics that have mapped error codes
|
|
232
247
|
for (const diag of [...this.errors, ...this.warnings]) {
|
|
233
248
|
if (!diag.docUrl && ERROR_DOC_URLS[diag.code]) {
|
|
@@ -423,6 +423,17 @@ Visual tags/badges on the instance. Each tag has a label string and optional too
|
|
|
423
423
|
@node myNode MyType [tags: "async" "Runs asynchronously", "beta"]
|
|
424
424
|
```
|
|
425
425
|
|
|
426
|
+
### Suppress Warnings (`[suppress: ...]`)
|
|
427
|
+
|
|
428
|
+
Silences specific validator warnings on a per-instance basis. Useful when a warning is intentional, such as output ports that are deliberately left unconnected in CI/CD workflows where data is discarded by design.
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
@node fetch fetchData [suppress: "UNUSED_OUTPUT_PORT"]
|
|
432
|
+
@node check runCheck [suppress: "UNUSED_OUTPUT_PORT", "UNREACHABLE_EXIT_PORT"]
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
The suppression is scoped to the annotated instance only. Other instances of the same type still produce warnings normally. The codes correspond to the warning codes listed in the error codes reference.
|
|
436
|
+
|
|
426
437
|
### Combining Attributes
|
|
427
438
|
|
|
428
439
|
Multiple attribute brackets can appear on the same `@node`:
|
|
@@ -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, context]
|
|
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, modify, implement, status]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# CLI Reference
|
|
@@ -34,6 +34,9 @@ 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
|
+
| `modify` | Add/remove/rename nodes, connections, positions, and labels |
|
|
38
|
+
| `implement` | Replace stub node with function skeleton |
|
|
39
|
+
| `status` | Report implementation progress |
|
|
37
40
|
| `context` | Generate LLM context bundle |
|
|
38
41
|
| `docs` | Browse reference documentation |
|
|
39
42
|
| `ui` | Send commands to the editor |
|
|
@@ -475,6 +478,120 @@ flow-weaver create node checker my-workflow.ts --template validator
|
|
|
475
478
|
|
|
476
479
|
---
|
|
477
480
|
|
|
481
|
+
### modify
|
|
482
|
+
|
|
483
|
+
Modify workflow structure programmatically. Parses the file, applies the operation, and regenerates the JSDoc annotations in place. Useful for scripting, CI pipelines, and the genesis self-evolution system.
|
|
484
|
+
|
|
485
|
+
#### modify addNode
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
flow-weaver modify addNode --file <path> --nodeId <id> --nodeType <type>
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Adds a new node instance to the workflow. Auto-positions to the right of the rightmost existing node. Warns if the node type isn't defined in the file.
|
|
492
|
+
|
|
493
|
+
#### modify removeNode
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
flow-weaver modify removeNode --file <path> --nodeId <id>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Removes a node instance and all connections attached to it.
|
|
500
|
+
|
|
501
|
+
#### modify addConnection
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
flow-weaver modify addConnection --file <path> --from <node.port> --to <node.port>
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Adds a connection between two ports. Both nodes must exist. Port names are validated against the node type definition when available.
|
|
508
|
+
|
|
509
|
+
#### modify removeConnection
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
flow-weaver modify removeConnection --file <path> --from <node.port> --to <node.port>
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Removes an existing connection.
|
|
516
|
+
|
|
517
|
+
#### modify renameNode
|
|
518
|
+
|
|
519
|
+
```bash
|
|
520
|
+
flow-weaver modify renameNode --file <path> --oldId <id> --newId <id>
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
Renames a node instance and updates all connections that reference it.
|
|
524
|
+
|
|
525
|
+
#### modify setPosition
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
flow-weaver modify setPosition --file <path> --nodeId <id> --x <number> --y <number>
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
Sets the canvas position of a node instance.
|
|
532
|
+
|
|
533
|
+
#### modify setLabel
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
flow-weaver modify setLabel --file <path> --nodeId <id> --label <text>
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
Sets the display label for a node instance.
|
|
540
|
+
|
|
541
|
+
**Examples:**
|
|
542
|
+
```bash
|
|
543
|
+
flow-weaver modify addNode --file workflow.ts --nodeId validator --nodeType validateInput
|
|
544
|
+
flow-weaver modify addConnection --file workflow.ts --from Start.data --to validator.input
|
|
545
|
+
flow-weaver modify removeNode --file workflow.ts --nodeId oldStep
|
|
546
|
+
flow-weaver modify removeConnection --file workflow.ts --from a.output --to b.input
|
|
547
|
+
flow-weaver modify renameNode --file workflow.ts --oldId step1 --newId validateStep
|
|
548
|
+
flow-weaver modify setPosition --file workflow.ts --nodeId step1 --x 200 --y 100
|
|
549
|
+
flow-weaver modify setLabel --file workflow.ts --nodeId step1 --label "Validate Input"
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
### implement
|
|
555
|
+
|
|
556
|
+
Replace a stub node (`declare function`) with a real function skeleton containing the correct signature, JSDoc annotations, and return type.
|
|
557
|
+
|
|
558
|
+
```bash
|
|
559
|
+
flow-weaver implement <input> <node> [options]
|
|
560
|
+
flow-weaver implement <input> --nodeId <id> [options]
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
The node can be specified as a positional argument or with the `--nodeId` flag.
|
|
564
|
+
|
|
565
|
+
| Flag | Description | Default |
|
|
566
|
+
|------|-------------|---------|
|
|
567
|
+
| `-w, --workflow <name>` | Specific workflow name | — |
|
|
568
|
+
| `--nodeId <id>` | Node to implement (alternative to positional arg) | — |
|
|
569
|
+
| `-p, --preview` | Preview without writing | `false` |
|
|
570
|
+
|
|
571
|
+
**Examples:**
|
|
572
|
+
```bash
|
|
573
|
+
flow-weaver implement workflow.ts validateInput
|
|
574
|
+
flow-weaver implement workflow.ts --nodeId validateInput
|
|
575
|
+
flow-weaver implement workflow.ts myNode --preview
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
### status
|
|
581
|
+
|
|
582
|
+
Report implementation progress for stub workflows. Shows which nodes are implemented vs still declared as stubs.
|
|
583
|
+
|
|
584
|
+
```bash
|
|
585
|
+
flow-weaver status <input> [options]
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
| Flag | Description | Default |
|
|
589
|
+
|------|-------------|---------|
|
|
590
|
+
| `-w, --workflow <name>` | Specific workflow name | — |
|
|
591
|
+
| `--json` | Output as JSON | `false` |
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
478
595
|
### templates
|
|
479
596
|
|
|
480
597
|
List available workflow and node templates.
|
|
@@ -540,7 +540,7 @@ const fetchData = (execute: boolean, url: string, apiKey: string) => { ... };
|
|
|
540
540
|
| Severity | Warning |
|
|
541
541
|
| Meaning | A node's data output port is never connected to anything. The data produced by this port is discarded. |
|
|
542
542
|
| Common Causes | The node produces an output that is not needed by the current workflow. A connection from this port was removed but the port still exists. Control flow ports (`onSuccess`, `onFailure`) and scoped ports are excluded from this check. |
|
|
543
|
-
| Fix | If the output is needed, connect it to a downstream node or to `Exit`. If
|
|
543
|
+
| Fix | If the output is needed, connect it to a downstream node or to `Exit`. If the output is intentionally discarded, add `[suppress: "UNUSED_OUTPUT_PORT"]` to the `@node` annotation to silence this warning for that instance. |
|
|
544
544
|
|
|
545
545
|
#### UNREACHABLE_EXIT_PORT (warning)
|
|
546
546
|
|
|
@@ -212,8 +212,8 @@ attributeBracket ::= "[" nodeAttr { "," nodeAttr } "]"
|
|
|
212
212
|
|
|
213
213
|
nodeAttr ::= labelAttr | exprAttr | portOrderAttr | portLabelAttr
|
|
214
214
|
| minimizedAttr | pullExecutionAttr | sizeAttr
|
|
215
|
-
| colorAttr | iconAttr | tagsAttr |
|
|
216
|
-
| jobAttr | environmentAttr
|
|
215
|
+
| colorAttr | iconAttr | tagsAttr | suppressAttr
|
|
216
|
+
| positionAttr | jobAttr | environmentAttr
|
|
217
217
|
|
|
218
218
|
labelAttr ::= "label:" STRING
|
|
219
219
|
exprAttr ::= "expr:" IDENTIFIER "=" STRING { "," IDENTIFIER "=" STRING }
|
|
@@ -229,6 +229,7 @@ tagEntry ::= STRING [ STRING ]
|
|
|
229
229
|
positionAttr ::= "position:" INTEGER INTEGER
|
|
230
230
|
jobAttr ::= "job:" STRING
|
|
231
231
|
environmentAttr ::= "environment:" STRING
|
|
232
|
+
suppressAttr ::= "suppress:" STRING { "," STRING }
|
|
232
233
|
```
|
|
233
234
|
|
|
234
235
|
Multiple attribute brackets are allowed (zero or more). Attributes can be split across brackets or combined in one.
|
|
@@ -250,6 +251,7 @@ Multiple attribute brackets are allowed (zero or more). Attributes can be split
|
|
|
250
251
|
@node myAdd Add [label: "hi"] [color: "#f00"] [position: 360 0]
|
|
251
252
|
@node build npmBuild [job: "build"]
|
|
252
253
|
@node deploy deploySsh [job: "deploy"] [environment: "production"]
|
|
254
|
+
@node fetch fetchData [suppress: "UNUSED_OUTPUT_PORT"]
|
|
253
255
|
```
|
|
254
256
|
|
|
255
257
|
## @connect
|
|
@@ -159,6 +159,80 @@ The `market pack` command validates packages against additional rules beyond sta
|
|
|
159
159
|
|
|
160
160
|
---
|
|
161
161
|
|
|
162
|
+
## Custom Tag Handlers
|
|
163
|
+
|
|
164
|
+
Tag handlers let packs extend the parser with custom JSDoc annotations. When the parser encounters a tag it doesn't recognize natively, it delegates to registered pack handlers before emitting "Unknown annotation" warnings.
|
|
165
|
+
|
|
166
|
+
The CI/CD pack (`flowweaver-pack-cicd`) is the primary example: it registers handlers for `@secret`, `@runner`, `@cache`, `@artifact`, and other CI/CD tags.
|
|
167
|
+
|
|
168
|
+
### Writing a handler
|
|
169
|
+
|
|
170
|
+
A tag handler is a function matching the `TTagHandlerFn` signature:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import type { TTagHandlerFn } from '@synergenius/flow-weaver/api';
|
|
174
|
+
|
|
175
|
+
export const myHandler: TTagHandlerFn = (tagName, comment, ctx) => {
|
|
176
|
+
// tagName: the tag without '@', e.g. "secret"
|
|
177
|
+
// comment: everything after the tag on that line
|
|
178
|
+
// ctx.deploy: the deploy map for your namespace (mutate it directly)
|
|
179
|
+
// ctx.warnings: push parser warnings here
|
|
180
|
+
|
|
181
|
+
const value = comment.trim();
|
|
182
|
+
if (!value) {
|
|
183
|
+
ctx.warnings.push(`Empty @${tagName} tag`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const items = (ctx.deploy['items'] as string[]) ?? [];
|
|
188
|
+
items.push(value);
|
|
189
|
+
ctx.deploy['items'] = items;
|
|
190
|
+
};
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The handler receives one call per tag occurrence. Parsed data goes into `ctx.deploy`, which maps to `workflow.deploy[namespace]` or `nodeType.deploy[namespace]` in the final AST.
|
|
194
|
+
|
|
195
|
+
### Declaring handlers in the manifest
|
|
196
|
+
|
|
197
|
+
Add a `tagHandlers` entry to your `flowweaver.manifest.json`:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"manifestVersion": 2,
|
|
202
|
+
"tagHandlers": [
|
|
203
|
+
{
|
|
204
|
+
"tags": ["secret", "runner", "cache"],
|
|
205
|
+
"namespace": "cicd",
|
|
206
|
+
"scope": "both",
|
|
207
|
+
"file": "dist/tag-handler.js",
|
|
208
|
+
"exportName": "cicdTagHandler"
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Fields:
|
|
215
|
+
|
|
216
|
+
| Field | Description |
|
|
217
|
+
|-------|-------------|
|
|
218
|
+
| `tags` | Tag names this handler processes (without the `@` prefix) |
|
|
219
|
+
| `namespace` | Key in the deploy map where parsed data is stored |
|
|
220
|
+
| `scope` | `workflow` for workflow-level tags, `nodeType` for node type tags, `both` for either |
|
|
221
|
+
| `file` | Relative path to the compiled JS file exporting the handler |
|
|
222
|
+
| `exportName` | Named export from the file (omit for `default` export) |
|
|
223
|
+
|
|
224
|
+
### Handler scope
|
|
225
|
+
|
|
226
|
+
A handler scoped to `workflow` only runs for tags inside `@flowWeaver workflow` blocks. A handler scoped to `nodeType` only runs inside `@flowWeaver nodeType` blocks. Use `both` when your tags are valid in either context.
|
|
227
|
+
|
|
228
|
+
If a tag appears in the wrong scope, the parser emits a warning and skips the handler call.
|
|
229
|
+
|
|
230
|
+
### How discovery works
|
|
231
|
+
|
|
232
|
+
When `parseWorkflow()` is called with a `projectDir` option (or when the CLI runs from a project directory), the parser scans `node_modules` for installed packs with a `flowweaver.manifest.json`. It reads the `tagHandlers` array from each manifest, dynamically imports the handler files, and registers them in the `TagHandlerRegistry`. This scan runs once per project directory and is cached for subsequent parse calls.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
162
236
|
## External Plugins
|
|
163
237
|
|
|
164
238
|
Plugins extend the Flow Weaver Studio IDE with custom UI components, system logic, and integrations.
|
package/package.json
CHANGED