@bpmnkit/core 0.1.1 → 0.1.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/README.md +2 -0
- package/dist/bpmn/agentic.d.ts +121 -0
- package/dist/bpmn/agentic.js +97 -0
- package/dist/bpmn/auto-layout.d.ts +5 -5
- package/dist/bpmn/auto-layout.js +592 -36
- package/dist/bpmn/bpmn-builder.d.ts +56 -0
- package/dist/bpmn/bpmn-builder.js +148 -182
- package/dist/bpmn/bpmn-model.d.ts +4 -0
- package/dist/bpmn/bpmn-parser.js +9 -1
- package/dist/bpmn/bpmn-serializer.js +6 -0
- package/dist/bpmn/optimize/agentic.d.ts +10 -0
- package/dist/bpmn/optimize/agentic.js +88 -0
- package/dist/bpmn/optimize/deploy.d.ts +16 -0
- package/dist/bpmn/optimize/deploy.js +143 -0
- package/dist/bpmn/optimize/feel-syntax.d.ts +12 -0
- package/dist/bpmn/optimize/feel-syntax.js +87 -0
- package/dist/bpmn/optimize/feel.js +5 -2
- package/dist/bpmn/optimize/flow.js +22 -2
- package/dist/bpmn/optimize/index.js +20 -9
- package/dist/bpmn/optimize/types.d.ts +10 -1
- package/dist/bpmn/zeebe-extensions.d.ts +27 -0
- package/dist/bpmn/zeebe-extensions.js +38 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2 -0
- package/dist/layout/annotations.js +36 -1
- package/dist/layout/collaboration/alignment.d.ts +26 -0
- package/dist/layout/collaboration/alignment.js +66 -0
- package/dist/layout/collaboration/ordering.d.ts +21 -0
- package/dist/layout/collaboration/ordering.js +102 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/layout-engine.d.ts +13 -3
- package/dist/layout/layout-engine.js +9 -4
- package/dist/layout/semantic/bands.d.ts +19 -0
- package/dist/layout/semantic/bands.js +324 -0
- package/dist/layout/semantic/graph.d.ts +29 -0
- package/dist/layout/semantic/graph.js +217 -0
- package/dist/layout/semantic/index.d.ts +13 -0
- package/dist/layout/semantic/index.js +181 -0
- package/dist/layout/semantic/place.d.ts +40 -0
- package/dist/layout/semantic/place.js +271 -0
- package/dist/layout/semantic/route.d.ts +14 -0
- package/dist/layout/semantic/route.js +454 -0
- package/dist/layout/types.d.ts +17 -0
- package/dist/plan/compile.d.ts +39 -0
- package/dist/plan/compile.js +380 -0
- package/dist/plan/extract.d.ts +31 -0
- package/dist/plan/extract.js +248 -0
- package/dist/plan/index.d.ts +6 -0
- package/dist/plan/index.js +5 -0
- package/dist/plan/merge.d.ts +13 -0
- package/dist/plan/merge.js +80 -0
- package/dist/plan/slug.d.ts +5 -0
- package/dist/plan/slug.js +22 -0
- package/dist/plan/types.d.ts +225 -0
- package/dist/plan/types.js +13 -0
- package/package.json +2 -2
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { AI_AGENT_JOB_WORKER_TASK_TYPE } from "../agentic.js";
|
|
2
|
+
import { readZeebeIoMapping, readZeebeTaskType } from "./utils.js";
|
|
3
|
+
const FROM_AI_CALL = /fromAi\(\s*([^,)]+)/g;
|
|
4
|
+
function isAiAgentSubProcess(el) {
|
|
5
|
+
return readZeebeTaskType(el.extensionElements) === AI_AGENT_JOB_WORKER_TASK_TYPE;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Agentic-specific checks for the Camunda 8 AI Agent Sub-process pattern:
|
|
9
|
+
* tools must be root nodes with a description the LLM can read, every
|
|
10
|
+
* `fromAi()` call must reference `toolCall.*`, and the agent should aggregate
|
|
11
|
+
* tool results and cap its model-call budget.
|
|
12
|
+
*/
|
|
13
|
+
export function analyzeAgentic(p) {
|
|
14
|
+
const findings = [];
|
|
15
|
+
const processId = p.id;
|
|
16
|
+
for (const el of p.flowElements) {
|
|
17
|
+
if (el.type !== "adHocSubProcess" || !isAiAgentSubProcess(el))
|
|
18
|
+
continue;
|
|
19
|
+
const adHocExt = el.extensionElements.find((e) => e.name === "zeebe:adHoc");
|
|
20
|
+
if (!adHocExt?.attributes.outputCollection) {
|
|
21
|
+
findings.push({
|
|
22
|
+
id: "agentic/no-output-collection",
|
|
23
|
+
category: "agentic",
|
|
24
|
+
severity: "warning",
|
|
25
|
+
message: `AI Agent "${el.name ?? el.id}" has no outputCollection — tool call results won't be aggregated.`,
|
|
26
|
+
suggestion: 'Set zeebe:adHoc outputCollection (e.g. "toolCallResults").',
|
|
27
|
+
processId,
|
|
28
|
+
elementIds: [el.id],
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const io = readZeebeIoMapping(el.extensionElements);
|
|
32
|
+
const hasLimit = io?.inputs.some((i) => i.target === "data.limits.maxModelCalls") ?? false;
|
|
33
|
+
if (!hasLimit) {
|
|
34
|
+
findings.push({
|
|
35
|
+
id: "agentic/limits-missing",
|
|
36
|
+
category: "agentic",
|
|
37
|
+
severity: "info",
|
|
38
|
+
message: `AI Agent "${el.name ?? el.id}" has no data.limits.maxModelCalls binding.`,
|
|
39
|
+
suggestion: "Set a model-call limit as a safety net against infinite tool loops.",
|
|
40
|
+
processId,
|
|
41
|
+
elementIds: [el.id],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
for (const tool of el.flowElements) {
|
|
45
|
+
if (tool.incoming.length > 0) {
|
|
46
|
+
findings.push({
|
|
47
|
+
id: "agentic/tool-not-root",
|
|
48
|
+
category: "agentic",
|
|
49
|
+
severity: "error",
|
|
50
|
+
message: `"${tool.name ?? tool.id}" inside AI Agent "${el.name ?? el.id}" has an incoming sequence flow — the connector only discovers tools with no incoming flow.`,
|
|
51
|
+
suggestion: "Remove the incoming sequence flow; tools must be root nodes.",
|
|
52
|
+
processId,
|
|
53
|
+
elementIds: [tool.id],
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (!tool.documentation?.trim()) {
|
|
57
|
+
findings.push({
|
|
58
|
+
id: "agentic/tool-no-description",
|
|
59
|
+
category: "agentic",
|
|
60
|
+
severity: "warning",
|
|
61
|
+
message: `Tool "${tool.name ?? tool.id}" has no documentation — the LLM sees no description for this tool.`,
|
|
62
|
+
suggestion: "Set <bpmn:documentation> describing what this tool does.",
|
|
63
|
+
processId,
|
|
64
|
+
elementIds: [tool.id],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const toolIo = readZeebeIoMapping(tool.extensionElements);
|
|
68
|
+
for (const input of toolIo?.inputs ?? []) {
|
|
69
|
+
for (const match of input.source.matchAll(FROM_AI_CALL)) {
|
|
70
|
+
const firstArg = match[1]?.trim();
|
|
71
|
+
if (firstArg && !firstArg.startsWith("toolCall.")) {
|
|
72
|
+
findings.push({
|
|
73
|
+
id: "agentic/fromai-bad-ref",
|
|
74
|
+
category: "agentic",
|
|
75
|
+
severity: "error",
|
|
76
|
+
message: `fromAi() on "${tool.name ?? tool.id}" input "${input.target}" references "${firstArg}", not a toolCall.* field.`,
|
|
77
|
+
suggestion: 'fromAi()\'s first argument must reference "toolCall.<param>".',
|
|
78
|
+
processId,
|
|
79
|
+
elementIds: [tool.id],
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return findings;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=agentic.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BpmnProcess } from "../bpmn-model.js";
|
|
2
|
+
import type { OptimizationFinding } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Given a bundled connector template id and the set of value keys bound on
|
|
5
|
+
* the element (io-mapping targets, task-header keys, zeebe:property names),
|
|
6
|
+
* returns the required keys that are missing. Inject `applyConnectorTemplate`-
|
|
7
|
+
* backed logic from `@bpmnkit/connectors` here; core stays dependency-free.
|
|
8
|
+
*/
|
|
9
|
+
export type ConnectorRequirementsResolver = (templateId: string, boundKeys: string[]) => string[];
|
|
10
|
+
/**
|
|
11
|
+
* Zeebe/Reebe deploy-parity checks — mirrors what Camunda 8 rejects at
|
|
12
|
+
* deployment time (`apps/reebe/crates/reebe-bpmn/src/validator.rs`), so a
|
|
13
|
+
* process that passes this profile deploys without a validation error.
|
|
14
|
+
*/
|
|
15
|
+
export declare function analyzeDeploy(p: BpmnProcess, resolveConnectorRequirements?: ConnectorRequirementsResolver): OptimizationFinding[];
|
|
16
|
+
//# sourceMappingURL=deploy.d.ts.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { readZeebeIoMapping, readZeebeTaskHeaders, readZeebeTaskType } from "./utils.js";
|
|
2
|
+
function findExt(el, name) {
|
|
3
|
+
return el.extensionElements.find((e) => e.name === name);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Zeebe/Reebe deploy-parity checks — mirrors what Camunda 8 rejects at
|
|
7
|
+
* deployment time (`apps/reebe/crates/reebe-bpmn/src/validator.rs`), so a
|
|
8
|
+
* process that passes this profile deploys without a validation error.
|
|
9
|
+
*/
|
|
10
|
+
export function analyzeDeploy(p, resolveConnectorRequirements) {
|
|
11
|
+
const findings = [];
|
|
12
|
+
const processId = p.id;
|
|
13
|
+
if (p.isExecutable !== true) {
|
|
14
|
+
findings.push({
|
|
15
|
+
id: "deploy/process-not-executable",
|
|
16
|
+
category: "deploy",
|
|
17
|
+
severity: "error",
|
|
18
|
+
message: `Process "${processId}" is not marked executable.`,
|
|
19
|
+
suggestion: 'Set isExecutable="true" — Camunda 8 refuses to deploy a non-executable process.',
|
|
20
|
+
processId,
|
|
21
|
+
elementIds: [],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
for (const el of p.flowElements) {
|
|
25
|
+
if (el.type === "serviceTask" || el.type === "sendTask") {
|
|
26
|
+
const type = readZeebeTaskType(el.extensionElements);
|
|
27
|
+
if (!type) {
|
|
28
|
+
findings.push({
|
|
29
|
+
id: "deploy/service-task-no-type",
|
|
30
|
+
category: "deploy",
|
|
31
|
+
severity: "error",
|
|
32
|
+
message: `"${el.name ?? el.id}" (${el.type}) has no zeebe:taskDefinition type.`,
|
|
33
|
+
suggestion: "Set a job type — Camunda 8 refuses to deploy a task with no task definition.",
|
|
34
|
+
processId,
|
|
35
|
+
elementIds: [el.id],
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (el.type === "businessRuleTask") {
|
|
40
|
+
const hasType = readZeebeTaskType(el.extensionElements) !== null;
|
|
41
|
+
const hasDecision = findExt(el, "zeebe:calledDecision") !== undefined;
|
|
42
|
+
if (!hasType && !hasDecision) {
|
|
43
|
+
findings.push({
|
|
44
|
+
id: "deploy/service-task-no-type",
|
|
45
|
+
category: "deploy",
|
|
46
|
+
severity: "error",
|
|
47
|
+
message: `"${el.name ?? el.id}" (businessRuleTask) has neither a zeebe:taskDefinition type nor a zeebe:calledDecision.`,
|
|
48
|
+
suggestion: "Set a decisionId or a job type.",
|
|
49
|
+
processId,
|
|
50
|
+
elementIds: [el.id],
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (el.type === "callActivity") {
|
|
55
|
+
const called = findExt(el, "zeebe:calledElement");
|
|
56
|
+
if (!called?.attributes.processId) {
|
|
57
|
+
findings.push({
|
|
58
|
+
id: "deploy/call-activity-no-process",
|
|
59
|
+
category: "deploy",
|
|
60
|
+
severity: "error",
|
|
61
|
+
message: `Call activity "${el.name ?? el.id}" has no zeebe:calledElement processId.`,
|
|
62
|
+
suggestion: "Set the process id to call.",
|
|
63
|
+
processId,
|
|
64
|
+
elementIds: [el.id],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (el.type === "startEvent") {
|
|
69
|
+
const messageDef = el.eventDefinitions.find((d) => d.type === "message");
|
|
70
|
+
if (messageDef && !messageDef.messageRef) {
|
|
71
|
+
findings.push({
|
|
72
|
+
id: "deploy/message-start-no-name",
|
|
73
|
+
category: "deploy",
|
|
74
|
+
severity: "error",
|
|
75
|
+
message: `Message start event "${el.name ?? el.id}" has no message name.`,
|
|
76
|
+
suggestion: "Set a message name — Camunda 8 refuses to deploy an unnamed message reference.",
|
|
77
|
+
processId,
|
|
78
|
+
elementIds: [el.id],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (el.type === "intermediateCatchEvent" ||
|
|
83
|
+
el.type === "boundaryEvent" ||
|
|
84
|
+
el.type === "receiveTask") {
|
|
85
|
+
const messageDef = el.type === "receiveTask"
|
|
86
|
+
? undefined
|
|
87
|
+
: el.eventDefinitions.find((d) => d.type === "message");
|
|
88
|
+
const isMessageCatch = el.type === "receiveTask" ? el.messageRef !== undefined : messageDef !== undefined;
|
|
89
|
+
if (isMessageCatch) {
|
|
90
|
+
const subscription = findExt(el, "zeebe:subscription");
|
|
91
|
+
if (!subscription?.attributes.correlationKey) {
|
|
92
|
+
findings.push({
|
|
93
|
+
id: "deploy/message-catch-no-correlation",
|
|
94
|
+
category: "deploy",
|
|
95
|
+
severity: "error",
|
|
96
|
+
message: `Message catch "${el.name ?? el.id}" (${el.type}) has no zeebe:subscription correlationKey.`,
|
|
97
|
+
suggestion: "Set a correlation key — required for every message catch in Camunda 8.",
|
|
98
|
+
processId,
|
|
99
|
+
elementIds: [el.id],
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (resolveConnectorRequirements) {
|
|
105
|
+
const templateId = el.unknownAttributes["zeebe:modelerTemplate"];
|
|
106
|
+
if (templateId) {
|
|
107
|
+
const boundKeys = new Set();
|
|
108
|
+
const io = readZeebeIoMapping(el.extensionElements);
|
|
109
|
+
if (io) {
|
|
110
|
+
for (const i of io.inputs)
|
|
111
|
+
boundKeys.add(i.target);
|
|
112
|
+
for (const o of io.outputs)
|
|
113
|
+
boundKeys.add(o.target);
|
|
114
|
+
}
|
|
115
|
+
const headers = readZeebeTaskHeaders(el.extensionElements);
|
|
116
|
+
if (headers)
|
|
117
|
+
for (const h of headers.headers)
|
|
118
|
+
boundKeys.add(h.key);
|
|
119
|
+
const propsExt = findExt(el, "zeebe:properties");
|
|
120
|
+
if (propsExt) {
|
|
121
|
+
for (const child of propsExt.children ?? []) {
|
|
122
|
+
if (child.attributes.name)
|
|
123
|
+
boundKeys.add(child.attributes.name);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const missing = resolveConnectorRequirements(templateId, [...boundKeys]);
|
|
127
|
+
for (const key of missing) {
|
|
128
|
+
findings.push({
|
|
129
|
+
id: "connector/missing-required",
|
|
130
|
+
category: "connector",
|
|
131
|
+
severity: "error",
|
|
132
|
+
message: `"${el.name ?? el.id}" is missing required connector value "${key}" for template "${templateId}".`,
|
|
133
|
+
suggestion: `Set a value for "${key}".`,
|
|
134
|
+
processId,
|
|
135
|
+
elementIds: [el.id],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return findings;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=deploy.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BpmnProcess } from "../bpmn-model.js";
|
|
2
|
+
import type { OptimizationFinding } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Parse-validates every FEEL-looking expression (leading "=") in the process:
|
|
5
|
+
* sequence-flow conditions, zeebe:input/output sources, script task
|
|
6
|
+
* expressions, and ad-hoc sub-process completion conditions/outputElement —
|
|
7
|
+
* including inside nested sub-processes. Unlike `feel.ts` (heuristic
|
|
8
|
+
* complexity scoring), this uses the real `@bpmnkit/feel` parser and reports
|
|
9
|
+
* genuine syntax errors, not style suggestions.
|
|
10
|
+
*/
|
|
11
|
+
export declare function analyzeFeelSyntax(p: BpmnProcess): OptimizationFinding[];
|
|
12
|
+
//# sourceMappingURL=feel-syntax.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { parseExpression } from "@bpmnkit/feel";
|
|
2
|
+
import { readZeebeIoMapping } from "./utils.js";
|
|
3
|
+
/** Recursively yields every flow element in a process, including inside sub-processes/ad-hoc sub-processes. */
|
|
4
|
+
function* walkElements(elements) {
|
|
5
|
+
for (const el of elements) {
|
|
6
|
+
yield el;
|
|
7
|
+
if (el.type === "subProcess" ||
|
|
8
|
+
el.type === "adHocSubProcess" ||
|
|
9
|
+
el.type === "eventSubProcess" ||
|
|
10
|
+
el.type === "transaction") {
|
|
11
|
+
yield* walkElements(el.flowElements);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Recursively yields every sequence flow, including inside sub-processes/ad-hoc sub-processes. */
|
|
16
|
+
function* walkFlows(elements, topLevel) {
|
|
17
|
+
yield* topLevel;
|
|
18
|
+
for (const el of elements) {
|
|
19
|
+
if (el.type === "subProcess" ||
|
|
20
|
+
el.type === "adHocSubProcess" ||
|
|
21
|
+
el.type === "eventSubProcess" ||
|
|
22
|
+
el.type === "transaction") {
|
|
23
|
+
yield* walkFlows(el.flowElements, el.sequenceFlows);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function checkFeel(text, elementId, processId, surface, findings) {
|
|
28
|
+
const trimmed = text.trim();
|
|
29
|
+
if (!trimmed.startsWith("="))
|
|
30
|
+
return;
|
|
31
|
+
const { errors } = parseExpression(trimmed.slice(1));
|
|
32
|
+
for (const err of errors) {
|
|
33
|
+
findings.push({
|
|
34
|
+
id: "feel-syntax/parse-error",
|
|
35
|
+
category: "feel-syntax",
|
|
36
|
+
severity: "error",
|
|
37
|
+
message: `Invalid FEEL expression on ${surface} of "${elementId}": ${err.message}`,
|
|
38
|
+
suggestion: "Fix the FEEL syntax — see the error position for the offending token.",
|
|
39
|
+
processId,
|
|
40
|
+
elementIds: [elementId],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse-validates every FEEL-looking expression (leading "=") in the process:
|
|
46
|
+
* sequence-flow conditions, zeebe:input/output sources, script task
|
|
47
|
+
* expressions, and ad-hoc sub-process completion conditions/outputElement —
|
|
48
|
+
* including inside nested sub-processes. Unlike `feel.ts` (heuristic
|
|
49
|
+
* complexity scoring), this uses the real `@bpmnkit/feel` parser and reports
|
|
50
|
+
* genuine syntax errors, not style suggestions.
|
|
51
|
+
*/
|
|
52
|
+
export function analyzeFeelSyntax(p) {
|
|
53
|
+
const findings = [];
|
|
54
|
+
const processId = p.id;
|
|
55
|
+
for (const flow of walkFlows(p.flowElements, p.sequenceFlows)) {
|
|
56
|
+
if (flow.conditionExpression?.text) {
|
|
57
|
+
checkFeel(flow.conditionExpression.text, flow.id, processId, "condition", findings);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const el of walkElements(p.flowElements)) {
|
|
61
|
+
const io = readZeebeIoMapping(el.extensionElements);
|
|
62
|
+
if (io) {
|
|
63
|
+
for (const input of io.inputs)
|
|
64
|
+
checkFeel(input.source, el.id, processId, `input "${input.target}"`, findings);
|
|
65
|
+
for (const output of io.outputs)
|
|
66
|
+
checkFeel(output.source, el.id, processId, `output "${output.target}"`, findings);
|
|
67
|
+
}
|
|
68
|
+
const scriptExt = el.extensionElements.find((e) => e.name === "zeebe:script");
|
|
69
|
+
if (scriptExt?.attributes.expression) {
|
|
70
|
+
checkFeel(scriptExt.attributes.expression, el.id, processId, "script expression", findings);
|
|
71
|
+
}
|
|
72
|
+
if (el.type === "adHocSubProcess") {
|
|
73
|
+
if (el.completionCondition?.text) {
|
|
74
|
+
checkFeel(el.completionCondition.text, el.id, processId, "completion condition", findings);
|
|
75
|
+
}
|
|
76
|
+
const adHocExt = el.extensionElements.find((e) => e.name === "zeebe:adHoc");
|
|
77
|
+
if (adHocExt?.attributes.outputElement) {
|
|
78
|
+
checkFeel(adHocExt.attributes.outputElement, el.id, processId, "outputElement", findings);
|
|
79
|
+
}
|
|
80
|
+
if (adHocExt?.attributes.activeElementsCollection) {
|
|
81
|
+
checkFeel(adHocExt.attributes.activeElementsCollection, el.id, processId, "activeElementsCollection", findings);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return findings;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=feel-syntax.js.map
|
|
@@ -63,10 +63,13 @@ export function analyzeFeel(p, opts) {
|
|
|
63
63
|
// Track FEEL expressions across all sequence flows for duplicate detection
|
|
64
64
|
const exprToFlowIds = new Map();
|
|
65
65
|
for (const flow of p.sequenceFlows) {
|
|
66
|
-
// Determine if source is an exclusive/inclusive gateway
|
|
66
|
+
// Determine if source is an exclusive/inclusive gateway with more than one
|
|
67
|
+
// outgoing flow — a join (single outgoing flow) makes no decision, so its
|
|
68
|
+
// one outgoing flow needs neither a condition nor a default marker.
|
|
67
69
|
const srcEl = p.flowElements.find((e) => e.id === flow.sourceRef);
|
|
68
70
|
const srcIsDecisionGateway = srcEl !== undefined &&
|
|
69
|
-
(srcEl.type === "exclusiveGateway" || srcEl.type === "inclusiveGateway")
|
|
71
|
+
(srcEl.type === "exclusiveGateway" || srcEl.type === "inclusiveGateway") &&
|
|
72
|
+
(bySource.get(srcEl.id)?.length ?? 0) > 1;
|
|
70
73
|
if (srcIsDecisionGateway && srcEl !== undefined) {
|
|
71
74
|
const defaultFlowId = srcEl.type === "exclusiveGateway" || srcEl.type === "inclusiveGateway"
|
|
72
75
|
? srcEl.default
|
|
@@ -32,8 +32,28 @@ export function analyzeFlow(p, _opts) {
|
|
|
32
32
|
elementIds: [],
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
-
// BFS reachability from start events
|
|
36
|
-
|
|
35
|
+
// BFS reachability from start events. Boundary events have no incoming
|
|
36
|
+
// sequence flow by design (they attach via `attachedToRef`), so add a
|
|
37
|
+
// synthetic edge from each host element to its boundary event(s) —
|
|
38
|
+
// otherwise every boundary event (and everything chained after it) would
|
|
39
|
+
// always be flagged as unreachable.
|
|
40
|
+
const reachabilityEdges = new Map(bySource);
|
|
41
|
+
for (const el of p.flowElements) {
|
|
42
|
+
if (el.type !== "boundaryEvent")
|
|
43
|
+
continue;
|
|
44
|
+
const hostEdges = reachabilityEdges.get(el.attachedToRef) ?? [];
|
|
45
|
+
reachabilityEdges.set(el.attachedToRef, [
|
|
46
|
+
...hostEdges,
|
|
47
|
+
{
|
|
48
|
+
id: `synthetic-attachment-${el.id}`,
|
|
49
|
+
sourceRef: el.attachedToRef,
|
|
50
|
+
targetRef: el.id,
|
|
51
|
+
extensionElements: [],
|
|
52
|
+
unknownAttributes: {},
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
const reachable = reachableFrom(startIds, reachabilityEdges);
|
|
37
57
|
for (const el of p.flowElements) {
|
|
38
58
|
// flow/unreachable
|
|
39
59
|
if (!reachable.has(el.id)) {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { analyzeAgentic } from "./agentic.js";
|
|
2
|
+
import { analyzeDeploy } from "./deploy.js";
|
|
3
|
+
import { analyzeFeelSyntax } from "./feel-syntax.js";
|
|
1
4
|
import { analyzeFeel } from "./feel.js";
|
|
2
5
|
import { analyzeFlow } from "./flow.js";
|
|
3
6
|
import { analyzeNaming } from "./naming.js";
|
|
@@ -6,12 +9,16 @@ import { analyzeTasks } from "./tasks.js";
|
|
|
6
9
|
import { analyzeVariableFlow } from "./variable-flow.js";
|
|
7
10
|
const ALL_CATEGORIES = [
|
|
8
11
|
"feel",
|
|
12
|
+
"feel-syntax",
|
|
9
13
|
"flow",
|
|
10
14
|
"naming",
|
|
11
15
|
"task-reuse",
|
|
12
16
|
"extract",
|
|
13
17
|
"pattern",
|
|
14
18
|
"data-flow",
|
|
19
|
+
"deploy",
|
|
20
|
+
"agentic",
|
|
21
|
+
"connector",
|
|
15
22
|
];
|
|
16
23
|
function resolveOptions(opts) {
|
|
17
24
|
return {
|
|
@@ -21,6 +28,7 @@ function resolveOptions(opts) {
|
|
|
21
28
|
feelVariableThreshold: opts?.feelVariableThreshold ?? 4,
|
|
22
29
|
reuseThreshold: opts?.reuseThreshold ?? 2,
|
|
23
30
|
categories: opts?.categories ?? [...ALL_CATEGORIES],
|
|
31
|
+
resolveConnectorRequirements: opts?.resolveConnectorRequirements,
|
|
24
32
|
};
|
|
25
33
|
}
|
|
26
34
|
/** Run static analysis on a BPMN definitions object. */
|
|
@@ -31,6 +39,9 @@ export function optimize(defs, options) {
|
|
|
31
39
|
if (resolved.categories.includes("feel")) {
|
|
32
40
|
findings.push(...analyzeFeel(process, resolved));
|
|
33
41
|
}
|
|
42
|
+
if (resolved.categories.includes("feel-syntax")) {
|
|
43
|
+
findings.push(...analyzeFeelSyntax(process));
|
|
44
|
+
}
|
|
34
45
|
if (resolved.categories.includes("flow")) {
|
|
35
46
|
findings.push(...analyzeFlow(process, resolved));
|
|
36
47
|
}
|
|
@@ -46,16 +57,16 @@ export function optimize(defs, options) {
|
|
|
46
57
|
if (resolved.categories.includes("data-flow")) {
|
|
47
58
|
findings.push(...analyzeVariableFlow(process));
|
|
48
59
|
}
|
|
60
|
+
if (resolved.categories.includes("deploy") || resolved.categories.includes("connector")) {
|
|
61
|
+
const deployFindings = analyzeDeploy(process, resolved.resolveConnectorRequirements);
|
|
62
|
+
findings.push(...deployFindings.filter((f) => (f.category === "deploy" && resolved.categories.includes("deploy")) ||
|
|
63
|
+
(f.category === "connector" && resolved.categories.includes("connector"))));
|
|
64
|
+
}
|
|
65
|
+
if (resolved.categories.includes("agentic")) {
|
|
66
|
+
findings.push(...analyzeAgentic(process));
|
|
67
|
+
}
|
|
49
68
|
}
|
|
50
|
-
const byCategory = Object.fromEntries([
|
|
51
|
-
"feel",
|
|
52
|
-
"flow",
|
|
53
|
-
"naming",
|
|
54
|
-
"task-reuse",
|
|
55
|
-
"extract",
|
|
56
|
-
"pattern",
|
|
57
|
-
"data-flow",
|
|
58
|
-
].map((c) => [c, findings.filter((f) => f.category === c).length]));
|
|
69
|
+
const byCategory = Object.fromEntries(ALL_CATEGORIES.map((c) => [c, findings.filter((f) => f.category === c).length]));
|
|
59
70
|
const bySeverity = Object.fromEntries(["info", "warning", "error"].map((s) => [
|
|
60
71
|
s,
|
|
61
72
|
findings.filter((f) => f.severity === s).length,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BpmnDefinitions } from "../bpmn-model.js";
|
|
2
2
|
export type OptimizationSeverity = "info" | "warning" | "error";
|
|
3
|
-
export type OptimizationCategory = "feel" | "flow" | "naming" | "task-reuse" | "extract" | "pattern" | "data-flow";
|
|
3
|
+
export type OptimizationCategory = "feel" | "feel-syntax" | "flow" | "naming" | "task-reuse" | "extract" | "pattern" | "data-flow" | "deploy" | "agentic" | "connector";
|
|
4
4
|
export interface ApplyFixResult {
|
|
5
5
|
description: string;
|
|
6
6
|
/** New BpmnDefinitions generated by the fix (e.g. extracted reusable sub-process). */
|
|
@@ -36,6 +36,14 @@ export interface OptimizeOptions {
|
|
|
36
36
|
feelVariableThreshold?: number;
|
|
37
37
|
reuseThreshold?: number;
|
|
38
38
|
categories?: OptimizationCategory[];
|
|
39
|
+
/**
|
|
40
|
+
* Resolves a bundled connector template's missing required value keys —
|
|
41
|
+
* pass `(templateId, boundKeys) => applyConnectorTemplate(templateId,
|
|
42
|
+
* Object.fromEntries(boundKeys.map(k => [k, "x"]))).problems...` or an
|
|
43
|
+
* equivalent from `@bpmnkit/connectors`. Without it, `connector/*` findings
|
|
44
|
+
* are skipped (core has no dependency on the connector catalog).
|
|
45
|
+
*/
|
|
46
|
+
resolveConnectorRequirements?: (templateId: string, boundKeys: string[]) => string[];
|
|
39
47
|
}
|
|
40
48
|
export interface ResolvedOptions {
|
|
41
49
|
feelLengthThreshold: number;
|
|
@@ -44,5 +52,6 @@ export interface ResolvedOptions {
|
|
|
44
52
|
feelVariableThreshold: number;
|
|
45
53
|
reuseThreshold: number;
|
|
46
54
|
categories: OptimizationCategory[];
|
|
55
|
+
resolveConnectorRequirements?: (templateId: string, boundKeys: string[]) => string[];
|
|
47
56
|
}
|
|
48
57
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -53,6 +53,25 @@ export interface ZeebeCalledDecision {
|
|
|
53
53
|
/** Process variable that receives the decision result. */
|
|
54
54
|
resultVariable: string;
|
|
55
55
|
}
|
|
56
|
+
/** Zeebe user task assignment (assignee / candidate groups / candidate users). */
|
|
57
|
+
export interface ZeebeAssignmentDefinition {
|
|
58
|
+
assignee?: string;
|
|
59
|
+
candidateGroups?: string;
|
|
60
|
+
candidateUsers?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Zeebe user task scheduling (due date / follow-up date). */
|
|
63
|
+
export interface ZeebeTaskSchedule {
|
|
64
|
+
dueDate?: string;
|
|
65
|
+
followUpDate?: string;
|
|
66
|
+
}
|
|
67
|
+
/** Zeebe user task priority (0-100, default 50). */
|
|
68
|
+
export interface ZeebePriorityDefinition {
|
|
69
|
+
priority: string;
|
|
70
|
+
}
|
|
71
|
+
/** Zeebe message correlation key for a message catch/boundary/receive element. */
|
|
72
|
+
export interface ZeebeSubscription {
|
|
73
|
+
correlationKey: string;
|
|
74
|
+
}
|
|
56
75
|
/** Collected Zeebe extensions on a service task. */
|
|
57
76
|
export interface ZeebeExtensions {
|
|
58
77
|
taskDefinition?: ZeebeTaskDefinition;
|
|
@@ -66,6 +85,14 @@ export interface ZeebeExtensions {
|
|
|
66
85
|
calledDecision?: ZeebeCalledDecision;
|
|
67
86
|
/** Marks this as a Camunda 8 native user task (zeebe:userTask). */
|
|
68
87
|
userTask?: true;
|
|
88
|
+
/** User task assignee/candidates (zeebe:assignmentDefinition). */
|
|
89
|
+
assignmentDefinition?: ZeebeAssignmentDefinition;
|
|
90
|
+
/** User task due/follow-up dates (zeebe:taskSchedule). */
|
|
91
|
+
taskSchedule?: ZeebeTaskSchedule;
|
|
92
|
+
/** User task priority (zeebe:priorityDefinition). */
|
|
93
|
+
priorityDefinition?: ZeebePriorityDefinition;
|
|
94
|
+
/** Message correlation key for a message catch/boundary/receive element (zeebe:subscription). */
|
|
95
|
+
subscription?: ZeebeSubscription;
|
|
69
96
|
/** Unrecognized extension elements preserved for roundtrip. */
|
|
70
97
|
unknownElements?: XmlElement[];
|
|
71
98
|
}
|
|
@@ -91,6 +91,44 @@ export function zeebeExtensionsToXmlElements(extensions) {
|
|
|
91
91
|
children: [],
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
|
+
if (extensions.assignmentDefinition) {
|
|
95
|
+
const attrs = {};
|
|
96
|
+
const { assignee, candidateGroups, candidateUsers } = extensions.assignmentDefinition;
|
|
97
|
+
if (assignee)
|
|
98
|
+
attrs.assignee = assignee;
|
|
99
|
+
if (candidateGroups)
|
|
100
|
+
attrs.candidateGroups = candidateGroups;
|
|
101
|
+
if (candidateUsers)
|
|
102
|
+
attrs.candidateUsers = candidateUsers;
|
|
103
|
+
if (Object.keys(attrs).length > 0) {
|
|
104
|
+
elements.push({ name: "zeebe:assignmentDefinition", attributes: attrs, children: [] });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (extensions.taskSchedule) {
|
|
108
|
+
const attrs = {};
|
|
109
|
+
if (extensions.taskSchedule.dueDate)
|
|
110
|
+
attrs.dueDate = extensions.taskSchedule.dueDate;
|
|
111
|
+
if (extensions.taskSchedule.followUpDate) {
|
|
112
|
+
attrs.followUpDate = extensions.taskSchedule.followUpDate;
|
|
113
|
+
}
|
|
114
|
+
if (Object.keys(attrs).length > 0) {
|
|
115
|
+
elements.push({ name: "zeebe:taskSchedule", attributes: attrs, children: [] });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (extensions.priorityDefinition) {
|
|
119
|
+
elements.push({
|
|
120
|
+
name: "zeebe:priorityDefinition",
|
|
121
|
+
attributes: { priority: extensions.priorityDefinition.priority },
|
|
122
|
+
children: [],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (extensions.subscription) {
|
|
126
|
+
elements.push({
|
|
127
|
+
name: "zeebe:subscription",
|
|
128
|
+
attributes: { correlationKey: extensions.subscription.correlationKey },
|
|
129
|
+
children: [],
|
|
130
|
+
});
|
|
131
|
+
}
|
|
94
132
|
if (extensions.unknownElements) {
|
|
95
133
|
elements.push(...extensions.unknownElements);
|
|
96
134
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,8 +11,10 @@ export { DiagramBuilder } from "./bpmn/bpmn-builder.js";
|
|
|
11
11
|
export type { ProcessBuilder, BranchBuilder, SubProcessContentBuilder, ServiceTaskOptions, ScriptTaskOptions, UserTaskOptions, CallActivityOptions, BusinessRuleTaskOptions, ElementOptions, GatewayOptions, MultiInstanceOptions, SubProcessOptions, StartEventOptions, IntermediateCatchEventOptions, IntermediateThrowEventOptions, EndEventOptions, BoundaryEventOptions, AdHocSubProcessOptions, } from "./bpmn/bpmn-builder.js";
|
|
12
12
|
export type { BpmnDefinitions, BpmnProcess, BpmnFlowNode, BpmnFlowElement, BpmnSequenceFlow, BpmnBoundaryEvent, BpmnElementType, BpmnStartEvent, BpmnEndEvent, BpmnIntermediateCatchEvent, BpmnIntermediateThrowEvent, BpmnTask, BpmnServiceTask, BpmnScriptTask, BpmnUserTask, BpmnSendTask, BpmnReceiveTask, BpmnBusinessRuleTask, BpmnManualTask, BpmnCallActivity, BpmnSubProcess, BpmnAdHocSubProcess, BpmnEventSubProcess, BpmnTransaction, BpmnExclusiveGateway, BpmnParallelGateway, BpmnInclusiveGateway, BpmnEventBasedGateway, BpmnComplexGateway, BpmnCollaboration, BpmnParticipant, BpmnMessageFlow, BpmnLane, BpmnLaneSet, BpmnError, BpmnEscalation, BpmnMessage, BpmnSignal, BpmnTextAnnotation, BpmnAssociation, BpmnGroup, BpmnDataObject, BpmnDataObjectReference, BpmnDataStoreReference, BpmnConditionExpression, BpmnEventDefinition, BpmnTimerEventDefinition, BpmnErrorEventDefinition, BpmnEscalationEventDefinition, BpmnMessageEventDefinition, BpmnSignalEventDefinition, BpmnConditionalEventDefinition, BpmnLinkEventDefinition, BpmnCancelEventDefinition, BpmnTerminateEventDefinition, BpmnCompensateEventDefinition, BpmnMultiInstanceLoopCharacteristics, BpmnDiagram, BpmnDiPlane, BpmnDiShape, BpmnDiEdge, BpmnDiLabel, BpmnBounds, BpmnWaypoint, } from "./bpmn/bpmn-model.js";
|
|
13
13
|
export type { RestConnectorConfig, RestAuthentication, HttpMethod, } from "./bpmn/rest-connector.js";
|
|
14
|
-
export type { ZeebeExtensions, ZeebeTaskDefinition, ZeebeIoMapping, ZeebeIoMappingEntry, ZeebeTaskHeaders, ZeebeTaskHeaderEntry, ZeebeProperties, ZeebePropertyEntry, ZeebeFormDefinition, ZeebeCalledDecision, } from "./bpmn/zeebe-extensions.js";
|
|
14
|
+
export type { ZeebeExtensions, ZeebeTaskDefinition, ZeebeIoMapping, ZeebeIoMappingEntry, ZeebeTaskHeaders, ZeebeTaskHeaderEntry, ZeebeProperties, ZeebePropertyEntry, ZeebeFormDefinition, ZeebeCalledDecision, ZeebeAssignmentDefinition, ZeebeTaskSchedule, ZeebePriorityDefinition, ZeebeSubscription, } from "./bpmn/zeebe-extensions.js";
|
|
15
15
|
export { zeebeExtensionsToXmlElements } from "./bpmn/zeebe-extensions.js";
|
|
16
|
+
export { buildAiAgentSubProcess, AI_AGENT_JOB_WORKER_TASK_TYPE, AI_AGENT_DEFAULT_OUTPUT_ELEMENT, } from "./bpmn/agentic.js";
|
|
17
|
+
export type { AiAgentOptions, AiAgentModelConfig, AiAgentToolSpec, AiAgentToolParam, AiAgentToolParamType, AiAgentBuild, } from "./bpmn/agentic.js";
|
|
16
18
|
export { Dmn, layoutDmn, benchmarkDmnLayout, compactifyDmn, expandDmn } from "./dmn/index.js";
|
|
17
19
|
export type { DmnBenchmarkResult, DmnElementPosition, CompactDmn, CompactDmnDecision, CompactDmnInput, CompactDmnOutput, CompactDmnRule, } from "./dmn/index.js";
|
|
18
20
|
export { Form, compactifyForm, expandForm } from "./form/index.js";
|
|
@@ -33,6 +35,7 @@ export type { StoryRenderOptions } from "./bpmn/story.js";
|
|
|
33
35
|
export { analyzeVariableFlow, extractFeelIdentifiers } from "./bpmn/optimize/variable-flow.js";
|
|
34
36
|
export type { OptimizationReport, OptimizationFinding, OptimizationSeverity, OptimizationCategory, ApplyFixResult, OptimizeOptions, } from "./bpmn/optimize/types.js";
|
|
35
37
|
export { layoutProcess, layoutFlowNodes } from "./layout/index.js";
|
|
38
|
+
export type { LayoutEngine } from "./layout/index.js";
|
|
36
39
|
export { benchmarkLayout, compareLayouts, formatBenchmarkResult, generateAutoLayout, parseReferenceLayout, } from "./layout/index.js";
|
|
37
40
|
export type { BenchmarkResult, BoundingBox, ElementComparison, ElementPosition, FlowOrderViolation, } from "./layout/index.js";
|
|
38
41
|
export type { Bounds, LayoutEdge, LayoutNode, LayoutResult, Waypoint } from "./layout/index.js";
|
|
@@ -45,4 +48,6 @@ export type { InputVariableDef, ValidationStructure, ValidationVariableType, } f
|
|
|
45
48
|
export { exportSvg } from "./bpmn/svg.js";
|
|
46
49
|
export type { SvgExportOptions } from "./bpmn/svg.js";
|
|
47
50
|
export type { CompactDiagram, CompactElement, CompactFlow, CompactProcess, } from "./bpmn/compact.js";
|
|
51
|
+
export { compilePlan, extractPlan, mergePlan, slugify, uniqueId, } from "./plan/index.js";
|
|
52
|
+
export type { CompilePlanOptions, ConnectorApplyResult, ConnectorResolver, ExtractResult, PlanAgentTool, PlanAiAgentStep, PlanBranch, PlanBusinessRuleTaskStep, PlanCallActivityStep, PlanConnectorRef, PlanConnectorStep, PlanEndStep, PlanErrorBoundary, PlanGatewayStep, PlanInputVariable, PlanProblem, PlanRawStep, PlanReceiveTaskStep, PlanScenario, PlanScriptTaskStep, PlanSendTaskStep, PlanServiceTaskStep, PlanStartStep, PlanStep, PlanSubProcessStep, PlanTimerBoundary, PlanUserTaskStep, PlanWaitStep, ProcessPlan, SynthResult, UnsupportedElement, } from "./plan/index.js";
|
|
48
53
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export { checkDiCompleteness } from "./bpmn/di-check.js";
|
|
|
7
7
|
export { planeForElement, listPlaneElementIds } from "./bpmn/di-planes.js";
|
|
8
8
|
export { DiagramBuilder } from "./bpmn/bpmn-builder.js";
|
|
9
9
|
export { zeebeExtensionsToXmlElements } from "./bpmn/zeebe-extensions.js";
|
|
10
|
+
export { buildAiAgentSubProcess, AI_AGENT_JOB_WORKER_TASK_TYPE, AI_AGENT_DEFAULT_OUTPUT_ELEMENT, } from "./bpmn/agentic.js";
|
|
10
11
|
export { Dmn, layoutDmn, benchmarkDmnLayout, compactifyDmn, expandDmn } from "./dmn/index.js";
|
|
11
12
|
export { Form, compactifyForm, expandForm } from "./form/index.js";
|
|
12
13
|
export { FormBuilder, GroupBuilder } from "./form/form-builder.js";
|
|
@@ -23,4 +24,5 @@ export { compactify, expand } from "./bpmn/compact.js";
|
|
|
23
24
|
export { applyOperations } from "./bpmn/operations.js";
|
|
24
25
|
export { buildValidationDmn, findValidationStructure, getValidationInputNames, insertValidationStructure, removeValidationStructure, validationDecisionId, } from "./bpmn/input-validation.js";
|
|
25
26
|
export { exportSvg } from "./bpmn/svg.js";
|
|
27
|
+
export { compilePlan, extractPlan, mergePlan, slugify, uniqueId, } from "./plan/index.js";
|
|
26
28
|
//# sourceMappingURL=index.js.map
|