FN-5792: fix GitHub auto-close reconciliation for soft-deleted tasks

Ensure GitHub issue auto-close reconciliation still processes soft-deleted and archived tracked tasks after restart.

- Add pagination-aware reconcile scanning in core store with combined deleted+archived coverage and hasMore metadata.
- Update dashboard GitHub tracking reconciler and route handling to consume paged reconcile batches safely.
- Expand reconcile tests (core + dashboard) with restart/periodic sweep and source-issue scenarios to lock behavior.
- Add release changeset and dashboard guide note for the reconciliation sweep behavior.

Files changed:
 .changeset/fn-5792-github-tracking-sweep.md        |   5 +
 docs/dashboard-guide.md                            |   1 +
 .../store-github-tracking-reconcile.test.ts        |  45 ++++++++-
 packages/core/src/store.ts                         |  32 ++++--
 .../github-source-issue-reconciler.test.ts         |  22 ++--
 ...ithub-tracking-periodic-reconcile-sweep.test.ts |  90 +++++++++++++++++
 .../__tests__/github-tracking-reconciler.test.ts   | 111 +++++----------------
 .../dashboard/src/github-tracking-reconciler.ts    |  19 ++--
 .../dashboard/src/routes/register-git-github.ts    |  57 +++++++++--
 9 files changed, 257 insertions(+), 125 deletions(-)

Fusion-Task-Id: FN-5792

Fusion-Task-Lineage: 259f07cf-3470-47c7-b47a-86d212d44d52
This commit is contained in:
gsxdsm
2026-05-31 17:23:26 -07:00
parent 716f3964c5
commit ffadb0c77f
9 changed files with 257 additions and 125 deletions

View File

@@ -9,7 +9,7 @@ export class GitHubTrackingReconciler {
async reconcile(store: TaskStore): Promise<{ scanned: number; closed: number; skipped: number; errors: number }> {
const listedTasks = await store.listTasks({ slim: true, includeArchived: false });
const tasks = (Array.isArray(listedTasks) ? listedTasks : [])
.filter((task) => task.status === "done")
.filter((task) => task.column === "done")
.slice(0, RECONCILE_SCAN_LIMIT);
const projectSettings = ((await store.getSettings()) ?? {}) as Pick<ProjectSettings, "githubAuthMode" | "githubAuthToken">;
@@ -62,7 +62,7 @@ export class GitHubTrackingReconciler {
async reconcileSourceIssues(store: TaskStore): Promise<{ scanned: number; closed: number; skipped: number; errors: number }> {
const listedTasks = await store.listTasks({ slim: false, includeArchived: false });
const tasks = (Array.isArray(listedTasks) ? listedTasks : [])
.filter((task) => task.status === "done" && task.sourceIssue?.provider === "github")
.filter((task) => task.column === "done" && task.sourceIssue?.provider === "github")
.slice(0, RECONCILE_SCAN_LIMIT);
const projectSettings = ((await store.getSettings()) ?? {}) as Pick<ProjectSettings, "githubCloseSourceIssueOnDone" | "githubAuthMode" | "githubAuthToken">;
@@ -120,9 +120,14 @@ export class GitHubTrackingReconciler {
return { scanned: tasks.length, closed, skipped, errors };
}
async reconcileDeletedAndArchived(store: TaskStore): Promise<{ scanned: number; closed: number; skipped: number; errors: number }> {
const listedTasks = await store.listTasksForGithubTrackingReconcile();
const tasks = (Array.isArray(listedTasks) ? listedTasks : []).slice(0, RECONCILE_SCAN_LIMIT);
async reconcileDeletedAndArchived(
store: TaskStore,
options?: { offset?: number; limit?: number },
): Promise<{ scanned: number; closed: number; skipped: number; errors: number; hasMore: boolean }> {
// Pagination is authoritative in TaskStore.listTasksForGithubTrackingReconcile.
const listedTasks = await store.listTasksForGithubTrackingReconcile(options);
const tasks = Array.isArray(listedTasks?.tasks) ? listedTasks.tasks : [];
const hasMore = listedTasks?.hasMore === true;
const projectSettings = ((await store.getSettings()) ?? {}) as Pick<ProjectSettings, "githubAuthMode" | "githubAuthToken">;
const globalSettings = (await store.getGlobalSettingsStore?.()?.getSettings?.() ?? {}) as Pick<GlobalSettings, never>;
@@ -131,7 +136,7 @@ export class GitHubTrackingReconciler {
for (const task of tasks) {
await store.logEntry(task.id, "Skipped GitHub tracking issue reconciliation (deleted/archived pass)", resolution.message);
}
return { scanned: tasks.length, closed: 0, skipped: tasks.length, errors: 0 };
return { scanned: tasks.length, closed: 0, skipped: tasks.length, errors: 0, hasMore };
}
const client = resolution.auth.mode === "token"
@@ -176,7 +181,7 @@ export class GitHubTrackingReconciler {
}
});
return { scanned: tasks.length, closed, skipped, errors };
return { scanned: tasks.length, closed, skipped, errors, hasMore };
}
}