feat(FN-686): persist mission linkage on linked tasks

- Store missionId alongside sliceId when MissionStore links a feature to a task
- Clear both linkage fields together when features are unlinked from tasks
- Preserve mission linkage fields through TaskStore reads, writes, and updates
- Expand mission and task store coverage for mission linkage persistence and cleanup
This commit is contained in:
gsxdsm
2026-04-01 14:42:06 -07:00
parent 4e297104b3
commit d1f4eff9db
7 changed files with 167 additions and 24 deletions

View File

@@ -196,6 +196,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
breakIntoSubtasks: row.breakIntoSubtasks ? true : undefined,
enabledWorkflowSteps: (() => { const e = fromJson<string[]>(row.enabledWorkflowSteps); return e && e.length > 0 ? e : undefined; })(),
modifiedFiles: (() => { const m = fromJson<string[]>(row.modifiedFiles); return m && m.length > 0 ? m : undefined; })(),
missionId: row.missionId || undefined,
sliceId: row.sliceId || undefined,
};
}
@@ -212,10 +213,10 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
summary, thinkingLevel, createdAt, updatedAt, columnMovedAt,
dependencies, steps, log, attachments, steeringComments,
comments, workflowStepResults, prInfo, issueInfo, mergeDetails,
breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, sliceId
breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
`).run(
task.id,
@@ -256,6 +257,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
task.breakIntoSubtasks ? 1 : 0,
toJson(task.enabledWorkflowSteps || []),
toJson(task.modifiedFiles || []),
task.missionId ?? null,
task.sliceId ?? null,
);
this.db.bumpLastModified();
@@ -918,7 +920,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
async updateTask(
id: string,
updates: { title?: string; description?: string; prompt?: string; worktree?: string; status?: string | null; dependencies?: string[]; blockedBy?: string | null; paused?: boolean; baseBranch?: string; baseCommitSha?: string; size?: "S" | "M" | "L"; reviewLevel?: number; mergeRetries?: number; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; error?: string | null; summary?: string | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; modifiedFiles?: string[] | null },
updates: { title?: string; description?: string; prompt?: string; worktree?: string; status?: string | null; dependencies?: string[]; blockedBy?: string | null; paused?: boolean; baseBranch?: string; baseCommitSha?: string; size?: "S" | "M" | "L"; reviewLevel?: number; mergeRetries?: number; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; error?: string | null; summary?: string | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; modifiedFiles?: string[] | null; missionId?: string | null; sliceId?: string | null },
): Promise<Task> {
return this.withTaskLock(id, async () => {
// Validate that task doesn't depend on itself
@@ -1012,6 +1014,16 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
} else if (updates.modifiedFiles !== undefined) {
task.modifiedFiles = updates.modifiedFiles;
}
if (updates.missionId === null) {
task.missionId = undefined;
} else if (updates.missionId !== undefined) {
task.missionId = updates.missionId;
}
if (updates.sliceId === null) {
task.sliceId = undefined;
} else if (updates.sliceId !== undefined) {
task.sliceId = updates.sliceId;
}
task.updatedAt = new Date().toISOString();
await this.atomicWriteTaskJson(dir, task);