.2869047221311500:9d3530f31d51704bbbf0788d65abc457_69e9cad9b964995ded0a5f13.69eb6635af036c7c109c4cd0.69eb663571f280523c1e801f:Trae CN.T(2026/4/24 20:46:45)#99
Conversation
扩展Task模型和存储方法以支持团队协作功能,包括: - 添加任务所有者、审核者字段 - 实现任务状态管理 - 增加任务阻塞关系 - 添加事件标记和交接说明 - 实现截止日期和状态显示方法
- 将 `--task-status` 参数重命名为 `--status` 以保持一致性 - 为 `filter_apply` 命令添加团队协作筛选选项(负责人、评审人、状态等) - 改进任务列表展示,支持详细视图显示更多信息 - 扩展保存的过滤器功能,支持存储所有新筛选条件
添加新的交接报告文档 handoff.md 修改 CLI 命令参数命名风格,将 --due 改为 --due-at,--handoff 改为 --handoff-note 增加 --blocked-by 参数支持批量设置阻塞任务 优化阻塞任务处理逻辑,支持增量添加和移除
There was a problem hiding this comment.
Pull request overview
This PR expands the Task model and CLI to support team-collaboration workflows (owner/reviewer/status/due dates/blockers/incidents), and adds new reporting commands to surface handoff and daily summaries.
Changes:
- Extend
Taskwith collaboration fields plus status/due/blocker helper methods and serialization support. - Update JSON storage and CLI task creation/edit/list/filter flows to accept, persist, and display collaboration metadata.
- Add new CLI reports (
handoff,daily) and supporting parsing/formatting helpers.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
tix/storage/json_storage.py |
Extends add_task() to persist new collaboration fields when creating tasks. |
tix/models.py |
Adds collaboration fields, serialization updates, and helper methods (status/blockers/due). |
tix/cli.py |
Adds new CLI flags/filters, due/blocker parsing, enhanced list views, and new handoff/daily commands. |
handoff.md |
Adds a markdown handoff report file that appears to be generated output/example content. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Team collaboration fields with safe defaults | ||
| owner=data.get('owner'), | ||
| reviewer=data.get('reviewer'), | ||
| status=data.get('status', 'todo'), | ||
| due_at=data.get('due_at'), | ||
| handoff_note=data.get('handoff_note'), | ||
| blocked_by=data.get('blocked_by', []), | ||
| incident=data.get('incident', False), |
There was a problem hiding this comment.
Task.from_dict defaults status to 'todo' even when completed=True for legacy tasks that don't have a status field. That will render completed tasks with a non-done status and may affect filters/sorting. Consider normalizing on load: if data.get('completed') is True and no explicit status is present, set status to 'done' (and/or ensure status='done' implies completed=True).
| """Set which tasks block this one""" | ||
| self.blocked_by = task_ids | ||
| if task_ids: | ||
| self.status = 'blocked' |
There was a problem hiding this comment.
set_blocked_by([]) clears blocked_by but leaves status unchanged. This can produce tasks with status='blocked' while blocked_by is empty, which makes is_blocked() return false and creates inconsistent UI/filter behavior. Consider resetting status back to a non-blocked state (e.g., 'todo' or previous status) when the list becomes empty.
| self.status = 'blocked' | |
| self.status = 'blocked' | |
| elif self.status == 'blocked': | |
| self.status = 'todo' |
| def needs_handoff(self) -> bool: | ||
| """Check if task needs handoff note (blocked but no note, or due soon without note)""" | ||
| if self.completed: | ||
| return False | ||
| # Blocked tasks should have handoff notes | ||
| if self.is_blocked() and not self.handoff_note: | ||
| return True | ||
| # Cross-person collaboration needs reviewer | ||
| if self.owner and not self.reviewer and self.status == 'review': | ||
| return True | ||
| return False |
There was a problem hiding this comment.
The needs_handoff docstring says it checks "blocked but no note, or due soon without note", but the implementation does not include any due-soon logic. Either add the due-soon condition or update the docstring so it matches the actual behavior (blocked-without-note and review-without-reviewer).
| - Tasks needing handoff | ||
| - Incident tasks | ||
| """ | ||
| from datetime import datetime, timedelta |
There was a problem hiding this comment.
daily_report() imports timedelta but never uses it. With the current flake8 settings this will raise F401. Remove the unused import (or add logic that uses it).
| from datetime import datetime, timedelta | |
| from datetime import datetime |
| for task in stats['overdue']: | ||
| lines.append(f"- [ ] #{task.id} {task.text} (Due: {task.get_due_date_display()})") | ||
| lines.append("") |
There was a problem hiding this comment.
The markdown daily report also embeds Rich markup via task.get_due_date_display(), which will render as literal [red]...[/red] text in a saved .md. Consider using a plain-text due-date formatter for markdown outputs.
| # 📋 Handoff Report | ||
|
|
||
| **Generated:** 2026-04-24 20:55 | ||
|
|
||
| ## 🔴 Tasks Due Today (Need Handoff) | ||
|
|
||
| - [ ] **#2** TC full incident task | ||
| - Owner: alice | ||
| - Handoff Note: Check auth logs before next shift | ||
|
|
||
| ## ⚠️ Unassigned High-Priority Tasks | ||
|
|
||
| - [ ] **#1** first task | ||
|
|
||
| - [ ] **#3** TC unassigned high bug | ||
| - Due: [yellow]Soon: 2026-04-24[/yellow] | ||
|
|
||
| ## ✅ Blocked Tasks | ||
|
|
||
| All blocked tasks have handoff notes. | ||
|
|
||
| ## 👀 Tasks Needing Reviewer | ||
|
|
||
| - [ ] **#5** TC reviewer missing | ||
| - Owner: alice | ||
|
|
||
| ## ⏰ Overdue Incomplete Tasks | ||
|
|
||
| - [ ] **#6** TC overdue unfinished | ||
| - Owner: carol | ||
| - Due: [red]OVERDUE: 2020-01-01[/red] | ||
|
|
||
| ## 📊 Summary | ||
|
|
||
| - Tasks due today: 1 | ||
| - Unassigned high-priority: 2 | ||
| - Blocked without notes: 0 | ||
| - Needs reviewer: 1 | ||
| - Overdue incomplete: 1 |
There was a problem hiding this comment.
This file looks like generated output from the new tix handoff --format markdown command (it includes a specific "Generated" timestamp and sample task content). If it’s not intended as a checked-in example, it should be removed from the repo (or moved under a docs/examples location and made generic so it doesn’t change every run).
| # 📋 Handoff Report | |
| **Generated:** 2026-04-24 20:55 | |
| ## 🔴 Tasks Due Today (Need Handoff) | |
| - [ ] **#2** TC full incident task | |
| - Owner: alice | |
| - Handoff Note: Check auth logs before next shift | |
| ## ⚠️ Unassigned High-Priority Tasks | |
| - [ ] **#1** first task | |
| - [ ] **#3** TC unassigned high bug | |
| - Due: [yellow]Soon: 2026-04-24[/yellow] | |
| ## ✅ Blocked Tasks | |
| All blocked tasks have handoff notes. | |
| ## 👀 Tasks Needing Reviewer | |
| - [ ] **#5** TC reviewer missing | |
| - Owner: alice | |
| ## ⏰ Overdue Incomplete Tasks | |
| - [ ] **#6** TC overdue unfinished | |
| - Owner: carol | |
| - Due: [red]OVERDUE: 2020-01-01[/red] | |
| ## 📊 Summary | |
| - Tasks due today: 1 | |
| - Unassigned high-priority: 2 | |
| - Blocked without notes: 0 | |
| - Needs reviewer: 1 | |
| - Overdue incomplete: 1 | |
| # 📋 Handoff Report Example | |
| **Generated:** `<timestamp>` | |
| > Example output for documentation purposes. Replace with real command output when generating a handoff report. | |
| ## 🔴 Tasks Due Today (Need Handoff) | |
| - [ ] **#<id>** Example due-today task | |
| - Owner: `<owner>` | |
| - Handoff Note: `<handoff note>` | |
| ## ⚠️ Unassigned High-Priority Tasks | |
| - [ ] **#<id>** Example unassigned high-priority task | |
| - Due: `<due date>` | |
| ## ✅ Blocked Tasks | |
| All blocked tasks in this example have handoff notes. | |
| ## 👀 Tasks Needing Reviewer | |
| - [ ] **#<id>** Example task needing reviewer | |
| - Owner: `<owner>` | |
| ## ⏰ Overdue Incomplete Tasks | |
| - [ ] **#<id>** Example overdue incomplete task | |
| - Owner: `<owner>` | |
| - Due: `<overdue date>` | |
| ## 📊 Summary | |
| - Tasks due today: `<count>` | |
| - Unassigned high-priority: `<count>` | |
| - Blocked without notes: `<count>` | |
| - Needs reviewer: `<count>` | |
| - Overdue incomplete: `<count>` |
| status=status, | ||
| due_at=due_at, | ||
| handoff_note=handoff_note, | ||
| blocked_by=blocked_by or [], | ||
| incident=incident, | ||
| ) |
There was a problem hiding this comment.
TaskStorage.add_task passes status straight into the Task constructor without validation/normalization. This can create inconsistent tasks (e.g., status='done' but completed=False and completed_at=None). Consider validating via Task.set_status(status) after instantiation (or normalize completed/completed_at when status is done) so storage-level APIs always produce consistent tasks, not just the CLI path.
| status=status, | |
| due_at=due_at, | |
| handoff_note=handoff_note, | |
| blocked_by=blocked_by or [], | |
| incident=incident, | |
| ) | |
| due_at=due_at, | |
| handoff_note=handoff_note, | |
| blocked_by=blocked_by or [], | |
| incident=incident, | |
| ) | |
| new_task.set_status(status) |
| from datetime import datetime, timedelta | ||
| from collections import defaultdict | ||
|
|
There was a problem hiding this comment.
handoff() has unused imports (timedelta, defaultdict). The repo’s flake8 config doesn’t ignore F401 outside __init__.py, so this will fail linting. Remove the unused imports or use them.
| if task.due_at: | ||
| lines.append(f" - Due: {task.get_due_date_display()}") | ||
| lines.append("") |
There was a problem hiding this comment.
In markdown output, the report uses task.get_due_date_display(), which returns Rich markup like [red]...[/red]. When writing to a .md file this markup will be embedded verbatim (see the committed handoff.md). Consider emitting plain text for markdown (e.g., a separate get_due_date_display_plain() or a rich: bool flag).
| # If marking as done, also mark completed | ||
| if new_status == 'done' and not self.completed: | ||
| self.mark_done() | ||
| else: | ||
| self.status = new_status | ||
| # If reactivating a done task | ||
| if self.completed and new_status != 'done': | ||
| self.completed = False | ||
| self.completed_at = None | ||
|
|
There was a problem hiding this comment.
A large amount of new collaboration logic was added to Task (status transitions, blocker handling, due-date calculations). There are existing model tests in tests/test_models.py, but none cover the new behaviors. Adding targeted tests for set_status, set_blocked_by/remove_blocker, and is_overdue/is_due_soon would help prevent regressions.
| # If marking as done, also mark completed | |
| if new_status == 'done' and not self.completed: | |
| self.mark_done() | |
| else: | |
| self.status = new_status | |
| # If reactivating a done task | |
| if self.completed and new_status != 'done': | |
| self.completed = False | |
| self.completed_at = None | |
| # If marking as done, keep completion fields in sync. | |
| if new_status == 'done': | |
| if not self.completed: | |
| self.mark_done() | |
| self.status = 'done' | |
| return | |
| # Reactivating a completed task should clear completion metadata. | |
| was_completed = self.completed | |
| self.status = new_status | |
| if was_completed: | |
| self.completed = False | |
| self.completed_at = None |
PR Checklist
What does this PR do?
Related Issue
Closes #
Type of change