@renseiai/agentfactory 0.8.12 → 0.8.14

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 (147) hide show
  1. package/dist/src/config/repository-config.d.ts +24 -0
  2. package/dist/src/config/repository-config.d.ts.map +1 -1
  3. package/dist/src/config/repository-config.js +21 -0
  4. package/dist/src/config/repository-config.test.js +202 -0
  5. package/dist/src/governor/decision-engine.d.ts +2 -0
  6. package/dist/src/governor/decision-engine.d.ts.map +1 -1
  7. package/dist/src/governor/decision-engine.js +7 -0
  8. package/dist/src/governor/decision-engine.test.js +63 -0
  9. package/dist/src/governor/governor-types.d.ts +2 -1
  10. package/dist/src/governor/governor-types.d.ts.map +1 -1
  11. package/dist/src/index.d.ts +1 -0
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/index.js +1 -0
  14. package/dist/src/merge-queue/conflict-resolver.d.ts +62 -0
  15. package/dist/src/merge-queue/conflict-resolver.d.ts.map +1 -0
  16. package/dist/src/merge-queue/conflict-resolver.js +168 -0
  17. package/dist/src/merge-queue/conflict-resolver.test.d.ts +2 -0
  18. package/dist/src/merge-queue/conflict-resolver.test.d.ts.map +1 -0
  19. package/dist/src/merge-queue/conflict-resolver.test.js +405 -0
  20. package/dist/src/merge-queue/lock-file-regeneration.d.ts +14 -0
  21. package/dist/src/merge-queue/lock-file-regeneration.d.ts.map +1 -0
  22. package/dist/src/merge-queue/lock-file-regeneration.js +82 -0
  23. package/dist/src/merge-queue/lock-file-regeneration.test.d.ts +2 -0
  24. package/dist/src/merge-queue/lock-file-regeneration.test.d.ts.map +1 -0
  25. package/dist/src/merge-queue/lock-file-regeneration.test.js +236 -0
  26. package/dist/src/merge-queue/merge-worker.d.ts +79 -0
  27. package/dist/src/merge-queue/merge-worker.d.ts.map +1 -0
  28. package/dist/src/merge-queue/merge-worker.js +221 -0
  29. package/dist/src/merge-queue/merge-worker.test.d.ts +2 -0
  30. package/dist/src/merge-queue/merge-worker.test.d.ts.map +1 -0
  31. package/dist/src/merge-queue/merge-worker.test.js +883 -0
  32. package/dist/src/merge-queue/strategies/index.d.ts +19 -0
  33. package/dist/src/merge-queue/strategies/index.d.ts.map +1 -0
  34. package/dist/src/merge-queue/strategies/index.js +30 -0
  35. package/dist/src/merge-queue/strategies/merge-commit-strategy.d.ts +14 -0
  36. package/dist/src/merge-queue/strategies/merge-commit-strategy.d.ts.map +1 -0
  37. package/dist/src/merge-queue/strategies/merge-commit-strategy.js +58 -0
  38. package/dist/src/merge-queue/strategies/rebase-strategy.d.ts +14 -0
  39. package/dist/src/merge-queue/strategies/rebase-strategy.d.ts.map +1 -0
  40. package/dist/src/merge-queue/strategies/rebase-strategy.js +62 -0
  41. package/dist/src/merge-queue/strategies/squash-strategy.d.ts +14 -0
  42. package/dist/src/merge-queue/strategies/squash-strategy.d.ts.map +1 -0
  43. package/dist/src/merge-queue/strategies/squash-strategy.js +59 -0
  44. package/dist/src/merge-queue/strategies/strategies.test.d.ts +2 -0
  45. package/dist/src/merge-queue/strategies/strategies.test.d.ts.map +1 -0
  46. package/dist/src/merge-queue/strategies/strategies.test.js +354 -0
  47. package/dist/src/merge-queue/strategies/types.d.ts +62 -0
  48. package/dist/src/merge-queue/strategies/types.d.ts.map +1 -0
  49. package/dist/src/merge-queue/strategies/types.js +7 -0
  50. package/dist/src/orchestrator/completion-contracts.d.ts +89 -0
  51. package/dist/src/orchestrator/completion-contracts.d.ts.map +1 -0
  52. package/dist/src/orchestrator/completion-contracts.js +228 -0
  53. package/dist/src/orchestrator/completion-contracts.test.d.ts +2 -0
  54. package/dist/src/orchestrator/completion-contracts.test.d.ts.map +1 -0
  55. package/dist/src/orchestrator/completion-contracts.test.js +195 -0
  56. package/dist/src/orchestrator/index.d.ts +4 -0
  57. package/dist/src/orchestrator/index.d.ts.map +1 -1
  58. package/dist/src/orchestrator/index.js +3 -0
  59. package/dist/src/orchestrator/orchestrator.d.ts +32 -0
  60. package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
  61. package/dist/src/orchestrator/orchestrator.js +157 -26
  62. package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
  63. package/dist/src/orchestrator/parse-work-result.js +22 -0
  64. package/dist/src/orchestrator/parse-work-result.test.js +49 -0
  65. package/dist/src/orchestrator/session-backstop.d.ts +67 -0
  66. package/dist/src/orchestrator/session-backstop.d.ts.map +1 -0
  67. package/dist/src/orchestrator/session-backstop.js +394 -0
  68. package/dist/src/orchestrator/session-backstop.test.d.ts +2 -0
  69. package/dist/src/orchestrator/session-backstop.test.d.ts.map +1 -0
  70. package/dist/src/orchestrator/session-backstop.test.js +245 -0
  71. package/dist/src/orchestrator/worktree-checks.test.d.ts +2 -0
  72. package/dist/src/orchestrator/worktree-checks.test.d.ts.map +1 -0
  73. package/dist/src/orchestrator/worktree-checks.test.js +159 -0
  74. package/dist/src/providers/a2a-provider.d.ts +4 -0
  75. package/dist/src/providers/a2a-provider.d.ts.map +1 -1
  76. package/dist/src/providers/a2a-provider.js +4 -0
  77. package/dist/src/providers/amp-provider.d.ts +4 -0
  78. package/dist/src/providers/amp-provider.d.ts.map +1 -1
  79. package/dist/src/providers/amp-provider.js +4 -0
  80. package/dist/src/providers/claude-provider.d.ts +4 -0
  81. package/dist/src/providers/claude-provider.d.ts.map +1 -1
  82. package/dist/src/providers/claude-provider.js +6 -0
  83. package/dist/src/providers/codex-provider.d.ts +4 -0
  84. package/dist/src/providers/codex-provider.d.ts.map +1 -1
  85. package/dist/src/providers/codex-provider.js +4 -0
  86. package/dist/src/providers/index.d.ts +2 -1
  87. package/dist/src/providers/index.d.ts.map +1 -1
  88. package/dist/src/providers/plugin-types.d.ts +177 -0
  89. package/dist/src/providers/plugin-types.d.ts.map +1 -0
  90. package/dist/src/providers/plugin-types.js +10 -0
  91. package/dist/src/providers/plugin-types.test.d.ts +2 -0
  92. package/dist/src/providers/plugin-types.test.d.ts.map +1 -0
  93. package/dist/src/providers/plugin-types.test.js +810 -0
  94. package/dist/src/providers/spring-ai-provider.d.ts +4 -0
  95. package/dist/src/providers/spring-ai-provider.d.ts.map +1 -1
  96. package/dist/src/providers/spring-ai-provider.js +4 -0
  97. package/dist/src/providers/types.d.ts +22 -0
  98. package/dist/src/providers/types.d.ts.map +1 -1
  99. package/dist/src/registry/index.d.ts +4 -0
  100. package/dist/src/registry/index.d.ts.map +1 -0
  101. package/dist/src/registry/index.js +2 -0
  102. package/dist/src/registry/loader.d.ts +25 -0
  103. package/dist/src/registry/loader.d.ts.map +1 -0
  104. package/dist/src/registry/loader.js +88 -0
  105. package/dist/src/registry/node-type-registry.d.ts +52 -0
  106. package/dist/src/registry/node-type-registry.d.ts.map +1 -0
  107. package/dist/src/registry/node-type-registry.js +130 -0
  108. package/dist/src/registry/types.d.ts +65 -0
  109. package/dist/src/registry/types.d.ts.map +1 -0
  110. package/dist/src/registry/types.js +10 -0
  111. package/dist/src/templates/types.d.ts +3 -0
  112. package/dist/src/templates/types.d.ts.map +1 -1
  113. package/dist/src/templates/types.js +2 -0
  114. package/dist/src/tools/index.d.ts +1 -0
  115. package/dist/src/tools/index.d.ts.map +1 -1
  116. package/dist/src/tools/registry.d.ts +6 -1
  117. package/dist/src/tools/registry.d.ts.map +1 -1
  118. package/dist/src/tools/registry.js +5 -1
  119. package/dist/src/workflow/expression/ast.d.ts +1 -1
  120. package/dist/src/workflow/expression/ast.d.ts.map +1 -1
  121. package/dist/src/workflow/expression/context.d.ts +4 -0
  122. package/dist/src/workflow/expression/context.d.ts.map +1 -1
  123. package/dist/src/workflow/expression/context.js +5 -1
  124. package/dist/src/workflow/expression/evaluator.d.ts.map +1 -1
  125. package/dist/src/workflow/expression/evaluator.js +24 -1
  126. package/dist/src/workflow/expression/evaluator.test.js +174 -0
  127. package/dist/src/workflow/expression/expression.test.js +140 -1
  128. package/dist/src/workflow/expression/helpers.d.ts +4 -0
  129. package/dist/src/workflow/expression/helpers.d.ts.map +1 -1
  130. package/dist/src/workflow/expression/helpers.js +51 -0
  131. package/dist/src/workflow/expression/index.d.ts +14 -0
  132. package/dist/src/workflow/expression/index.d.ts.map +1 -1
  133. package/dist/src/workflow/expression/index.js +28 -1
  134. package/dist/src/workflow/expression/lexer.d.ts.map +1 -1
  135. package/dist/src/workflow/expression/lexer.js +43 -0
  136. package/dist/src/workflow/expression/parser.js +1 -1
  137. package/dist/src/workflow/index.d.ts +3 -3
  138. package/dist/src/workflow/index.d.ts.map +1 -1
  139. package/dist/src/workflow/index.js +4 -2
  140. package/dist/src/workflow/workflow-loader.d.ts +8 -2
  141. package/dist/src/workflow/workflow-loader.d.ts.map +1 -1
  142. package/dist/src/workflow/workflow-loader.js +21 -2
  143. package/dist/src/workflow/workflow-types.d.ts +781 -12
  144. package/dist/src/workflow/workflow-types.d.ts.map +1 -1
  145. package/dist/src/workflow/workflow-types.js +248 -3
  146. package/dist/src/workflow/workflow-types.test.js +621 -1
  147. package/package.json +3 -2
@@ -127,6 +127,30 @@ export declare const RepositoryConfigSchema: z.ZodObject<{
127
127
  enabled: z.ZodDefault<z.ZodBoolean>;
128
128
  autoMerge: z.ZodDefault<z.ZodBoolean>;
129
129
  requiredChecks: z.ZodOptional<z.ZodArray<z.ZodString>>;
130
+ strategy: z.ZodDefault<z.ZodEnum<{
131
+ merge: "merge";
132
+ rebase: "rebase";
133
+ squash: "squash";
134
+ }>>;
135
+ testCommand: z.ZodDefault<z.ZodString>;
136
+ testTimeout: z.ZodDefault<z.ZodNumber>;
137
+ lockFileRegenerate: z.ZodDefault<z.ZodBoolean>;
138
+ mergiraf: z.ZodDefault<z.ZodBoolean>;
139
+ pollInterval: z.ZodDefault<z.ZodNumber>;
140
+ maxRetries: z.ZodDefault<z.ZodNumber>;
141
+ escalation: z.ZodOptional<z.ZodObject<{
142
+ onConflict: z.ZodDefault<z.ZodEnum<{
143
+ reassign: "reassign";
144
+ notify: "notify";
145
+ park: "park";
146
+ }>>;
147
+ onTestFailure: z.ZodDefault<z.ZodEnum<{
148
+ notify: "notify";
149
+ park: "park";
150
+ retry: "retry";
151
+ }>>;
152
+ }, z.core.$strip>>;
153
+ deleteBranchOnMerge: z.ZodDefault<z.ZodBoolean>;
130
154
  }, z.core.$strip>>;
131
155
  worktree: z.ZodOptional<z.ZodObject<{
132
156
  directory: z.ZodDefault<z.ZodString>;
@@ -1 +1 @@
1
- {"version":3,"file":"repository-config.d.ts","sourceRoot":"","sources":["../../../src/config/repository-config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAMxD,qEAAqE;AACrE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;iBAW9B,CAAA;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAW/D,uCAAuC;AACvC,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;iBAOhC,CAAA;AAEF,6DAA6D;AAC7D,eAAO,MAAM,0BAA0B;;;;;;;iBAarC,CAAA;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyFlC,CAAA;AAMD,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AAMrE;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,GAAG,SAAS,CAK1F;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAepG;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKhG;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,eAAe,GAAG,SAAS,CAExF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,aAAa,GAAG,SAAS,CAEpF;AAMD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAQ7E"}
1
+ {"version":3,"file":"repository-config.d.ts","sourceRoot":"","sources":["../../../src/config/repository-config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAMxD,qEAAqE;AACrE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;iBAW9B,CAAA;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAW/D,uCAAuC;AACvC,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;iBAOhC,CAAA;AAEF,6DAA6D;AAC7D,eAAO,MAAM,0BAA0B;;;;;;;iBAarC,CAAA;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8GlC,CAAA;AAMD,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AAMrE;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,GAAG,SAAS,CAK1F;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAepG;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKhG;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,eAAe,GAAG,SAAS,CAExF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,aAAa,GAAG,SAAS,CAEpF;AAMD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAQ7E"}
@@ -123,6 +123,27 @@ export const RepositoryConfigSchema = z.object({
123
123
  autoMerge: z.boolean().default(true),
124
124
  /** Required CI checks that must pass before merge (provider-specific) */
125
125
  requiredChecks: z.array(z.string()).optional(),
126
+ /** Merge strategy: rebase, merge, or squash */
127
+ strategy: z.enum(['rebase', 'merge', 'squash']).default('rebase'),
128
+ /** Command to run after rebase (e.g., test suite) */
129
+ testCommand: z.string().default('pnpm test'),
130
+ /** Timeout for test command in milliseconds */
131
+ testTimeout: z.number().default(300_000),
132
+ /** Regenerate lock files after rebase */
133
+ lockFileRegenerate: z.boolean().default(true),
134
+ /** Use mergiraf for syntax-aware conflict resolution */
135
+ mergiraf: z.boolean().default(true),
136
+ /** Queue polling interval in milliseconds */
137
+ pollInterval: z.number().default(10_000),
138
+ /** Maximum retries for failed merges */
139
+ maxRetries: z.number().default(2),
140
+ /** Escalation policy for conflicts and test failures */
141
+ escalation: z.object({
142
+ onConflict: z.enum(['reassign', 'notify', 'park']).default('reassign'),
143
+ onTestFailure: z.enum(['notify', 'park', 'retry']).default('notify'),
144
+ }).optional(),
145
+ /** Delete PR branch after successful merge */
146
+ deleteBranchOnMerge: z.boolean().default(true),
126
147
  }).optional(),
127
148
  /**
128
149
  * Worktree configuration.
@@ -687,3 +687,205 @@ describe('getRoutingConfig', () => {
687
687
  expect(getRoutingConfig(config)).toBeUndefined();
688
688
  });
689
689
  });
690
+ describe('RepositoryConfigSchema mergeQueue — Refinery fields', () => {
691
+ const base = { apiVersion: 'v1', kind: 'RepositoryConfig' };
692
+ it('applies default values when new fields are absent (backward compat)', () => {
693
+ const result = RepositoryConfigSchema.parse({
694
+ ...base,
695
+ mergeQueue: { enabled: true },
696
+ });
697
+ expect(result.mergeQueue).toBeDefined();
698
+ expect(result.mergeQueue.provider).toBe('github-native');
699
+ expect(result.mergeQueue.enabled).toBe(true);
700
+ expect(result.mergeQueue.autoMerge).toBe(true);
701
+ expect(result.mergeQueue.requiredChecks).toBeUndefined();
702
+ expect(result.mergeQueue.strategy).toBe('rebase');
703
+ expect(result.mergeQueue.testCommand).toBe('pnpm test');
704
+ expect(result.mergeQueue.testTimeout).toBe(300_000);
705
+ expect(result.mergeQueue.lockFileRegenerate).toBe(true);
706
+ expect(result.mergeQueue.mergiraf).toBe(true);
707
+ expect(result.mergeQueue.pollInterval).toBe(10_000);
708
+ expect(result.mergeQueue.maxRetries).toBe(2);
709
+ expect(result.mergeQueue.escalation).toBeUndefined();
710
+ expect(result.mergeQueue.deleteBranchOnMerge).toBe(true);
711
+ });
712
+ it('accepts an empty mergeQueue object and applies all defaults', () => {
713
+ const result = RepositoryConfigSchema.parse({
714
+ ...base,
715
+ mergeQueue: {},
716
+ });
717
+ expect(result.mergeQueue.enabled).toBe(false);
718
+ expect(result.mergeQueue.strategy).toBe('rebase');
719
+ expect(result.mergeQueue.testCommand).toBe('pnpm test');
720
+ expect(result.mergeQueue.testTimeout).toBe(300_000);
721
+ expect(result.mergeQueue.lockFileRegenerate).toBe(true);
722
+ expect(result.mergeQueue.mergiraf).toBe(true);
723
+ expect(result.mergeQueue.pollInterval).toBe(10_000);
724
+ expect(result.mergeQueue.maxRetries).toBe(2);
725
+ expect(result.mergeQueue.deleteBranchOnMerge).toBe(true);
726
+ });
727
+ it('validates strategy enum values', () => {
728
+ for (const strategy of ['rebase', 'merge', 'squash']) {
729
+ const result = RepositoryConfigSchema.parse({
730
+ ...base,
731
+ mergeQueue: { strategy },
732
+ });
733
+ expect(result.mergeQueue.strategy).toBe(strategy);
734
+ }
735
+ });
736
+ it('rejects invalid strategy enum value', () => {
737
+ expect(() => RepositoryConfigSchema.parse({
738
+ ...base,
739
+ mergeQueue: { strategy: 'cherry-pick' },
740
+ })).toThrow();
741
+ });
742
+ it('validates escalation onConflict enum values', () => {
743
+ for (const onConflict of ['reassign', 'notify', 'park']) {
744
+ const result = RepositoryConfigSchema.parse({
745
+ ...base,
746
+ mergeQueue: { escalation: { onConflict } },
747
+ });
748
+ expect(result.mergeQueue.escalation.onConflict).toBe(onConflict);
749
+ }
750
+ });
751
+ it('validates escalation onTestFailure enum values', () => {
752
+ for (const onTestFailure of ['notify', 'park', 'retry']) {
753
+ const result = RepositoryConfigSchema.parse({
754
+ ...base,
755
+ mergeQueue: { escalation: { onTestFailure } },
756
+ });
757
+ expect(result.mergeQueue.escalation.onTestFailure).toBe(onTestFailure);
758
+ }
759
+ });
760
+ it('rejects invalid escalation onConflict value', () => {
761
+ expect(() => RepositoryConfigSchema.parse({
762
+ ...base,
763
+ mergeQueue: { escalation: { onConflict: 'ignore' } },
764
+ })).toThrow();
765
+ });
766
+ it('rejects invalid escalation onTestFailure value', () => {
767
+ expect(() => RepositoryConfigSchema.parse({
768
+ ...base,
769
+ mergeQueue: { escalation: { onTestFailure: 'ignore' } },
770
+ })).toThrow();
771
+ });
772
+ it('applies escalation defaults when escalation is provided as empty object', () => {
773
+ const result = RepositoryConfigSchema.parse({
774
+ ...base,
775
+ mergeQueue: { escalation: {} },
776
+ });
777
+ expect(result.mergeQueue.escalation.onConflict).toBe('reassign');
778
+ expect(result.mergeQueue.escalation.onTestFailure).toBe('notify');
779
+ });
780
+ it('validates full config with all new fields', () => {
781
+ const result = RepositoryConfigSchema.parse({
782
+ ...base,
783
+ mergeQueue: {
784
+ provider: 'mergify',
785
+ enabled: true,
786
+ autoMerge: false,
787
+ requiredChecks: ['ci/build', 'ci/test'],
788
+ strategy: 'squash',
789
+ testCommand: 'npm run test:ci',
790
+ testTimeout: 600_000,
791
+ lockFileRegenerate: false,
792
+ mergiraf: false,
793
+ pollInterval: 30_000,
794
+ maxRetries: 5,
795
+ escalation: {
796
+ onConflict: 'park',
797
+ onTestFailure: 'retry',
798
+ },
799
+ deleteBranchOnMerge: false,
800
+ },
801
+ });
802
+ expect(result.mergeQueue.provider).toBe('mergify');
803
+ expect(result.mergeQueue.enabled).toBe(true);
804
+ expect(result.mergeQueue.autoMerge).toBe(false);
805
+ expect(result.mergeQueue.requiredChecks).toEqual(['ci/build', 'ci/test']);
806
+ expect(result.mergeQueue.strategy).toBe('squash');
807
+ expect(result.mergeQueue.testCommand).toBe('npm run test:ci');
808
+ expect(result.mergeQueue.testTimeout).toBe(600_000);
809
+ expect(result.mergeQueue.lockFileRegenerate).toBe(false);
810
+ expect(result.mergeQueue.mergiraf).toBe(false);
811
+ expect(result.mergeQueue.pollInterval).toBe(30_000);
812
+ expect(result.mergeQueue.maxRetries).toBe(5);
813
+ expect(result.mergeQueue.escalation).toEqual({
814
+ onConflict: 'park',
815
+ onTestFailure: 'retry',
816
+ });
817
+ expect(result.mergeQueue.deleteBranchOnMerge).toBe(false);
818
+ });
819
+ it('mergeQueue remains optional on the top-level schema', () => {
820
+ const result = RepositoryConfigSchema.parse(base);
821
+ expect(result.mergeQueue).toBeUndefined();
822
+ });
823
+ });
824
+ describe('loadRepositoryConfig with mergeQueue Refinery fields', () => {
825
+ beforeEach(() => {
826
+ vi.clearAllMocks();
827
+ });
828
+ afterEach(() => {
829
+ vi.restoreAllMocks();
830
+ });
831
+ it('parses YAML with all mergeQueue Refinery fields', () => {
832
+ mockExistsSync.mockReturnValue(true);
833
+ mockReadFileSync.mockReturnValue(`apiVersion: v1
834
+ kind: RepositoryConfig
835
+ mergeQueue:
836
+ provider: trunk
837
+ enabled: true
838
+ autoMerge: true
839
+ requiredChecks:
840
+ - ci/build
841
+ strategy: merge
842
+ testCommand: "yarn test"
843
+ testTimeout: 120000
844
+ lockFileRegenerate: false
845
+ mergiraf: false
846
+ pollInterval: 5000
847
+ maxRetries: 3
848
+ escalation:
849
+ onConflict: notify
850
+ onTestFailure: park
851
+ deleteBranchOnMerge: false
852
+ `);
853
+ const result = loadRepositoryConfig('/some/repo');
854
+ expect(result?.mergeQueue).toBeDefined();
855
+ expect(result.mergeQueue.provider).toBe('trunk');
856
+ expect(result.mergeQueue.strategy).toBe('merge');
857
+ expect(result.mergeQueue.testCommand).toBe('yarn test');
858
+ expect(result.mergeQueue.testTimeout).toBe(120_000);
859
+ expect(result.mergeQueue.lockFileRegenerate).toBe(false);
860
+ expect(result.mergeQueue.mergiraf).toBe(false);
861
+ expect(result.mergeQueue.pollInterval).toBe(5_000);
862
+ expect(result.mergeQueue.maxRetries).toBe(3);
863
+ expect(result.mergeQueue.escalation).toEqual({
864
+ onConflict: 'notify',
865
+ onTestFailure: 'park',
866
+ });
867
+ expect(result.mergeQueue.deleteBranchOnMerge).toBe(false);
868
+ });
869
+ it('parses YAML with only existing mergeQueue fields — new fields get defaults', () => {
870
+ mockExistsSync.mockReturnValue(true);
871
+ mockReadFileSync.mockReturnValue(`apiVersion: v1
872
+ kind: RepositoryConfig
873
+ mergeQueue:
874
+ enabled: true
875
+ provider: github-native
876
+ `);
877
+ const result = loadRepositoryConfig('/some/repo');
878
+ expect(result?.mergeQueue).toBeDefined();
879
+ expect(result.mergeQueue.enabled).toBe(true);
880
+ expect(result.mergeQueue.provider).toBe('github-native');
881
+ expect(result.mergeQueue.strategy).toBe('rebase');
882
+ expect(result.mergeQueue.testCommand).toBe('pnpm test');
883
+ expect(result.mergeQueue.testTimeout).toBe(300_000);
884
+ expect(result.mergeQueue.lockFileRegenerate).toBe(true);
885
+ expect(result.mergeQueue.mergiraf).toBe(true);
886
+ expect(result.mergeQueue.pollInterval).toBe(10_000);
887
+ expect(result.mergeQueue.maxRetries).toBe(2);
888
+ expect(result.mergeQueue.escalation).toBeUndefined();
889
+ expect(result.mergeQueue.deleteBranchOnMerge).toBe(true);
890
+ });
891
+ });
@@ -35,6 +35,8 @@ export interface DecisionContext {
35
35
  workflowRegistry?: WorkflowRegistry;
36
36
  /** Gate evaluation result from the gate system (Phase 4) */
37
37
  gateEvaluation?: GateEvaluationResult;
38
+ /** Whether the merge queue is enabled for this repository */
39
+ mergeQueueEnabled?: boolean;
38
40
  }
39
41
  /** Max agent sessions before the circuit breaker trips and the issue is held */
40
42
  export declare const MAX_SESSION_ATTEMPTS = 3;
@@ -1 +1 @@
1
- {"version":3,"file":"decision-engine.d.ts","sourceRoot":"","sources":["../../../src/governor/decision-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAMxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AAExE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAA;AAM/E;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAA;IACpB,MAAM,EAAE,cAAc,CAAA;IACtB,gBAAgB,EAAE,OAAO,CAAA;IACzB,MAAM,EAAE,OAAO,CAAA;IACf,gBAAgB,EAAE,OAAO,CAAA;IACzB,aAAa,EAAE,OAAO,CAAA;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,wBAAwB,EAAE,OAAO,CAAA;IACjC,8EAA8E;IAC9E,qBAAqB,EAAE,MAAM,CAAA;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC,4DAA4D;IAC5D,cAAc,CAAC,EAAE,oBAAoB,CAAA;CACtC;AAED,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,IAAI,CAAA;AAMrC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,cAAc,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;CACf;AAaD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,cAAc,CAkHjE"}
1
+ {"version":3,"file":"decision-engine.d.ts","sourceRoot":"","sources":["../../../src/governor/decision-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAMxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AAExE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAA;AAM/E;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAA;IACpB,MAAM,EAAE,cAAc,CAAA;IACtB,gBAAgB,EAAE,OAAO,CAAA;IACzB,MAAM,EAAE,OAAO,CAAA;IACf,gBAAgB,EAAE,OAAO,CAAA;IACzB,aAAa,EAAE,OAAO,CAAA;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,wBAAwB,EAAE,OAAO,CAAA;IACjC,8EAA8E;IAC9E,qBAAqB,EAAE,MAAM,CAAA;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC,4DAA4D;IAC5D,cAAc,CAAC,EAAE,oBAAoB,CAAA;IACrC,6DAA6D;IAC7D,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,IAAI,CAAA;AAMrC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,cAAc,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;CACf;AAaD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,cAAc,CAkHjE"}
@@ -244,6 +244,13 @@ function decideFinished(ctx) {
244
244
  reason: `Issue ${issue.identifier} is in Finished with decompose strategy — triggering decomposition`,
245
245
  };
246
246
  }
247
+ // When merge queue is enabled, enqueue to merge queue instead of QA
248
+ if (ctx.mergeQueueEnabled) {
249
+ return {
250
+ action: 'trigger-merge',
251
+ reason: `Issue ${issue.identifier} is in Finished — enqueuing to merge queue`,
252
+ };
253
+ }
247
254
  return {
248
255
  action: 'trigger-qa',
249
256
  reason: `Issue ${issue.identifier} is in Finished — triggering QA`,
@@ -406,6 +406,69 @@ describe('decideAction — Finished (QA)', () => {
406
406
  });
407
407
  });
408
408
  // ---------------------------------------------------------------------------
409
+ // Finished (merge queue)
410
+ // ---------------------------------------------------------------------------
411
+ describe('decideAction — Finished (merge queue)', () => {
412
+ it('triggers trigger-merge when mergeQueueEnabled is true', () => {
413
+ const ctx = makeContext({
414
+ issue: makeIssue({ status: 'Finished' }),
415
+ mergeQueueEnabled: true,
416
+ });
417
+ const result = decideAction(ctx);
418
+ expect(result.action).toBe('trigger-merge');
419
+ expect(result.reason).toContain('merge queue');
420
+ });
421
+ it('triggers QA when mergeQueueEnabled is false', () => {
422
+ const ctx = makeContext({
423
+ issue: makeIssue({ status: 'Finished' }),
424
+ mergeQueueEnabled: false,
425
+ });
426
+ const result = decideAction(ctx);
427
+ expect(result.action).toBe('trigger-qa');
428
+ expect(result.reason).toContain('triggering QA');
429
+ });
430
+ it('triggers QA when mergeQueueEnabled is not configured (undefined)', () => {
431
+ const ctx = makeContext({
432
+ issue: makeIssue({ status: 'Finished' }),
433
+ });
434
+ const result = decideAction(ctx);
435
+ expect(result.action).toBe('trigger-qa');
436
+ expect(result.reason).toContain('triggering QA');
437
+ });
438
+ it('escalation strategy takes precedence over merge queue', () => {
439
+ const ctx = makeContext({
440
+ issue: makeIssue({ status: 'Finished' }),
441
+ mergeQueueEnabled: true,
442
+ workflowStrategy: 'escalate-human',
443
+ });
444
+ const result = decideAction(ctx);
445
+ expect(result.action).toBe('escalate-human');
446
+ });
447
+ it('decompose strategy takes precedence over merge queue', () => {
448
+ const ctx = makeContext({
449
+ issue: makeIssue({ status: 'Finished' }),
450
+ mergeQueueEnabled: true,
451
+ workflowStrategy: 'decompose',
452
+ });
453
+ const result = decideAction(ctx);
454
+ expect(result.action).toBe('decompose');
455
+ });
456
+ it('auto-QA disabled takes precedence over merge queue', () => {
457
+ const ctx = makeContext({
458
+ issue: makeIssue({ status: 'Finished' }),
459
+ mergeQueueEnabled: true,
460
+ config: {
461
+ ...DEFAULT_GOVERNOR_CONFIG,
462
+ projects: ['TestProject'],
463
+ enableAutoQA: false,
464
+ },
465
+ });
466
+ const result = decideAction(ctx);
467
+ expect(result.action).toBe('none');
468
+ expect(result.reason).toContain('disabled');
469
+ });
470
+ });
471
+ // ---------------------------------------------------------------------------
409
472
  // Delivered (acceptance)
410
473
  // ---------------------------------------------------------------------------
411
474
  describe('decideAction — Delivered (acceptance)', () => {
@@ -15,12 +15,13 @@ import type { TopOfFunnelConfig } from './top-of-funnel.js';
15
15
  * - trigger-development: Dispatch a development agent (Backlog)
16
16
  * - trigger-qa: Dispatch a QA agent (Finished)
17
17
  * - trigger-acceptance: Dispatch an acceptance agent (Delivered)
18
+ * - trigger-merge: Dispatch a merge queue agent (Finished, merge queue enabled)
18
19
  * - trigger-refinement: Dispatch a refinement agent (Rejected)
19
20
  * - decompose: Trigger task decomposition (escalation strategy)
20
21
  * - escalate-human: Create a human escalation touchpoint
21
22
  * - none: No action needed
22
23
  */
23
- export type GovernorAction = 'trigger-research' | 'trigger-backlog-creation' | 'trigger-development' | 'trigger-qa' | 'trigger-acceptance' | 'trigger-refinement' | 'trigger-parallel-group' | 'decompose' | 'escalate-human' | 'none';
24
+ export type GovernorAction = 'trigger-research' | 'trigger-backlog-creation' | 'trigger-development' | 'trigger-qa' | 'trigger-acceptance' | 'trigger-merge' | 'trigger-refinement' | 'trigger-parallel-group' | 'decompose' | 'escalate-human' | 'none';
24
25
  /**
25
26
  * Configuration for the Workflow Governor scan loop.
26
27
  */
@@ -1 +1 @@
1
- {"version":3,"file":"governor-types.d.ts","sourceRoot":"","sources":["../../../src/governor/governor-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAM3D;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,0BAA0B,GAC1B,qBAAqB,GACrB,YAAY,GACZ,oBAAoB,GACpB,oBAAoB,GACpB,wBAAwB,GACxB,WAAW,GACX,gBAAgB,GAChB,MAAM,CAAA;AAMV;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,uBAAuB;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,qDAAqD;IACrD,cAAc,EAAE,MAAM,CAAA;IACtB,0DAA0D;IAC1D,uBAAuB,EAAE,MAAM,CAAA;IAC/B,uDAAuD;IACvD,kBAAkB,EAAE,OAAO,CAAA;IAC3B,+DAA+D;IAC/D,yBAAyB,EAAE,OAAO,CAAA;IAClC,2DAA2D;IAC3D,qBAAqB,EAAE,OAAO,CAAA;IAC9B,mDAAmD;IACnD,YAAY,EAAE,OAAO,CAAA;IACrB,4DAA4D;IAC5D,oBAAoB,EAAE,OAAO,CAAA;IAC7B,gEAAgE;IAChE,sBAAsB,EAAE,MAAM,CAAA;IAC9B,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;CACzC;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,EAAE,cAUrC,CAAA;AAMD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAClD;AAMD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB"}
1
+ {"version":3,"file":"governor-types.d.ts","sourceRoot":"","sources":["../../../src/governor/governor-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAM3D;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,0BAA0B,GAC1B,qBAAqB,GACrB,YAAY,GACZ,oBAAoB,GACpB,eAAe,GACf,oBAAoB,GACpB,wBAAwB,GACxB,WAAW,GACX,gBAAgB,GAChB,MAAM,CAAA;AAMV;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,uBAAuB;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,qDAAqD;IACrD,cAAc,EAAE,MAAM,CAAA;IACtB,0DAA0D;IAC1D,uBAAuB,EAAE,MAAM,CAAA;IAC/B,uDAAuD;IACvD,kBAAkB,EAAE,OAAO,CAAA;IAC3B,+DAA+D;IAC/D,yBAAyB,EAAE,OAAO,CAAA;IAClC,2DAA2D;IAC3D,qBAAqB,EAAE,OAAO,CAAA;IAC9B,mDAAmD;IACnD,YAAY,EAAE,OAAO,CAAA;IACrB,4DAA4D;IAC5D,oBAAoB,EAAE,OAAO,CAAA;IAC7B,gEAAgE;IAChE,sBAAsB,EAAE,MAAM,CAAA;IAC9B,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;CACzC;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,EAAE,cAUrC,CAAA;AAMD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAClD;AAMD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB"}
@@ -10,4 +10,5 @@ export * from './manifest/index.js';
10
10
  export * from './tools/index.js';
11
11
  export * from './workflow/index.js';
12
12
  export * from './routing/index.js';
13
+ export * from './registry/index.js';
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAA;AACvC,cAAc,sBAAsB,CAAA;AACpC,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAA;AACvC,cAAc,sBAAsB,CAAA;AACpC,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA"}
package/dist/src/index.js CHANGED
@@ -10,3 +10,4 @@ export * from './manifest/index.js';
10
10
  export * from './tools/index.js';
11
11
  export * from './workflow/index.js';
12
12
  export * from './routing/index.js';
13
+ export * from './registry/index.js';
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Conflict Resolver
3
+ *
4
+ * Attempts automatic conflict resolution via mergiraf merge driver,
5
+ * then escalates remaining conflicts using the configured strategy.
6
+ *
7
+ * Lifecycle:
8
+ * 1. If mergiraf is enabled, check each conflict file for remaining
9
+ * conflict markers (mergiraf runs as a git merge driver during rebase).
10
+ * 2. Stage any files that mergiraf resolved (no conflict markers).
11
+ * 3. If all files resolved, run `git rebase --continue`.
12
+ * 4. If any files remain unresolved, escalate per the configured strategy.
13
+ */
14
+ export interface ConflictContext {
15
+ repoPath: string;
16
+ worktreePath: string;
17
+ sourceBranch: string;
18
+ targetBranch: string;
19
+ prNumber: number;
20
+ issueIdentifier: string;
21
+ conflictFiles: string[];
22
+ conflictDetails?: string;
23
+ }
24
+ export type EscalationStrategy = 'reassign' | 'notify' | 'park';
25
+ export interface ResolutionResult {
26
+ status: 'resolved' | 'escalated' | 'parked';
27
+ method: 'mergiraf' | 'escalation';
28
+ resolvedFiles?: string[];
29
+ unresolvedFiles?: string[];
30
+ escalationAction?: EscalationStrategy;
31
+ message?: string;
32
+ }
33
+ export interface ConflictResolverConfig {
34
+ mergirafEnabled: boolean;
35
+ escalationStrategy: EscalationStrategy;
36
+ }
37
+ export declare class ConflictResolver {
38
+ private config;
39
+ constructor(config: ConflictResolverConfig);
40
+ /**
41
+ * Resolve merge conflicts using mergiraf (if enabled), then escalate
42
+ * any remaining unresolved files using the configured strategy.
43
+ */
44
+ resolve(ctx: ConflictContext): Promise<ResolutionResult>;
45
+ /**
46
+ * Check each conflict file for remaining conflict markers.
47
+ * Mergiraf runs as a git merge driver during rebase, so by the time we
48
+ * inspect the files, successfully resolved ones will have no markers.
49
+ */
50
+ private attemptMergiraf;
51
+ /**
52
+ * Check whether a file contains git conflict markers.
53
+ * grep returns exit code 1 when no matches are found.
54
+ */
55
+ private fileHasConflictMarkers;
56
+ private escalate;
57
+ private escalateReassign;
58
+ private escalateNotify;
59
+ private escalatePark;
60
+ private getConflictDiff;
61
+ }
62
+ //# sourceMappingURL=conflict-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflict-resolver.d.ts","sourceRoot":"","sources":["../../../src/merge-queue/conflict-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAWH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAA;AAE/D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAA;IAC3C,MAAM,EAAE,UAAU,GAAG,YAAY,CAAA;IACjC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,gBAAgB,CAAC,EAAE,kBAAkB,CAAA;IACrC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,eAAe,EAAE,OAAO,CAAA;IACxB,kBAAkB,EAAE,kBAAkB,CAAA;CACvC;AAMD,qBAAa,gBAAgB;IACf,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,sBAAsB;IAElD;;;OAGG;IACG,OAAO,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAmB9D;;;;OAIG;YACW,eAAe;IAgD7B;;;OAGG;YACW,sBAAsB;YAiBtB,QAAQ;YAaR,gBAAgB;YAWhB,cAAc;YAUd,YAAY;YAcZ,eAAe;CAc9B"}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Conflict Resolver
3
+ *
4
+ * Attempts automatic conflict resolution via mergiraf merge driver,
5
+ * then escalates remaining conflicts using the configured strategy.
6
+ *
7
+ * Lifecycle:
8
+ * 1. If mergiraf is enabled, check each conflict file for remaining
9
+ * conflict markers (mergiraf runs as a git merge driver during rebase).
10
+ * 2. Stage any files that mergiraf resolved (no conflict markers).
11
+ * 3. If all files resolved, run `git rebase --continue`.
12
+ * 4. If any files remain unresolved, escalate per the configured strategy.
13
+ */
14
+ import { exec as execCb } from 'child_process';
15
+ import { promisify } from 'util';
16
+ const exec = promisify(execCb);
17
+ // ---------------------------------------------------------------------------
18
+ // ConflictResolver
19
+ // ---------------------------------------------------------------------------
20
+ export class ConflictResolver {
21
+ config;
22
+ constructor(config) {
23
+ this.config = config;
24
+ }
25
+ /**
26
+ * Resolve merge conflicts using mergiraf (if enabled), then escalate
27
+ * any remaining unresolved files using the configured strategy.
28
+ */
29
+ async resolve(ctx) {
30
+ // 1. If mergiraf enabled, attempt auto-resolution
31
+ if (this.config.mergirafEnabled) {
32
+ const mergirafResult = await this.attemptMergiraf(ctx);
33
+ if (mergirafResult.status === 'resolved') {
34
+ return mergirafResult;
35
+ }
36
+ // Partial resolution — update context with remaining files
37
+ ctx = { ...ctx, conflictFiles: mergirafResult.unresolvedFiles ?? ctx.conflictFiles };
38
+ }
39
+ // 2. Escalate remaining conflicts
40
+ return this.escalate(ctx);
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // Mergiraf auto-resolution
44
+ // ---------------------------------------------------------------------------
45
+ /**
46
+ * Check each conflict file for remaining conflict markers.
47
+ * Mergiraf runs as a git merge driver during rebase, so by the time we
48
+ * inspect the files, successfully resolved ones will have no markers.
49
+ */
50
+ async attemptMergiraf(ctx) {
51
+ const resolvedFiles = [];
52
+ const unresolvedFiles = [];
53
+ for (const file of ctx.conflictFiles) {
54
+ const hasConflict = await this.fileHasConflictMarkers(ctx.worktreePath, file);
55
+ if (!hasConflict) {
56
+ // Mergiraf resolved this file — stage it
57
+ await exec(`git add "${file}"`, { cwd: ctx.worktreePath });
58
+ resolvedFiles.push(file);
59
+ }
60
+ else {
61
+ unresolvedFiles.push(file);
62
+ }
63
+ }
64
+ if (unresolvedFiles.length === 0) {
65
+ // All resolved — continue rebase
66
+ try {
67
+ await exec('git rebase --continue', {
68
+ cwd: ctx.worktreePath,
69
+ env: { ...process.env, GIT_EDITOR: 'true' },
70
+ });
71
+ return {
72
+ status: 'resolved',
73
+ method: 'mergiraf',
74
+ resolvedFiles,
75
+ };
76
+ }
77
+ catch {
78
+ // Rebase continue failed — may need more resolution rounds
79
+ return {
80
+ status: 'escalated',
81
+ method: 'mergiraf',
82
+ resolvedFiles,
83
+ unresolvedFiles: ctx.conflictFiles,
84
+ message: 'Mergiraf resolved conflict files but rebase --continue failed',
85
+ };
86
+ }
87
+ }
88
+ return {
89
+ status: 'escalated',
90
+ method: 'mergiraf',
91
+ resolvedFiles,
92
+ unresolvedFiles,
93
+ message: `Mergiraf resolved ${resolvedFiles.length}/${ctx.conflictFiles.length} files`,
94
+ };
95
+ }
96
+ /**
97
+ * Check whether a file contains git conflict markers.
98
+ * grep returns exit code 1 when no matches are found.
99
+ */
100
+ async fileHasConflictMarkers(worktreePath, file) {
101
+ try {
102
+ const { stdout } = await exec(`grep -c "^<<<<<<<\\|^=======\\|^>>>>>>>" "${file}"`, { cwd: worktreePath });
103
+ return parseInt(stdout.trim(), 10) > 0;
104
+ }
105
+ catch {
106
+ // grep returns exit code 1 when no matches — no conflict markers
107
+ return false;
108
+ }
109
+ }
110
+ // ---------------------------------------------------------------------------
111
+ // Escalation strategies
112
+ // ---------------------------------------------------------------------------
113
+ async escalate(ctx) {
114
+ switch (this.config.escalationStrategy) {
115
+ case 'reassign':
116
+ return this.escalateReassign(ctx);
117
+ case 'notify':
118
+ return this.escalateNotify(ctx);
119
+ case 'park':
120
+ return this.escalatePark(ctx);
121
+ default:
122
+ return this.escalateNotify(ctx);
123
+ }
124
+ }
125
+ async escalateReassign(ctx) {
126
+ const diffOutput = await this.getConflictDiff(ctx);
127
+ return {
128
+ status: 'escalated',
129
+ method: 'escalation',
130
+ unresolvedFiles: ctx.conflictFiles,
131
+ escalationAction: 'reassign',
132
+ message: `Conflict on ${ctx.issueIdentifier} PR #${ctx.prNumber}. Files: ${ctx.conflictFiles.join(', ')}. Agent should resolve and re-submit.\n\nDiff:\n${diffOutput}`,
133
+ };
134
+ }
135
+ async escalateNotify(ctx) {
136
+ return {
137
+ status: 'escalated',
138
+ method: 'escalation',
139
+ unresolvedFiles: ctx.conflictFiles,
140
+ escalationAction: 'notify',
141
+ message: `Merge conflict on ${ctx.issueIdentifier} PR #${ctx.prNumber} requires resolution. Files: ${ctx.conflictFiles.join(', ')}`,
142
+ };
143
+ }
144
+ async escalatePark(ctx) {
145
+ return {
146
+ status: 'parked',
147
+ method: 'escalation',
148
+ unresolvedFiles: ctx.conflictFiles,
149
+ escalationAction: 'park',
150
+ message: `PR #${ctx.prNumber} parked due to conflicts in: ${ctx.conflictFiles.join(', ')}. Will auto-retry after other merges complete.`,
151
+ };
152
+ }
153
+ // ---------------------------------------------------------------------------
154
+ // Helpers
155
+ // ---------------------------------------------------------------------------
156
+ async getConflictDiff(ctx) {
157
+ try {
158
+ const { stdout } = await exec(`git diff ${ctx.conflictFiles.map(f => `"${f}"`).join(' ')}`, {
159
+ cwd: ctx.worktreePath,
160
+ maxBuffer: 1024 * 1024, // 1MB
161
+ });
162
+ return stdout.slice(0, 5000); // Truncate for readability
163
+ }
164
+ catch {
165
+ return '(unable to generate diff)';
166
+ }
167
+ }
168
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=conflict-resolver.test.d.ts.map