@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.
- 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/completion-contracts.d.ts +89 -0
- package/dist/src/orchestrator/completion-contracts.d.ts.map +1 -0
- package/dist/src/orchestrator/completion-contracts.js +228 -0
- package/dist/src/orchestrator/completion-contracts.test.d.ts +2 -0
- package/dist/src/orchestrator/completion-contracts.test.d.ts.map +1 -0
- package/dist/src/orchestrator/completion-contracts.test.js +195 -0
- package/dist/src/orchestrator/index.d.ts +4 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -1
- package/dist/src/orchestrator/index.js +3 -0
- package/dist/src/orchestrator/orchestrator.d.ts +32 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +157 -26
- 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/orchestrator/session-backstop.d.ts +67 -0
- package/dist/src/orchestrator/session-backstop.d.ts.map +1 -0
- package/dist/src/orchestrator/session-backstop.js +394 -0
- package/dist/src/orchestrator/session-backstop.test.d.ts +2 -0
- package/dist/src/orchestrator/session-backstop.test.d.ts.map +1 -0
- package/dist/src/orchestrator/session-backstop.test.js +245 -0
- package/dist/src/orchestrator/worktree-checks.test.d.ts +2 -0
- package/dist/src/orchestrator/worktree-checks.test.d.ts.map +1 -0
- package/dist/src/orchestrator/worktree-checks.test.js +159 -0
- package/dist/src/providers/a2a-provider.d.ts +4 -0
- package/dist/src/providers/a2a-provider.d.ts.map +1 -1
- package/dist/src/providers/a2a-provider.js +4 -0
- package/dist/src/providers/amp-provider.d.ts +4 -0
- package/dist/src/providers/amp-provider.d.ts.map +1 -1
- package/dist/src/providers/amp-provider.js +4 -0
- package/dist/src/providers/claude-provider.d.ts +4 -0
- package/dist/src/providers/claude-provider.d.ts.map +1 -1
- package/dist/src/providers/claude-provider.js +6 -0
- package/dist/src/providers/codex-provider.d.ts +4 -0
- package/dist/src/providers/codex-provider.d.ts.map +1 -1
- package/dist/src/providers/codex-provider.js +4 -0
- package/dist/src/providers/index.d.ts +2 -1
- 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/providers/spring-ai-provider.d.ts +4 -0
- package/dist/src/providers/spring-ai-provider.d.ts.map +1 -1
- package/dist/src/providers/spring-ai-provider.js +4 -0
- package/dist/src/providers/types.d.ts +22 -0
- package/dist/src/providers/types.d.ts.map +1 -1
- 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/templates/types.d.ts +3 -0
- package/dist/src/templates/types.d.ts.map +1 -1
- package/dist/src/templates/types.js +2 -0
- package/dist/src/tools/index.d.ts +1 -0
- package/dist/src/tools/index.d.ts.map +1 -1
- package/dist/src/tools/registry.d.ts +6 -1
- package/dist/src/tools/registry.d.ts.map +1 -1
- package/dist/src/tools/registry.js +5 -1
- 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
|
@@ -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
|
|
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;
|
|
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
|
|
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"}
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.d.ts.map
CHANGED
|
@@ -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
|
@@ -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
|
+
}
|