@synergenius/flow-weaver 0.10.8 → 0.10.9
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/cli/commands/describe.d.ts +2 -2
- package/dist/cli/commands/describe.js +6 -0
- package/dist/cli/commands/diagram.d.ts +2 -2
- package/dist/cli/commands/diagram.js +13 -6
- package/dist/cli/flow-weaver.mjs +964 -361
- package/dist/cli/index.js +2 -2
- package/dist/diagram/ascii-renderer.d.ts +13 -0
- package/dist/diagram/ascii-renderer.js +528 -0
- package/dist/diagram/index.d.ts +4 -0
- package/dist/diagram/index.js +26 -0
- package/dist/diagram/types.d.ts +1 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/prompts.d.ts +3 -0
- package/dist/mcp/prompts.js +68 -0
- package/dist/mcp/server.js +2 -0
- package/dist/mcp/tools-diagram.d.ts +2 -1
- package/dist/mcp/tools-diagram.js +50 -14
- package/dist/mcp/tools-query.js +2 -2
- package/dist/parser.d.ts +6 -0
- package/dist/parser.js +59 -25
- package/package.json +1 -1
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -47472,26 +47472,44 @@ ${fn.getText()}` : fn.getText();
|
|
|
47472
47472
|
ports.execute = { dataType: "STEP", tsType: "boolean", label: "Execute" };
|
|
47473
47473
|
}
|
|
47474
47474
|
if (params.length > 1) {
|
|
47475
|
-
const
|
|
47476
|
-
const
|
|
47477
|
-
|
|
47478
|
-
|
|
47479
|
-
const
|
|
47480
|
-
const
|
|
47481
|
-
|
|
47482
|
-
|
|
47483
|
-
|
|
47484
|
-
|
|
47485
|
-
|
|
47486
|
-
|
|
47487
|
-
|
|
47488
|
-
|
|
47489
|
-
|
|
47490
|
-
|
|
47491
|
-
|
|
47492
|
-
|
|
47493
|
-
|
|
47494
|
-
|
|
47475
|
+
const dataParams = params.slice(1).filter((p) => p.getName() !== "__abortSignal__");
|
|
47476
|
+
const shouldExpandProperties = dataParams.length === 1 && this.isExpandableObjectType(dataParams[0].getType());
|
|
47477
|
+
if (shouldExpandProperties) {
|
|
47478
|
+
const dataParam = dataParams[0];
|
|
47479
|
+
const dataParamType = dataParam.getType();
|
|
47480
|
+
const properties = dataParamType.getProperties();
|
|
47481
|
+
properties.forEach((prop) => {
|
|
47482
|
+
const propName = prop.getName();
|
|
47483
|
+
const propType = prop.getTypeAtLocation(dataParam);
|
|
47484
|
+
const portType = this.inferPortType(propType);
|
|
47485
|
+
const propTypeText = propType.getText();
|
|
47486
|
+
const tsSchema = portType === "OBJECT" ? this.extractTypeSchema(propType) : void 0;
|
|
47487
|
+
const startPortConfig = config2?.startPorts?.[propName];
|
|
47488
|
+
ports[propName] = {
|
|
47489
|
+
dataType: portType,
|
|
47490
|
+
label: startPortConfig?.label || this.capitalize(propName),
|
|
47491
|
+
...startPortConfig?.metadata && { metadata: startPortConfig.metadata },
|
|
47492
|
+
...propTypeText && { tsType: propTypeText },
|
|
47493
|
+
...tsSchema && Object.keys(tsSchema).length > 0 && { tsSchema }
|
|
47494
|
+
};
|
|
47495
|
+
});
|
|
47496
|
+
} else {
|
|
47497
|
+
for (const param of dataParams) {
|
|
47498
|
+
const paramName = param.getName();
|
|
47499
|
+
const paramType = param.getType();
|
|
47500
|
+
const portType = this.inferPortType(paramType);
|
|
47501
|
+
const paramTypeText = paramType.getText(param);
|
|
47502
|
+
const tsSchema = portType === "OBJECT" ? this.extractTypeSchema(paramType) : void 0;
|
|
47503
|
+
const startPortConfig = config2?.startPorts?.[paramName];
|
|
47504
|
+
ports[paramName] = {
|
|
47505
|
+
dataType: portType,
|
|
47506
|
+
label: startPortConfig?.label || this.capitalize(paramName),
|
|
47507
|
+
...startPortConfig?.metadata && { metadata: startPortConfig.metadata },
|
|
47508
|
+
...paramTypeText && { tsType: paramTypeText },
|
|
47509
|
+
...tsSchema && Object.keys(tsSchema).length > 0 && { tsSchema }
|
|
47510
|
+
};
|
|
47511
|
+
}
|
|
47512
|
+
}
|
|
47495
47513
|
}
|
|
47496
47514
|
} else {
|
|
47497
47515
|
throw new Error(
|
|
@@ -47578,6 +47596,18 @@ ${fn.getText()}` : fn.getText();
|
|
|
47578
47596
|
}
|
|
47579
47597
|
return Object.keys(schema2).length > 0 ? schema2 : void 0;
|
|
47580
47598
|
}
|
|
47599
|
+
/**
|
|
47600
|
+
* Check if a type should be expanded into individual ports via getProperties().
|
|
47601
|
+
* Returns true for object literals and interfaces, false for primitives, arrays,
|
|
47602
|
+
* and built-in types whose properties are prototype methods (string, number, etc.).
|
|
47603
|
+
*/
|
|
47604
|
+
isExpandableObjectType(tsType) {
|
|
47605
|
+
const typeText = tsType.getText();
|
|
47606
|
+
const primitiveTypes2 = /* @__PURE__ */ new Set(["string", "number", "boolean", "any", "unknown", "never", "object", "Object"]);
|
|
47607
|
+
if (primitiveTypes2.has(typeText)) return false;
|
|
47608
|
+
if (typeText.endsWith("[]") || typeText.startsWith("Array<")) return false;
|
|
47609
|
+
return tsType.isObject() && tsType.getProperties().length > 0;
|
|
47610
|
+
}
|
|
47581
47611
|
inferPortType(tsType) {
|
|
47582
47612
|
const typeText = tsType.getText();
|
|
47583
47613
|
return inferDataTypeFromTS(typeText);
|
|
@@ -59279,331 +59309,6 @@ async function createNodeCommand(name, file, options = {}) {
|
|
|
59279
59309
|
import * as fs11 from "fs";
|
|
59280
59310
|
import * as path11 from "path";
|
|
59281
59311
|
init_validator();
|
|
59282
|
-
function buildNodeInfo(instance, nodeType) {
|
|
59283
|
-
return {
|
|
59284
|
-
id: instance.id,
|
|
59285
|
-
type: instance.nodeType,
|
|
59286
|
-
inputs: nodeType ? Object.keys(nodeType.inputs) : [],
|
|
59287
|
-
outputs: nodeType ? Object.keys(nodeType.outputs) : []
|
|
59288
|
-
};
|
|
59289
|
-
}
|
|
59290
|
-
function buildAdjacency(ast) {
|
|
59291
|
-
const fromStart = [];
|
|
59292
|
-
const toExit = /* @__PURE__ */ new Set();
|
|
59293
|
-
const edges = /* @__PURE__ */ new Map();
|
|
59294
|
-
ast.connections.forEach((conn) => {
|
|
59295
|
-
if (conn.from.node === "Start") {
|
|
59296
|
-
if (!fromStart.includes(conn.to.node)) {
|
|
59297
|
-
fromStart.push(conn.to.node);
|
|
59298
|
-
}
|
|
59299
|
-
} else if (conn.to.node === "Exit") {
|
|
59300
|
-
toExit.add(conn.from.node);
|
|
59301
|
-
} else {
|
|
59302
|
-
const targets = edges.get(conn.from.node) || [];
|
|
59303
|
-
if (!targets.includes(conn.to.node)) {
|
|
59304
|
-
targets.push(conn.to.node);
|
|
59305
|
-
}
|
|
59306
|
-
edges.set(conn.from.node, targets);
|
|
59307
|
-
}
|
|
59308
|
-
});
|
|
59309
|
-
return { fromStart, toExit, edges };
|
|
59310
|
-
}
|
|
59311
|
-
function enumeratePaths(ast) {
|
|
59312
|
-
const { fromStart, toExit, edges } = buildAdjacency(ast);
|
|
59313
|
-
const paths = [];
|
|
59314
|
-
function dfs(current2, path42, visited) {
|
|
59315
|
-
if (toExit.has(current2)) {
|
|
59316
|
-
paths.push([...path42, "Exit"]);
|
|
59317
|
-
}
|
|
59318
|
-
const targets = edges.get(current2) || [];
|
|
59319
|
-
for (const next of targets) {
|
|
59320
|
-
if (!visited.has(next)) {
|
|
59321
|
-
visited.add(next);
|
|
59322
|
-
path42.push(next);
|
|
59323
|
-
dfs(next, path42, visited);
|
|
59324
|
-
path42.pop();
|
|
59325
|
-
visited.delete(next);
|
|
59326
|
-
}
|
|
59327
|
-
}
|
|
59328
|
-
}
|
|
59329
|
-
fromStart.forEach((startNode) => {
|
|
59330
|
-
const visited = /* @__PURE__ */ new Set([startNode]);
|
|
59331
|
-
dfs(startNode, ["Start", startNode], visited);
|
|
59332
|
-
});
|
|
59333
|
-
return paths;
|
|
59334
|
-
}
|
|
59335
|
-
function buildGraph(ast) {
|
|
59336
|
-
const { fromStart, toExit, edges } = buildAdjacency(ast);
|
|
59337
|
-
const lines = [];
|
|
59338
|
-
function dfs(current2, path42, visited) {
|
|
59339
|
-
if (toExit.has(current2)) {
|
|
59340
|
-
lines.push([...path42, "Exit"].join(" -> "));
|
|
59341
|
-
}
|
|
59342
|
-
const targets = edges.get(current2) || [];
|
|
59343
|
-
for (const next of targets) {
|
|
59344
|
-
if (!visited.has(next)) {
|
|
59345
|
-
visited.add(next);
|
|
59346
|
-
path42.push(next);
|
|
59347
|
-
dfs(next, path42, visited);
|
|
59348
|
-
path42.pop();
|
|
59349
|
-
visited.delete(next);
|
|
59350
|
-
}
|
|
59351
|
-
}
|
|
59352
|
-
if (targets.length === 0 && !toExit.has(current2)) {
|
|
59353
|
-
lines.push(path42.join(" -> "));
|
|
59354
|
-
}
|
|
59355
|
-
}
|
|
59356
|
-
fromStart.forEach((startNode) => {
|
|
59357
|
-
const visited = /* @__PURE__ */ new Set([startNode]);
|
|
59358
|
-
dfs(startNode, ["Start", startNode], visited);
|
|
59359
|
-
});
|
|
59360
|
-
return lines.join("\n");
|
|
59361
|
-
}
|
|
59362
|
-
function formatPaths(ast) {
|
|
59363
|
-
const paths = enumeratePaths(ast);
|
|
59364
|
-
if (paths.length === 0) return "(no complete Start-to-Exit paths found)";
|
|
59365
|
-
return paths.map((p) => p.join(" -> ")).join("\n");
|
|
59366
|
-
}
|
|
59367
|
-
function generateMermaid(ast) {
|
|
59368
|
-
const lines = ["graph LR"];
|
|
59369
|
-
ast.instances.forEach((instance) => {
|
|
59370
|
-
lines.push(` ${instance.id}[${instance.id}: ${instance.nodeType}]`);
|
|
59371
|
-
});
|
|
59372
|
-
const seenEdges = /* @__PURE__ */ new Set();
|
|
59373
|
-
ast.connections.forEach((conn) => {
|
|
59374
|
-
const edgeKey = `${conn.from.node}->${conn.to.node}`;
|
|
59375
|
-
if (!seenEdges.has(edgeKey)) {
|
|
59376
|
-
seenEdges.add(edgeKey);
|
|
59377
|
-
const from = conn.from.node === "Start" ? "Start((Start))" : conn.from.node;
|
|
59378
|
-
const to = conn.to.node === "Exit" ? "Exit((Exit))" : conn.to.node;
|
|
59379
|
-
lines.push(` ${from} --> ${to}`);
|
|
59380
|
-
}
|
|
59381
|
-
});
|
|
59382
|
-
return lines.join("\n");
|
|
59383
|
-
}
|
|
59384
|
-
function describeWorkflow(ast, options = {}) {
|
|
59385
|
-
const { node: focusNodeId } = options;
|
|
59386
|
-
const validation = validator.validate(ast);
|
|
59387
|
-
const validationOutput = {
|
|
59388
|
-
valid: validation.valid,
|
|
59389
|
-
errors: validation.errors.map((e) => e.message),
|
|
59390
|
-
warnings: validation.warnings.map((w) => w.message)
|
|
59391
|
-
};
|
|
59392
|
-
const nodeTypeMap = /* @__PURE__ */ new Map();
|
|
59393
|
-
ast.nodeTypes.forEach((nt) => nodeTypeMap.set(nt.functionName, nt));
|
|
59394
|
-
if (focusNodeId) {
|
|
59395
|
-
const nodeInstance = getNode(ast, focusNodeId);
|
|
59396
|
-
if (!nodeInstance) {
|
|
59397
|
-
throw new Error(`Node not found: ${focusNodeId}`);
|
|
59398
|
-
}
|
|
59399
|
-
const nodeType = nodeTypeMap.get(nodeInstance.nodeType);
|
|
59400
|
-
const incoming = getIncomingConnections(ast, focusNodeId).map((c) => ({
|
|
59401
|
-
from: `${c.from.node}.${c.from.port}`,
|
|
59402
|
-
to: `${c.to.node}.${c.to.port}`
|
|
59403
|
-
}));
|
|
59404
|
-
const outgoing = getOutgoingConnections(ast, focusNodeId).map((c) => ({
|
|
59405
|
-
from: `${c.from.node}.${c.from.port}`,
|
|
59406
|
-
to: `${c.to.node}.${c.to.port}`
|
|
59407
|
-
}));
|
|
59408
|
-
return {
|
|
59409
|
-
focusNode: focusNodeId,
|
|
59410
|
-
node: buildNodeInfo(nodeInstance, nodeType),
|
|
59411
|
-
incoming,
|
|
59412
|
-
outgoing,
|
|
59413
|
-
validation: validationOutput
|
|
59414
|
-
};
|
|
59415
|
-
}
|
|
59416
|
-
const nodes = ast.instances.map((instance) => {
|
|
59417
|
-
const nodeType = nodeTypeMap.get(instance.nodeType);
|
|
59418
|
-
return buildNodeInfo(instance, nodeType);
|
|
59419
|
-
});
|
|
59420
|
-
const connections = ast.connections.map((c) => ({
|
|
59421
|
-
from: `${c.from.node}.${c.from.port}`,
|
|
59422
|
-
to: `${c.to.node}.${c.to.port}`
|
|
59423
|
-
}));
|
|
59424
|
-
return {
|
|
59425
|
-
name: ast.functionName,
|
|
59426
|
-
description: ast.description || null,
|
|
59427
|
-
nodes,
|
|
59428
|
-
connections,
|
|
59429
|
-
graph: buildGraph(ast),
|
|
59430
|
-
validation: validationOutput
|
|
59431
|
-
};
|
|
59432
|
-
}
|
|
59433
|
-
function formatTextOutput(ast, output) {
|
|
59434
|
-
if ("focusNode" in output) {
|
|
59435
|
-
const focused = output;
|
|
59436
|
-
const lines2 = [];
|
|
59437
|
-
lines2.push(`Node: ${focused.node.id} [${focused.node.type}]`);
|
|
59438
|
-
lines2.push(` Inputs: ${focused.node.inputs.join(", ") || "(none)"}`);
|
|
59439
|
-
lines2.push(` Outputs: ${focused.node.outputs.join(", ") || "(none)"}`);
|
|
59440
|
-
if (focused.incoming.length > 0) {
|
|
59441
|
-
lines2.push("");
|
|
59442
|
-
lines2.push("Incoming:");
|
|
59443
|
-
focused.incoming.forEach((c) => lines2.push(` ${c.from} -> ${c.to}`));
|
|
59444
|
-
}
|
|
59445
|
-
if (focused.outgoing.length > 0) {
|
|
59446
|
-
lines2.push("");
|
|
59447
|
-
lines2.push("Outgoing:");
|
|
59448
|
-
focused.outgoing.forEach((c) => lines2.push(` ${c.from} -> ${c.to}`));
|
|
59449
|
-
}
|
|
59450
|
-
return lines2.join("\n");
|
|
59451
|
-
}
|
|
59452
|
-
const desc = output;
|
|
59453
|
-
const lines = [];
|
|
59454
|
-
lines.push(`Workflow: ${desc.name}`);
|
|
59455
|
-
if (desc.description) {
|
|
59456
|
-
lines.push(` ${desc.description}`);
|
|
59457
|
-
}
|
|
59458
|
-
const nodeTypeMap = /* @__PURE__ */ new Map();
|
|
59459
|
-
ast.nodeTypes.forEach((nt) => nodeTypeMap.set(nt.functionName, nt));
|
|
59460
|
-
lines.push("");
|
|
59461
|
-
lines.push(`Nodes (${desc.nodes.length}):`);
|
|
59462
|
-
const maxIdLen = Math.max(...desc.nodes.map((n) => n.id.length), 2);
|
|
59463
|
-
const maxTypeLen = Math.max(...desc.nodes.map((n) => n.type.length), 2);
|
|
59464
|
-
desc.nodes.forEach((node) => {
|
|
59465
|
-
const id = node.id.padEnd(maxIdLen);
|
|
59466
|
-
const type2 = `[${node.type}]`.padEnd(maxTypeLen + 2);
|
|
59467
|
-
const nodeType = nodeTypeMap.get(node.type);
|
|
59468
|
-
const isExpression = nodeType?.expression === true;
|
|
59469
|
-
const scopeNames = nodeType?.scopes ?? (nodeType?.scope ? [nodeType.scope] : []);
|
|
59470
|
-
const filterPorts = (ports, direction) => {
|
|
59471
|
-
if (!isExpression || !nodeType) return ports;
|
|
59472
|
-
return ports.filter((portName) => {
|
|
59473
|
-
if (portName === "execute" || portName === "onSuccess" || portName === "onFailure") {
|
|
59474
|
-
return false;
|
|
59475
|
-
}
|
|
59476
|
-
const portDef = direction === "input" ? nodeType.inputs[portName] : nodeType.outputs[portName];
|
|
59477
|
-
return !(portDef?.isControlFlow || portDef?.dataType === "STEP");
|
|
59478
|
-
});
|
|
59479
|
-
};
|
|
59480
|
-
if (scopeNames.length > 0 && nodeType) {
|
|
59481
|
-
const regularInputs = [];
|
|
59482
|
-
const regularOutputs = [];
|
|
59483
|
-
const scopedPorts = {};
|
|
59484
|
-
scopeNames.forEach((s) => {
|
|
59485
|
-
scopedPorts[s] = { sends: [], receives: [], children: [] };
|
|
59486
|
-
});
|
|
59487
|
-
Object.entries(nodeType.inputs).forEach(([name, port]) => {
|
|
59488
|
-
if (port.scope && scopedPorts[port.scope]) {
|
|
59489
|
-
scopedPorts[port.scope].receives.push(name + (port.isControlFlow ? " (STEP)" : ""));
|
|
59490
|
-
} else {
|
|
59491
|
-
regularInputs.push(name);
|
|
59492
|
-
}
|
|
59493
|
-
});
|
|
59494
|
-
Object.entries(nodeType.outputs).forEach(([name, port]) => {
|
|
59495
|
-
if (port.scope && scopedPorts[port.scope]) {
|
|
59496
|
-
scopedPorts[port.scope].sends.push(name + (port.isControlFlow ? " (STEP)" : ""));
|
|
59497
|
-
} else {
|
|
59498
|
-
regularOutputs.push(name);
|
|
59499
|
-
}
|
|
59500
|
-
});
|
|
59501
|
-
if (ast.scopes) {
|
|
59502
|
-
for (const [scopeKey, childIds] of Object.entries(ast.scopes)) {
|
|
59503
|
-
const dotIdx = scopeKey.indexOf(".");
|
|
59504
|
-
const scopeName = dotIdx >= 0 ? scopeKey.substring(dotIdx + 1) : scopeKey;
|
|
59505
|
-
if (scopedPorts[scopeName]) {
|
|
59506
|
-
scopedPorts[scopeName].children.push(...childIds);
|
|
59507
|
-
}
|
|
59508
|
-
}
|
|
59509
|
-
}
|
|
59510
|
-
const ins = regularInputs.length > 0 ? `IN: ${regularInputs.join(", ")}` : "";
|
|
59511
|
-
const outs = regularOutputs.length > 0 ? `OUT: ${regularOutputs.join(", ")}` : "";
|
|
59512
|
-
const ports = [ins, outs].filter(Boolean).join(" ");
|
|
59513
|
-
lines.push(` ${id} ${type2} ${ports}`);
|
|
59514
|
-
for (const [scopeName, info] of Object.entries(scopedPorts)) {
|
|
59515
|
-
lines.push(` Scope "${scopeName}":`);
|
|
59516
|
-
if (info.sends.length > 0) {
|
|
59517
|
-
lines.push(` Sends to children: ${info.sends.join(", ")}`);
|
|
59518
|
-
}
|
|
59519
|
-
if (info.receives.length > 0) {
|
|
59520
|
-
lines.push(` Receives from children: ${info.receives.join(", ")}`);
|
|
59521
|
-
}
|
|
59522
|
-
if (info.children.length > 0) {
|
|
59523
|
-
lines.push(` Children: ${info.children.join(", ")}`);
|
|
59524
|
-
}
|
|
59525
|
-
}
|
|
59526
|
-
} else {
|
|
59527
|
-
const filteredInputs = filterPorts(node.inputs, "input");
|
|
59528
|
-
const filteredOutputs = filterPorts(node.outputs, "output");
|
|
59529
|
-
const ins = filteredInputs.length > 0 ? `IN: ${filteredInputs.join(", ")}` : "";
|
|
59530
|
-
const outs = filteredOutputs.length > 0 ? `OUT: ${filteredOutputs.join(", ")}` : "";
|
|
59531
|
-
const ports = [ins, outs].filter(Boolean).join(" ");
|
|
59532
|
-
lines.push(` ${id} ${type2} ${ports}`);
|
|
59533
|
-
}
|
|
59534
|
-
});
|
|
59535
|
-
if (desc.graph) {
|
|
59536
|
-
lines.push("");
|
|
59537
|
-
const paths = enumeratePaths(ast);
|
|
59538
|
-
if (paths.length > 1) {
|
|
59539
|
-
lines.push(`Paths (${paths.length}):`);
|
|
59540
|
-
paths.forEach((p, i) => {
|
|
59541
|
-
lines.push(` Path ${i + 1}: ${p.join(" -> ")}`);
|
|
59542
|
-
});
|
|
59543
|
-
} else {
|
|
59544
|
-
lines.push("Flow:");
|
|
59545
|
-
desc.graph.split("\n").forEach((flowLine) => {
|
|
59546
|
-
lines.push(` ${flowLine}`);
|
|
59547
|
-
});
|
|
59548
|
-
}
|
|
59549
|
-
}
|
|
59550
|
-
lines.push("");
|
|
59551
|
-
lines.push(
|
|
59552
|
-
`Validation: ${desc.validation.errors.length} errors, ${desc.validation.warnings.length} warnings`
|
|
59553
|
-
);
|
|
59554
|
-
return lines.join("\n");
|
|
59555
|
-
}
|
|
59556
|
-
function formatDescribeOutput(ast, output, format) {
|
|
59557
|
-
switch (format) {
|
|
59558
|
-
case "text":
|
|
59559
|
-
return formatTextOutput(ast, output);
|
|
59560
|
-
case "mermaid":
|
|
59561
|
-
return generateMermaid(ast);
|
|
59562
|
-
case "paths":
|
|
59563
|
-
return formatPaths(ast);
|
|
59564
|
-
case "json":
|
|
59565
|
-
default:
|
|
59566
|
-
return JSON.stringify(output, null, 2);
|
|
59567
|
-
}
|
|
59568
|
-
}
|
|
59569
|
-
async function describeCommand(input, options = {}) {
|
|
59570
|
-
const { format = "json", node: focusNodeId, workflowName, compile = false } = options;
|
|
59571
|
-
const filePath = path11.resolve(input);
|
|
59572
|
-
if (!fs11.existsSync(filePath)) {
|
|
59573
|
-
logger.error(`File not found: ${filePath}`);
|
|
59574
|
-
process.exit(1);
|
|
59575
|
-
}
|
|
59576
|
-
try {
|
|
59577
|
-
const parseResult = await parseWorkflow(filePath, { workflowName });
|
|
59578
|
-
if (parseResult.errors.length > 0) {
|
|
59579
|
-
logger.error(`Parse errors:`);
|
|
59580
|
-
parseResult.errors.forEach((err) => logger.error(` ${err}`));
|
|
59581
|
-
process.exit(1);
|
|
59582
|
-
}
|
|
59583
|
-
const ast = parseResult.ast;
|
|
59584
|
-
if (compile) {
|
|
59585
|
-
const sourceCode = fs11.readFileSync(filePath, "utf8");
|
|
59586
|
-
const generated = generateInPlace(sourceCode, ast, { production: false });
|
|
59587
|
-
if (generated.hasChanges) {
|
|
59588
|
-
fs11.writeFileSync(filePath, generated.code, "utf8");
|
|
59589
|
-
logger.info(`Updated runtime markers in ${path11.basename(filePath)}`);
|
|
59590
|
-
}
|
|
59591
|
-
}
|
|
59592
|
-
const output = describeWorkflow(ast, { node: focusNodeId });
|
|
59593
|
-
console.log(formatDescribeOutput(ast, output, format));
|
|
59594
|
-
} catch (error2) {
|
|
59595
|
-
if (error2 instanceof Error && error2.message.startsWith("Node not found:")) {
|
|
59596
|
-
logger.error(error2.message);
|
|
59597
|
-
process.exit(1);
|
|
59598
|
-
}
|
|
59599
|
-
logger.error(`Failed to describe workflow: ${getErrorMessage(error2)}`);
|
|
59600
|
-
process.exit(1);
|
|
59601
|
-
}
|
|
59602
|
-
}
|
|
59603
|
-
|
|
59604
|
-
// src/cli/commands/diagram.ts
|
|
59605
|
-
import * as fs12 from "fs";
|
|
59606
|
-
import * as path12 from "path";
|
|
59607
59312
|
|
|
59608
59313
|
// src/diagram/geometry.ts
|
|
59609
59314
|
init_constants();
|
|
@@ -61257,6 +60962,772 @@ function maxPortLabelExtent(ports) {
|
|
|
61257
60962
|
return max;
|
|
61258
60963
|
}
|
|
61259
60964
|
|
|
60965
|
+
// src/diagram/ascii-renderer.ts
|
|
60966
|
+
function groupByLayer(nodes) {
|
|
60967
|
+
if (nodes.length === 0) return [];
|
|
60968
|
+
const sorted = [...nodes].sort((a, b) => a.x - b.x);
|
|
60969
|
+
const layers = [];
|
|
60970
|
+
let currentX = sorted[0].x;
|
|
60971
|
+
let currentLayer = [];
|
|
60972
|
+
for (const node of sorted) {
|
|
60973
|
+
if (Math.abs(node.x - currentX) > 10) {
|
|
60974
|
+
layers.push(currentLayer);
|
|
60975
|
+
currentLayer = [];
|
|
60976
|
+
currentX = node.x;
|
|
60977
|
+
}
|
|
60978
|
+
currentLayer.push(node);
|
|
60979
|
+
}
|
|
60980
|
+
if (currentLayer.length > 0) layers.push(currentLayer);
|
|
60981
|
+
return layers;
|
|
60982
|
+
}
|
|
60983
|
+
function buildConnectedPorts(connections) {
|
|
60984
|
+
const s = /* @__PURE__ */ new Set();
|
|
60985
|
+
for (const c of connections) {
|
|
60986
|
+
s.add(`${c.fromNode}.${c.fromPort}`);
|
|
60987
|
+
s.add(`${c.toNode}.${c.toPort}`);
|
|
60988
|
+
}
|
|
60989
|
+
return s;
|
|
60990
|
+
}
|
|
60991
|
+
function portSymbol(nodeId, port, connected) {
|
|
60992
|
+
return connected.has(`${nodeId}.${port.name}`) ? "\u25CF" : "\u25CB";
|
|
60993
|
+
}
|
|
60994
|
+
function cornerChar(isStep, hDir, vDir) {
|
|
60995
|
+
if (hDir === "right" && vDir === "down") return isStep ? "\u2554" : "\u250C";
|
|
60996
|
+
if (hDir === "left" && vDir === "down") return isStep ? "\u2557" : "\u2510";
|
|
60997
|
+
if (hDir === "right" && vDir === "up") return isStep ? "\u255A" : "\u2514";
|
|
60998
|
+
if (hDir === "left" && vDir === "up") return isStep ? "\u255D" : "\u2518";
|
|
60999
|
+
return "\u253C";
|
|
61000
|
+
}
|
|
61001
|
+
var H_CHARS = new Set("\u2500\u2550");
|
|
61002
|
+
var V_CHARS = new Set("\u2502\u2551");
|
|
61003
|
+
var CharGrid = class {
|
|
61004
|
+
cells;
|
|
61005
|
+
width;
|
|
61006
|
+
height;
|
|
61007
|
+
constructor(w, h) {
|
|
61008
|
+
this.width = w;
|
|
61009
|
+
this.height = h;
|
|
61010
|
+
this.cells = new Array(w * h).fill(" ");
|
|
61011
|
+
}
|
|
61012
|
+
set(x, y, ch) {
|
|
61013
|
+
if (x < 0 || x >= this.width || y < 0 || y >= this.height) return;
|
|
61014
|
+
const existing = this.cells[y * this.width + x];
|
|
61015
|
+
if (existing !== " ") {
|
|
61016
|
+
if (H_CHARS.has(ch) && V_CHARS.has(existing) || V_CHARS.has(ch) && H_CHARS.has(existing)) {
|
|
61017
|
+
this.cells[y * this.width + x] = "\u253C";
|
|
61018
|
+
return;
|
|
61019
|
+
}
|
|
61020
|
+
if (H_CHARS.has(ch) && H_CHARS.has(existing) || V_CHARS.has(ch) && V_CHARS.has(existing)) {
|
|
61021
|
+
return;
|
|
61022
|
+
}
|
|
61023
|
+
}
|
|
61024
|
+
this.cells[y * this.width + x] = ch;
|
|
61025
|
+
}
|
|
61026
|
+
get(x, y) {
|
|
61027
|
+
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
|
|
61028
|
+
return this.cells[y * this.width + x];
|
|
61029
|
+
}
|
|
61030
|
+
return " ";
|
|
61031
|
+
}
|
|
61032
|
+
/** Force-write, ignoring collision logic (for boxes and text). */
|
|
61033
|
+
forceSet(x, y, ch) {
|
|
61034
|
+
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
|
|
61035
|
+
this.cells[y * this.width + x] = ch;
|
|
61036
|
+
}
|
|
61037
|
+
}
|
|
61038
|
+
writeStr(x, y, s) {
|
|
61039
|
+
for (let i = 0; i < s.length; i++) {
|
|
61040
|
+
this.forceSet(x + i, y, s[i]);
|
|
61041
|
+
}
|
|
61042
|
+
}
|
|
61043
|
+
toLines() {
|
|
61044
|
+
const lines = [];
|
|
61045
|
+
for (let y = 0; y < this.height; y++) {
|
|
61046
|
+
let line = "";
|
|
61047
|
+
for (let x = 0; x < this.width; x++) {
|
|
61048
|
+
line += this.cells[y * this.width + x];
|
|
61049
|
+
}
|
|
61050
|
+
lines.push(line.trimEnd());
|
|
61051
|
+
}
|
|
61052
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
61053
|
+
return lines;
|
|
61054
|
+
}
|
|
61055
|
+
};
|
|
61056
|
+
function mergePortRows(inputs, outputs) {
|
|
61057
|
+
const rows = [];
|
|
61058
|
+
const maxLen = Math.max(inputs.length, outputs.length);
|
|
61059
|
+
for (let i = 0; i < maxLen; i++) {
|
|
61060
|
+
rows.push({
|
|
61061
|
+
input: i < inputs.length ? inputs[i] : null,
|
|
61062
|
+
output: i < outputs.length ? outputs[i] : null
|
|
61063
|
+
});
|
|
61064
|
+
}
|
|
61065
|
+
return rows;
|
|
61066
|
+
}
|
|
61067
|
+
function measureBox(node) {
|
|
61068
|
+
const portRows = mergePortRows(node.inputs, node.outputs);
|
|
61069
|
+
const maxInputPortLen = node.inputs.length > 0 ? Math.max(...node.inputs.map((p) => p.name.length)) : 0;
|
|
61070
|
+
const maxOutputPortLen = node.outputs.length > 0 ? Math.max(...node.outputs.map((p) => p.name.length)) : 0;
|
|
61071
|
+
const portsContentWidth = (maxInputPortLen > 0 ? maxInputPortLen + 2 : 0) + 2 + (maxOutputPortLen > 0 ? maxOutputPortLen + 2 : 0);
|
|
61072
|
+
const innerWidth = Math.max(node.label.length + 2, portsContentWidth, 10);
|
|
61073
|
+
const boxWidth = innerWidth + 2;
|
|
61074
|
+
const headerRows = 3;
|
|
61075
|
+
const portRowCount = Math.max(portRows.length, 1);
|
|
61076
|
+
const boxHeight = headerRows + portRowCount + 1;
|
|
61077
|
+
const portRowOffsets = /* @__PURE__ */ new Map();
|
|
61078
|
+
for (let i = 0; i < portRows.length; i++) {
|
|
61079
|
+
if (portRows[i].input) portRowOffsets.set(portRows[i].input.name, headerRows + i);
|
|
61080
|
+
if (portRows[i].output) portRowOffsets.set(portRows[i].output.name, headerRows + i);
|
|
61081
|
+
}
|
|
61082
|
+
return { node, innerWidth, boxWidth, portRows, boxHeight, portRowOffsets };
|
|
61083
|
+
}
|
|
61084
|
+
function drawBox(grid, box, bx, by, connected) {
|
|
61085
|
+
const { innerWidth, portRows, node } = box;
|
|
61086
|
+
grid.forceSet(bx, by, "\u250C");
|
|
61087
|
+
for (let i = 0; i < innerWidth; i++) grid.forceSet(bx + 1 + i, by, "\u2500");
|
|
61088
|
+
grid.forceSet(bx + innerWidth + 1, by, "\u2510");
|
|
61089
|
+
const labelPad = innerWidth - node.label.length;
|
|
61090
|
+
const padL = Math.floor(labelPad / 2);
|
|
61091
|
+
grid.forceSet(bx, by + 1, "\u2502");
|
|
61092
|
+
grid.writeStr(bx + 1 + padL, by + 1, node.label);
|
|
61093
|
+
grid.forceSet(bx + innerWidth + 1, by + 1, "\u2502");
|
|
61094
|
+
grid.forceSet(bx, by + 2, "\u251C");
|
|
61095
|
+
for (let i = 0; i < innerWidth; i++) grid.forceSet(bx + 1 + i, by + 2, "\u2500");
|
|
61096
|
+
grid.forceSet(bx + innerWidth + 1, by + 2, "\u2524");
|
|
61097
|
+
for (let i = 0; i < Math.max(portRows.length, 1); i++) {
|
|
61098
|
+
const ry = by + 3 + i;
|
|
61099
|
+
grid.forceSet(bx, ry, "\u2502");
|
|
61100
|
+
grid.forceSet(bx + innerWidth + 1, ry, "\u2502");
|
|
61101
|
+
if (i < portRows.length) {
|
|
61102
|
+
const row = portRows[i];
|
|
61103
|
+
if (row.input) {
|
|
61104
|
+
grid.writeStr(bx + 1, ry, `${portSymbol(node.id, row.input, connected)} ${row.input.name}`);
|
|
61105
|
+
}
|
|
61106
|
+
if (row.output) {
|
|
61107
|
+
const txt = `${row.output.name} ${portSymbol(node.id, row.output, connected)}`;
|
|
61108
|
+
grid.writeStr(bx + innerWidth + 1 - txt.length, ry, txt);
|
|
61109
|
+
}
|
|
61110
|
+
}
|
|
61111
|
+
}
|
|
61112
|
+
const bottomY = by + box.boxHeight - 1;
|
|
61113
|
+
grid.forceSet(bx, bottomY, "\u2514");
|
|
61114
|
+
for (let i = 0; i < innerWidth; i++) grid.forceSet(bx + 1 + i, bottomY, "\u2500");
|
|
61115
|
+
grid.forceSet(bx + innerWidth + 1, bottomY, "\u2518");
|
|
61116
|
+
}
|
|
61117
|
+
function renderASCII(graph) {
|
|
61118
|
+
const layers = groupByLayer(graph.nodes);
|
|
61119
|
+
if (layers.length === 0) return `${graph.workflowName}
|
|
61120
|
+
(empty workflow)`;
|
|
61121
|
+
const layerBoxes = layers.map((layer) => layer.map(measureBox));
|
|
61122
|
+
const colWidths = layerBoxes.map((boxes) => Math.max(...boxes.map((b) => b.boxWidth)));
|
|
61123
|
+
const wireGap = 14;
|
|
61124
|
+
const colX = [];
|
|
61125
|
+
let cx = 1;
|
|
61126
|
+
for (let c = 0; c < layerBoxes.length; c++) {
|
|
61127
|
+
colX.push(cx);
|
|
61128
|
+
cx += colWidths[c] + wireGap;
|
|
61129
|
+
}
|
|
61130
|
+
const gridWidth = cx + 1;
|
|
61131
|
+
const nodeColMap = /* @__PURE__ */ new Map();
|
|
61132
|
+
for (let c = 0; c < layerBoxes.length; c++) {
|
|
61133
|
+
for (const box of layerBoxes[c]) nodeColMap.set(box.node.id, c);
|
|
61134
|
+
}
|
|
61135
|
+
let spanCount = 0;
|
|
61136
|
+
for (const conn of graph.connections) {
|
|
61137
|
+
const fc = nodeColMap.get(conn.fromNode);
|
|
61138
|
+
const tc = nodeColMap.get(conn.toNode);
|
|
61139
|
+
if (fc !== void 0 && tc !== void 0 && Math.abs(tc - fc) > 1) spanCount++;
|
|
61140
|
+
}
|
|
61141
|
+
const highwayMargin = spanCount > 0 ? spanCount + 1 : 0;
|
|
61142
|
+
const nodeGapY = 2;
|
|
61143
|
+
const boxPositions = /* @__PURE__ */ new Map();
|
|
61144
|
+
const boxStartY = 3 + highwayMargin;
|
|
61145
|
+
let maxGridHeight = 0;
|
|
61146
|
+
for (let c = 0; c < layerBoxes.length; c++) {
|
|
61147
|
+
let y = boxStartY;
|
|
61148
|
+
for (const box of layerBoxes[c]) {
|
|
61149
|
+
boxPositions.set(box.node.id, { col: c, bx: colX[c], by: y, box });
|
|
61150
|
+
y += box.boxHeight + nodeGapY;
|
|
61151
|
+
}
|
|
61152
|
+
if (y > maxGridHeight) maxGridHeight = y;
|
|
61153
|
+
}
|
|
61154
|
+
maxGridHeight += highwayMargin + 3;
|
|
61155
|
+
const connected = buildConnectedPorts(graph.connections);
|
|
61156
|
+
const grid = new CharGrid(gridWidth, maxGridHeight);
|
|
61157
|
+
const titleX = Math.max(0, Math.floor((gridWidth - graph.workflowName.length) / 2));
|
|
61158
|
+
grid.writeStr(titleX, 1, graph.workflowName);
|
|
61159
|
+
for (const pos of boxPositions.values()) {
|
|
61160
|
+
drawBox(grid, pos.box, pos.bx, pos.by, connected);
|
|
61161
|
+
}
|
|
61162
|
+
drawConnections(grid, graph.connections, boxPositions, boxStartY, maxGridHeight);
|
|
61163
|
+
const scopeLines = [];
|
|
61164
|
+
for (const node of graph.nodes) {
|
|
61165
|
+
if (node.scopeChildren && node.scopeChildren.length > 0) {
|
|
61166
|
+
scopeLines.push("");
|
|
61167
|
+
scopeLines.push(` Scope [${node.label}]:`);
|
|
61168
|
+
if (node.scopeConnections) {
|
|
61169
|
+
for (const sc of node.scopeConnections) {
|
|
61170
|
+
const arrow = sc.isStepConnection ? "\u2550\u2550\u25B6" : "\u2500\u2500\u25B6";
|
|
61171
|
+
scopeLines.push(` ${sc.fromNode}.${sc.fromPort} ${arrow} ${sc.toNode}.${sc.toPort}${sc.isStepConnection ? " STEP" : ""}`);
|
|
61172
|
+
}
|
|
61173
|
+
}
|
|
61174
|
+
}
|
|
61175
|
+
}
|
|
61176
|
+
const lines = grid.toLines();
|
|
61177
|
+
lines.push(...scopeLines);
|
|
61178
|
+
lines.push("");
|
|
61179
|
+
lines.push(" \u25CF connected \u25CB not connected \u2550\u2550\u25B6 STEP \u2500\u2500\u25B6 DATA");
|
|
61180
|
+
return lines.join("\n");
|
|
61181
|
+
}
|
|
61182
|
+
function drawConnections(grid, connections, positions, boxAreaTop, _gridHeight) {
|
|
61183
|
+
let globalMinY = Infinity;
|
|
61184
|
+
let globalMaxY = 0;
|
|
61185
|
+
for (const pos of positions.values()) {
|
|
61186
|
+
globalMinY = Math.min(globalMinY, pos.by);
|
|
61187
|
+
globalMaxY = Math.max(globalMaxY, pos.by + pos.box.boxHeight);
|
|
61188
|
+
}
|
|
61189
|
+
const sorted = [...connections].sort((a, b) => {
|
|
61190
|
+
const fa = positions.get(a.fromNode);
|
|
61191
|
+
const ta = positions.get(a.toNode);
|
|
61192
|
+
const fb = positions.get(b.fromNode);
|
|
61193
|
+
const tb = positions.get(b.toNode);
|
|
61194
|
+
if (!fa || !ta || !fb || !tb) return 0;
|
|
61195
|
+
const spanA = Math.abs(ta.col - fa.col);
|
|
61196
|
+
const spanB = Math.abs(tb.col - fb.col);
|
|
61197
|
+
if (spanA !== spanB) return spanA - spanB;
|
|
61198
|
+
const ya = fa.by + (fa.box.portRowOffsets.get(a.fromPort) ?? 0);
|
|
61199
|
+
const yb = fb.by + (fb.box.portRowOffsets.get(b.fromPort) ?? 0);
|
|
61200
|
+
return ya - yb;
|
|
61201
|
+
});
|
|
61202
|
+
const usedTracks = /* @__PURE__ */ new Map();
|
|
61203
|
+
let nextAboveHighway = globalMinY - 2;
|
|
61204
|
+
let nextBelowHighway = globalMaxY + 1;
|
|
61205
|
+
for (const conn of sorted) {
|
|
61206
|
+
const fromPos = positions.get(conn.fromNode);
|
|
61207
|
+
const toPos = positions.get(conn.toNode);
|
|
61208
|
+
if (!fromPos || !toPos) continue;
|
|
61209
|
+
const fromRowOff = fromPos.box.portRowOffsets.get(conn.fromPort);
|
|
61210
|
+
const toRowOff = toPos.box.portRowOffsets.get(conn.toPort);
|
|
61211
|
+
if (fromRowOff === void 0 || toRowOff === void 0) continue;
|
|
61212
|
+
const y1 = fromPos.by + fromRowOff;
|
|
61213
|
+
const y2 = toPos.by + toRowOff;
|
|
61214
|
+
const x1 = fromPos.bx + fromPos.box.boxWidth;
|
|
61215
|
+
const x2 = toPos.bx - 1;
|
|
61216
|
+
const isStep = conn.isStepConnection;
|
|
61217
|
+
const hCh = isStep ? "\u2550" : "\u2500";
|
|
61218
|
+
const vCh = isStep ? "\u2551" : "\u2502";
|
|
61219
|
+
const isAdjacent = toPos.col === fromPos.col + 1;
|
|
61220
|
+
if (isAdjacent) {
|
|
61221
|
+
drawAdjacentRoute(grid, x1, y1, x2, y2, hCh, vCh, isStep, usedTracks, fromPos, toPos);
|
|
61222
|
+
} else {
|
|
61223
|
+
const avgY = (y1 + y2) / 2;
|
|
61224
|
+
const midBoxY = (globalMinY + globalMaxY) / 2;
|
|
61225
|
+
let highwayY;
|
|
61226
|
+
if (avgY <= midBoxY) {
|
|
61227
|
+
highwayY = nextAboveHighway;
|
|
61228
|
+
nextAboveHighway--;
|
|
61229
|
+
} else {
|
|
61230
|
+
highwayY = nextBelowHighway;
|
|
61231
|
+
nextBelowHighway++;
|
|
61232
|
+
}
|
|
61233
|
+
drawSpanningRoute(grid, x1, y1, x2, y2, highwayY, hCh, vCh, isStep, usedTracks, fromPos, toPos);
|
|
61234
|
+
}
|
|
61235
|
+
}
|
|
61236
|
+
}
|
|
61237
|
+
function drawAdjacentRoute(grid, x1, y1, x2, y2, hCh, vCh, isStep, usedTracks, fromPos, toPos) {
|
|
61238
|
+
if (y1 === y2) {
|
|
61239
|
+
for (let x = x1; x < x2; x++) grid.set(x, y1, hCh);
|
|
61240
|
+
grid.set(x2, y1, "\u25B6");
|
|
61241
|
+
return;
|
|
61242
|
+
}
|
|
61243
|
+
const gapStart = fromPos.bx + fromPos.box.boxWidth + 1;
|
|
61244
|
+
const gapEnd = toPos.bx - 2;
|
|
61245
|
+
const midX = findTrack(usedTracks, gapStart, gapEnd, y1, y2);
|
|
61246
|
+
markTrack(usedTracks, midX, y1, y2);
|
|
61247
|
+
for (let x = x1; x < midX; x++) grid.set(x, y1, hCh);
|
|
61248
|
+
grid.set(midX, y1, cornerChar(isStep, "left", y2 > y1 ? "down" : "up"));
|
|
61249
|
+
drawVerticalSegment(grid, midX, y1, y2, vCh);
|
|
61250
|
+
grid.set(midX, y2, cornerChar(isStep, "right", y2 > y1 ? "up" : "down"));
|
|
61251
|
+
for (let x = midX + 1; x < x2; x++) grid.set(x, y2, hCh);
|
|
61252
|
+
grid.set(x2, y2, "\u25B6");
|
|
61253
|
+
}
|
|
61254
|
+
function drawSpanningRoute(grid, x1, y1, x2, y2, highwayY, hCh, vCh, isStep, usedTracks, fromPos, toPos) {
|
|
61255
|
+
const srcGapStart = fromPos.bx + fromPos.box.boxWidth + 1;
|
|
61256
|
+
const srcGapEnd = srcGapStart + 10;
|
|
61257
|
+
const srcMidX = findTrack(usedTracks, srcGapStart, srcGapEnd, y1, highwayY);
|
|
61258
|
+
markTrack(usedTracks, srcMidX, y1, highwayY);
|
|
61259
|
+
const tgtGapEnd = toPos.bx - 2;
|
|
61260
|
+
const tgtGapStart = tgtGapEnd - 10;
|
|
61261
|
+
const tgtMidX = findTrack(usedTracks, tgtGapStart, tgtGapEnd, highwayY, y2);
|
|
61262
|
+
markTrack(usedTracks, tgtMidX, highwayY, y2);
|
|
61263
|
+
const goingDown = highwayY > y1;
|
|
61264
|
+
const goingUp = y2 < highwayY;
|
|
61265
|
+
for (let x = x1; x < srcMidX; x++) grid.set(x, y1, hCh);
|
|
61266
|
+
grid.set(srcMidX, y1, cornerChar(isStep, "left", goingDown ? "down" : "up"));
|
|
61267
|
+
drawVerticalSegment(grid, srcMidX, y1, highwayY, vCh);
|
|
61268
|
+
grid.set(srcMidX, highwayY, cornerChar(isStep, "right", goingDown ? "up" : "down"));
|
|
61269
|
+
for (let x = srcMidX + 1; x < tgtMidX; x++) grid.set(x, highwayY, hCh);
|
|
61270
|
+
grid.set(tgtMidX, highwayY, cornerChar(isStep, "left", goingUp ? "up" : "down"));
|
|
61271
|
+
drawVerticalSegment(grid, tgtMidX, highwayY, y2, vCh);
|
|
61272
|
+
grid.set(tgtMidX, y2, cornerChar(isStep, "right", goingUp ? "down" : "up"));
|
|
61273
|
+
for (let x = tgtMidX + 1; x < x2; x++) grid.set(x, y2, hCh);
|
|
61274
|
+
grid.set(x2, y2, "\u25B6");
|
|
61275
|
+
}
|
|
61276
|
+
function drawVerticalSegment(grid, x, y1, y2, vCh) {
|
|
61277
|
+
const minY = Math.min(y1, y2);
|
|
61278
|
+
const maxY = Math.max(y1, y2);
|
|
61279
|
+
for (let y = minY + 1; y < maxY; y++) {
|
|
61280
|
+
grid.set(x, y, vCh);
|
|
61281
|
+
}
|
|
61282
|
+
}
|
|
61283
|
+
function findTrack(usedTracks, gapStart, gapEnd, y1, y2) {
|
|
61284
|
+
const minY = Math.min(y1, y2);
|
|
61285
|
+
const maxY = Math.max(y1, y2);
|
|
61286
|
+
const actualStart = Math.min(gapStart, gapEnd);
|
|
61287
|
+
const actualEnd = Math.max(gapStart, gapEnd);
|
|
61288
|
+
const mid = Math.floor((actualStart + actualEnd) / 2);
|
|
61289
|
+
for (let offset = 0; offset <= actualEnd - actualStart; offset++) {
|
|
61290
|
+
for (const candidate of [mid + offset, mid - offset]) {
|
|
61291
|
+
if (candidate < actualStart || candidate > actualEnd) continue;
|
|
61292
|
+
const existing = usedTracks.get(candidate);
|
|
61293
|
+
if (!existing) return candidate;
|
|
61294
|
+
let conflict = false;
|
|
61295
|
+
for (let y = minY; y <= maxY; y++) {
|
|
61296
|
+
if (existing.has(y)) {
|
|
61297
|
+
conflict = true;
|
|
61298
|
+
break;
|
|
61299
|
+
}
|
|
61300
|
+
}
|
|
61301
|
+
if (!conflict) return candidate;
|
|
61302
|
+
}
|
|
61303
|
+
}
|
|
61304
|
+
return mid;
|
|
61305
|
+
}
|
|
61306
|
+
function markTrack(usedTracks, x, y1, y2) {
|
|
61307
|
+
if (!usedTracks.has(x)) usedTracks.set(x, /* @__PURE__ */ new Set());
|
|
61308
|
+
const s = usedTracks.get(x);
|
|
61309
|
+
const minY = Math.min(y1, y2);
|
|
61310
|
+
const maxY = Math.max(y1, y2);
|
|
61311
|
+
for (let y = minY; y <= maxY; y++) s.add(y);
|
|
61312
|
+
}
|
|
61313
|
+
function renderASCIICompact(graph) {
|
|
61314
|
+
const layers = groupByLayer(graph.nodes);
|
|
61315
|
+
if (layers.length === 0) return `${graph.workflowName}
|
|
61316
|
+
(empty workflow)`;
|
|
61317
|
+
const outputLines = [];
|
|
61318
|
+
outputLines.push(graph.workflowName);
|
|
61319
|
+
outputLines.push("");
|
|
61320
|
+
const mainChain = [];
|
|
61321
|
+
const parallelNodes = [];
|
|
61322
|
+
for (const layer of layers) {
|
|
61323
|
+
mainChain.push(layer[0]);
|
|
61324
|
+
for (let i = 1; i < layer.length; i++) parallelNodes.push(layer[i]);
|
|
61325
|
+
}
|
|
61326
|
+
const topParts = [];
|
|
61327
|
+
const midParts = [];
|
|
61328
|
+
const botParts = [];
|
|
61329
|
+
for (let i = 0; i < mainChain.length; i++) {
|
|
61330
|
+
const node = mainChain[i];
|
|
61331
|
+
const innerWidth = Math.max(node.label.length + 2, 5);
|
|
61332
|
+
const padLeft = Math.floor((innerWidth - node.label.length) / 2);
|
|
61333
|
+
const padRight = innerWidth - node.label.length - padLeft;
|
|
61334
|
+
topParts.push("\u250C" + "\u2500".repeat(innerWidth) + "\u2510");
|
|
61335
|
+
midParts.push("\u2502" + " ".repeat(padLeft) + node.label + " ".repeat(padRight) + "\u2502");
|
|
61336
|
+
botParts.push("\u2514" + "\u2500".repeat(innerWidth) + "\u2518");
|
|
61337
|
+
if (i < mainChain.length - 1) {
|
|
61338
|
+
topParts.push(" ");
|
|
61339
|
+
midParts.push("\u2501\u2501\u2501\u25B6");
|
|
61340
|
+
botParts.push(" ");
|
|
61341
|
+
}
|
|
61342
|
+
}
|
|
61343
|
+
outputLines.push(" " + topParts.join(""));
|
|
61344
|
+
outputLines.push(" " + midParts.join(""));
|
|
61345
|
+
outputLines.push(" " + botParts.join(""));
|
|
61346
|
+
if (parallelNodes.length > 0) {
|
|
61347
|
+
outputLines.push("");
|
|
61348
|
+
outputLines.push(" Parallel: " + parallelNodes.map((n) => n.label).join(", "));
|
|
61349
|
+
}
|
|
61350
|
+
for (const node of graph.nodes) {
|
|
61351
|
+
if (node.scopeChildren && node.scopeChildren.length > 0) {
|
|
61352
|
+
outputLines.push("");
|
|
61353
|
+
outputLines.push(` Scope [${node.label}]: ` + node.scopeChildren.map((c) => c.label).join(" \u2501\u25B6 "));
|
|
61354
|
+
}
|
|
61355
|
+
}
|
|
61356
|
+
return outputLines.join("\n");
|
|
61357
|
+
}
|
|
61358
|
+
function renderText(graph) {
|
|
61359
|
+
const lines = [];
|
|
61360
|
+
const connected = buildConnectedPorts(graph.connections);
|
|
61361
|
+
lines.push(graph.workflowName);
|
|
61362
|
+
lines.push("\u2550".repeat(graph.workflowName.length));
|
|
61363
|
+
lines.push("");
|
|
61364
|
+
lines.push("Nodes:");
|
|
61365
|
+
const maxLabelLen = Math.max(...graph.nodes.map((n) => n.label.length), 5);
|
|
61366
|
+
for (const node of graph.nodes) {
|
|
61367
|
+
const label = node.label.padEnd(maxLabelLen);
|
|
61368
|
+
const inputPorts = node.inputs.map((p) => `${p.name}${portSymbol(node.id, p, connected)}`);
|
|
61369
|
+
const outputPorts = node.outputs.map((p) => `${p.name}${portSymbol(node.id, p, connected)}`);
|
|
61370
|
+
const inputStr = inputPorts.length > 0 ? `[${inputPorts.join(", ")}]` : "";
|
|
61371
|
+
const outputStr = outputPorts.length > 0 ? `[${outputPorts.join(", ")}]` : "";
|
|
61372
|
+
if (inputStr && outputStr) {
|
|
61373
|
+
lines.push(` ${label} ${inputStr} \u2192 ${outputStr}`);
|
|
61374
|
+
} else if (outputStr) {
|
|
61375
|
+
lines.push(` ${label} ${outputStr}`);
|
|
61376
|
+
} else if (inputStr) {
|
|
61377
|
+
lines.push(` ${label} ${inputStr}`);
|
|
61378
|
+
} else {
|
|
61379
|
+
lines.push(` ${label}`);
|
|
61380
|
+
}
|
|
61381
|
+
if (node.scopeChildren && node.scopeChildren.length > 0) {
|
|
61382
|
+
lines.push(` ${" ".repeat(maxLabelLen)} scope: ${node.scopeChildren.map((c) => c.label).join(", ")}`);
|
|
61383
|
+
}
|
|
61384
|
+
}
|
|
61385
|
+
if (graph.connections.length > 0) {
|
|
61386
|
+
lines.push("");
|
|
61387
|
+
lines.push("Connections:");
|
|
61388
|
+
const maxFromLen = Math.max(...graph.connections.map((c) => `${c.fromNode}.${c.fromPort}`.length));
|
|
61389
|
+
for (const conn of graph.connections) {
|
|
61390
|
+
const from = `${conn.fromNode}.${conn.fromPort}`.padEnd(maxFromLen);
|
|
61391
|
+
const arrow = conn.isStepConnection ? "\u2501\u2501\u25B6" : "\u2500\u2500\u25B6";
|
|
61392
|
+
const to = `${conn.toNode}.${conn.toPort}`;
|
|
61393
|
+
const type2 = conn.isStepConnection ? "STEP" : "";
|
|
61394
|
+
lines.push(` ${from} ${arrow} ${to}${type2 ? " " + type2 : ""}`);
|
|
61395
|
+
}
|
|
61396
|
+
}
|
|
61397
|
+
return lines.join("\n");
|
|
61398
|
+
}
|
|
61399
|
+
|
|
61400
|
+
// src/cli/commands/describe.ts
|
|
61401
|
+
function buildNodeInfo(instance, nodeType) {
|
|
61402
|
+
return {
|
|
61403
|
+
id: instance.id,
|
|
61404
|
+
type: instance.nodeType,
|
|
61405
|
+
inputs: nodeType ? Object.keys(nodeType.inputs) : [],
|
|
61406
|
+
outputs: nodeType ? Object.keys(nodeType.outputs) : []
|
|
61407
|
+
};
|
|
61408
|
+
}
|
|
61409
|
+
function buildAdjacency(ast) {
|
|
61410
|
+
const fromStart = [];
|
|
61411
|
+
const toExit = /* @__PURE__ */ new Set();
|
|
61412
|
+
const edges = /* @__PURE__ */ new Map();
|
|
61413
|
+
ast.connections.forEach((conn) => {
|
|
61414
|
+
if (conn.from.node === "Start") {
|
|
61415
|
+
if (!fromStart.includes(conn.to.node)) {
|
|
61416
|
+
fromStart.push(conn.to.node);
|
|
61417
|
+
}
|
|
61418
|
+
} else if (conn.to.node === "Exit") {
|
|
61419
|
+
toExit.add(conn.from.node);
|
|
61420
|
+
} else {
|
|
61421
|
+
const targets = edges.get(conn.from.node) || [];
|
|
61422
|
+
if (!targets.includes(conn.to.node)) {
|
|
61423
|
+
targets.push(conn.to.node);
|
|
61424
|
+
}
|
|
61425
|
+
edges.set(conn.from.node, targets);
|
|
61426
|
+
}
|
|
61427
|
+
});
|
|
61428
|
+
return { fromStart, toExit, edges };
|
|
61429
|
+
}
|
|
61430
|
+
function enumeratePaths(ast) {
|
|
61431
|
+
const { fromStart, toExit, edges } = buildAdjacency(ast);
|
|
61432
|
+
const paths = [];
|
|
61433
|
+
function dfs(current2, path42, visited) {
|
|
61434
|
+
if (toExit.has(current2)) {
|
|
61435
|
+
paths.push([...path42, "Exit"]);
|
|
61436
|
+
}
|
|
61437
|
+
const targets = edges.get(current2) || [];
|
|
61438
|
+
for (const next of targets) {
|
|
61439
|
+
if (!visited.has(next)) {
|
|
61440
|
+
visited.add(next);
|
|
61441
|
+
path42.push(next);
|
|
61442
|
+
dfs(next, path42, visited);
|
|
61443
|
+
path42.pop();
|
|
61444
|
+
visited.delete(next);
|
|
61445
|
+
}
|
|
61446
|
+
}
|
|
61447
|
+
}
|
|
61448
|
+
fromStart.forEach((startNode) => {
|
|
61449
|
+
const visited = /* @__PURE__ */ new Set([startNode]);
|
|
61450
|
+
dfs(startNode, ["Start", startNode], visited);
|
|
61451
|
+
});
|
|
61452
|
+
return paths;
|
|
61453
|
+
}
|
|
61454
|
+
function buildGraph(ast) {
|
|
61455
|
+
const { fromStart, toExit, edges } = buildAdjacency(ast);
|
|
61456
|
+
const lines = [];
|
|
61457
|
+
function dfs(current2, path42, visited) {
|
|
61458
|
+
if (toExit.has(current2)) {
|
|
61459
|
+
lines.push([...path42, "Exit"].join(" -> "));
|
|
61460
|
+
}
|
|
61461
|
+
const targets = edges.get(current2) || [];
|
|
61462
|
+
for (const next of targets) {
|
|
61463
|
+
if (!visited.has(next)) {
|
|
61464
|
+
visited.add(next);
|
|
61465
|
+
path42.push(next);
|
|
61466
|
+
dfs(next, path42, visited);
|
|
61467
|
+
path42.pop();
|
|
61468
|
+
visited.delete(next);
|
|
61469
|
+
}
|
|
61470
|
+
}
|
|
61471
|
+
if (targets.length === 0 && !toExit.has(current2)) {
|
|
61472
|
+
lines.push(path42.join(" -> "));
|
|
61473
|
+
}
|
|
61474
|
+
}
|
|
61475
|
+
fromStart.forEach((startNode) => {
|
|
61476
|
+
const visited = /* @__PURE__ */ new Set([startNode]);
|
|
61477
|
+
dfs(startNode, ["Start", startNode], visited);
|
|
61478
|
+
});
|
|
61479
|
+
return lines.join("\n");
|
|
61480
|
+
}
|
|
61481
|
+
function formatPaths(ast) {
|
|
61482
|
+
const paths = enumeratePaths(ast);
|
|
61483
|
+
if (paths.length === 0) return "(no complete Start-to-Exit paths found)";
|
|
61484
|
+
return paths.map((p) => p.join(" -> ")).join("\n");
|
|
61485
|
+
}
|
|
61486
|
+
function generateMermaid(ast) {
|
|
61487
|
+
const lines = ["graph LR"];
|
|
61488
|
+
ast.instances.forEach((instance) => {
|
|
61489
|
+
lines.push(` ${instance.id}[${instance.id}: ${instance.nodeType}]`);
|
|
61490
|
+
});
|
|
61491
|
+
const seenEdges = /* @__PURE__ */ new Set();
|
|
61492
|
+
ast.connections.forEach((conn) => {
|
|
61493
|
+
const edgeKey = `${conn.from.node}->${conn.to.node}`;
|
|
61494
|
+
if (!seenEdges.has(edgeKey)) {
|
|
61495
|
+
seenEdges.add(edgeKey);
|
|
61496
|
+
const from = conn.from.node === "Start" ? "Start((Start))" : conn.from.node;
|
|
61497
|
+
const to = conn.to.node === "Exit" ? "Exit((Exit))" : conn.to.node;
|
|
61498
|
+
lines.push(` ${from} --> ${to}`);
|
|
61499
|
+
}
|
|
61500
|
+
});
|
|
61501
|
+
return lines.join("\n");
|
|
61502
|
+
}
|
|
61503
|
+
function describeWorkflow(ast, options = {}) {
|
|
61504
|
+
const { node: focusNodeId } = options;
|
|
61505
|
+
const validation = validator.validate(ast);
|
|
61506
|
+
const validationOutput = {
|
|
61507
|
+
valid: validation.valid,
|
|
61508
|
+
errors: validation.errors.map((e) => e.message),
|
|
61509
|
+
warnings: validation.warnings.map((w) => w.message)
|
|
61510
|
+
};
|
|
61511
|
+
const nodeTypeMap = /* @__PURE__ */ new Map();
|
|
61512
|
+
ast.nodeTypes.forEach((nt) => nodeTypeMap.set(nt.functionName, nt));
|
|
61513
|
+
if (focusNodeId) {
|
|
61514
|
+
const nodeInstance = getNode(ast, focusNodeId);
|
|
61515
|
+
if (!nodeInstance) {
|
|
61516
|
+
throw new Error(`Node not found: ${focusNodeId}`);
|
|
61517
|
+
}
|
|
61518
|
+
const nodeType = nodeTypeMap.get(nodeInstance.nodeType);
|
|
61519
|
+
const incoming = getIncomingConnections(ast, focusNodeId).map((c) => ({
|
|
61520
|
+
from: `${c.from.node}.${c.from.port}`,
|
|
61521
|
+
to: `${c.to.node}.${c.to.port}`
|
|
61522
|
+
}));
|
|
61523
|
+
const outgoing = getOutgoingConnections(ast, focusNodeId).map((c) => ({
|
|
61524
|
+
from: `${c.from.node}.${c.from.port}`,
|
|
61525
|
+
to: `${c.to.node}.${c.to.port}`
|
|
61526
|
+
}));
|
|
61527
|
+
return {
|
|
61528
|
+
focusNode: focusNodeId,
|
|
61529
|
+
node: buildNodeInfo(nodeInstance, nodeType),
|
|
61530
|
+
incoming,
|
|
61531
|
+
outgoing,
|
|
61532
|
+
validation: validationOutput
|
|
61533
|
+
};
|
|
61534
|
+
}
|
|
61535
|
+
const nodes = ast.instances.map((instance) => {
|
|
61536
|
+
const nodeType = nodeTypeMap.get(instance.nodeType);
|
|
61537
|
+
return buildNodeInfo(instance, nodeType);
|
|
61538
|
+
});
|
|
61539
|
+
const connections = ast.connections.map((c) => ({
|
|
61540
|
+
from: `${c.from.node}.${c.from.port}`,
|
|
61541
|
+
to: `${c.to.node}.${c.to.port}`
|
|
61542
|
+
}));
|
|
61543
|
+
return {
|
|
61544
|
+
name: ast.functionName,
|
|
61545
|
+
description: ast.description || null,
|
|
61546
|
+
nodes,
|
|
61547
|
+
connections,
|
|
61548
|
+
graph: buildGraph(ast),
|
|
61549
|
+
validation: validationOutput
|
|
61550
|
+
};
|
|
61551
|
+
}
|
|
61552
|
+
function formatTextOutput(ast, output) {
|
|
61553
|
+
if ("focusNode" in output) {
|
|
61554
|
+
const focused = output;
|
|
61555
|
+
const lines2 = [];
|
|
61556
|
+
lines2.push(`Node: ${focused.node.id} [${focused.node.type}]`);
|
|
61557
|
+
lines2.push(` Inputs: ${focused.node.inputs.join(", ") || "(none)"}`);
|
|
61558
|
+
lines2.push(` Outputs: ${focused.node.outputs.join(", ") || "(none)"}`);
|
|
61559
|
+
if (focused.incoming.length > 0) {
|
|
61560
|
+
lines2.push("");
|
|
61561
|
+
lines2.push("Incoming:");
|
|
61562
|
+
focused.incoming.forEach((c) => lines2.push(` ${c.from} -> ${c.to}`));
|
|
61563
|
+
}
|
|
61564
|
+
if (focused.outgoing.length > 0) {
|
|
61565
|
+
lines2.push("");
|
|
61566
|
+
lines2.push("Outgoing:");
|
|
61567
|
+
focused.outgoing.forEach((c) => lines2.push(` ${c.from} -> ${c.to}`));
|
|
61568
|
+
}
|
|
61569
|
+
return lines2.join("\n");
|
|
61570
|
+
}
|
|
61571
|
+
const desc = output;
|
|
61572
|
+
const lines = [];
|
|
61573
|
+
lines.push(`Workflow: ${desc.name}`);
|
|
61574
|
+
if (desc.description) {
|
|
61575
|
+
lines.push(` ${desc.description}`);
|
|
61576
|
+
}
|
|
61577
|
+
const nodeTypeMap = /* @__PURE__ */ new Map();
|
|
61578
|
+
ast.nodeTypes.forEach((nt) => nodeTypeMap.set(nt.functionName, nt));
|
|
61579
|
+
lines.push("");
|
|
61580
|
+
lines.push(`Nodes (${desc.nodes.length}):`);
|
|
61581
|
+
const maxIdLen = Math.max(...desc.nodes.map((n) => n.id.length), 2);
|
|
61582
|
+
const maxTypeLen = Math.max(...desc.nodes.map((n) => n.type.length), 2);
|
|
61583
|
+
desc.nodes.forEach((node) => {
|
|
61584
|
+
const id = node.id.padEnd(maxIdLen);
|
|
61585
|
+
const type2 = `[${node.type}]`.padEnd(maxTypeLen + 2);
|
|
61586
|
+
const nodeType = nodeTypeMap.get(node.type);
|
|
61587
|
+
const isExpression = nodeType?.expression === true;
|
|
61588
|
+
const scopeNames = nodeType?.scopes ?? (nodeType?.scope ? [nodeType.scope] : []);
|
|
61589
|
+
const filterPorts = (ports, direction) => {
|
|
61590
|
+
if (!isExpression || !nodeType) return ports;
|
|
61591
|
+
return ports.filter((portName) => {
|
|
61592
|
+
if (portName === "execute" || portName === "onSuccess" || portName === "onFailure") {
|
|
61593
|
+
return false;
|
|
61594
|
+
}
|
|
61595
|
+
const portDef = direction === "input" ? nodeType.inputs[portName] : nodeType.outputs[portName];
|
|
61596
|
+
return !(portDef?.isControlFlow || portDef?.dataType === "STEP");
|
|
61597
|
+
});
|
|
61598
|
+
};
|
|
61599
|
+
if (scopeNames.length > 0 && nodeType) {
|
|
61600
|
+
const regularInputs = [];
|
|
61601
|
+
const regularOutputs = [];
|
|
61602
|
+
const scopedPorts = {};
|
|
61603
|
+
scopeNames.forEach((s) => {
|
|
61604
|
+
scopedPorts[s] = { sends: [], receives: [], children: [] };
|
|
61605
|
+
});
|
|
61606
|
+
Object.entries(nodeType.inputs).forEach(([name, port]) => {
|
|
61607
|
+
if (port.scope && scopedPorts[port.scope]) {
|
|
61608
|
+
scopedPorts[port.scope].receives.push(name + (port.isControlFlow ? " (STEP)" : ""));
|
|
61609
|
+
} else {
|
|
61610
|
+
regularInputs.push(name);
|
|
61611
|
+
}
|
|
61612
|
+
});
|
|
61613
|
+
Object.entries(nodeType.outputs).forEach(([name, port]) => {
|
|
61614
|
+
if (port.scope && scopedPorts[port.scope]) {
|
|
61615
|
+
scopedPorts[port.scope].sends.push(name + (port.isControlFlow ? " (STEP)" : ""));
|
|
61616
|
+
} else {
|
|
61617
|
+
regularOutputs.push(name);
|
|
61618
|
+
}
|
|
61619
|
+
});
|
|
61620
|
+
if (ast.scopes) {
|
|
61621
|
+
for (const [scopeKey, childIds] of Object.entries(ast.scopes)) {
|
|
61622
|
+
const dotIdx = scopeKey.indexOf(".");
|
|
61623
|
+
const scopeName = dotIdx >= 0 ? scopeKey.substring(dotIdx + 1) : scopeKey;
|
|
61624
|
+
if (scopedPorts[scopeName]) {
|
|
61625
|
+
scopedPorts[scopeName].children.push(...childIds);
|
|
61626
|
+
}
|
|
61627
|
+
}
|
|
61628
|
+
}
|
|
61629
|
+
const ins = regularInputs.length > 0 ? `IN: ${regularInputs.join(", ")}` : "";
|
|
61630
|
+
const outs = regularOutputs.length > 0 ? `OUT: ${regularOutputs.join(", ")}` : "";
|
|
61631
|
+
const ports = [ins, outs].filter(Boolean).join(" ");
|
|
61632
|
+
lines.push(` ${id} ${type2} ${ports}`);
|
|
61633
|
+
for (const [scopeName, info] of Object.entries(scopedPorts)) {
|
|
61634
|
+
lines.push(` Scope "${scopeName}":`);
|
|
61635
|
+
if (info.sends.length > 0) {
|
|
61636
|
+
lines.push(` Sends to children: ${info.sends.join(", ")}`);
|
|
61637
|
+
}
|
|
61638
|
+
if (info.receives.length > 0) {
|
|
61639
|
+
lines.push(` Receives from children: ${info.receives.join(", ")}`);
|
|
61640
|
+
}
|
|
61641
|
+
if (info.children.length > 0) {
|
|
61642
|
+
lines.push(` Children: ${info.children.join(", ")}`);
|
|
61643
|
+
}
|
|
61644
|
+
}
|
|
61645
|
+
} else {
|
|
61646
|
+
const filteredInputs = filterPorts(node.inputs, "input");
|
|
61647
|
+
const filteredOutputs = filterPorts(node.outputs, "output");
|
|
61648
|
+
const ins = filteredInputs.length > 0 ? `IN: ${filteredInputs.join(", ")}` : "";
|
|
61649
|
+
const outs = filteredOutputs.length > 0 ? `OUT: ${filteredOutputs.join(", ")}` : "";
|
|
61650
|
+
const ports = [ins, outs].filter(Boolean).join(" ");
|
|
61651
|
+
lines.push(` ${id} ${type2} ${ports}`);
|
|
61652
|
+
}
|
|
61653
|
+
});
|
|
61654
|
+
if (desc.graph) {
|
|
61655
|
+
lines.push("");
|
|
61656
|
+
const paths = enumeratePaths(ast);
|
|
61657
|
+
if (paths.length > 1) {
|
|
61658
|
+
lines.push(`Paths (${paths.length}):`);
|
|
61659
|
+
paths.forEach((p, i) => {
|
|
61660
|
+
lines.push(` Path ${i + 1}: ${p.join(" -> ")}`);
|
|
61661
|
+
});
|
|
61662
|
+
} else {
|
|
61663
|
+
lines.push("Flow:");
|
|
61664
|
+
desc.graph.split("\n").forEach((flowLine) => {
|
|
61665
|
+
lines.push(` ${flowLine}`);
|
|
61666
|
+
});
|
|
61667
|
+
}
|
|
61668
|
+
}
|
|
61669
|
+
lines.push("");
|
|
61670
|
+
lines.push(
|
|
61671
|
+
`Validation: ${desc.validation.errors.length} errors, ${desc.validation.warnings.length} warnings`
|
|
61672
|
+
);
|
|
61673
|
+
return lines.join("\n");
|
|
61674
|
+
}
|
|
61675
|
+
function formatDescribeOutput(ast, output, format) {
|
|
61676
|
+
switch (format) {
|
|
61677
|
+
case "text":
|
|
61678
|
+
return formatTextOutput(ast, output);
|
|
61679
|
+
case "mermaid":
|
|
61680
|
+
return generateMermaid(ast);
|
|
61681
|
+
case "paths":
|
|
61682
|
+
return formatPaths(ast);
|
|
61683
|
+
case "ascii":
|
|
61684
|
+
return renderASCII(buildDiagramGraph(ast));
|
|
61685
|
+
case "ascii-compact":
|
|
61686
|
+
return renderASCIICompact(buildDiagramGraph(ast));
|
|
61687
|
+
case "json":
|
|
61688
|
+
default:
|
|
61689
|
+
return JSON.stringify(output, null, 2);
|
|
61690
|
+
}
|
|
61691
|
+
}
|
|
61692
|
+
async function describeCommand(input, options = {}) {
|
|
61693
|
+
const { format = "json", node: focusNodeId, workflowName, compile = false } = options;
|
|
61694
|
+
const filePath = path11.resolve(input);
|
|
61695
|
+
if (!fs11.existsSync(filePath)) {
|
|
61696
|
+
logger.error(`File not found: ${filePath}`);
|
|
61697
|
+
process.exit(1);
|
|
61698
|
+
}
|
|
61699
|
+
try {
|
|
61700
|
+
const parseResult = await parseWorkflow(filePath, { workflowName });
|
|
61701
|
+
if (parseResult.errors.length > 0) {
|
|
61702
|
+
logger.error(`Parse errors:`);
|
|
61703
|
+
parseResult.errors.forEach((err) => logger.error(` ${err}`));
|
|
61704
|
+
process.exit(1);
|
|
61705
|
+
}
|
|
61706
|
+
const ast = parseResult.ast;
|
|
61707
|
+
if (compile) {
|
|
61708
|
+
const sourceCode = fs11.readFileSync(filePath, "utf8");
|
|
61709
|
+
const generated = generateInPlace(sourceCode, ast, { production: false });
|
|
61710
|
+
if (generated.hasChanges) {
|
|
61711
|
+
fs11.writeFileSync(filePath, generated.code, "utf8");
|
|
61712
|
+
logger.info(`Updated runtime markers in ${path11.basename(filePath)}`);
|
|
61713
|
+
}
|
|
61714
|
+
}
|
|
61715
|
+
const output = describeWorkflow(ast, { node: focusNodeId });
|
|
61716
|
+
console.log(formatDescribeOutput(ast, output, format));
|
|
61717
|
+
} catch (error2) {
|
|
61718
|
+
if (error2 instanceof Error && error2.message.startsWith("Node not found:")) {
|
|
61719
|
+
logger.error(error2.message);
|
|
61720
|
+
process.exit(1);
|
|
61721
|
+
}
|
|
61722
|
+
logger.error(`Failed to describe workflow: ${getErrorMessage(error2)}`);
|
|
61723
|
+
process.exit(1);
|
|
61724
|
+
}
|
|
61725
|
+
}
|
|
61726
|
+
|
|
61727
|
+
// src/cli/commands/diagram.ts
|
|
61728
|
+
import * as fs12 from "fs";
|
|
61729
|
+
import * as path12 from "path";
|
|
61730
|
+
|
|
61260
61731
|
// src/diagram/renderer.ts
|
|
61261
61732
|
init_theme();
|
|
61262
61733
|
function escapeXml(str2) {
|
|
@@ -62555,10 +63026,20 @@ function workflowToSVG(ast, options = {}) {
|
|
|
62555
63026
|
const graph = buildDiagramGraph(ast, options);
|
|
62556
63027
|
return renderSVG(graph, options);
|
|
62557
63028
|
}
|
|
63029
|
+
function sourceToSVG(code, options = {}) {
|
|
63030
|
+
const result = parser.parseFromString(code);
|
|
63031
|
+
return pickAndRender(result.workflows, options);
|
|
63032
|
+
}
|
|
62558
63033
|
function fileToSVG(filePath, options = {}) {
|
|
62559
63034
|
const result = parser.parse(filePath);
|
|
62560
63035
|
return pickAndRender(result.workflows, options);
|
|
62561
63036
|
}
|
|
63037
|
+
function sourceToHTML(code, options = {}) {
|
|
63038
|
+
const result = parser.parseFromString(code);
|
|
63039
|
+
const ast = pickWorkflow(result.workflows, options);
|
|
63040
|
+
const svg = workflowToSVG(ast, options);
|
|
63041
|
+
return wrapSVGInHTML(svg, { title: options.workflowName ?? ast.name, theme: options.theme, nodeSources: buildNodeSourceMap(ast) });
|
|
63042
|
+
}
|
|
62562
63043
|
function fileToHTML(filePath, options = {}) {
|
|
62563
63044
|
const result = parser.parse(filePath);
|
|
62564
63045
|
const ast = pickWorkflow(result.workflows, options);
|
|
@@ -62612,8 +63093,31 @@ function pickWorkflow(workflows, options) {
|
|
|
62612
63093
|
function pickAndRender(workflows, options) {
|
|
62613
63094
|
return workflowToSVG(pickWorkflow(workflows, options), options);
|
|
62614
63095
|
}
|
|
63096
|
+
function renderByFormat(graph, format) {
|
|
63097
|
+
switch (format) {
|
|
63098
|
+
case "ascii":
|
|
63099
|
+
return renderASCII(graph);
|
|
63100
|
+
case "ascii-compact":
|
|
63101
|
+
return renderASCIICompact(graph);
|
|
63102
|
+
case "text":
|
|
63103
|
+
return renderText(graph);
|
|
63104
|
+
}
|
|
63105
|
+
}
|
|
63106
|
+
function sourceToASCII(code, options = {}) {
|
|
63107
|
+
const result = parser.parseFromString(code);
|
|
63108
|
+
const ast = pickWorkflow(result.workflows, options);
|
|
63109
|
+
const graph = buildDiagramGraph(ast, options);
|
|
63110
|
+
return renderByFormat(graph, options.format ?? "ascii");
|
|
63111
|
+
}
|
|
63112
|
+
function fileToASCII(filePath, options = {}) {
|
|
63113
|
+
const result = parser.parse(filePath);
|
|
63114
|
+
const ast = pickWorkflow(result.workflows, options);
|
|
63115
|
+
const graph = buildDiagramGraph(ast, options);
|
|
63116
|
+
return renderByFormat(graph, options.format ?? "ascii");
|
|
63117
|
+
}
|
|
62615
63118
|
|
|
62616
63119
|
// src/cli/commands/diagram.ts
|
|
63120
|
+
var ASCII_FORMATS = /* @__PURE__ */ new Set(["ascii", "ascii-compact", "text"]);
|
|
62617
63121
|
async function diagramCommand(input, options = {}) {
|
|
62618
63122
|
const { output, format = "svg", ...diagramOptions } = options;
|
|
62619
63123
|
const filePath = path12.resolve(input);
|
|
@@ -62622,8 +63126,14 @@ async function diagramCommand(input, options = {}) {
|
|
|
62622
63126
|
process.exit(1);
|
|
62623
63127
|
}
|
|
62624
63128
|
try {
|
|
62625
|
-
|
|
62626
|
-
|
|
63129
|
+
let result;
|
|
63130
|
+
if (ASCII_FORMATS.has(format)) {
|
|
63131
|
+
result = fileToASCII(filePath, { ...diagramOptions, format });
|
|
63132
|
+
} else if (format === "html") {
|
|
63133
|
+
result = fileToHTML(filePath, diagramOptions);
|
|
63134
|
+
} else {
|
|
63135
|
+
result = fileToSVG(filePath, diagramOptions);
|
|
63136
|
+
}
|
|
62627
63137
|
if (output) {
|
|
62628
63138
|
const outputPath = path12.resolve(output);
|
|
62629
63139
|
fs12.writeFileSync(outputPath, result, "utf-8");
|
|
@@ -86027,7 +86537,7 @@ function registerQueryTools(mcp) {
|
|
|
86027
86537
|
"Describe a workflow in LLM-friendly format (nodes, connections, graph, validation).",
|
|
86028
86538
|
{
|
|
86029
86539
|
filePath: external_exports.string().describe("Path to the workflow .ts file"),
|
|
86030
|
-
format: external_exports.enum(["json", "text", "mermaid", "paths"]).optional().describe("Output format (default: json)"),
|
|
86540
|
+
format: external_exports.enum(["json", "text", "mermaid", "paths", "ascii", "ascii-compact"]).optional().describe("Output format (default: json). ascii/ascii-compact produce terminal-readable diagrams."),
|
|
86031
86541
|
node: external_exports.string().optional().describe("Focus on a specific node ID"),
|
|
86032
86542
|
workflowName: external_exports.string().optional().describe("Specific workflow if file has multiple")
|
|
86033
86543
|
},
|
|
@@ -93200,31 +93710,54 @@ function resolvePackageName(spec) {
|
|
|
93200
93710
|
// src/mcp/tools-diagram.ts
|
|
93201
93711
|
import * as fs26 from "fs";
|
|
93202
93712
|
import * as path29 from "path";
|
|
93713
|
+
var ASCII_FORMATS2 = /* @__PURE__ */ new Set(["ascii", "ascii-compact", "text"]);
|
|
93203
93714
|
function registerDiagramTools(mcp) {
|
|
93204
93715
|
mcp.tool(
|
|
93205
93716
|
"fw_diagram",
|
|
93206
|
-
"Generate
|
|
93717
|
+
"Generate a diagram of a workflow. Formats: svg/html produce visual markup, ascii/ascii-compact/text produce plain text readable in terminal. Provide either filePath (workflow .ts file) or source (inline code).",
|
|
93207
93718
|
{
|
|
93208
|
-
filePath: external_exports.string().describe("Path to the workflow .ts file"),
|
|
93209
|
-
|
|
93719
|
+
filePath: external_exports.string().optional().describe("Path to the workflow .ts file (required if source is not provided)"),
|
|
93720
|
+
source: external_exports.string().optional().describe("Inline workflow source code (required if filePath is not provided)"),
|
|
93721
|
+
outputPath: external_exports.string().optional().describe("Output file path. If omitted, returns content as text."),
|
|
93210
93722
|
workflowName: external_exports.string().optional().describe("Specific workflow name if file has multiple"),
|
|
93211
93723
|
theme: external_exports.enum(["dark", "light"]).optional().describe("Color theme (default: dark)"),
|
|
93212
93724
|
showPortLabels: external_exports.boolean().optional().describe("Show port labels on diagram (default: true)"),
|
|
93213
|
-
format: external_exports.enum(["svg", "html"]).optional().describe("Output format: svg (default)
|
|
93725
|
+
format: external_exports.enum(["svg", "html", "ascii", "ascii-compact", "text"]).optional().describe("Output format: svg (default), html (interactive viewer), ascii (port-level detail), ascii-compact (compact boxes), text (structured list)")
|
|
93214
93726
|
},
|
|
93215
93727
|
async (args) => {
|
|
93216
93728
|
try {
|
|
93217
|
-
|
|
93218
|
-
|
|
93219
|
-
return makeErrorResult("FILE_NOT_FOUND", `File not found: ${resolvedPath}`);
|
|
93729
|
+
if (!args.filePath && !args.source) {
|
|
93730
|
+
return makeErrorResult("MISSING_PARAM", "Provide either filePath or source parameter");
|
|
93220
93731
|
}
|
|
93732
|
+
const format = args.format ?? "svg";
|
|
93221
93733
|
const diagramOptions = {
|
|
93222
93734
|
workflowName: args.workflowName,
|
|
93223
93735
|
theme: args.theme,
|
|
93224
|
-
showPortLabels: args.showPortLabels
|
|
93736
|
+
showPortLabels: args.showPortLabels,
|
|
93737
|
+
format
|
|
93225
93738
|
};
|
|
93226
|
-
|
|
93227
|
-
|
|
93739
|
+
let result;
|
|
93740
|
+
if (args.source) {
|
|
93741
|
+
if (ASCII_FORMATS2.has(format)) {
|
|
93742
|
+
result = sourceToASCII(args.source, diagramOptions);
|
|
93743
|
+
} else if (format === "html") {
|
|
93744
|
+
result = sourceToHTML(args.source, diagramOptions);
|
|
93745
|
+
} else {
|
|
93746
|
+
result = sourceToSVG(args.source, diagramOptions);
|
|
93747
|
+
}
|
|
93748
|
+
} else {
|
|
93749
|
+
const resolvedPath = path29.resolve(args.filePath);
|
|
93750
|
+
if (!fs26.existsSync(resolvedPath)) {
|
|
93751
|
+
return makeErrorResult("FILE_NOT_FOUND", `File not found: ${resolvedPath}`);
|
|
93752
|
+
}
|
|
93753
|
+
if (ASCII_FORMATS2.has(format)) {
|
|
93754
|
+
result = fileToASCII(resolvedPath, diagramOptions);
|
|
93755
|
+
} else if (format === "html") {
|
|
93756
|
+
result = fileToHTML(resolvedPath, diagramOptions);
|
|
93757
|
+
} else {
|
|
93758
|
+
result = fileToSVG(resolvedPath, diagramOptions);
|
|
93759
|
+
}
|
|
93760
|
+
}
|
|
93228
93761
|
if (args.outputPath) {
|
|
93229
93762
|
const outputResolved = path29.resolve(args.outputPath);
|
|
93230
93763
|
fs26.writeFileSync(outputResolved, result, "utf-8");
|
|
@@ -93821,6 +94354,75 @@ function registerResources(mcp, connection, buffer) {
|
|
|
93821
94354
|
);
|
|
93822
94355
|
}
|
|
93823
94356
|
|
|
94357
|
+
// src/mcp/prompts.ts
|
|
94358
|
+
var NOCODE_SYSTEM_PROMPT = `You are a workflow developer powered by Flow Weaver. You write real, production-quality TypeScript behind the scenes, but you present everything to the user as plain-language step descriptions, connections, and diagrams. The user never needs to see code unless they ask for it.
|
|
94359
|
+
|
|
94360
|
+
## How you work
|
|
94361
|
+
|
|
94362
|
+
When the user describes a workflow:
|
|
94363
|
+
|
|
94364
|
+
1. Break their description into discrete steps (nodes), each with typed inputs and outputs.
|
|
94365
|
+
2. Call fw_create_model to generate the workflow skeleton with declare stubs.
|
|
94366
|
+
3. Call fw_implement_node for each step, writing real TypeScript function bodies that do what the user described. Write actual working code, not placeholder comments.
|
|
94367
|
+
4. Call fw_validate to check the result. If there are errors you can fix (missing connections, type mismatches), fix them silently with fw_modify. Only tell the user about problems you genuinely cannot resolve.
|
|
94368
|
+
5. Show the result as:
|
|
94369
|
+
- A numbered list of steps with one-sentence descriptions
|
|
94370
|
+
- An ASCII diagram (call fw_diagram with format "ascii-compact")
|
|
94371
|
+
Never show TypeScript code in this summary.
|
|
94372
|
+
|
|
94373
|
+
When the user asks to modify an existing workflow:
|
|
94374
|
+
- Use fw_modify or fw_modify_batch for structural changes (add/remove nodes, connections).
|
|
94375
|
+
- Use fw_implement_node to update a step's logic.
|
|
94376
|
+
- Re-validate after every change.
|
|
94377
|
+
|
|
94378
|
+
## When to show code
|
|
94379
|
+
|
|
94380
|
+
Only reveal TypeScript when the user explicitly asks: "show me the code", "let me see the implementation", "what does step X look like", etc. When showing code, use fw_describe with format "text" or read the file directly.
|
|
94381
|
+
|
|
94382
|
+
## Tool usage patterns
|
|
94383
|
+
|
|
94384
|
+
- fw_create_model: Create new workflows from a structured description (steps, inputs/outputs, flow path).
|
|
94385
|
+
- fw_implement_node: Replace a declare stub with a real function body.
|
|
94386
|
+
- fw_modify / fw_modify_batch: Add/remove nodes and connections, rename nodes, reposition.
|
|
94387
|
+
- fw_validate: Always run after changes. Fix what you can, report what you cannot.
|
|
94388
|
+
- fw_describe: Inspect workflow structure. Use format "text" for human-readable, "json" for programmatic.
|
|
94389
|
+
- fw_diagram: Generate visual representation. Prefer format "ascii-compact" for chat.
|
|
94390
|
+
- fw_workflow_status: Check which steps are implemented vs still stubs.
|
|
94391
|
+
- fw_scaffold / fw_list_templates: Bootstrap from templates when the user's request matches a known pattern.
|
|
94392
|
+
- fw_docs: Look up Flow Weaver features (scoped ports, branching, iteration, agents) when you need specifics.
|
|
94393
|
+
- fw_find_workflows: Locate existing workflow files in a directory.
|
|
94394
|
+
- fw_compile: Generate executable JavaScript from the workflow source.
|
|
94395
|
+
|
|
94396
|
+
## Interaction style
|
|
94397
|
+
|
|
94398
|
+
- Ask 1-2 clarifying questions when the request is vague, not a long interrogation list.
|
|
94399
|
+
- Default to sensible choices for things the user did not specify (file names, port types, node names).
|
|
94400
|
+
- After creating or modifying a workflow, always show the step summary and ASCII diagram.
|
|
94401
|
+
- Use fw_docs to look up advanced features before guessing at syntax.
|
|
94402
|
+
|
|
94403
|
+
## File conventions
|
|
94404
|
+
|
|
94405
|
+
- Workflow files use .ts extension.
|
|
94406
|
+
- Default location is the current working directory.
|
|
94407
|
+
- Node names use camelCase (e.g., validateEmail, sendNotification).
|
|
94408
|
+
- Workflow names use PascalCase (e.g., EmailValidation, OrderProcessing).`;
|
|
94409
|
+
function registerPrompts(mcp) {
|
|
94410
|
+
mcp.registerPrompt("flow-weaver-nocode", {
|
|
94411
|
+
title: "Flow Weaver No-Code Mode",
|
|
94412
|
+
description: "Describe workflows in plain language. The agent writes real TypeScript behind the scenes and presents results as step summaries and diagrams. Code is available on request."
|
|
94413
|
+
}, () => ({
|
|
94414
|
+
messages: [
|
|
94415
|
+
{
|
|
94416
|
+
role: "assistant",
|
|
94417
|
+
content: {
|
|
94418
|
+
type: "text",
|
|
94419
|
+
text: NOCODE_SYSTEM_PROMPT
|
|
94420
|
+
}
|
|
94421
|
+
}
|
|
94422
|
+
]
|
|
94423
|
+
}));
|
|
94424
|
+
}
|
|
94425
|
+
|
|
93824
94426
|
// src/mcp/server.ts
|
|
93825
94427
|
function parseEventFilterFromEnv() {
|
|
93826
94428
|
const filter3 = {};
|
|
@@ -93870,6 +94472,7 @@ async function startMcpServer(options) {
|
|
|
93870
94472
|
registerDocsTools(mcp);
|
|
93871
94473
|
registerModelTools(mcp);
|
|
93872
94474
|
registerResources(mcp, connection, buffer);
|
|
94475
|
+
registerPrompts(mcp);
|
|
93873
94476
|
if (!options._testDeps && options.stdio) {
|
|
93874
94477
|
const transport = new StdioServerTransport();
|
|
93875
94478
|
await mcp.connect(transport);
|
|
@@ -97168,7 +97771,7 @@ function displayInstalledPackage(pkg) {
|
|
|
97168
97771
|
}
|
|
97169
97772
|
|
|
97170
97773
|
// src/cli/index.ts
|
|
97171
|
-
var version2 = true ? "0.10.
|
|
97774
|
+
var version2 = true ? "0.10.9" : "0.0.0-dev";
|
|
97172
97775
|
var program2 = new Command();
|
|
97173
97776
|
program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").version(version2, "-v, --version", "Output the current version");
|
|
97174
97777
|
program2.configureOutput({
|
|
@@ -97196,7 +97799,7 @@ program2.command("strip <input>").description("Remove generated code from compil
|
|
|
97196
97799
|
process.exit(1);
|
|
97197
97800
|
}
|
|
97198
97801
|
});
|
|
97199
|
-
program2.command("describe <input>").description("Output workflow structure in LLM-friendly formats (JSON, text, mermaid)").option("-f, --format <format>", "Output format: json (default), text, mermaid", "json").option("-n, --node <id>", "Focus on a specific node").option("--compile", "Also update runtime markers in the source file").option("-w, --workflow-name <name>", "Specific workflow name to describe").action(async (input, options) => {
|
|
97802
|
+
program2.command("describe <input>").description("Output workflow structure in LLM-friendly formats (JSON, text, mermaid)").option("-f, --format <format>", "Output format: json (default), text, mermaid, paths, ascii, ascii-compact", "json").option("-n, --node <id>", "Focus on a specific node").option("--compile", "Also update runtime markers in the source file").option("-w, --workflow-name <name>", "Specific workflow name to describe").action(async (input, options) => {
|
|
97200
97803
|
try {
|
|
97201
97804
|
await describeCommand(input, options);
|
|
97202
97805
|
} catch (error2) {
|
|
@@ -97204,7 +97807,7 @@ program2.command("describe <input>").description("Output workflow structure in L
|
|
|
97204
97807
|
process.exit(1);
|
|
97205
97808
|
}
|
|
97206
97809
|
});
|
|
97207
|
-
program2.command("diagram <input>").description("Generate SVG or interactive HTML diagram of a workflow").option("-t, --theme <theme>", "Color theme: dark (default), light", "dark").option("-w, --width <pixels>", "SVG width in pixels").option("-p, --padding <pixels>", "Canvas padding in pixels").option("--no-port-labels", "Hide data type labels on ports").option("--workflow-name <name>", "Specific workflow to render").option("-f, --format <format>", "Output format: svg (default), html
|
|
97810
|
+
program2.command("diagram <input>").description("Generate SVG or interactive HTML diagram of a workflow").option("-t, --theme <theme>", "Color theme: dark (default), light", "dark").option("-w, --width <pixels>", "SVG width in pixels").option("-p, --padding <pixels>", "Canvas padding in pixels").option("--no-port-labels", "Hide data type labels on ports").option("--workflow-name <name>", "Specific workflow to render").option("-f, --format <format>", "Output format: svg (default), html, ascii, ascii-compact, text", "svg").option("-o, --output <file>", "Write output to file instead of stdout").action(async (input, options) => {
|
|
97208
97811
|
try {
|
|
97209
97812
|
if (options.width) options.width = Number(options.width);
|
|
97210
97813
|
if (options.padding) options.padding = Number(options.padding);
|