@renseiai/agentfactory 0.8.11 → 0.8.13
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/src/config/repository-config.d.ts +24 -0
- package/dist/src/config/repository-config.d.ts.map +1 -1
- package/dist/src/config/repository-config.js +21 -0
- package/dist/src/config/repository-config.test.js +202 -0
- package/dist/src/governor/decision-engine.d.ts +2 -0
- package/dist/src/governor/decision-engine.d.ts.map +1 -1
- package/dist/src/governor/decision-engine.js +7 -0
- package/dist/src/governor/decision-engine.test.js +63 -0
- package/dist/src/governor/governor-types.d.ts +2 -1
- package/dist/src/governor/governor-types.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/merge-queue/conflict-resolver.d.ts +62 -0
- package/dist/src/merge-queue/conflict-resolver.d.ts.map +1 -0
- package/dist/src/merge-queue/conflict-resolver.js +168 -0
- package/dist/src/merge-queue/conflict-resolver.test.d.ts +2 -0
- package/dist/src/merge-queue/conflict-resolver.test.d.ts.map +1 -0
- package/dist/src/merge-queue/conflict-resolver.test.js +405 -0
- package/dist/src/merge-queue/lock-file-regeneration.d.ts +14 -0
- package/dist/src/merge-queue/lock-file-regeneration.d.ts.map +1 -0
- package/dist/src/merge-queue/lock-file-regeneration.js +82 -0
- package/dist/src/merge-queue/lock-file-regeneration.test.d.ts +2 -0
- package/dist/src/merge-queue/lock-file-regeneration.test.d.ts.map +1 -0
- package/dist/src/merge-queue/lock-file-regeneration.test.js +236 -0
- package/dist/src/merge-queue/merge-worker.d.ts +79 -0
- package/dist/src/merge-queue/merge-worker.d.ts.map +1 -0
- package/dist/src/merge-queue/merge-worker.js +221 -0
- package/dist/src/merge-queue/merge-worker.test.d.ts +2 -0
- package/dist/src/merge-queue/merge-worker.test.d.ts.map +1 -0
- package/dist/src/merge-queue/merge-worker.test.js +883 -0
- package/dist/src/merge-queue/strategies/index.d.ts +19 -0
- package/dist/src/merge-queue/strategies/index.d.ts.map +1 -0
- package/dist/src/merge-queue/strategies/index.js +30 -0
- package/dist/src/merge-queue/strategies/merge-commit-strategy.d.ts +14 -0
- package/dist/src/merge-queue/strategies/merge-commit-strategy.d.ts.map +1 -0
- package/dist/src/merge-queue/strategies/merge-commit-strategy.js +58 -0
- package/dist/src/merge-queue/strategies/rebase-strategy.d.ts +14 -0
- package/dist/src/merge-queue/strategies/rebase-strategy.d.ts.map +1 -0
- package/dist/src/merge-queue/strategies/rebase-strategy.js +62 -0
- package/dist/src/merge-queue/strategies/squash-strategy.d.ts +14 -0
- package/dist/src/merge-queue/strategies/squash-strategy.d.ts.map +1 -0
- package/dist/src/merge-queue/strategies/squash-strategy.js +59 -0
- package/dist/src/merge-queue/strategies/strategies.test.d.ts +2 -0
- package/dist/src/merge-queue/strategies/strategies.test.d.ts.map +1 -0
- package/dist/src/merge-queue/strategies/strategies.test.js +354 -0
- package/dist/src/merge-queue/strategies/types.d.ts +62 -0
- package/dist/src/merge-queue/strategies/types.d.ts.map +1 -0
- package/dist/src/merge-queue/strategies/types.js +7 -0
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
- package/dist/src/orchestrator/parse-work-result.js +22 -0
- package/dist/src/orchestrator/parse-work-result.test.js +49 -0
- package/dist/src/providers/index.d.ts +1 -0
- package/dist/src/providers/index.d.ts.map +1 -1
- package/dist/src/providers/plugin-types.d.ts +177 -0
- package/dist/src/providers/plugin-types.d.ts.map +1 -0
- package/dist/src/providers/plugin-types.js +10 -0
- package/dist/src/providers/plugin-types.test.d.ts +2 -0
- package/dist/src/providers/plugin-types.test.d.ts.map +1 -0
- package/dist/src/providers/plugin-types.test.js +810 -0
- package/dist/src/registry/index.d.ts +4 -0
- package/dist/src/registry/index.d.ts.map +1 -0
- package/dist/src/registry/index.js +2 -0
- package/dist/src/registry/loader.d.ts +25 -0
- package/dist/src/registry/loader.d.ts.map +1 -0
- package/dist/src/registry/loader.js +88 -0
- package/dist/src/registry/node-type-registry.d.ts +52 -0
- package/dist/src/registry/node-type-registry.d.ts.map +1 -0
- package/dist/src/registry/node-type-registry.js +130 -0
- package/dist/src/registry/types.d.ts +65 -0
- package/dist/src/registry/types.d.ts.map +1 -0
- package/dist/src/registry/types.js +10 -0
- package/dist/src/workflow/expression/ast.d.ts +1 -1
- package/dist/src/workflow/expression/ast.d.ts.map +1 -1
- package/dist/src/workflow/expression/context.d.ts +4 -0
- package/dist/src/workflow/expression/context.d.ts.map +1 -1
- package/dist/src/workflow/expression/context.js +5 -1
- package/dist/src/workflow/expression/evaluator.d.ts.map +1 -1
- package/dist/src/workflow/expression/evaluator.js +24 -1
- package/dist/src/workflow/expression/evaluator.test.js +174 -0
- package/dist/src/workflow/expression/expression.test.js +140 -1
- package/dist/src/workflow/expression/helpers.d.ts +4 -0
- package/dist/src/workflow/expression/helpers.d.ts.map +1 -1
- package/dist/src/workflow/expression/helpers.js +51 -0
- package/dist/src/workflow/expression/index.d.ts +14 -0
- package/dist/src/workflow/expression/index.d.ts.map +1 -1
- package/dist/src/workflow/expression/index.js +28 -1
- package/dist/src/workflow/expression/lexer.d.ts.map +1 -1
- package/dist/src/workflow/expression/lexer.js +43 -0
- package/dist/src/workflow/expression/parser.js +1 -1
- package/dist/src/workflow/index.d.ts +3 -3
- package/dist/src/workflow/index.d.ts.map +1 -1
- package/dist/src/workflow/index.js +4 -2
- package/dist/src/workflow/workflow-loader.d.ts +8 -2
- package/dist/src/workflow/workflow-loader.d.ts.map +1 -1
- package/dist/src/workflow/workflow-loader.js +21 -2
- package/dist/src/workflow/workflow-types.d.ts +781 -12
- package/dist/src/workflow/workflow-types.d.ts.map +1 -1
- package/dist/src/workflow/workflow-types.js +248 -3
- package/dist/src/workflow/workflow-types.test.js +621 -1
- package/package.json +3 -2
|
@@ -88,6 +88,22 @@ describe('evaluate — variable lookup', () => {
|
|
|
88
88
|
const ast = { type: 'VariableRef', name: 'data' };
|
|
89
89
|
expect(evaluate(ast, ctx({ data: null }))).toBe(null);
|
|
90
90
|
});
|
|
91
|
+
it('resolves dotted variable path from nested context', () => {
|
|
92
|
+
const ast = { type: 'VariableRef', name: 'trigger.data.issueId' };
|
|
93
|
+
expect(evaluate(ast, ctx({ trigger: { data: { issueId: 'SUP-123' } } }))).toBe('SUP-123');
|
|
94
|
+
});
|
|
95
|
+
it('resolves two-level dotted path', () => {
|
|
96
|
+
const ast = { type: 'VariableRef', name: 'steps.route.result' };
|
|
97
|
+
expect(evaluate(ast, ctx({ steps: { route: { result: 'development' } } }))).toBe('development');
|
|
98
|
+
});
|
|
99
|
+
it('returns false for dotted path when intermediate is missing', () => {
|
|
100
|
+
const ast = { type: 'VariableRef', name: 'trigger.data.issueId' };
|
|
101
|
+
expect(evaluate(ast, ctx({}))).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
it('returns false for dotted path when intermediate is null', () => {
|
|
104
|
+
const ast = { type: 'VariableRef', name: 'trigger.data.issueId' };
|
|
105
|
+
expect(evaluate(ast, ctx({ trigger: null }))).toBe(false);
|
|
106
|
+
});
|
|
91
107
|
});
|
|
92
108
|
// ---------------------------------------------------------------------------
|
|
93
109
|
// 3. Boolean operators
|
|
@@ -437,6 +453,30 @@ describe('evaluate — comparisons', () => {
|
|
|
437
453
|
});
|
|
438
454
|
});
|
|
439
455
|
// ---------------------------------------------------------------------------
|
|
456
|
+
// 4b. in operator
|
|
457
|
+
// ---------------------------------------------------------------------------
|
|
458
|
+
describe('evaluate — in operator', () => {
|
|
459
|
+
it('returns true when value is in array', () => {
|
|
460
|
+
const result = evaluateCondition("{{ 'bug' in labels }}", ctx({ labels: ['bug', 'enhancement'] }));
|
|
461
|
+
expect(result).toBe(true);
|
|
462
|
+
});
|
|
463
|
+
it('returns false when value is not in array', () => {
|
|
464
|
+
const result = evaluateCondition("{{ 'feature' in labels }}", ctx({ labels: ['bug', 'enhancement'] }));
|
|
465
|
+
expect(result).toBe(false);
|
|
466
|
+
});
|
|
467
|
+
it('returns true for substring in string', () => {
|
|
468
|
+
const result = evaluateCondition("{{ 'fix' in title }}", ctx({ title: 'Quick fix for bug' }));
|
|
469
|
+
expect(result).toBe(true);
|
|
470
|
+
});
|
|
471
|
+
it('returns false when substring not in string', () => {
|
|
472
|
+
const result = evaluateCondition("{{ 'hotfix' in title }}", ctx({ title: 'Quick fix for bug' }));
|
|
473
|
+
expect(result).toBe(false);
|
|
474
|
+
});
|
|
475
|
+
it('throws for invalid right operand type', () => {
|
|
476
|
+
expect(() => evaluateCondition("{{ 'x' in count }}", ctx({ count: 42 }))).toThrow(EvaluationError);
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
// ---------------------------------------------------------------------------
|
|
440
480
|
// 5. Function calls
|
|
441
481
|
// ---------------------------------------------------------------------------
|
|
442
482
|
describe('evaluate — function calls', () => {
|
|
@@ -561,6 +601,91 @@ describe('built-in helpers', () => {
|
|
|
561
601
|
expect(helpers.isParentIssue()).toBe(false);
|
|
562
602
|
});
|
|
563
603
|
});
|
|
604
|
+
describe('hasSubIssues', () => {
|
|
605
|
+
it('returns true when hasSubIssues is true', () => {
|
|
606
|
+
const issue = makeIssue();
|
|
607
|
+
const helpers = createBuiltinHelpers(issue, { hasSubIssues: true });
|
|
608
|
+
expect(helpers.hasSubIssues()).toBe(true);
|
|
609
|
+
});
|
|
610
|
+
it('returns false when hasSubIssues is false', () => {
|
|
611
|
+
const issue = makeIssue();
|
|
612
|
+
const helpers = createBuiltinHelpers(issue, { hasSubIssues: false });
|
|
613
|
+
expect(helpers.hasSubIssues()).toBe(false);
|
|
614
|
+
});
|
|
615
|
+
it('returns false when hasSubIssues is not specified', () => {
|
|
616
|
+
const issue = makeIssue();
|
|
617
|
+
const helpers = createBuiltinHelpers(issue);
|
|
618
|
+
expect(helpers.hasSubIssues()).toBe(false);
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
describe('isAssignedToHuman', () => {
|
|
622
|
+
it('returns true when isAssignedToHuman is true', () => {
|
|
623
|
+
const issue = makeIssue();
|
|
624
|
+
const helpers = createBuiltinHelpers(issue, { isAssignedToHuman: true });
|
|
625
|
+
expect(helpers.isAssignedToHuman()).toBe(true);
|
|
626
|
+
});
|
|
627
|
+
it('returns false when isAssignedToHuman is false', () => {
|
|
628
|
+
const issue = makeIssue();
|
|
629
|
+
const helpers = createBuiltinHelpers(issue, { isAssignedToHuman: false });
|
|
630
|
+
expect(helpers.isAssignedToHuman()).toBe(false);
|
|
631
|
+
});
|
|
632
|
+
it('returns false when isAssignedToHuman is not specified', () => {
|
|
633
|
+
const issue = makeIssue();
|
|
634
|
+
const helpers = createBuiltinHelpers(issue);
|
|
635
|
+
expect(helpers.isAssignedToHuman()).toBe(false);
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
describe('hasBlockingIncomplete', () => {
|
|
639
|
+
it('returns true when hasBlockingIncomplete is true', () => {
|
|
640
|
+
const issue = makeIssue();
|
|
641
|
+
const helpers = createBuiltinHelpers(issue, { hasBlockingIncomplete: true });
|
|
642
|
+
expect(helpers.hasBlockingIncomplete()).toBe(true);
|
|
643
|
+
});
|
|
644
|
+
it('returns false when hasBlockingIncomplete is false', () => {
|
|
645
|
+
const issue = makeIssue();
|
|
646
|
+
const helpers = createBuiltinHelpers(issue, { hasBlockingIncomplete: false });
|
|
647
|
+
expect(helpers.hasBlockingIncomplete()).toBe(false);
|
|
648
|
+
});
|
|
649
|
+
it('returns false when hasBlockingIncomplete is not specified', () => {
|
|
650
|
+
const issue = makeIssue();
|
|
651
|
+
const helpers = createBuiltinHelpers(issue);
|
|
652
|
+
expect(helpers.hasBlockingIncomplete()).toBe(false);
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
describe('startsWith', () => {
|
|
656
|
+
it('returns true when string starts with prefix', () => {
|
|
657
|
+
const issue = makeIssue({ title: 'fix: resolve bug' });
|
|
658
|
+
const helpers = createBuiltinHelpers(issue);
|
|
659
|
+
expect(helpers.startsWith('fix: resolve bug', 'fix')).toBe(true);
|
|
660
|
+
});
|
|
661
|
+
it('returns false when string does not start with prefix', () => {
|
|
662
|
+
const issue = makeIssue();
|
|
663
|
+
const helpers = createBuiltinHelpers(issue);
|
|
664
|
+
expect(helpers.startsWith('hello world', 'world')).toBe(false);
|
|
665
|
+
});
|
|
666
|
+
it('returns false for non-string arguments', () => {
|
|
667
|
+
const issue = makeIssue();
|
|
668
|
+
const helpers = createBuiltinHelpers(issue);
|
|
669
|
+
expect(helpers.startsWith(123, 'fix')).toBe(false);
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
describe('contains', () => {
|
|
673
|
+
it('returns true when string contains substring', () => {
|
|
674
|
+
const issue = makeIssue();
|
|
675
|
+
const helpers = createBuiltinHelpers(issue);
|
|
676
|
+
expect(helpers.contains('hello world', 'world')).toBe(true);
|
|
677
|
+
});
|
|
678
|
+
it('returns false when string does not contain substring', () => {
|
|
679
|
+
const issue = makeIssue();
|
|
680
|
+
const helpers = createBuiltinHelpers(issue);
|
|
681
|
+
expect(helpers.contains('hello world', 'foo')).toBe(false);
|
|
682
|
+
});
|
|
683
|
+
it('returns false for non-string arguments', () => {
|
|
684
|
+
const issue = makeIssue();
|
|
685
|
+
const helpers = createBuiltinHelpers(issue);
|
|
686
|
+
expect(helpers.contains(42, 'test')).toBe(false);
|
|
687
|
+
});
|
|
688
|
+
});
|
|
564
689
|
});
|
|
565
690
|
// ---------------------------------------------------------------------------
|
|
566
691
|
// 7. Complex expressions (end-to-end with evaluateCondition)
|
|
@@ -635,6 +760,37 @@ describe('evaluateCondition — end-to-end', () => {
|
|
|
635
760
|
const context = buildEvaluationContext(issue, { researchCompleted: true }, { hasSubIssues: false });
|
|
636
761
|
expect(evaluateCondition("{{ hasLabel('bug') and researchCompleted }}", context)).toBe(true);
|
|
637
762
|
});
|
|
763
|
+
it('evaluates hasSubIssues() in condition', () => {
|
|
764
|
+
const issue = makeIssue();
|
|
765
|
+
const context = buildEvaluationContext(issue, undefined, { hasSubIssues: true });
|
|
766
|
+
expect(evaluateCondition('{{ hasSubIssues() }}', context)).toBe(true);
|
|
767
|
+
});
|
|
768
|
+
it('evaluates isAssignedToHuman() in condition', () => {
|
|
769
|
+
const issue = makeIssue();
|
|
770
|
+
const context = buildEvaluationContext(issue, undefined, { isAssignedToHuman: true });
|
|
771
|
+
expect(evaluateCondition('{{ isAssignedToHuman() }}', context)).toBe(true);
|
|
772
|
+
});
|
|
773
|
+
it('evaluates hasBlockingIncomplete() in condition', () => {
|
|
774
|
+
const issue = makeIssue();
|
|
775
|
+
const context = buildEvaluationContext(issue, undefined, { hasBlockingIncomplete: true });
|
|
776
|
+
expect(evaluateCondition('{{ hasBlockingIncomplete() }}', context)).toBe(true);
|
|
777
|
+
});
|
|
778
|
+
it('evaluates dotted path expression end-to-end', () => {
|
|
779
|
+
const context = ctx({ trigger: { data: { issueId: 'SUP-123' } } });
|
|
780
|
+
expect(evaluateCondition("{{ trigger.data.issueId == 'SUP-123' }}", context)).toBe(true);
|
|
781
|
+
});
|
|
782
|
+
it('evaluates symbolic operators end-to-end', () => {
|
|
783
|
+
const context = ctx({ priority: 5 });
|
|
784
|
+
expect(evaluateCondition('{{ priority > 3 }}', context)).toBe(true);
|
|
785
|
+
});
|
|
786
|
+
it('evaluates startsWith() function in condition', () => {
|
|
787
|
+
const context = ctx({ title: 'fix: resolve memory leak' }, { startsWith: (...args) => typeof args[0] === 'string' && typeof args[1] === 'string' && args[0].startsWith(args[1]) });
|
|
788
|
+
expect(evaluateCondition("{{ startsWith(title, 'fix') }}", context)).toBe(true);
|
|
789
|
+
});
|
|
790
|
+
it('evaluates contains() function in condition', () => {
|
|
791
|
+
const context = ctx({ description: 'This is a hotfix release' }, { contains: (...args) => typeof args[0] === 'string' && typeof args[1] === 'string' && args[0].includes(args[1]) });
|
|
792
|
+
expect(evaluateCondition("{{ contains(description, 'hotfix') }}", context)).toBe(true);
|
|
793
|
+
});
|
|
638
794
|
});
|
|
639
795
|
// ---------------------------------------------------------------------------
|
|
640
796
|
// 8. Error cases
|
|
@@ -774,6 +930,24 @@ describe('buildEvaluationContext', () => {
|
|
|
774
930
|
expect(context.functions.isParentIssue).toBeDefined();
|
|
775
931
|
expect(context.functions.isParentIssue()).toBe(true);
|
|
776
932
|
});
|
|
933
|
+
it('registers hasSubIssues function', () => {
|
|
934
|
+
const issue = makeIssue();
|
|
935
|
+
const context = buildEvaluationContext(issue, undefined, { hasSubIssues: true });
|
|
936
|
+
expect(context.functions.hasSubIssues).toBeDefined();
|
|
937
|
+
expect(context.functions.hasSubIssues()).toBe(true);
|
|
938
|
+
});
|
|
939
|
+
it('registers isAssignedToHuman function', () => {
|
|
940
|
+
const issue = makeIssue();
|
|
941
|
+
const context = buildEvaluationContext(issue, undefined, { isAssignedToHuman: true });
|
|
942
|
+
expect(context.functions.isAssignedToHuman).toBeDefined();
|
|
943
|
+
expect(context.functions.isAssignedToHuman()).toBe(true);
|
|
944
|
+
});
|
|
945
|
+
it('registers hasBlockingIncomplete function', () => {
|
|
946
|
+
const issue = makeIssue();
|
|
947
|
+
const context = buildEvaluationContext(issue, undefined, { hasBlockingIncomplete: true });
|
|
948
|
+
expect(context.functions.hasBlockingIncomplete).toBeDefined();
|
|
949
|
+
expect(context.functions.hasBlockingIncomplete()).toBe(true);
|
|
950
|
+
});
|
|
777
951
|
it('works end-to-end with evaluateCondition', () => {
|
|
778
952
|
const issue = makeIssue({
|
|
779
953
|
labels: ['bug', 'priority-high'],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { parseExpression, tokenize, parse, ParseError } from './index.js';
|
|
2
|
+
import { parseExpression, tokenize, parse, ParseError, interpolateTemplate } from './index.js';
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
// Lexer tests
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
@@ -67,6 +67,43 @@ describe('tokenize', () => {
|
|
|
67
67
|
const tokens = tokenize('{{ x }}');
|
|
68
68
|
expect(tokens[tokens.length - 1].type).toBe('EOF');
|
|
69
69
|
});
|
|
70
|
+
it('tokenizes dotted variable path as single Identifier', () => {
|
|
71
|
+
const tokens = tokenize('{{ trigger.data.issueId }}');
|
|
72
|
+
const nonEof = tokens.filter((t) => t.type !== 'EOF');
|
|
73
|
+
expect(nonEof).toHaveLength(1);
|
|
74
|
+
expect(nonEof[0].type).toBe('Identifier');
|
|
75
|
+
expect(nonEof[0].value).toBe('trigger.data.issueId');
|
|
76
|
+
});
|
|
77
|
+
it('tokenizes symbolic operators == != > < >= <=', () => {
|
|
78
|
+
const cases = [
|
|
79
|
+
{ input: '{{ x == y }}', expected: 'eq' },
|
|
80
|
+
{ input: '{{ x != y }}', expected: 'neq' },
|
|
81
|
+
{ input: '{{ x > y }}', expected: 'gt' },
|
|
82
|
+
{ input: '{{ x < y }}', expected: 'lt' },
|
|
83
|
+
{ input: '{{ x >= y }}', expected: 'gte' },
|
|
84
|
+
{ input: '{{ x <= y }}', expected: 'lte' },
|
|
85
|
+
];
|
|
86
|
+
for (const { input, expected } of cases) {
|
|
87
|
+
const tokens = tokenize(input);
|
|
88
|
+
const ops = tokens.filter((t) => t.type === 'Operator');
|
|
89
|
+
expect(ops).toHaveLength(1);
|
|
90
|
+
expect(ops[0].value).toBe(expected);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
it('tokenizes mixed symbolic and keyword operators', () => {
|
|
94
|
+
const tokens = tokenize('{{ x == 5 and y > 3 }}');
|
|
95
|
+
const types = tokens.map((t) => `${t.type}:${t.value}`);
|
|
96
|
+
expect(types).toEqual([
|
|
97
|
+
'Identifier:x',
|
|
98
|
+
'Operator:eq',
|
|
99
|
+
'NumberLiteral:5',
|
|
100
|
+
'Operator:and',
|
|
101
|
+
'Identifier:y',
|
|
102
|
+
'Operator:gt',
|
|
103
|
+
'NumberLiteral:3',
|
|
104
|
+
'EOF:',
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
70
107
|
});
|
|
71
108
|
// ---------------------------------------------------------------------------
|
|
72
109
|
// Parser tests — simple expressions
|
|
@@ -224,6 +261,59 @@ describe('parseExpression', () => {
|
|
|
224
261
|
right: { type: 'NumberLiteral', value: 5 },
|
|
225
262
|
});
|
|
226
263
|
});
|
|
264
|
+
it('parses in', () => {
|
|
265
|
+
const ast = parseExpression("{{ 'bug' in labels }}");
|
|
266
|
+
expect(ast).toEqual({
|
|
267
|
+
type: 'BinaryOp',
|
|
268
|
+
operator: 'in',
|
|
269
|
+
left: { type: 'StringLiteral', value: 'bug' },
|
|
270
|
+
right: { type: 'VariableRef', name: 'labels' },
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
// -------------------------------------------------------------------------
|
|
275
|
+
// Dotted variable paths
|
|
276
|
+
// -------------------------------------------------------------------------
|
|
277
|
+
describe('dotted variable paths', () => {
|
|
278
|
+
it('parses dotted variable reference', () => {
|
|
279
|
+
const ast = parseExpression('{{ trigger.data.issueId }}');
|
|
280
|
+
expect(ast).toEqual({
|
|
281
|
+
type: 'VariableRef',
|
|
282
|
+
name: 'trigger.data.issueId',
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
it('parses dotted path with symbolic comparison', () => {
|
|
286
|
+
const ast = parseExpression('{{ trigger.data.priority > 3 }}');
|
|
287
|
+
expect(ast).toEqual({
|
|
288
|
+
type: 'BinaryOp',
|
|
289
|
+
operator: 'gt',
|
|
290
|
+
left: { type: 'VariableRef', name: 'trigger.data.priority' },
|
|
291
|
+
right: { type: 'NumberLiteral', value: 3 },
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
// -------------------------------------------------------------------------
|
|
296
|
+
// Symbolic operators
|
|
297
|
+
// -------------------------------------------------------------------------
|
|
298
|
+
describe('symbolic operators', () => {
|
|
299
|
+
it('parses expression with symbolic == operator', () => {
|
|
300
|
+
const ast = parseExpression("{{ status == 'active' }}");
|
|
301
|
+
expect(ast).toEqual({
|
|
302
|
+
type: 'BinaryOp',
|
|
303
|
+
operator: 'eq',
|
|
304
|
+
left: { type: 'VariableRef', name: 'status' },
|
|
305
|
+
right: { type: 'StringLiteral', value: 'active' },
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
it('parses expression with symbolic != operator', () => {
|
|
309
|
+
const ast = parseExpression("{{ status != 'closed' }}");
|
|
310
|
+
expect(ast).toEqual({
|
|
311
|
+
type: 'BinaryOp',
|
|
312
|
+
operator: 'neq',
|
|
313
|
+
left: { type: 'VariableRef', name: 'status' },
|
|
314
|
+
right: { type: 'StringLiteral', value: 'closed' },
|
|
315
|
+
});
|
|
316
|
+
});
|
|
227
317
|
});
|
|
228
318
|
// -------------------------------------------------------------------------
|
|
229
319
|
// Function calls
|
|
@@ -514,3 +604,52 @@ describe('parseExpression', () => {
|
|
|
514
604
|
});
|
|
515
605
|
});
|
|
516
606
|
});
|
|
607
|
+
// ---------------------------------------------------------------------------
|
|
608
|
+
// Template interpolation tests
|
|
609
|
+
// ---------------------------------------------------------------------------
|
|
610
|
+
describe('interpolateTemplate', () => {
|
|
611
|
+
/** Create a context with the given variables and optional functions. */
|
|
612
|
+
function ctx(variables, functions = {}) {
|
|
613
|
+
return { variables, functions };
|
|
614
|
+
}
|
|
615
|
+
it('resolves a single expression', () => {
|
|
616
|
+
const result = interpolateTemplate('Issue {{ issueId }} is ready', ctx({ issueId: 'SUP-123' }));
|
|
617
|
+
expect(result).toBe('Issue SUP-123 is ready');
|
|
618
|
+
});
|
|
619
|
+
it('resolves multiple expressions', () => {
|
|
620
|
+
const result = interpolateTemplate('{{ name }} has {{ count }} items', ctx({ name: 'Alice', count: 5 }));
|
|
621
|
+
expect(result).toBe('Alice has 5 items');
|
|
622
|
+
});
|
|
623
|
+
it('resolves dotted path expressions', () => {
|
|
624
|
+
const result = interpolateTemplate('Issue {{ trigger.data.issueId }} is ready', ctx({ trigger: { data: { issueId: 'SUP-123' } } }));
|
|
625
|
+
expect(result).toBe('Issue SUP-123 is ready');
|
|
626
|
+
});
|
|
627
|
+
it('coerces boolean to string', () => {
|
|
628
|
+
const result = interpolateTemplate('Result: {{ active }}', ctx({ active: true }));
|
|
629
|
+
expect(result).toBe('Result: true');
|
|
630
|
+
});
|
|
631
|
+
it('coerces number to string', () => {
|
|
632
|
+
const result = interpolateTemplate('Count: {{ count }}', ctx({ count: 42 }));
|
|
633
|
+
expect(result).toBe('Count: 42');
|
|
634
|
+
});
|
|
635
|
+
it('passes through static string without expressions', () => {
|
|
636
|
+
const result = interpolateTemplate('No expressions here', ctx({}));
|
|
637
|
+
expect(result).toBe('No expressions here');
|
|
638
|
+
});
|
|
639
|
+
it('resolves expression-only template', () => {
|
|
640
|
+
const result = interpolateTemplate('{{ status }}', ctx({ status: 'In Progress' }));
|
|
641
|
+
expect(result).toBe('In Progress');
|
|
642
|
+
});
|
|
643
|
+
it('resolves function calls in template', () => {
|
|
644
|
+
const result = interpolateTemplate('Has bug: {{ hasLabel(\'bug\') }}', ctx({}, { hasLabel: (...args) => args[0] === 'bug' }));
|
|
645
|
+
expect(result).toBe('Has bug: true');
|
|
646
|
+
});
|
|
647
|
+
it('throws ParseError on empty expression', () => {
|
|
648
|
+
expect(() => interpolateTemplate('Hello {{ }}', ctx({}))).toThrow(ParseError);
|
|
649
|
+
expect(() => interpolateTemplate('Hello {{ }}', ctx({}))).toThrow(/Empty expression/);
|
|
650
|
+
});
|
|
651
|
+
it('resolves false for undefined variables', () => {
|
|
652
|
+
const result = interpolateTemplate('Value: {{ missing }}', ctx({}));
|
|
653
|
+
expect(result).toBe('Value: false');
|
|
654
|
+
});
|
|
655
|
+
});
|
|
@@ -9,6 +9,10 @@ import type { GovernorIssue } from '../../governor/governor-types.js';
|
|
|
9
9
|
export interface BuiltinHelperOptions {
|
|
10
10
|
/** Whether the issue has sub-issues (i.e., it is a parent issue) */
|
|
11
11
|
hasSubIssues?: boolean;
|
|
12
|
+
/** Whether the issue is assigned to a human user (vs unassigned or bot) */
|
|
13
|
+
isAssignedToHuman?: boolean;
|
|
14
|
+
/** Whether the issue has incomplete blocking relations */
|
|
15
|
+
hasBlockingIncomplete?: boolean;
|
|
12
16
|
}
|
|
13
17
|
/**
|
|
14
18
|
* Create the set of built-in helper functions bound to the given issue.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../src/workflow/expression/helpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAMrE,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,YAAY,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../src/workflow/expression/helpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAMrE,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,0DAA0D;IAC1D,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC;AAMD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,EACpB,IAAI,GAAE,oBAAyB,GAC9B,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAgGjD"}
|
|
@@ -52,5 +52,56 @@ export function createBuiltinHelpers(issue, opts = {}) {
|
|
|
52
52
|
isParentIssue() {
|
|
53
53
|
return opts.hasSubIssues === true;
|
|
54
54
|
},
|
|
55
|
+
/**
|
|
56
|
+
* Check whether the issue has sub-issues.
|
|
57
|
+
* Alias for `isParentIssue()`.
|
|
58
|
+
*
|
|
59
|
+
* Usage in expressions: `hasSubIssues()`
|
|
60
|
+
*/
|
|
61
|
+
hasSubIssues() {
|
|
62
|
+
return opts.hasSubIssues === true;
|
|
63
|
+
},
|
|
64
|
+
/**
|
|
65
|
+
* Check whether the issue is assigned to a human user (vs unassigned or bot).
|
|
66
|
+
*
|
|
67
|
+
* Usage in expressions: `isAssignedToHuman()`
|
|
68
|
+
*/
|
|
69
|
+
isAssignedToHuman() {
|
|
70
|
+
return opts.isAssignedToHuman === true;
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Check whether the issue has incomplete blocking relations.
|
|
74
|
+
*
|
|
75
|
+
* Usage in expressions: `hasBlockingIncomplete()`
|
|
76
|
+
*/
|
|
77
|
+
hasBlockingIncomplete() {
|
|
78
|
+
return opts.hasBlockingIncomplete === true;
|
|
79
|
+
},
|
|
80
|
+
/**
|
|
81
|
+
* Check whether a string starts with a given prefix.
|
|
82
|
+
*
|
|
83
|
+
* Usage in expressions: `startsWith(title, 'fix')`
|
|
84
|
+
*/
|
|
85
|
+
startsWith(...args) {
|
|
86
|
+
const str = args[0];
|
|
87
|
+
const prefix = args[1];
|
|
88
|
+
if (typeof str !== 'string' || typeof prefix !== 'string') {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return str.startsWith(prefix);
|
|
92
|
+
},
|
|
93
|
+
/**
|
|
94
|
+
* Check whether a string contains a given substring.
|
|
95
|
+
*
|
|
96
|
+
* Usage in expressions: `contains(title, 'hotfix')`
|
|
97
|
+
*/
|
|
98
|
+
contains(...args) {
|
|
99
|
+
const str = args[0];
|
|
100
|
+
const substring = args[1];
|
|
101
|
+
if (typeof str !== 'string' || typeof substring !== 'string') {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return str.includes(substring);
|
|
105
|
+
},
|
|
55
106
|
};
|
|
56
107
|
}
|
|
@@ -52,4 +52,18 @@ export declare function parseExpression(condition: string): ASTNode;
|
|
|
52
52
|
* @throws {EvaluationError} on runtime errors (unknown functions, type mismatches).
|
|
53
53
|
*/
|
|
54
54
|
export declare function evaluateCondition(condition: string, context: EvaluationContext): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Interpolate template expressions within a string.
|
|
57
|
+
*
|
|
58
|
+
* Finds all `{{ ... }}` expressions in the input string, evaluates each
|
|
59
|
+
* against the context, and replaces the placeholder with the string
|
|
60
|
+
* representation of the result.
|
|
61
|
+
*
|
|
62
|
+
* @param template - The template string containing `{{ }}` expressions.
|
|
63
|
+
* @param context - The sandboxed evaluation context.
|
|
64
|
+
* @returns The fully resolved string.
|
|
65
|
+
* @throws {ParseError} on syntax errors in expressions.
|
|
66
|
+
* @throws {EvaluationError} on runtime errors in expressions.
|
|
67
|
+
*/
|
|
68
|
+
export declare function interpolateTemplate(template: string, context: EvaluationContext): string;
|
|
55
69
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/workflow/expression/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AASrD,YAAY,EACV,OAAO,EACP,WAAW,EACX,cAAc,EACd,aAAa,EACb,aAAa,EACb,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,UAAU,CAAA;AAMjB,YAAY,EACV,KAAK,EACL,SAAS,EACT,cAAc,GACf,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAMjD,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAMnC,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAM1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAMrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAMnD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAIxF"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/workflow/expression/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AASrD,YAAY,EACV,OAAO,EACP,WAAW,EACX,cAAc,EACd,aAAa,EACb,aAAa,EACb,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,UAAU,CAAA;AAMjB,YAAY,EACV,KAAK,EACL,SAAS,EACT,cAAc,GACf,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAMjD,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAMnC,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAM1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAMrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAMnD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAIxF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAcxF"}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* // => true or false
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
-
import { tokenize } from './lexer.js';
|
|
20
|
+
import { tokenize, ParseError } from './lexer.js';
|
|
21
21
|
import { parse } from './parser.js';
|
|
22
22
|
import { evaluate } from './evaluator.js';
|
|
23
23
|
export { ParseError, tokenize } from './lexer.js';
|
|
@@ -69,3 +69,30 @@ export function evaluateCondition(condition, context) {
|
|
|
69
69
|
const result = evaluate(ast, context);
|
|
70
70
|
return Boolean(result);
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Interpolate template expressions within a string.
|
|
74
|
+
*
|
|
75
|
+
* Finds all `{{ ... }}` expressions in the input string, evaluates each
|
|
76
|
+
* against the context, and replaces the placeholder with the string
|
|
77
|
+
* representation of the result.
|
|
78
|
+
*
|
|
79
|
+
* @param template - The template string containing `{{ }}` expressions.
|
|
80
|
+
* @param context - The sandboxed evaluation context.
|
|
81
|
+
* @returns The fully resolved string.
|
|
82
|
+
* @throws {ParseError} on syntax errors in expressions.
|
|
83
|
+
* @throws {EvaluationError} on runtime errors in expressions.
|
|
84
|
+
*/
|
|
85
|
+
export function interpolateTemplate(template, context) {
|
|
86
|
+
// Regex to match {{ ... }} expressions (non-greedy)
|
|
87
|
+
const EXPR_PATTERN = /\{\{(.*?)\}\}/g;
|
|
88
|
+
return template.replace(EXPR_PATTERN, (_match, expr) => {
|
|
89
|
+
const trimmed = expr.trim();
|
|
90
|
+
if (trimmed.length === 0) {
|
|
91
|
+
throw new ParseError('Empty expression in template', { offset: 0, column: 1 });
|
|
92
|
+
}
|
|
93
|
+
const tokens = tokenize(trimmed);
|
|
94
|
+
const ast = parse(tokens);
|
|
95
|
+
const result = evaluate(ast, context);
|
|
96
|
+
return String(result);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../../../../src/workflow/expression/lexer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,MAAM,MAAM,SAAS,GACjB,YAAY,GACZ,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,UAAU,GACV,WAAW,GACX,YAAY,GACZ,OAAO,GACP,KAAK,CAAA;AAET,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;CAClC;AAMD;;;GAGG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;gBAErB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc;CAKtD;
|
|
1
|
+
{"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../../../../src/workflow/expression/lexer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,MAAM,MAAM,SAAS,GACjB,YAAY,GACZ,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,UAAU,GACV,WAAW,GACX,YAAY,GACZ,OAAO,GACP,KAAK,CAAA;AAET,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;CAClC;AAMD;;;GAGG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;gBAErB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc;CAKtD;AAuED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,CAkI/C"}
|
|
@@ -26,6 +26,7 @@ export class ParseError extends Error {
|
|
|
26
26
|
const OPERATORS = new Set([
|
|
27
27
|
'and', 'or', 'not',
|
|
28
28
|
'eq', 'neq', 'gt', 'lt', 'gte', 'lte',
|
|
29
|
+
'in',
|
|
29
30
|
]);
|
|
30
31
|
const BOOLEAN_LITERALS = new Set(['true', 'false']);
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
@@ -129,6 +130,15 @@ export function tokenize(input) {
|
|
|
129
130
|
const start = i;
|
|
130
131
|
while (i < inner.length && isIdentChar(inner[i]))
|
|
131
132
|
i++;
|
|
133
|
+
// Dotted variable paths: consume `.` followed by another identifier segment
|
|
134
|
+
while (i < inner.length &&
|
|
135
|
+
inner[i] === '.' &&
|
|
136
|
+
i + 1 < inner.length &&
|
|
137
|
+
isIdentStart(inner[i + 1])) {
|
|
138
|
+
i++; // skip the dot
|
|
139
|
+
while (i < inner.length && isIdentChar(inner[i]))
|
|
140
|
+
i++;
|
|
141
|
+
}
|
|
132
142
|
const word = inner.slice(start, i);
|
|
133
143
|
if (BOOLEAN_LITERALS.has(word)) {
|
|
134
144
|
tokens.push({ type: 'BooleanLiteral', value: word, position: pos(start) });
|
|
@@ -158,6 +168,39 @@ export function tokenize(input) {
|
|
|
158
168
|
i++;
|
|
159
169
|
continue;
|
|
160
170
|
}
|
|
171
|
+
// Symbolic comparison operators
|
|
172
|
+
if (ch === '=' && i + 1 < inner.length && inner[i + 1] === '=') {
|
|
173
|
+
tokens.push({ type: 'Operator', value: 'eq', position: pos(i) });
|
|
174
|
+
i += 2;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (ch === '!' && i + 1 < inner.length && inner[i + 1] === '=') {
|
|
178
|
+
tokens.push({ type: 'Operator', value: 'neq', position: pos(i) });
|
|
179
|
+
i += 2;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (ch === '>') {
|
|
183
|
+
if (i + 1 < inner.length && inner[i + 1] === '=') {
|
|
184
|
+
tokens.push({ type: 'Operator', value: 'gte', position: pos(i) });
|
|
185
|
+
i += 2;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
tokens.push({ type: 'Operator', value: 'gt', position: pos(i) });
|
|
189
|
+
i += 1;
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (ch === '<') {
|
|
194
|
+
if (i + 1 < inner.length && inner[i + 1] === '=') {
|
|
195
|
+
tokens.push({ type: 'Operator', value: 'lte', position: pos(i) });
|
|
196
|
+
i += 2;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
tokens.push({ type: 'Operator', value: 'lt', position: pos(i) });
|
|
200
|
+
i += 1;
|
|
201
|
+
}
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
161
204
|
// Unknown character
|
|
162
205
|
throw new ParseError(`Unexpected character '${ch}'`, pos(i));
|
|
163
206
|
}
|
|
@@ -14,7 +14,7 @@ import { ParseError } from './lexer.js';
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// Operator sets
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
|
-
const COMPARISON_OPERATORS = new Set(['eq', 'neq', 'gt', 'lt', 'gte', 'lte']);
|
|
17
|
+
const COMPARISON_OPERATORS = new Set(['eq', 'neq', 'gt', 'lt', 'gte', 'lte', 'in']);
|
|
18
18
|
function peek(state) {
|
|
19
19
|
return state.tokens[state.pos];
|
|
20
20
|
}
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Declarative workflow graph definitions using YAML (v1.1 schema extension).
|
|
5
5
|
* Defines phases, transitions, escalation ladder, gates, and parallelism.
|
|
6
6
|
*/
|
|
7
|
-
export type { EscalationStrategy, PhaseDefinition, PhaseOutputDeclaration, PhaseInputDeclaration, TransitionDefinition, EscalationLadderRung, EscalationConfig, GateDefinition, ParallelismGroupDefinition, WorkflowDefinition, BranchingDefinition, TemplateRetryConfig, TemplateTimeoutConfig, } from './workflow-types.js';
|
|
8
|
-
export { PhaseDefinitionSchema, PhaseOutputDeclarationSchema, PhaseInputDeclarationSchema, TransitionDefinitionSchema, EscalationLadderRungSchema, EscalationConfigSchema, GateDefinitionSchema, ParallelismGroupDefinitionSchema, WorkflowDefinitionSchema, BranchingDefinitionSchema, TemplateRetryConfigSchema, TemplateTimeoutConfigSchema, validateWorkflowDefinition, } from './workflow-types.js';
|
|
9
|
-
export { loadWorkflowDefinitionFile, getBuiltinWorkflowDir, getBuiltinWorkflowPath, } from './workflow-loader.js';
|
|
7
|
+
export type { EscalationStrategy, PhaseDefinition, PhaseOutputDeclaration, PhaseInputDeclaration, TransitionDefinition, EscalationLadderRung, EscalationConfig, GateDefinition, ParallelismGroupDefinition, WorkflowDefinition, BranchingDefinition, TemplateRetryConfig, TemplateTimeoutConfig, WorkflowTriggerDefinition, ProviderRequirement, WorkflowConfig, StepDefinition, NodeDefinition, WorkflowDefinitionV2, AnyWorkflowDefinition, } from './workflow-types.js';
|
|
8
|
+
export { PhaseDefinitionSchema, PhaseOutputDeclarationSchema, PhaseInputDeclarationSchema, TransitionDefinitionSchema, EscalationLadderRungSchema, EscalationConfigSchema, GateDefinitionSchema, ParallelismGroupDefinitionSchema, WorkflowDefinitionSchema, BranchingDefinitionSchema, TemplateRetryConfigSchema, TemplateTimeoutConfigSchema, validateWorkflowDefinition, WorkflowTriggerDefinitionSchema, ProviderRequirementSchema, WorkflowConfigSchema, StepDefinitionSchema, NodeDefinitionSchema, WorkflowDefinitionV2Schema, AnyWorkflowDefinitionSchema, validateAnyWorkflowDefinition, crossValidateWorkflowV2, } from './workflow-types.js';
|
|
9
|
+
export { loadWorkflowDefinitionFile, loadAnyWorkflowDefinitionFile, getBuiltinWorkflowDir, getBuiltinWorkflowPath, } from './workflow-loader.js';
|
|
10
10
|
export type { WorkflowRegistryConfig, WorkflowStoreSource } from './workflow-registry.js';
|
|
11
11
|
export { WorkflowRegistry } from './workflow-registry.js';
|
|
12
12
|
export type { TransitionContext, TransitionResult } from './transition-engine.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/workflow/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EACV,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,0BAA0B,EAC1B,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EACL,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,EACpB,gCAAgC,EAChC,wBAAwB,EACxB,yBAAyB,EACzB,yBAAyB,EACzB,2BAA2B,EAC3B,0BAA0B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/workflow/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* Declarative workflow graph definitions using YAML (v1.1 schema extension).
|
|
5
5
|
* Defines phases, transitions, escalation ladder, gates, and parallelism.
|
|
6
6
|
*/
|
|
7
|
-
export { PhaseDefinitionSchema, PhaseOutputDeclarationSchema, PhaseInputDeclarationSchema, TransitionDefinitionSchema, EscalationLadderRungSchema, EscalationConfigSchema, GateDefinitionSchema, ParallelismGroupDefinitionSchema, WorkflowDefinitionSchema, BranchingDefinitionSchema, TemplateRetryConfigSchema, TemplateTimeoutConfigSchema, validateWorkflowDefinition,
|
|
8
|
-
|
|
7
|
+
export { PhaseDefinitionSchema, PhaseOutputDeclarationSchema, PhaseInputDeclarationSchema, TransitionDefinitionSchema, EscalationLadderRungSchema, EscalationConfigSchema, GateDefinitionSchema, ParallelismGroupDefinitionSchema, WorkflowDefinitionSchema, BranchingDefinitionSchema, TemplateRetryConfigSchema, TemplateTimeoutConfigSchema, validateWorkflowDefinition,
|
|
8
|
+
// v2 schemas & validation
|
|
9
|
+
WorkflowTriggerDefinitionSchema, ProviderRequirementSchema, WorkflowConfigSchema, StepDefinitionSchema, NodeDefinitionSchema, WorkflowDefinitionV2Schema, AnyWorkflowDefinitionSchema, validateAnyWorkflowDefinition, crossValidateWorkflowV2, } from './workflow-types.js';
|
|
10
|
+
export { loadWorkflowDefinitionFile, loadAnyWorkflowDefinitionFile, getBuiltinWorkflowDir, getBuiltinWorkflowPath, } from './workflow-loader.js';
|
|
9
11
|
export { WorkflowRegistry } from './workflow-registry.js';
|
|
10
12
|
export { evaluateTransitions } from './transition-engine.js';
|
|
11
13
|
export { evaluateBranching } from './branching-router.js';
|