feat(FN-5347): reset manual retry counters across task retry surfaces
- Add a shared manual retry reset helper in core and export it for consumers - Wire retry counter resets into CLI task retry, extension task retry, and dashboard task workflow routes - Align retry reset behavior with task typing updates and remove superseded task-helper reset paths - Add focused core/CLI/extension/dashboard tests plus docs and a changeset describing retry reset behavior
This commit is contained in:
committed by
gsxdsm
parent
a0aa6c8c5b
commit
216c32bfe5
38
packages/core/src/__tests__/manual-retry-reset.test.ts
Normal file
38
packages/core/src/__tests__/manual-retry-reset.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildManualRetryResetPatch, MANUAL_RETRY_RESET_COUNTER_KEYS } from "../manual-retry-reset.js";
|
||||
|
||||
const RETRY_SUMMARY_COUNTER_REGEX = /toCount\(task\.(\w+)\)/g;
|
||||
|
||||
describe("buildManualRetryResetPatch", () => {
|
||||
it("resets all manual retry counters to zero", () => {
|
||||
const patch = buildManualRetryResetPatch();
|
||||
|
||||
for (const key of MANUAL_RETRY_RESET_COUNTER_KEYS) {
|
||||
expect(patch[key]).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it("includes all retry-summary counters in the reset key list", () => {
|
||||
const retrySummarySource = readFileSync(new URL("../retry-summary.ts", import.meta.url), "utf-8");
|
||||
const retrySummaryKeys = new Set<string>();
|
||||
let match: RegExpExecArray | null = RETRY_SUMMARY_COUNTER_REGEX.exec(retrySummarySource);
|
||||
while (match) {
|
||||
retrySummaryKeys.add(match[1]);
|
||||
match = RETRY_SUMMARY_COUNTER_REGEX.exec(retrySummarySource);
|
||||
}
|
||||
|
||||
for (const key of retrySummaryKeys) {
|
||||
expect(MANUAL_RETRY_RESET_COUNTER_KEYS).toContain(key);
|
||||
}
|
||||
});
|
||||
|
||||
it("sets mergeRetries only when requested", () => {
|
||||
expect(buildManualRetryResetPatch()).not.toHaveProperty("mergeRetries");
|
||||
expect(buildManualRetryResetPatch({ resetMergeRetries: true })).toMatchObject({ mergeRetries: 0 });
|
||||
});
|
||||
|
||||
it("clears nextRecoveryAt", () => {
|
||||
expect(buildManualRetryResetPatch()).toMatchObject({ nextRecoveryAt: null });
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildManualRetryResetPatch, getPrimaryPrInfo } from "../task-helpers.js";
|
||||
import { getPrimaryPrInfo } from "../task-helpers.js";
|
||||
|
||||
describe("getPrimaryPrInfo", () => {
|
||||
it("returns prInfo when only legacy field is set", () => {
|
||||
const prInfo = { number: 1 } as any;
|
||||
@@ -23,12 +24,3 @@ describe("getPrimaryPrInfo", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildManualRetryResetPatch", () => {
|
||||
it("resets only manual retry counters", () => {
|
||||
expect(buildManualRetryResetPatch()).toEqual({
|
||||
taskDoneRetryCount: 0,
|
||||
workflowStepRetries: 0,
|
||||
stuckKillCount: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -201,7 +201,8 @@ export {
|
||||
hasTitleIdDrift,
|
||||
normalizeTitleForTaskId,
|
||||
} from "./task-title-id-drift.js";
|
||||
export { getPrimaryPrInfo, buildManualRetryResetPatch } from "./task-helpers.js";
|
||||
export { getPrimaryPrInfo } from "./task-helpers.js";
|
||||
export { MANUAL_RETRY_RESET_COUNTER_KEYS, buildManualRetryResetPatch } from "./manual-retry-reset.js";
|
||||
export type {
|
||||
TaskIdIntegrityAnomaly,
|
||||
TaskIdIntegrityAnomalyKind,
|
||||
|
||||
33
packages/core/src/manual-retry-reset.ts
Normal file
33
packages/core/src/manual-retry-reset.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { Task } from "./types.js";
|
||||
|
||||
export const MANUAL_RETRY_RESET_COUNTER_KEYS = [
|
||||
"stuckKillCount",
|
||||
"recoveryRetryCount",
|
||||
"taskDoneRetryCount",
|
||||
"worktreeSessionRetryCount",
|
||||
"workflowStepRetries",
|
||||
"verificationFailureCount",
|
||||
"postReviewFixCount",
|
||||
"mergeConflictBounceCount",
|
||||
"branchConflictRecoveryCount",
|
||||
"reviewerContextRetryCount",
|
||||
"reviewerFallbackRetryCount",
|
||||
"completionHandoffLimboRecoveryCount",
|
||||
"mergeAuditBounceCount",
|
||||
] as const satisfies ReadonlyArray<keyof Task>;
|
||||
|
||||
export function buildManualRetryResetPatch(options?: { resetMergeRetries?: boolean }): Partial<Task> {
|
||||
const patch: Partial<Task> = {
|
||||
nextRecoveryAt: null as unknown as Task["nextRecoveryAt"],
|
||||
};
|
||||
|
||||
for (const key of MANUAL_RETRY_RESET_COUNTER_KEYS) {
|
||||
patch[key] = 0;
|
||||
}
|
||||
|
||||
if (options?.resetMergeRetries) {
|
||||
patch.mergeRetries = 0;
|
||||
}
|
||||
|
||||
return patch;
|
||||
}
|
||||
@@ -3,11 +3,3 @@ import type { PrInfo, Task } from "./types.js";
|
||||
export function getPrimaryPrInfo(task: Pick<Task, "prInfo" | "prInfos">): PrInfo | undefined {
|
||||
return task.prInfos?.[0] ?? task.prInfo;
|
||||
}
|
||||
|
||||
export function buildManualRetryResetPatch(): Pick<Task, "taskDoneRetryCount" | "workflowStepRetries" | "stuckKillCount"> {
|
||||
return {
|
||||
taskDoneRetryCount: 0,
|
||||
workflowStepRetries: 0,
|
||||
stuckKillCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user