fix: resume selection under-reports task totals with multiple loops

The resume prompt counted total/completed from progress.tasks, which only
records TOUCHED tasks (started/completed/failed) — never-started tasks were
silently missing, and file-checkbox completions weren't counted unless
markCompleted had run. With e.g. 10 tasks where 3 completed and 5 untouched,
it showed 3/5 done instead of 3/10 done.

- add countPRDResumeStats (src/utils.ts): total from parseTaskFile of the
  PRD source; completed = progress completions ∪ PRD checkbox completions,
  deduped by task id; failed from progress. Falls back to touched-task
  counts when the source file is missing/unparseable.
- selectPRDToResume (index.ts) uses the helper instead of Object.keys().
- tests/resume-stats.test.ts: 4 regression tests.

Includes pre-existing uncommitted changes: index.ts tab-reformat (matches
src/ style) and package.json version bump 0.4.2 -> 0.4.3.
This commit is contained in:
2026-08-03 16:31:09 -04:00
parent f9b57ec2ed
commit b9a12931fe
4 changed files with 1234 additions and 1093 deletions

View File

@@ -7,6 +7,7 @@ import type {
ToolUsage,
} from "./types";
import { DEFAULT_CONFIG } from "./types";
import { parseTaskFile } from "./parser";
import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
import {
createAgentSession,
@@ -198,6 +199,49 @@ export function listPRDsSorted(
return entries;
}
export interface PRDResumeSummary {
total: number;
completed: number;
failed: number;
}
/**
* Count tasks for the resume-selection display.
*
* The progress tracker only records tasks that were TOUCHED (started,
* completed, or failed) — never-started tasks are absent from `prd.tasks`,
* so a naive Object.keys(prd.tasks).length under-reports the real total.
* The true total comes from parsing the PRD source file. Completed counts
* both progress-marked completions and PRD checkbox completions (a task
* checked off in the file is done even if the loop was interrupted before
* markCompleted), deduped by task id. Falls back to touched-task counts
* when the source file is missing or unparseable.
*/
export function countPRDResumeStats(
prd: PRDProgress,
sourcePath: string,
): PRDResumeSummary {
const touched = Object.entries(prd.tasks);
const failed = touched.filter(([, t]) => t.status === "failed").length;
const completedIds = new Set(
touched.filter(([, t]) => t.status === "completed").map(([id]) => id),
);
let total: number;
try {
const project = parseTaskFile(sourcePath);
total = project.tasks.length;
for (const task of project.tasks) {
if (task.status === "completed") completedIds.add(task.id);
}
} catch {
// PRD file missing/unparseable — fall back to touched-task counts
total = touched.length;
}
return { total, completed: completedIds.size, failed };
}
// ─── Model Resolution ───────────────────────────────────────────────────────
/**