( taskListId: string, taskId: string, )
| 308 | } |
| 309 | |
| 310 | export async function getTask( |
| 311 | taskListId: string, |
| 312 | taskId: string, |
| 313 | ): Promise<Task | null> { |
| 314 | const path = getTaskPath(taskListId, taskId) |
| 315 | try { |
| 316 | const content = await readFile(path, 'utf-8') |
| 317 | const data = jsonParse(content) as { status?: string } |
| 318 | |
| 319 | // TEMPORARY: Migrate old status names for existing sessions (ant-only) |
| 320 | if (process.env.USER_TYPE === 'ant') { |
| 321 | if (data.status === 'open') data.status = 'pending' |
| 322 | else if (data.status === 'resolved') data.status = 'completed' |
| 323 | // Migrate development task statuses to in_progress |
| 324 | else if ( |
| 325 | data.status && |
| 326 | ['planning', 'implementing', 'reviewing', 'verifying'].includes( |
| 327 | data.status, |
| 328 | ) |
| 329 | ) { |
| 330 | data.status = 'in_progress' |
| 331 | } |
| 332 | } |
| 333 | const parsed = TaskSchema().safeParse(data) |
| 334 | if (!parsed.success) { |
| 335 | logForDebugging( |
| 336 | `[Tasks] Task ${taskId} failed schema validation: ${parsed.error.message}`, |
| 337 | ) |
| 338 | return null |
| 339 | } |
| 340 | return parsed.data |
| 341 | } catch (e) { |
| 342 | const code = getErrnoCode(e) |
| 343 | if (code === 'ENOENT') { |
| 344 | return null |
| 345 | } |
| 346 | logForDebugging(`[Tasks] Failed to read task ${taskId}: ${errorMessage(e)}`) |
| 347 | logError(e) |
| 348 | return null |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | // Internal: no lock. Callers already holding a lock on taskPath must use this |
| 353 | // to avoid deadlock (claimTask, deleteTask cascade, etc.). |
no test coverage detected