Fix task card step progress

This commit is contained in:
gsxdsm
2026-04-10 21:46:06 -07:00
parent e258279a5f
commit 34b2af26a7
6 changed files with 72 additions and 40 deletions

View File

@@ -1351,6 +1351,38 @@ describe("TaskStore", () => {
expect(slim.steeringComments).toEqual(full.steeringComments);
});
it("slim mode hydrates step metadata from PROMPT.md for board cards", async () => {
const task = await store.createTask({ description: "Prompt-only steps" });
await store.updateTask(task.id, {
prompt: `# ${task.id}: Prompt-only steps
## Steps
### Step 0: Update the list payload
- [ ] Keep card progress visible
### Step 1: Add regression coverage
- [ ] Prove slim lists still include prompt steps
`,
});
const fullList = await store.listTasks();
const slimList = await store.listTasks({ slim: true });
const full = fullList.find((t) => t.id === task.id)!;
const slim = slimList.find((t) => t.id === task.id)!;
expect(full.steps).toEqual([]);
expect(slim.steps).toEqual([
{ name: "Update the list payload", status: "pending" },
{ name: "Add regression coverage", status: "pending" },
]);
const searchResults = await store.searchTasks("Prompt-only");
expect(searchResults.find((t) => t.id === task.id)?.steps).toEqual(slim.steps);
});
it("includeArchived=false excludes archived tasks; default includes them", async () => {
const keep = await store.createTask({ description: "Stays visible" });
const toArchive = await store.createTask({ description: "Will be archived" });

View File

@@ -1360,7 +1360,15 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
const sql = `SELECT ${selectClause} FROM tasks${whereClause} ORDER BY createdAt ASC`;
const rows = this.db.prepare(sql).all(...params);
const tasks = (rows as any[]).map((row) => this.rowToTask(row));
const tasks = await Promise.all((rows as any[]).map(async (row) => {
const task = this.rowToTask(row);
if (!slim || task.steps.length > 0) {
return task;
}
const steps = await this.parseStepsFromPrompt(task.id);
return steps.length > 0 ? { ...task, steps } : task;
}));
// Sort by createdAt, then by numeric ID suffix for tie-breaking
const sorted = tasks.sort((a, b) => {
@@ -1432,7 +1440,15 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
LIMIT ${limit >= 0 ? limit : -1}${offsetClause}
`).all(ftsQuery) as any[];
return rows.map((row) => this.rowToTask(row));
return Promise.all(rows.map(async (row) => {
const task = this.rowToTask(row);
if (task.steps.length > 0) {
return task;
}
const steps = await this.parseStepsFromPrompt(task.id);
return steps.length > 0 ? { ...task, steps } : task;
}));
}
async selectNextTaskForAgent(agentId: string): Promise<InboxTask | null> {
@@ -1588,7 +1604,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
async updateTask(
id: string,
updates: { title?: string; description?: string; prompt?: string; worktree?: string | null; status?: string | null; dependencies?: string[]; blockedBy?: string | null; assignedAgentId?: string | null; assigneeUserId?: string | null; checkedOutBy?: string | null; checkedOutAt?: string | null; paused?: boolean; baseBranch?: string | null; branch?: string | null; baseCommitSha?: string | null; size?: "S" | "M" | "L"; reviewLevel?: number; mergeRetries?: number; stuckKillCount?: number | null; recoveryRetryCount?: number | null; nextRecoveryAt?: string | null; enabledWorkflowSteps?: string[]; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; planningModelProvider?: string | null; planningModelId?: string | null; thinkingLevel?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; mergeDetails?: import("./types.js").MergeDetails | null; modifiedFiles?: string[] | null; missionId?: string | null; sliceId?: string | null },
updates: { title?: string; description?: string; prompt?: string; worktree?: string | null; status?: string | null; dependencies?: string[]; steps?: import("./types.js").TaskStep[]; blockedBy?: string | null; assignedAgentId?: string | null; assigneeUserId?: string | null; checkedOutBy?: string | null; checkedOutAt?: string | null; paused?: boolean; baseBranch?: string | null; branch?: string | null; baseCommitSha?: string | null; size?: "S" | "M" | "L"; reviewLevel?: number; mergeRetries?: number; stuckKillCount?: number | null; recoveryRetryCount?: number | null; nextRecoveryAt?: string | null; enabledWorkflowSteps?: string[]; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; planningModelProvider?: string | null; planningModelId?: string | null; thinkingLevel?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; mergeDetails?: import("./types.js").MergeDetails | null; modifiedFiles?: string[] | null; missionId?: string | null; sliceId?: string | null },
runContext?: RunMutationContext,
): Promise<Task> {
return this.withTaskLock(id, async () => {
@@ -1634,6 +1650,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
movedToTriage = true;
}
}
if (updates.steps !== undefined) task.steps = updates.steps;
if (updates.status === null) {
task.status = undefined;
} else if (updates.status !== undefined) {