diff --git a/packages/gittensory-engine/src/focus-manifest.ts b/packages/gittensory-engine/src/focus-manifest.ts index e04f99007f..fd0e155a17 100644 --- a/packages/gittensory-engine/src/focus-manifest.ts +++ b/packages/gittensory-engine/src/focus-manifest.ts @@ -323,6 +323,7 @@ export type FocusManifestSettings = Partial< | "reviewEvasionProtection" | "reviewEvasionLabel" | "reviewEvasionComment" + | "mergeTrainMode" > > & { // `typeLabels`/`linkedIssueLabelPropagation`/`linkedIssueHardRules` are declared PARTIAL here (not via the `Pick { // #2267: qualityGateMode: "block" is downgraded to "advisory" on write — readiness/quality can never // hard-block a PR, so the dashboard/API save path can't persist a value implying enforcement it doesn't // have. slopGateMode: "block" is a DIFFERENT, legitimately-blockable dimension and is left untouched. - body: JSON.stringify({ gateCheckMode: "enabled", slopGateMode: "block", slopGateMinScore: 55, qualityGateMode: "block", autonomy: { merge: "auto_with_approval", deploy: "auto" }, autoMaintain: { requireApprovals: 2, mergeMethod: "rebase" }, agentPaused: true, agentDryRun: true }), + body: JSON.stringify({ gateCheckMode: "enabled", slopGateMode: "block", slopGateMinScore: 55, qualityGateMode: "block", mergeTrainMode: "enforce", autonomy: { merge: "auto_with_approval", deploy: "auto" }, autoMaintain: { requireApprovals: 2, mergeMethod: "rebase" }, agentPaused: true, agentDryRun: true }), }, ownerEnv, ); @@ -2462,6 +2462,7 @@ describe("api routes", () => { slopGateMode: "block", slopGateMinScore: 55, qualityGateMode: "advisory", // #2267: downgraded, not persisted as "block" + mergeTrainMode: "enforce", autonomy: { merge: "auto_with_approval" }, // unknown action class dropped by the DB normalizer autoMaintain: { requireApprovals: 2, mergeMethod: "rebase" }, agentPaused: true, // #776 kill-switch diff --git a/test/unit/focus-manifest.test.ts b/test/unit/focus-manifest.test.ts index 5a4abb98f3..18dd7bbb1e 100644 --- a/test/unit/focus-manifest.test.ts +++ b/test/unit/focus-manifest.test.ts @@ -342,6 +342,7 @@ describe(".gittensory.yml.example field-exhaustiveness (#1670)", () => { reviewEvasionProtection: "reviewEvasionProtection:", reviewEvasionLabel: "reviewEvasionLabel:", reviewEvasionComment: "reviewEvasionComment:", + mergeTrainMode: "mergeTrainMode:", typeLabels: "typeLabels:", linkedIssueLabelPropagation: "linkedIssueLabelPropagation:", linkedIssueHardRules: "linkedIssueHardRules:", @@ -1786,13 +1787,14 @@ describe("parseFocusManifest settings override + resolveEffectiveSettings", () = it("drops invalid settings values with warnings and keeps the valid ones", () => { const m = parseFocusManifest({ - settings: { commentMode: "loud", qualityGateMinScore: "high", autoLabelEnabled: "yes", gittensorLabel: " ", publicSurface: "comment_only" }, + settings: { commentMode: "loud", qualityGateMinScore: "high", autoLabelEnabled: "yes", gittensorLabel: " ", mergeTrainMode: "later", publicSurface: "comment_only" }, }); expect(m.settings).toEqual({ publicSurface: "comment_only" }); expect(m.warnings.some((w) => /settings\.commentMode/.test(w))).toBe(true); expect(m.warnings.some((w) => /settings\.qualityGateMinScore/.test(w))).toBe(true); expect(m.warnings.some((w) => /settings\.autoLabelEnabled/.test(w))).toBe(true); expect(m.warnings.some((w) => /settings\.gittensorLabel/.test(w))).toBe(true); + expect(m.warnings.some((w) => /settings\.mergeTrainMode/.test(w))).toBe(true); }); it("ignores a non-mapping settings block and treats a settings-only manifest as present", () => { @@ -1815,7 +1817,7 @@ describe("parseFocusManifest settings override + resolveEffectiveSettings", () = }); it("round-trips settings through settingsOverrideToJson and serializes empty as null", () => { - const original = parseFocusManifest({ settings: { commentMode: "all_prs", qualityGateMinScore: 40 } }); + const original = parseFocusManifest({ settings: { commentMode: "all_prs", qualityGateMinScore: 40, mergeTrainMode: "audit" } }); const reparsed = parseFocusManifest({ settings: settingsOverrideToJson(original.settings) }); expect(reparsed.settings).toEqual(original.settings); expect(settingsOverrideToJson(parseFocusManifest({}).settings)).toBeNull();