Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tanstack-migrator/server/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
createSiteRegisterTool,
createSiteReorderTool,
createSiteResumeTool,
createSiteResetTool,
createSiteRetryTool,
createSiteTerminalTool,
} from "./sites.ts";
Expand All @@ -39,6 +40,7 @@ export const tools = [
createSitePauseTool,
createSiteResumeTool,
createSiteRetryTool,
createSiteResetTool,
createSiteEnqueueTool,
createSiteReorderTool,
createSiteMarkDoneTool,
Expand Down
69 changes: 69 additions & 0 deletions tanstack-migrator/server/tools/sites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,75 @@ export const createSiteRetryTool = (env: Env) =>
},
});

/** Phases a reset can rewind INTO (repo/sandbox/script/PR already exist). */
const RESET_TARGETS = [
"provisioning_sandbox",
"baselining",
"opening_pr",
"reviewing",
"triaging",
"fixing",
"paritying",
"deploying",
] as const;

export const createSiteResetTool = (env: Env) =>
createTool({
id: "SITE_RESET",
description:
"Rewind a migration back to the start of the loop (default: triaging) WITHOUT redoing the irreversible prefix — it never re-runs the migration script nor re-opens the PR (the existing branch/PR and CF deploy URL are kept). Clears parity score, iteration/fix-session/no-improve counters and session state so the triage → fix → review → merge → deploy → parity loop starts fresh. Use it to unstick a site (e.g. a legacy one trying to measure parity with an open PR from before the incremental-merge flow).",
inputSchema: z.object({
siteId: z.string(),
toStatus: z
.enum(RESET_TARGETS)
.optional()
.describe("Phase to rewind into (default: triaging)."),
}),
outputSchema: siteOutput,
execute: async ({ context }) => {
requireConnectionId(env);
const site = await getSite(context.siteId);
if (!site) throw new Error("Site not found");
if (!site.target_repo) {
throw new Error(
"Migration hasn't started yet — nothing to reset (enqueue it instead).",
);
}
if (site.status === "archived") {
throw new Error("Cannot reset an archived migration.");
}
const nextStatus = (context.toStatus ?? "triaging") as SiteStatus;
const updated = await updateSite(site.id, {
status: nextStatus,
// clear blockers + resume marker
resume_status: null,
error: null,
needs_human_reason: null,
// restart the loop from zero (keep repo/branch/PR/deploy URL intact)
parity_score: null,
best_score: null,
iterations_done: 0,
no_improve_count: 0,
fix_sessions_done: 0,
// reset the issue cache so triaging re-analyzes (it skips to fixing when
// issues_open > 0); GitHub stays the source of truth and is re-synced
issues_open: 0,
issues_total: 0,
issues_closed: 0,
// drop any in-flight session so the next tick starts clean
sandbox_session_id: null,
phase_thread_id: null,
transient_retries: 0,
last_progress_at: new Date().toISOString(),
});
await addEvent(
site.id,
`Reset to ${nextStatus} (kept repo/branch${site.pr_number ? `/PR #${site.pr_number}` : ""}${site.cf_deploy_url ? "/deploy" : ""}; loop restarted)`,
);
return { site: toSiteView(updated) };
},
});

export const createSiteMarkDoneTool = (env: Env) =>
createTool({
id: "SITE_MARK_DONE",
Expand Down
21 changes: 21 additions & 0 deletions tanstack-migrator/web/components/site-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Monitor,
Pause,
Play,
Rewind,
RotateCcw,
Trash2,
UserCircle2,
Expand Down Expand Up @@ -282,6 +283,7 @@ export function SiteDetailPanel({
} = usePollingTool<SiteDetail>("SITE_GET", { siteId }, 10_000);
const [busy, setBusy] = useState<string | null>(null);
const [confirmDelete, setConfirmDelete] = useState(false);
const [confirmReset, setConfirmReset] = useState(false);
const [error, setError] = useState<string | null>(null);
const [tab, setTab] = useState<Tab>("overview");
const [runFilter, setRunFilter] = useState<RunFilter>("all");
Expand Down Expand Up @@ -311,6 +313,16 @@ export function SiteDetailPanel({
}
};

const resetSite = async () => {
if (!confirmReset) {
setConfirmReset(true);
setTimeout(() => setConfirmReset(false), 4000);
return;
}
setConfirmReset(false);
await action("SITE_RESET");
};

const removeSite = async () => {
if (!confirmDelete) {
setConfirmDelete(true);
Expand Down Expand Up @@ -664,6 +676,15 @@ export function SiteDetailPanel({
onClick={() => action("SITE_RETRY")}
/>
)}
{site.targetRepo &&
!["queued", "archived"].includes(site.status) && (
<ActionButton
icon={Rewind}
label={confirmReset ? "Reset to triage?" : "Reset"}
busy={busy === "SITE_RESET"}
onClick={resetSite}
/>
)}
{site.status !== "done" && site.status !== "archived" && (
<ActionButton
icon={CheckCircle2}
Expand Down
Loading