@diff-review-system/drs 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/.pi/agents/review/unified-reviewer.md +2 -6
  2. package/.pi/agents/task/review-assistant.md +22 -0
  3. package/.pi/workflows/github-pr-fix-review-issues-stacked.yaml +10 -4
  4. package/.pi/workflows/github-pr-review-post.yaml +0 -1
  5. package/.pi/workflows/github-pr-review.yaml +14 -8
  6. package/.pi/workflows/github-pr-visual-explain.yaml +46 -1
  7. package/.pi/workflows/gitlab-mr-fix-review-issues-stacked.yaml +10 -4
  8. package/.pi/workflows/gitlab-mr-review.yaml +14 -8
  9. package/.pi/workflows/gitlab-mr-visual-explain.yaml +46 -1
  10. package/.pi/workflows/local-fix-review-issues.yaml +13 -6
  11. package/.pi/workflows/local-review.yaml +7 -1
  12. package/.pi/workflows/local-visual-explain.yaml +46 -1
  13. package/.pi/workflows/temporal-control-smoke.yaml +63 -0
  14. package/README.md +39 -0
  15. package/dist/cli/index.js +211 -4
  16. package/dist/cli/index.js.map +1 -1
  17. package/dist/cli/init.d.ts +6 -1
  18. package/dist/cli/init.d.ts.map +1 -1
  19. package/dist/cli/init.js +29 -10
  20. package/dist/cli/init.js.map +1 -1
  21. package/dist/cli/run-agent.d.ts +2 -7
  22. package/dist/cli/run-agent.d.ts.map +1 -1
  23. package/dist/cli/run-agent.js.map +1 -1
  24. package/dist/cli/workflow-executor-selection.d.ts +3 -0
  25. package/dist/cli/workflow-executor-selection.d.ts.map +1 -0
  26. package/dist/cli/workflow-executor-selection.js +15 -0
  27. package/dist/cli/workflow-executor-selection.js.map +1 -0
  28. package/dist/cli/workflow.d.ts +38 -42
  29. package/dist/cli/workflow.d.ts.map +1 -1
  30. package/dist/cli/workflow.js +190 -838
  31. package/dist/cli/workflow.js.map +1 -1
  32. package/dist/lib/agent-result.d.ts +8 -0
  33. package/dist/lib/agent-result.d.ts.map +1 -0
  34. package/dist/lib/agent-result.js +2 -0
  35. package/dist/lib/agent-result.js.map +1 -0
  36. package/dist/lib/config.d.ts +21 -0
  37. package/dist/lib/config.d.ts.map +1 -1
  38. package/dist/lib/config.js.map +1 -1
  39. package/dist/lib/output-paths.d.ts +0 -1
  40. package/dist/lib/output-paths.d.ts.map +1 -1
  41. package/dist/lib/output-paths.js +0 -1
  42. package/dist/lib/output-paths.js.map +1 -1
  43. package/dist/lib/project-setup.d.ts +10 -0
  44. package/dist/lib/project-setup.d.ts.map +1 -0
  45. package/dist/lib/project-setup.js +25 -0
  46. package/dist/lib/project-setup.js.map +1 -0
  47. package/dist/lib/review-artifact-store.d.ts +16 -0
  48. package/dist/lib/review-artifact-store.d.ts.map +1 -0
  49. package/dist/lib/review-artifact-store.js +84 -0
  50. package/dist/lib/review-artifact-store.js.map +1 -0
  51. package/dist/lib/review-artifact.d.ts.map +1 -1
  52. package/dist/lib/review-artifact.js +1 -0
  53. package/dist/lib/review-artifact.js.map +1 -1
  54. package/dist/lib/review-core.d.ts +1 -0
  55. package/dist/lib/review-core.d.ts.map +1 -1
  56. package/dist/lib/review-core.js +11 -14
  57. package/dist/lib/review-core.js.map +1 -1
  58. package/dist/lib/review-parser.d.ts.map +1 -1
  59. package/dist/lib/review-parser.js +15 -12
  60. package/dist/lib/review-parser.js.map +1 -1
  61. package/dist/lib/skills.d.ts +16 -0
  62. package/dist/lib/skills.d.ts.map +1 -0
  63. package/dist/lib/skills.js +140 -0
  64. package/dist/lib/skills.js.map +1 -0
  65. package/dist/lib/workflow/artifact-store.d.ts +71 -0
  66. package/dist/lib/workflow/artifact-store.d.ts.map +1 -0
  67. package/dist/lib/workflow/artifact-store.js +121 -0
  68. package/dist/lib/workflow/artifact-store.js.map +1 -0
  69. package/dist/lib/workflow/compiled-plan.d.ts +87 -0
  70. package/dist/lib/workflow/compiled-plan.d.ts.map +1 -0
  71. package/dist/lib/workflow/compiled-plan.js +62 -0
  72. package/dist/lib/workflow/compiled-plan.js.map +1 -0
  73. package/dist/lib/workflow/executor.d.ts +18 -0
  74. package/dist/lib/workflow/executor.d.ts.map +1 -0
  75. package/dist/lib/workflow/executor.js +2 -0
  76. package/dist/lib/workflow/executor.js.map +1 -0
  77. package/dist/lib/workflow/graph.d.ts +30 -0
  78. package/dist/lib/workflow/graph.d.ts.map +1 -0
  79. package/dist/lib/workflow/graph.js +139 -0
  80. package/dist/lib/workflow/graph.js.map +1 -0
  81. package/dist/lib/workflow/input.d.ts +4 -0
  82. package/dist/lib/workflow/input.d.ts.map +1 -0
  83. package/dist/lib/workflow/input.js +4 -0
  84. package/dist/lib/workflow/input.js.map +1 -0
  85. package/dist/lib/workflow/node-executor.d.ts +19 -0
  86. package/dist/lib/workflow/node-executor.d.ts.map +1 -0
  87. package/dist/lib/workflow/node-executor.js +2 -0
  88. package/dist/lib/workflow/node-executor.js.map +1 -0
  89. package/dist/lib/workflow/planning.d.ts +51 -0
  90. package/dist/lib/workflow/planning.d.ts.map +1 -0
  91. package/dist/lib/workflow/planning.js +814 -0
  92. package/dist/lib/workflow/planning.js.map +1 -0
  93. package/dist/lib/workflow/types.d.ts +66 -0
  94. package/dist/lib/workflow/types.d.ts.map +1 -0
  95. package/dist/lib/workflow/types.js +2 -0
  96. package/dist/lib/workflow/types.js.map +1 -0
  97. package/dist/lib/write-json-output.d.ts.map +1 -1
  98. package/dist/lib/write-json-output.js +3 -8
  99. package/dist/lib/write-json-output.js.map +1 -1
  100. package/dist/pi/sdk.d.ts.map +1 -1
  101. package/dist/pi/sdk.js +6 -2
  102. package/dist/pi/sdk.js.map +1 -1
  103. package/dist/runtime/built-in-paths.d.ts +1 -0
  104. package/dist/runtime/built-in-paths.d.ts.map +1 -1
  105. package/dist/runtime/built-in-paths.js +7 -0
  106. package/dist/runtime/built-in-paths.js.map +1 -1
  107. package/dist/temporal/activities.d.ts +26 -0
  108. package/dist/temporal/activities.d.ts.map +1 -0
  109. package/dist/temporal/activities.js +219 -0
  110. package/dist/temporal/activities.js.map +1 -0
  111. package/dist/temporal/config.d.ts +4 -0
  112. package/dist/temporal/config.d.ts.map +1 -0
  113. package/dist/temporal/config.js +36 -0
  114. package/dist/temporal/config.js.map +1 -0
  115. package/dist/temporal/executor.d.ts +7 -0
  116. package/dist/temporal/executor.d.ts.map +1 -0
  117. package/dist/temporal/executor.js +233 -0
  118. package/dist/temporal/executor.js.map +1 -0
  119. package/dist/temporal/retry-policy.d.ts +28 -0
  120. package/dist/temporal/retry-policy.d.ts.map +1 -0
  121. package/dist/temporal/retry-policy.js +56 -0
  122. package/dist/temporal/retry-policy.js.map +1 -0
  123. package/dist/temporal/types.d.ts +86 -0
  124. package/dist/temporal/types.d.ts.map +1 -0
  125. package/dist/temporal/types.js +2 -0
  126. package/dist/temporal/types.js.map +1 -0
  127. package/dist/temporal/worker.d.ts +3 -0
  128. package/dist/temporal/worker.d.ts.map +1 -0
  129. package/dist/temporal/worker.js +18 -0
  130. package/dist/temporal/worker.js.map +1 -0
  131. package/dist/temporal/workflow-id.d.ts +27 -0
  132. package/dist/temporal/workflow-id.d.ts.map +1 -0
  133. package/dist/temporal/workflow-id.js +53 -0
  134. package/dist/temporal/workflow-id.js.map +1 -0
  135. package/dist/temporal/workflows.d.ts +6 -0
  136. package/dist/temporal/workflows.d.ts.map +1 -0
  137. package/dist/temporal/workflows.js +342 -0
  138. package/dist/temporal/workflows.js.map +1 -0
  139. package/package.json +6 -1
@@ -0,0 +1,814 @@
1
+ export function getNodeNeeds(node) {
2
+ if (node.needs === undefined) {
3
+ return [];
4
+ }
5
+ if (!Array.isArray(node.needs)) {
6
+ throw new Error('Workflow node "needs" must be an array of node ids.');
7
+ }
8
+ return node.needs;
9
+ }
10
+ export function getControlTargets(node) {
11
+ const targets = [];
12
+ if (node.target)
13
+ targets.push(node.target);
14
+ if (node.exit)
15
+ targets.push(node.exit);
16
+ if (node.default)
17
+ targets.push(node.default);
18
+ if (node.cases)
19
+ targets.push(...Object.values(node.cases));
20
+ return targets;
21
+ }
22
+ export function validateWorkflowControlTargets(nodes) {
23
+ for (const [nodeId, node] of Object.entries(nodes)) {
24
+ for (const target of getControlTargets(node)) {
25
+ if (!nodes[target]) {
26
+ throw new Error(`Workflow node "${nodeId}" targets unknown node "${target}".`);
27
+ }
28
+ }
29
+ }
30
+ }
31
+ export function validateWorkflowControlRouteDirection(nodes, executionOrder) {
32
+ const segments = splitWorkflowSegments(nodes, executionOrder);
33
+ for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
34
+ const segment = segments[segmentIndex];
35
+ if (segment.type !== 'control') {
36
+ continue;
37
+ }
38
+ const node = nodes[segment.nodeId];
39
+ if (!node || node.control === 'loop') {
40
+ continue;
41
+ }
42
+ for (const target of getControlTargets(node)) {
43
+ const targetIndex = findWorkflowSegmentIndex(segments, target);
44
+ if (targetIndex <= segmentIndex) {
45
+ throw new Error(`Workflow control node "${segment.nodeId}" cannot jump backward to "${target}". Use control: loop with maxIterations for repeated execution.`);
46
+ }
47
+ }
48
+ }
49
+ }
50
+ export function validateWorkflowPassThroughShape(nodeId, node) {
51
+ if (node.control !== 'passThrough') {
52
+ return;
53
+ }
54
+ if (!node.target) {
55
+ throw new Error(`Workflow passThrough node "${nodeId}" must define "target".`);
56
+ }
57
+ const forbiddenKeys = [
58
+ 'if',
59
+ 'then',
60
+ 'else',
61
+ 'exit',
62
+ 'cases',
63
+ 'default',
64
+ 'maxIterations',
65
+ 'onMaxIterations',
66
+ 'value',
67
+ ];
68
+ const extraFields = forbiddenKeys.filter((key) => key in node);
69
+ if (extraFields.length > 0) {
70
+ throw new Error(`Workflow passThrough node "${nodeId}" must not define extra control logic: ${extraFields.join(', ')}.`);
71
+ }
72
+ }
73
+ export const WORKFLOW_NODE_FIELDS = new Set([
74
+ 'agent',
75
+ 'agentsFrom',
76
+ 'control',
77
+ 'action',
78
+ 'with',
79
+ 'needs',
80
+ 'if',
81
+ 'target',
82
+ 'exit',
83
+ 'maxIterations',
84
+ 'onMaxIterations',
85
+ 'value',
86
+ 'cases',
87
+ 'default',
88
+ 'input',
89
+ 'output',
90
+ 'writes',
91
+ 'json',
92
+ ]);
93
+ export const EXECUTABLE_NODE_FIELDS = new Set([
94
+ 'agent',
95
+ 'agentsFrom',
96
+ 'action',
97
+ 'with',
98
+ 'needs',
99
+ 'if',
100
+ 'input',
101
+ 'output',
102
+ 'writes',
103
+ 'json',
104
+ ]);
105
+ export const CONTROL_NODE_FIELDS = {
106
+ loop: new Set([
107
+ 'control',
108
+ 'needs',
109
+ 'if',
110
+ 'target',
111
+ 'exit',
112
+ 'maxIterations',
113
+ 'onMaxIterations',
114
+ 'output',
115
+ ]),
116
+ switch: new Set(['control', 'needs', 'value', 'cases', 'default', 'output']),
117
+ end: new Set(['control', 'needs', 'output']),
118
+ passThrough: new Set(['control', 'needs', 'target', 'output']),
119
+ };
120
+ export const ACTION_OPTION_FIELDS = {
121
+ write: new Set(),
122
+ 'git-diff': new Set(['staged']),
123
+ 'git-add': new Set(['path', 'paths']),
124
+ 'git-branch': new Set(['name', 'from', 'force']),
125
+ 'git-commit': new Set(['message', 'path', 'paths']),
126
+ 'git-push': new Set(['remote', 'branch', 'remoteBranch', 'setUpstream', 'force']),
127
+ 'has-diff': new Set(['path', 'paths']),
128
+ 'stack-guard': new Set(['source', 'allowStackedSource', 'reservedPrefixes']),
129
+ 'review-threshold': new Set(['review', 'severity', 'minIssues']),
130
+ 'save-artifact': new Set([
131
+ 'kind',
132
+ 'source',
133
+ 'artifact',
134
+ 'payload',
135
+ 'platform',
136
+ 'project',
137
+ 'projectId',
138
+ 'owner',
139
+ 'repo',
140
+ 'subject',
141
+ 'changeKind',
142
+ 'changeNumber',
143
+ 'pr',
144
+ 'mr',
145
+ 'branch',
146
+ ]),
147
+ 'load-artifact': new Set([
148
+ 'kind',
149
+ 'source',
150
+ 'id',
151
+ 'platform',
152
+ 'project',
153
+ 'projectId',
154
+ 'owner',
155
+ 'repo',
156
+ 'subject',
157
+ 'changeKind',
158
+ 'changeNumber',
159
+ 'pr',
160
+ 'mr',
161
+ 'branch',
162
+ ]),
163
+ 'artifact-exists': new Set([
164
+ 'kind',
165
+ 'source',
166
+ 'id',
167
+ 'platform',
168
+ 'project',
169
+ 'projectId',
170
+ 'owner',
171
+ 'repo',
172
+ 'subject',
173
+ 'changeKind',
174
+ 'changeNumber',
175
+ 'pr',
176
+ 'mr',
177
+ 'branch',
178
+ ]),
179
+ 'create-review-artifact': new Set(['source', 'review']),
180
+ 'review-artifact-status': new Set(['artifact']),
181
+ 'review-artifact-add-finding': new Set(['artifact', 'issue', 'source']),
182
+ 'review-artifact-update-findings': new Set([
183
+ 'artifact',
184
+ 'state',
185
+ 'disposition',
186
+ 'ids',
187
+ 'fingerprints',
188
+ 'severity',
189
+ ]),
190
+ 'review-artifact-promote-finding': new Set(['artifact', 'ids', 'fingerprints', 'severity']),
191
+ 'review-artifact-resolve-finding': new Set(['artifact', 'ids', 'fingerprints', 'severity']),
192
+ 'verify-fix': new Set(['artifact', 'review', 'fixChange', 'severity', 'minIssues']),
193
+ 'create-change-request': new Set([
194
+ 'platform',
195
+ 'owner',
196
+ 'repo',
197
+ 'project',
198
+ 'projectId',
199
+ 'sourceBranch',
200
+ 'head',
201
+ 'targetBranch',
202
+ 'base',
203
+ 'title',
204
+ 'body',
205
+ 'draft',
206
+ 'reuseExisting',
207
+ ]),
208
+ 'create-pr': new Set([
209
+ 'platform',
210
+ 'owner',
211
+ 'repo',
212
+ 'project',
213
+ 'projectId',
214
+ 'sourceBranch',
215
+ 'head',
216
+ 'targetBranch',
217
+ 'base',
218
+ 'title',
219
+ 'body',
220
+ 'draft',
221
+ 'reuseExisting',
222
+ ]),
223
+ 'create-mr': new Set([
224
+ 'platform',
225
+ 'owner',
226
+ 'repo',
227
+ 'project',
228
+ 'projectId',
229
+ 'sourceBranch',
230
+ 'head',
231
+ 'targetBranch',
232
+ 'base',
233
+ 'title',
234
+ 'body',
235
+ 'draft',
236
+ 'reuseExisting',
237
+ ]),
238
+ 'change-source': new Set([
239
+ 'type',
240
+ 'staged',
241
+ 'from',
242
+ 'to',
243
+ 'includePrereleaseFrom',
244
+ 'owner',
245
+ 'repo',
246
+ 'pr',
247
+ 'project',
248
+ 'projectId',
249
+ 'mr',
250
+ 'mrIid',
251
+ 'source',
252
+ 'fixChange',
253
+ ]),
254
+ review: new Set(['source', 'reviewArtifact', 'severity', 'artifact']),
255
+ 'review-context': new Set(['source', 'file', 'baseBranch']),
256
+ describe: new Set(['source', 'post', 'postDescription']),
257
+ 'code-quality-report': new Set(['review', 'path']),
258
+ 'post-comment': new Set([
259
+ 'source',
260
+ 'platform',
261
+ 'owner',
262
+ 'repo',
263
+ 'project',
264
+ 'projectId',
265
+ 'pr',
266
+ 'mr',
267
+ 'prNumber',
268
+ 'mrIid',
269
+ 'body',
270
+ 'marker',
271
+ ]),
272
+ 'post-review-comments': new Set(['source', 'review', 'removeErrorComment']),
273
+ 'post-fix-status': new Set([
274
+ 'platform',
275
+ 'owner',
276
+ 'repo',
277
+ 'project',
278
+ 'projectId',
279
+ 'pr',
280
+ 'mr',
281
+ 'source',
282
+ 'reviewArtifact',
283
+ 'fixReview',
284
+ 'fixChange',
285
+ 'severity',
286
+ 'stackedPrUrl',
287
+ 'marker',
288
+ ]),
289
+ };
290
+ export function validateAllowedFields(nodeId, value, allowed, subject) {
291
+ const unknownFields = Object.keys(value).filter((key) => !allowed.has(key));
292
+ if (unknownFields.length > 0) {
293
+ throw new Error(`Workflow ${subject} "${nodeId}" has unsupported field(s): ${unknownFields.join(', ')}.`);
294
+ }
295
+ }
296
+ export function validateWorkflowNodeShape(nodeId, node) {
297
+ validateAllowedFields(nodeId, node, WORKFLOW_NODE_FIELDS, 'node');
298
+ const kind = getNodeKind(node);
299
+ if (kind === 'control') {
300
+ const control = node.control;
301
+ if (!control || !CONTROL_NODE_FIELDS[control]) {
302
+ throw new Error(`Workflow control node "${nodeId}" has unsupported control "${String(control)}".`);
303
+ }
304
+ validateAllowedFields(nodeId, node, CONTROL_NODE_FIELDS[control], 'control node');
305
+ return;
306
+ }
307
+ validateAllowedFields(nodeId, node, EXECUTABLE_NODE_FIELDS, 'node');
308
+ if (node.action && node.with) {
309
+ const allowed = ACTION_OPTION_FIELDS[node.action];
310
+ if (!allowed) {
311
+ throw new Error(`Workflow action node "${nodeId}" has unsupported action "${node.action}".`);
312
+ }
313
+ validateAllowedFields(nodeId, node.with, allowed, `node "${nodeId}" with`);
314
+ }
315
+ }
316
+ export function validateWorkflowNodeKinds(nodes) {
317
+ for (const [nodeId, node] of Object.entries(nodes)) {
318
+ validateWorkflowNodeShape(nodeId, node);
319
+ validateWorkflowPassThroughShape(nodeId, node);
320
+ }
321
+ }
322
+ export function hasWorkflowControlNodes(nodes) {
323
+ return Object.values(nodes).some((node) => node.control !== undefined);
324
+ }
325
+ export function getWorkflowExecutionOrder(nodes) {
326
+ const nodeIds = Object.keys(nodes);
327
+ const visiting = new Set();
328
+ const visited = new Set();
329
+ const order = [];
330
+ function visit(nodeId) {
331
+ if (visited.has(nodeId)) {
332
+ return;
333
+ }
334
+ if (visiting.has(nodeId)) {
335
+ throw new Error(`Workflow contains a dependency cycle at node "${nodeId}".`);
336
+ }
337
+ const node = nodes[nodeId];
338
+ if (!node) {
339
+ throw new Error(`Workflow references unknown node "${nodeId}".`);
340
+ }
341
+ visiting.add(nodeId);
342
+ for (const dependency of getNodeNeeds(node)) {
343
+ if (!nodes[dependency]) {
344
+ throw new Error(`Workflow node "${nodeId}" needs unknown node "${dependency}".`);
345
+ }
346
+ visit(dependency);
347
+ }
348
+ visiting.delete(nodeId);
349
+ visited.add(nodeId);
350
+ order.push(nodeId);
351
+ }
352
+ for (const nodeId of nodeIds) {
353
+ visit(nodeId);
354
+ }
355
+ validateWorkflowNodeKinds(nodes);
356
+ validateWorkflowControlTargets(nodes);
357
+ const standaloneEndNodes = order.filter((nodeId) => nodes[nodeId]?.control === 'end' && getNodeNeeds(nodes[nodeId] ?? {}).length === 0);
358
+ const nonStandaloneEndNodes = order.filter((nodeId) => !standaloneEndNodes.includes(nodeId));
359
+ const executionOrder = [...nonStandaloneEndNodes, ...standaloneEndNodes];
360
+ validateWorkflowControlRouteDirection(nodes, executionOrder);
361
+ return executionOrder;
362
+ }
363
+ export function getWorkflowNodes(workflowName, workflow) {
364
+ const nodes = workflow.nodes;
365
+ if (typeof nodes !== 'object' ||
366
+ nodes === null ||
367
+ Array.isArray(nodes) ||
368
+ Object.keys(nodes).length === 0) {
369
+ throw new Error(`Workflow "${workflowName}" must define at least one node.`);
370
+ }
371
+ return nodes;
372
+ }
373
+ export function getWorkflowExecutionWaves(nodes, executionOrder) {
374
+ const depthByNode = new Map();
375
+ const waves = [];
376
+ for (const nodeId of executionOrder) {
377
+ const node = nodes[nodeId];
378
+ if (!node) {
379
+ throw new Error(`Workflow references unknown node "${nodeId}".`);
380
+ }
381
+ const depth = getNodeNeeds(node).reduce((maxDepth, dependency) => {
382
+ return Math.max(maxDepth, (depthByNode.get(dependency) ?? 0) + 1);
383
+ }, 0);
384
+ depthByNode.set(nodeId, depth);
385
+ waves[depth] = waves[depth] ?? [];
386
+ waves[depth].push(nodeId);
387
+ }
388
+ return waves;
389
+ }
390
+ export function getPathValue(root, path) {
391
+ return path.split('.').reduce((current, part) => {
392
+ if (current === undefined || current === null) {
393
+ return undefined;
394
+ }
395
+ if (typeof current !== 'object') {
396
+ return undefined;
397
+ }
398
+ return current[part];
399
+ }, root);
400
+ }
401
+ export function stringifyTemplateValue(value) {
402
+ if (value === undefined) {
403
+ return '';
404
+ }
405
+ if (typeof value === 'string') {
406
+ return value;
407
+ }
408
+ return JSON.stringify(value, null, 2);
409
+ }
410
+ export function renderTemplate(template, context) {
411
+ return template.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_match, rawPath) => {
412
+ const path = rawPath.trim();
413
+ const value = getPathValue(context, path);
414
+ if (value === undefined) {
415
+ throw new Error(`Unknown workflow template value "{{${path}}}".`);
416
+ }
417
+ return stringifyTemplateValue(value);
418
+ });
419
+ }
420
+ export function getNodeKind(node) {
421
+ const configuredKinds = [node.agent, node.agentsFrom, node.action, node.control].filter((value) => value !== undefined).length;
422
+ if (configuredKinds !== 1) {
423
+ throw new Error('Workflow node must define exactly one of agent, agentsFrom, action, or control.');
424
+ }
425
+ if (node.agent !== undefined)
426
+ return 'agent';
427
+ if (node.agentsFrom !== undefined)
428
+ return 'agents';
429
+ if (node.control !== undefined)
430
+ return 'control';
431
+ return 'action';
432
+ }
433
+ export function parseWorkflowExpressionValue(value, context) {
434
+ const trimmed = value.trim();
435
+ if (!trimmed)
436
+ return '';
437
+ const templateReference = trimmed.match(/^\{\{\s*([^}]+?)\s*\}\}$/);
438
+ if (templateReference && context) {
439
+ const path = templateReference[1]?.trim() ?? '';
440
+ const resolved = getPathValue(context, path);
441
+ if (resolved === undefined) {
442
+ throw new Error(`Unknown workflow template value "{{${path}}}".`);
443
+ }
444
+ return resolved;
445
+ }
446
+ if (trimmed === 'true')
447
+ return true;
448
+ if (trimmed === 'false')
449
+ return false;
450
+ if (trimmed === 'null')
451
+ return null;
452
+ if (/^-?\d+(\.\d+)?$/.test(trimmed))
453
+ return Number(trimmed);
454
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
455
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
456
+ const inner = trimmed.slice(1, -1);
457
+ return context ? renderTemplate(inner, context) : inner;
458
+ }
459
+ if (context && /^(inputs|nodes|artifacts|loop)\.[A-Za-z0-9_.-]+$/.test(trimmed)) {
460
+ const resolved = getPathValue(context, trimmed);
461
+ if (resolved === undefined) {
462
+ throw new Error(`Unknown workflow expression value "${trimmed}".`);
463
+ }
464
+ return resolved;
465
+ }
466
+ try {
467
+ return JSON.parse(trimmed);
468
+ }
469
+ catch {
470
+ return trimmed;
471
+ }
472
+ }
473
+ export function isWorkflowTruthy(value) {
474
+ if (value === undefined || value === null)
475
+ return false;
476
+ if (typeof value === 'boolean')
477
+ return value;
478
+ if (typeof value === 'number')
479
+ return value !== 0;
480
+ if (typeof value === 'string') {
481
+ const normalized = value.trim().toLowerCase();
482
+ return (normalized.length > 0 && normalized !== 'false' && normalized !== '0' && normalized !== 'no');
483
+ }
484
+ if (Array.isArray(value))
485
+ return value.length > 0;
486
+ if (typeof value === 'object')
487
+ return Object.keys(value).length > 0;
488
+ return true;
489
+ }
490
+ export function normalizeWorkflowBooleanLike(value) {
491
+ if (typeof value === 'boolean')
492
+ return value;
493
+ if (typeof value === 'number') {
494
+ if (value === 1)
495
+ return true;
496
+ if (value === 0)
497
+ return false;
498
+ }
499
+ if (typeof value === 'string') {
500
+ const normalized = value.trim().toLowerCase();
501
+ if (normalized === 'true' || normalized === '1' || normalized === 'yes')
502
+ return true;
503
+ if (normalized === 'false' || normalized === '0' || normalized === 'no')
504
+ return false;
505
+ }
506
+ return undefined;
507
+ }
508
+ export function compareWorkflowValues(left, operator, right) {
509
+ if (operator === '==' || operator === '!=') {
510
+ const leftBoolean = normalizeWorkflowBooleanLike(left);
511
+ const rightBoolean = normalizeWorkflowBooleanLike(right);
512
+ const matches = leftBoolean !== undefined || rightBoolean !== undefined
513
+ ? leftBoolean === rightBoolean
514
+ : String(left) === String(right);
515
+ return operator === '==' ? matches : !matches;
516
+ }
517
+ const leftNumber = Number(left);
518
+ const rightNumber = Number(right);
519
+ if (!Number.isFinite(leftNumber) || !Number.isFinite(rightNumber)) {
520
+ throw new Error(`Workflow expression operator "${operator}" requires numeric values.`);
521
+ }
522
+ if (operator === '>')
523
+ return leftNumber > rightNumber;
524
+ if (operator === '>=')
525
+ return leftNumber >= rightNumber;
526
+ if (operator === '<')
527
+ return leftNumber < rightNumber;
528
+ if (operator === '<=')
529
+ return leftNumber <= rightNumber;
530
+ throw new Error(`Unsupported workflow expression operator "${operator}".`);
531
+ }
532
+ export function splitWorkflowExpressionOperator(expression, operator) {
533
+ const parts = [];
534
+ let current = '';
535
+ let quote;
536
+ let depth = 0;
537
+ for (let i = 0; i < expression.length; i++) {
538
+ const char = expression[i] ?? '';
539
+ if ((char === '"' || char === "'") && expression[i - 1] !== '\\') {
540
+ quote = quote === char ? undefined : (quote ?? char);
541
+ current += char;
542
+ continue;
543
+ }
544
+ if (!quote && char === '(') {
545
+ depth += 1;
546
+ current += char;
547
+ continue;
548
+ }
549
+ if (!quote && char === ')' && depth > 0) {
550
+ depth -= 1;
551
+ current += char;
552
+ continue;
553
+ }
554
+ if (!quote && depth === 0 && expression.slice(i, i + operator.length) === operator) {
555
+ parts.push(current.trim());
556
+ current = '';
557
+ i += operator.length - 1;
558
+ continue;
559
+ }
560
+ current += char;
561
+ }
562
+ parts.push(current.trim());
563
+ return parts;
564
+ }
565
+ export function stripWorkflowExpressionParens(expression) {
566
+ const trimmed = expression.trim();
567
+ if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
568
+ return trimmed;
569
+ }
570
+ let quote;
571
+ let depth = 0;
572
+ for (let i = 0; i < trimmed.length; i++) {
573
+ const char = trimmed[i] ?? '';
574
+ if ((char === '"' || char === "'") && trimmed[i - 1] !== '\\') {
575
+ quote = quote === char ? undefined : (quote ?? char);
576
+ continue;
577
+ }
578
+ if (quote)
579
+ continue;
580
+ if (char === '(')
581
+ depth += 1;
582
+ if (char === ')')
583
+ depth -= 1;
584
+ if (depth === 0 && i < trimmed.length - 1) {
585
+ return trimmed;
586
+ }
587
+ }
588
+ return trimmed.slice(1, -1).trim();
589
+ }
590
+ export function evaluateWorkflowExpressionText(rendered, context) {
591
+ rendered = stripWorkflowExpressionParens(rendered);
592
+ const orParts = splitWorkflowExpressionOperator(rendered, '||');
593
+ if (orParts.length > 1) {
594
+ return orParts.some((part) => evaluateWorkflowExpressionText(part, context));
595
+ }
596
+ const andParts = splitWorkflowExpressionOperator(rendered, '&&');
597
+ if (andParts.length > 1) {
598
+ return andParts.every((part) => evaluateWorkflowExpressionText(part, context));
599
+ }
600
+ const match = rendered.match(/^(.+?)\s*(==|!=|>=|<=|>|<)\s*(.+)$/);
601
+ if (!match) {
602
+ return isWorkflowTruthy(parseWorkflowExpressionValue(rendered, context));
603
+ }
604
+ return compareWorkflowValues(parseWorkflowExpressionValue(match[1] ?? '', context), match[2] ?? '', parseWorkflowExpressionValue(match[3] ?? '', context));
605
+ }
606
+ export function evaluateWorkflowExpression(expression, context) {
607
+ return evaluateWorkflowExpressionText(expression.trim(), context);
608
+ }
609
+ export function splitWorkflowSegments(workflowNodes, executionOrder) {
610
+ const segments = [];
611
+ let currentDag = [];
612
+ for (const nodeId of executionOrder) {
613
+ const node = workflowNodes[nodeId];
614
+ if (!node) {
615
+ throw new Error(`Workflow references unknown node "${nodeId}".`);
616
+ }
617
+ if (node.control !== undefined) {
618
+ if (currentDag.length > 0) {
619
+ segments.push({ type: 'dag', nodeIds: currentDag });
620
+ currentDag = [];
621
+ }
622
+ segments.push({ type: 'control', nodeId });
623
+ }
624
+ else {
625
+ currentDag.push(nodeId);
626
+ }
627
+ }
628
+ if (currentDag.length > 0) {
629
+ segments.push({ type: 'dag', nodeIds: currentDag });
630
+ }
631
+ return segments;
632
+ }
633
+ export function findWorkflowSegmentIndex(segments, targetNodeId) {
634
+ return segments.findIndex((segment) => segment.type === 'control'
635
+ ? segment.nodeId === targetNodeId
636
+ : segment.nodeIds.includes(targetNodeId));
637
+ }
638
+ export function computeActiveWorkflowNodes(workflowNodes, nodeIds, rootNodeId, includeRootDependencies = true) {
639
+ const segmentNodeIds = new Set(nodeIds);
640
+ const downstream = new Set([rootNodeId]);
641
+ let changed = true;
642
+ while (changed) {
643
+ changed = false;
644
+ for (const nodeId of nodeIds) {
645
+ if (downstream.has(nodeId))
646
+ continue;
647
+ const needs = getNodeNeeds(workflowNodes[nodeId] ?? {});
648
+ if (needs.some((dependency) => downstream.has(dependency))) {
649
+ downstream.add(nodeId);
650
+ changed = true;
651
+ }
652
+ }
653
+ }
654
+ const active = new Set(downstream);
655
+ const includeDependencies = (nodeId) => {
656
+ const node = workflowNodes[nodeId];
657
+ if (!node)
658
+ return;
659
+ for (const dependency of getNodeNeeds(node)) {
660
+ if (!segmentNodeIds.has(dependency) || active.has(dependency))
661
+ continue;
662
+ active.add(dependency);
663
+ includeDependencies(dependency);
664
+ }
665
+ };
666
+ if (includeRootDependencies) {
667
+ for (const nodeId of downstream) {
668
+ includeDependencies(nodeId);
669
+ }
670
+ }
671
+ return active;
672
+ }
673
+ export function runControlWorkflowNode(nodeId, node, context) {
674
+ if (node.control === 'loop') {
675
+ const expression = node.if;
676
+ if (!expression) {
677
+ throw new Error(`Workflow loop node "${nodeId}" must define if.`);
678
+ }
679
+ if (!node.target || !node.exit) {
680
+ throw new Error(`Workflow loop node "${nodeId}" must define target and exit.`);
681
+ }
682
+ const rawMaxIterations = node.maxIterations;
683
+ const renderedMaxIterations = typeof rawMaxIterations === 'string'
684
+ ? renderTemplate(rawMaxIterations, context).trim()
685
+ : String(rawMaxIterations ?? '');
686
+ const configuredMaxIterations = Number.parseInt(renderedMaxIterations, 10);
687
+ if (renderedMaxIterations === '' ||
688
+ !Number.isInteger(configuredMaxIterations) ||
689
+ configuredMaxIterations <= 0) {
690
+ throw new Error(`Workflow loop node "${nodeId}" must define a positive maxIterations.`);
691
+ }
692
+ const maxIterations = configuredMaxIterations;
693
+ const shouldLoop = evaluateWorkflowExpression(expression, context);
694
+ const current = context.loop[nodeId] ?? { iteration: 1, maxIterations };
695
+ let nextNodeId = node.exit;
696
+ let decision = 'exit';
697
+ if (shouldLoop) {
698
+ if (current.iteration >= maxIterations) {
699
+ if (node.onMaxIterations === 'exit') {
700
+ nextNodeId = node.exit;
701
+ }
702
+ else {
703
+ throw new Error(`Workflow loop node "${nodeId}" reached maxIterations (${maxIterations}).`);
704
+ }
705
+ }
706
+ else {
707
+ decision = 'loop';
708
+ nextNodeId = node.target;
709
+ current.iteration += 1;
710
+ }
711
+ }
712
+ current.maxIterations = maxIterations;
713
+ current.lastDecision = decision;
714
+ context.loop[nodeId] = current;
715
+ return {
716
+ nextNodeId,
717
+ result: {
718
+ id: nodeId,
719
+ type: 'control',
720
+ status: 'success',
721
+ control: node.control,
722
+ decision,
723
+ target: nextNodeId,
724
+ response: decision,
725
+ output: {
726
+ matched: shouldLoop,
727
+ target: nextNodeId,
728
+ iteration: current.iteration,
729
+ maxIterations,
730
+ },
731
+ },
732
+ };
733
+ }
734
+ if (node.control === 'switch') {
735
+ if (!node.value || !node.cases) {
736
+ throw new Error(`Workflow switch node "${nodeId}" must define value and cases.`);
737
+ }
738
+ const value = renderTemplate(node.value, context).trim();
739
+ const nextNodeId = node.cases[value] ?? node.default;
740
+ if (!nextNodeId) {
741
+ throw new Error(`Workflow switch node "${nodeId}" has no case for "${value}" or default.`);
742
+ }
743
+ return {
744
+ nextNodeId,
745
+ result: {
746
+ id: nodeId,
747
+ type: 'control',
748
+ status: 'success',
749
+ control: node.control,
750
+ decision: value,
751
+ target: nextNodeId,
752
+ response: value,
753
+ output: { value, target: nextNodeId },
754
+ },
755
+ };
756
+ }
757
+ if (node.control === 'end') {
758
+ return {
759
+ ended: true,
760
+ result: {
761
+ id: nodeId,
762
+ type: 'control',
763
+ status: 'success',
764
+ control: node.control,
765
+ decision: 'end',
766
+ response: 'end',
767
+ output: { ended: true },
768
+ },
769
+ };
770
+ }
771
+ if (node.control === 'passThrough') {
772
+ const result = {
773
+ id: nodeId,
774
+ type: 'control',
775
+ control: 'passThrough',
776
+ target: node.target,
777
+ decision: 'pass',
778
+ response: `passed through to ${node.target}`,
779
+ };
780
+ return { result, nextNodeId: node.target };
781
+ }
782
+ throw new Error(`Unsupported workflow control "${node.control}" in node "${nodeId}".`);
783
+ }
784
+ export function createSkippedWorkflowNodeResult(nodeId) {
785
+ const timestamp = new Date().toISOString();
786
+ return {
787
+ id: nodeId,
788
+ type: 'skipped',
789
+ status: 'skipped',
790
+ startedAt: timestamp,
791
+ completedAt: timestamp,
792
+ durationMs: 0,
793
+ response: '',
794
+ output: undefined,
795
+ };
796
+ }
797
+ export function getWorkflowNodeRunCondition(node) {
798
+ return node.if;
799
+ }
800
+ export function getSkippedWorkflowDependencies(node, nodes) {
801
+ return getNodeNeeds(node).filter((dependency) => nodes[dependency]?.status === 'skipped');
802
+ }
803
+ export function getWorkflowNodeSkipReason(node, context) {
804
+ const skippedDependencies = getSkippedWorkflowDependencies(node, context.nodes);
805
+ if (skippedDependencies.length > 0) {
806
+ return `dependency skipped: ${skippedDependencies.join(', ')}`;
807
+ }
808
+ const ifExpression = getWorkflowNodeRunCondition(node);
809
+ if (ifExpression !== undefined && !evaluateWorkflowExpression(ifExpression, context)) {
810
+ return `if false: ${ifExpression}`;
811
+ }
812
+ return undefined;
813
+ }
814
+ //# sourceMappingURL=planning.js.map