@bpmnkit/cli 0.0.22 → 0.0.24

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 CHANGED
@@ -108,6 +108,8 @@ casen instances list --state active
108
108
  | [`@bpmnkit/operate`](https://www.npmjs.com/package/@bpmnkit/operate) | Monitoring & operations frontend for Camunda clusters |
109
109
  | [`@bpmnkit/connector-gen`](https://www.npmjs.com/package/@bpmnkit/connector-gen) | Generate connector templates from OpenAPI specs |
110
110
  | [`@bpmnkit/proxy`](https://www.npmjs.com/package/@bpmnkit/proxy) | Local AI bridge and Camunda API proxy server |
111
+ | [`@bpmnkit/patterns`](https://www.npmjs.com/package/@bpmnkit/patterns) | Domain process patterns for BPMNKit AIKit |
112
+ | [`@bpmnkit/worker-client`](https://www.npmjs.com/package/@bpmnkit/worker-client) | Thin Zeebe REST client for standalone workers |
111
113
  | [`@bpmnkit/cli-sdk`](https://www.npmjs.com/package/@bpmnkit/cli-sdk) | Plugin authoring SDK for the casen CLI |
112
114
  | [`@bpmnkit/create-casen-plugin`](https://www.npmjs.com/package/@bpmnkit/create-casen-plugin) | Scaffold a new casen CLI plugin in seconds |
113
115
  | [`@bpmnkit/casen-report`](https://www.npmjs.com/package/@bpmnkit/casen-report) | HTML reports from Camunda 8 incident and SLA data |
@@ -7,10 +7,14 @@ import { connectorGroup } from "./connector.js";
7
7
  import { lintGroup } from "./lint.js";
8
8
  import { pluginGroup } from "./plugin.js";
9
9
  import { profileGroup } from "./profile.js";
10
+ import { proxyGroup } from "./proxy.js";
11
+ import { reebeGroup } from "./reebe.js";
10
12
  import { computeRelations } from "./relations.js";
11
13
  import { settingsGroup } from "./settings.js";
14
+ import { skillsGroup } from "./skills.js";
12
15
  import { storyGroup } from "./story.js";
13
16
  import { testGroup } from "./test.js";
17
+ import { workerStartCmd } from "./worker-start.js";
14
18
  import { workerCmd } from "./worker.js";
15
19
  // Inject custom commands into generated groups without modifying generated files.
16
20
  // Also remove the broken generated get-x-m-l commands (return text/xml, not JSON)
@@ -45,13 +49,16 @@ const sortedOtherGroups = [
45
49
  ].sort((a, b) => a.name.localeCompare(b.name));
46
50
  const workerGroup = {
47
51
  name: "worker",
48
- description: workerCmd.description,
49
- commands: [workerCmd],
52
+ description: "Run job workers — auto-complete (casen worker <type>) or start scaffolded workers (casen worker start)",
53
+ commands: [workerCmd, workerStartCmd],
50
54
  };
51
55
  /** Pinned groups shown above the separator in the main TUI menu. */
52
56
  export const pinnedGroups = [
53
57
  askGroup,
54
58
  lintGroup,
59
+ proxyGroup,
60
+ reebeGroup,
61
+ skillsGroup,
55
62
  storyGroup,
56
63
  settingsGroup,
57
64
  testGroup,
@@ -0,0 +1,33 @@
1
+ import { startServer } from "@bpmnkit/proxy";
2
+ const startCmd = {
3
+ name: "start",
4
+ description: "Start the BPMN Kit proxy server (AI bridge + Camunda API proxy)",
5
+ flags: [
6
+ {
7
+ name: "port",
8
+ description: "Port to listen on",
9
+ type: "number",
10
+ default: 3033,
11
+ },
12
+ ],
13
+ examples: [
14
+ { description: "Start on default port (3033)", command: "casen proxy start" },
15
+ { description: "Start on a custom port", command: "casen proxy start --port 4000" },
16
+ ],
17
+ async run(ctx) {
18
+ const port = ctx.flags.port ?? 3033;
19
+ ctx.output.info(`Starting BPMN Kit proxy server on port ${port}...`);
20
+ startServer(port);
21
+ // Keep the CLI alive until the user presses Ctrl+C
22
+ await new Promise((resolve) => {
23
+ process.once("SIGINT", resolve);
24
+ process.once("SIGTERM", resolve);
25
+ });
26
+ },
27
+ };
28
+ export const proxyGroup = {
29
+ name: "proxy",
30
+ description: "Start the local AI bridge and Camunda API proxy server",
31
+ commands: [startCmd],
32
+ };
33
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1,77 @@
1
+ import { spawn } from "node:child_process";
2
+ const startCmd = {
3
+ name: "start",
4
+ description: "Start the Reebe workflow engine (Zeebe-compatible REST API)",
5
+ flags: [
6
+ {
7
+ name: "port",
8
+ description: "HTTP port to listen on",
9
+ type: "number",
10
+ default: 8080,
11
+ },
12
+ {
13
+ name: "database-url",
14
+ description: "PostgreSQL database URL. Omit to use embedded SQLite (no external database required).",
15
+ type: "string",
16
+ placeholder: "postgres://user:pass@host/db",
17
+ },
18
+ {
19
+ name: "config",
20
+ description: "Path to config.toml",
21
+ type: "string",
22
+ default: "config.toml",
23
+ placeholder: "PATH",
24
+ },
25
+ ],
26
+ examples: [
27
+ {
28
+ description: "Start with embedded SQLite (no external database required)",
29
+ command: "casen reebe start",
30
+ },
31
+ {
32
+ description: "Start with PostgreSQL",
33
+ command: "casen reebe start --database-url postgres://user:pass@localhost/reebe",
34
+ },
35
+ { description: "Start on a custom port", command: "casen reebe start --port 9090" },
36
+ ],
37
+ async run(ctx) {
38
+ const port = ctx.flags.port ?? 8080;
39
+ const dbUrl = ctx.flags["database-url"];
40
+ const configPath = ctx.flags.config ?? "config.toml";
41
+ ctx.output.info(`Starting Reebe workflow engine on port ${port}...`);
42
+ ctx.output.info(dbUrl ? `Database: ${dbUrl}` : "Database: embedded SQLite");
43
+ ctx.output.info("Press Ctrl+C to stop\n");
44
+ const args = ["--port", String(port), "--config", configPath];
45
+ const env = { ...process.env, REEBE_PORT: String(port) };
46
+ if (dbUrl)
47
+ env.REEBE_DATABASE_URL = dbUrl;
48
+ await new Promise((resolve, reject) => {
49
+ const child = spawn("reebe-server", args, { stdio: "inherit", env });
50
+ child.on("error", (err) => {
51
+ const code = err.code;
52
+ if (code === "ENOENT") {
53
+ reject(new Error([
54
+ "reebe-server not found.",
55
+ "Build from source:",
56
+ " cargo install --path apps/reebe/crates/reebe-server",
57
+ ].join("\n")));
58
+ }
59
+ else {
60
+ reject(err);
61
+ }
62
+ });
63
+ child.on("close", (exitCode) => {
64
+ if (exitCode === 0 || exitCode === null)
65
+ resolve();
66
+ else
67
+ reject(new Error(`Reebe engine exited with code ${exitCode}`));
68
+ });
69
+ });
70
+ },
71
+ };
72
+ export const reebeGroup = {
73
+ name: "reebe",
74
+ description: "Start the Reebe workflow engine (drop-in Zeebe replacement, ~50 MB)",
75
+ commands: [startCmd],
76
+ };
77
+ //# sourceMappingURL=reebe.js.map
@@ -0,0 +1,74 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /**
5
+ * Install BPMNKit AIKit skills into `.claude/commands/` in the current project.
6
+ * Skills are markdown prompt files that Claude Code executes as slash commands:
7
+ * /implement, /review, /test, /deploy
8
+ */
9
+ const skillsInstallCmd = {
10
+ name: "install",
11
+ description: "Install BPMNKit AIKit slash commands into .claude/commands/",
12
+ args: [],
13
+ flags: [
14
+ {
15
+ name: "force",
16
+ short: "f",
17
+ description: "Overwrite existing skill files",
18
+ type: "boolean",
19
+ },
20
+ ],
21
+ examples: [
22
+ { description: "Install AIKit skills", command: "casen skills install" },
23
+ {
24
+ description: "Reinstall and overwrite existing skills",
25
+ command: "casen skills install --force",
26
+ },
27
+ ],
28
+ async run(ctx) {
29
+ const force = ctx.flags.force === true;
30
+ // Locate the bundled skills directory relative to this binary
31
+ // Installed layout: dist/index.js → ../skills/<name>.md
32
+ const binDir = fileURLToPath(new URL(".", import.meta.url));
33
+ const skillsSrc = join(binDir, "..", "skills");
34
+ if (!existsSync(skillsSrc)) {
35
+ throw new Error(`Bundled skills directory not found at: ${skillsSrc}`);
36
+ }
37
+ const skillFiles = readdirSync(skillsSrc).filter((f) => f.endsWith(".md"));
38
+ if (skillFiles.length === 0) {
39
+ throw new Error("No skill files found in bundled skills directory");
40
+ }
41
+ const destDir = join(process.cwd(), ".claude", "commands");
42
+ mkdirSync(destDir, { recursive: true });
43
+ let installed = 0;
44
+ let skipped = 0;
45
+ for (const file of skillFiles) {
46
+ const dest = join(destDir, file);
47
+ if (existsSync(dest) && !force) {
48
+ ctx.output.info(` skip ${file} (already exists — use --force to overwrite)`);
49
+ skipped++;
50
+ continue;
51
+ }
52
+ const content = readFileSync(join(skillsSrc, file), "utf8");
53
+ writeFileSync(dest, content, "utf8");
54
+ ctx.output.ok(` wrote ${file}`);
55
+ installed++;
56
+ }
57
+ ctx.output.info("");
58
+ ctx.output.ok(`Installed ${installed} skill(s)${skipped > 0 ? `, skipped ${skipped}` : ""} → .claude/commands/`);
59
+ ctx.output.info("");
60
+ ctx.output.info("Available slash commands in Claude Code:");
61
+ for (const file of skillFiles) {
62
+ const name = file.replace(/\.md$/, "");
63
+ ctx.output.info(` /${name}`);
64
+ }
65
+ ctx.output.info("");
66
+ ctx.output.info("Make sure the BPMNKit AIKit MCP server is configured in .claude/mcp.json");
67
+ },
68
+ };
69
+ export const skillsGroup = {
70
+ name: "skills",
71
+ description: "Manage BPMNKit AIKit slash commands for Claude Code",
72
+ commands: [skillsInstallCmd],
73
+ };
74
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1,80 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ /**
5
+ * Start scaffolded workers from the ./workers/ directory.
6
+ * Workers must have a package.json with a `bpmnkit.jobType` field (written by worker_scaffold).
7
+ * Runs `npm start` in each worker directory — workers use tsx for development convenience.
8
+ */
9
+ export const workerStartCmd = {
10
+ name: "start",
11
+ description: "Start scaffolded workers from the ./workers/ directory",
12
+ args: [
13
+ {
14
+ name: "name",
15
+ description: "Worker name to start (default: start all workers)",
16
+ required: false,
17
+ },
18
+ ],
19
+ flags: [],
20
+ examples: [
21
+ { description: "Start all scaffolded workers", command: "casen worker start" },
22
+ { description: "Start a specific worker", command: "casen worker start send-invoice" },
23
+ ],
24
+ async run(ctx) {
25
+ const filterName = ctx.positional[0];
26
+ const workersDir = join(process.cwd(), "workers");
27
+ if (!existsSync(workersDir)) {
28
+ ctx.output.info("No workers/ directory found. Use the /implement skill or worker_scaffold MCP tool to create workers.");
29
+ return;
30
+ }
31
+ // Discover scaffolded workers (those with bpmnkit.jobType in their package.json)
32
+ const entries = readdirSync(workersDir, { withFileTypes: true })
33
+ .filter((e) => e.isDirectory())
34
+ .filter((e) => {
35
+ const pkgPath = join(workersDir, e.name, "package.json");
36
+ if (!existsSync(pkgPath))
37
+ return false;
38
+ try {
39
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
40
+ return Boolean(pkg.bpmnkit?.jobType);
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ });
46
+ if (entries.length === 0) {
47
+ ctx.output.info("No scaffolded workers found in workers/");
48
+ return;
49
+ }
50
+ const toStart = filterName ? entries.filter((e) => e.name === filterName) : entries;
51
+ if (filterName && toStart.length === 0) {
52
+ throw new Error(`Worker "${filterName}" not found in workers/`);
53
+ }
54
+ ctx.output.info(`Starting ${toStart.length} worker(s) — press Ctrl+C to stop`);
55
+ ctx.output.info("");
56
+ for (const entry of toStart) {
57
+ const workerDir = join(workersDir, entry.name);
58
+ // Read job type for display
59
+ let jobType = entry.name;
60
+ try {
61
+ const pkg = JSON.parse(readFileSync(join(workerDir, "package.json"), "utf8"));
62
+ jobType = pkg.bpmnkit?.jobType ?? entry.name;
63
+ }
64
+ catch {
65
+ /* ignore */
66
+ }
67
+ ctx.output.info(` [${entry.name}] starting — job type: ${jobType}`);
68
+ const child = spawn("npm", ["start"], {
69
+ cwd: workerDir,
70
+ stdio: "inherit",
71
+ shell: true,
72
+ });
73
+ child.on("error", (err) => {
74
+ process.stderr.write(`[${entry.name}] failed to start: ${err.message}\n` +
75
+ ` Did you run \`npm install\` in workers/${entry.name}/?\n`);
76
+ });
77
+ }
78
+ },
79
+ };
80
+ //# sourceMappingURL=worker-start.js.map
package/dist/run.js CHANGED
@@ -102,9 +102,34 @@ export async function run(argv) {
102
102
  process.exitCode = 1;
103
103
  return;
104
104
  }
105
- // ── worker: treat positional[1] as the job type argument ─────────────────
105
+ // ── proxy / reebe: run start directly when no subcommand is given ────────
106
+ if ((group.name === "proxy" || group.name === "reebe") && positional.length === 1 && !wantHelp) {
107
+ const output = createOutputWriter(outputFormat, noColor);
108
+ const ctx = { positional: [], flags, output, getClient, getAdminClient };
109
+ const cmd = group.commands[0];
110
+ if (cmd)
111
+ await cmd.run(ctx);
112
+ return;
113
+ }
114
+ // ── worker: route `casen worker start [name]` or treat positional[1] as job type ──
106
115
  if (group.name === "worker" && positional.length >= 2 && !wantHelp) {
107
116
  const output = createOutputWriter(outputFormat, noColor);
117
+ // `casen worker start [name]` → route to the start command
118
+ if (positional[1] === "start") {
119
+ const startCmd = group.commands.find((c) => c.name === "start");
120
+ if (startCmd) {
121
+ const ctx = {
122
+ positional: positional.slice(2),
123
+ flags,
124
+ output,
125
+ getClient,
126
+ getAdminClient,
127
+ };
128
+ await startCmd.run(ctx);
129
+ return;
130
+ }
131
+ }
132
+ // Legacy: treat positional[1] as the job type argument for the auto-complete worker
108
133
  const ctx = {
109
134
  positional: positional.slice(1),
110
135
  flags,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmnkit/cli",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "Command-line interface for Camunda 8 — deploy, manage, and monitor processes from the terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,18 +9,20 @@
9
9
  "files": [
10
10
  "LICENSE",
11
11
  "README.md",
12
- "dist/**/*.js"
12
+ "dist/**/*.js",
13
+ "skills/**/*.md"
13
14
  ],
14
15
  "engines": {
15
16
  "node": ">=20"
16
17
  },
17
18
  "dependencies": {
18
- "@bpmnkit/api": "0.0.16",
19
- "@bpmnkit/ascii": "0.0.20",
20
- "@bpmnkit/connector-gen": "0.0.11",
21
- "@bpmnkit/core": "0.0.20",
22
- "@bpmnkit/profiles": "0.0.14",
23
- "@bpmnkit/engine": "0.1.19"
19
+ "@bpmnkit/api": "0.0.17",
20
+ "@bpmnkit/ascii": "0.0.21",
21
+ "@bpmnkit/connector-gen": "0.0.12",
22
+ "@bpmnkit/core": "0.0.21",
23
+ "@bpmnkit/engine": "0.1.20",
24
+ "@bpmnkit/profiles": "0.0.15",
25
+ "@bpmnkit/proxy": "0.0.22"
24
26
  },
25
27
  "publishConfig": {
26
28
  "access": "public"
@@ -0,0 +1,31 @@
1
+ ---
2
+ description: Deploy a BPMN process to local reebe or Camunda 8
3
+ ---
4
+
5
+ Deploy the BPMN process at the given path.
6
+
7
+ ## File to deploy
8
+
9
+ $ARGUMENTS
10
+
11
+ ---
12
+
13
+ 1. Call `mcp__bpmnkit-aikit__bpmn_validate` on the file to check for errors before deploying.
14
+ - If there are errors, show them and ask: **"Fix errors first or deploy anyway?"**
15
+ - If warnings only: show them but proceed.
16
+
17
+ 2. Ask: **"Deploy to local reebe or Camunda 8?"**
18
+
19
+ 3. Call `mcp__bpmnkit-aikit__bpmn_deploy` with the chosen target:
20
+ - `"local"` — deploys to the local reebe instance at ZEEBE_ADDRESS
21
+ - `"camunda8"` — deploys using the active casen profile (run `casen profile create` if not set up)
22
+
23
+ 4. Report the result:
24
+ - On success: "Deployed successfully. Process ID: <id>"
25
+ - On failure: show the error and suggest a fix (profile not set up, reebe not running, etc.)
26
+
27
+ 5. If any scaffolded workers exist in ./workers/, remind:
28
+ ```
29
+ Don't forget to start your workers:
30
+ casen worker start
31
+ ```
@@ -0,0 +1,91 @@
1
+ ---
2
+ description: Implement a BPMN process end-to-end from a natural language description
3
+ ---
4
+
5
+ You are implementing a BPMN process end-to-end using BPMNKit AIKit tools. Work through these steps in order.
6
+
7
+ ## Request
8
+
9
+ $ARGUMENTS
10
+
11
+ ---
12
+
13
+ ## Step 1 — Check for a domain pattern
14
+
15
+ Call `mcp__bpmnkit-aikit__pattern_list` to see available domain patterns.
16
+ If any pattern keywords match the request, call `mcp__bpmnkit-aikit__pattern_get` to load the full pattern as context for the next step.
17
+
18
+ ---
19
+
20
+ ## Step 2 — Plan: Create the BPMN
21
+
22
+ Spawn a subagent with this task:
23
+
24
+ > Using the MCP tool `mcp__bpmnkit-aikit__bpmn_create`, generate a BPMN process for: **$ARGUMENTS**
25
+ >
26
+ > If a domain pattern was loaded in Step 1, pass its readme and worker specs as additional context in the description parameter.
27
+ >
28
+ > Return the file path of the generated BPMN.
29
+
30
+ ---
31
+
32
+ ## Step 3 — Implement: Wire workers
33
+
34
+ Spawn a subagent with this task:
35
+
36
+ > You are implementing workers for a BPMN process.
37
+ >
38
+ > 1. Call `mcp__bpmnkit-aikit__worker_list` to get the catalog of available workers.
39
+ > 2. Call `mcp__bpmnkit-aikit__bpmn_read` on the BPMN file from Step 2 to find all service task job types.
40
+ > 3. For each service task job type:
41
+ > - If a built-in or previously scaffolded worker matches: note it as "reused"
42
+ > - If no match exists: call `mcp__bpmnkit-aikit__worker_scaffold` with the job type, a description, and expected inputs/outputs derived from the BPMN context
43
+ > 4. Return: a list of `{ jobType, status: "reused" | "scaffolded", workerPath? }` for each service task
44
+
45
+ ---
46
+
47
+ ## Step 4 — Review: Validate the BPMN
48
+
49
+ Spawn a subagent with this task:
50
+
51
+ > Call `mcp__bpmnkit-aikit__bpmn_validate` on the BPMN file from Step 2.
52
+ > Identify any errors that block deployment and any warnings worth noting.
53
+ > Return: `{ errors: [...], warnings: [...] }`
54
+
55
+ ---
56
+
57
+ ## Step 5 — Test: Check coverage
58
+
59
+ Spawn a subagent with this task:
60
+
61
+ > Call `mcp__bpmnkit-aikit__bpmn_simulate` on the BPMN file from Step 2 with an empty scenarios array.
62
+ > Return: worker coverage report (total service tasks, covered, missing)
63
+
64
+ ---
65
+
66
+ ## Step 6 — Present summary and ask to deploy
67
+
68
+ Collect all results and present a summary:
69
+
70
+ ```
71
+ BPMN file: <path>
72
+ Pattern used: <id or "none">
73
+
74
+ Workers:
75
+ ✓ reused: <list>
76
+ + scaffolded: <list with paths>
77
+
78
+ Validation:
79
+ Errors: <count> — <list if any>
80
+ Warnings: <count> — <list if any>
81
+
82
+ Worker coverage: <covered>/<total> service tasks
83
+
84
+ Scaffolded workers require: npm install && npm start (in each workers/<name>/ directory)
85
+ ```
86
+
87
+ Then ask: **"Deploy to local reebe, deploy to Camunda 8, or skip deployment?"**
88
+
89
+ - If "local": call `mcp__bpmnkit-aikit__bpmn_deploy` with `target: "local"`
90
+ - If "camunda8": call `mcp__bpmnkit-aikit__bpmn_deploy` with `target: "camunda8"`
91
+ - If "skip": done
@@ -0,0 +1,33 @@
1
+ ---
2
+ description: Review a BPMN file and report findings with severity and fix suggestions
3
+ ---
4
+
5
+ Review the BPMN file at the given path using BPMNKit's pattern advisor.
6
+
7
+ ## File to review
8
+
9
+ $ARGUMENTS
10
+
11
+ ---
12
+
13
+ 1. Call `mcp__bpmnkit-aikit__bpmn_validate` on the path above.
14
+
15
+ 2. Present findings grouped by severity:
16
+
17
+ **Errors** (block deployment or indicate broken process flow)
18
+ - For each error: element IDs, message, suggested fix
19
+
20
+ **Warnings** (best-practice violations, missing patterns)
21
+ - For each warning: element IDs, message, suggested fix
22
+
23
+ **Info** (improvement suggestions)
24
+ - For each info item: message
25
+
26
+ 3. Show a summary:
27
+ ```
28
+ Total: <n> findings — <errors> errors, <warnings> warnings, <info> info
29
+ Auto-fixable: <n>
30
+ ```
31
+
32
+ 4. If there are auto-fixable findings, ask: **"Apply auto-fixes?"**
33
+ If yes, call `mcp__bpmnkit-aikit__bpmn_update` with instruction: "Apply all auto-fixable pattern advisor suggestions"
package/skills/test.md ADDED
@@ -0,0 +1,36 @@
1
+ ---
2
+ description: Analyse a BPMN process — check worker coverage and validation findings
3
+ ---
4
+
5
+ Analyse the BPMN process at the given path.
6
+
7
+ ## File to test
8
+
9
+ $ARGUMENTS
10
+
11
+ ---
12
+
13
+ 1. Call `mcp__bpmnkit-aikit__bpmn_read` to understand the process structure (elements, service tasks, gateways, event types).
14
+
15
+ 2. Call `mcp__bpmnkit-aikit__bpmn_simulate` with the path and an empty scenarios array to get worker coverage and validation analysis.
16
+
17
+ 3. Call `mcp__bpmnkit-aikit__worker_list` to show the full worker catalog.
18
+
19
+ 4. Present the results:
20
+
21
+ **Process structure**
22
+ - Pools / participants
23
+ - Service tasks and their job types
24
+ - Decision gateways and branch conditions
25
+ - Event types (timer, message, error, escalation)
26
+
27
+ **Worker coverage**
28
+ - ✓ Covered job types (matched to built-in or scaffolded workers)
29
+ - ✗ Missing job types (no worker found — scaffold with worker_scaffold)
30
+
31
+ **Validation findings**
32
+ - Errors and warnings from the pattern advisor
33
+
34
+ **Suggested test scenarios** (derived from the BPMN structure)
35
+ - Happy path: <describe the main success path>
36
+ - Edge cases: <describe key branches, timeouts, error conditions>