Add deliverables-to-todos sync feature
Dashboard now automatically syncs milestone deliverables to todos: - On load, checks each milestone's deliverables table - If a deliverable doesn't exist as a todo (matched by text + milestone), adds it - Maps deliverable status to todo priority (Done=completed, In Progress=high, Not Started=medium) - Saves updated todos.md if any items were added Also updated CLAUDE.md to document the sync behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e885ccc409
commit
a6937463d0
|
|
@ -86,6 +86,15 @@ src/development_hub/
|
||||||
| `Ctrl+]` | Expand all sections |
|
| `Ctrl+]` | Expand all sections |
|
||||||
| `Ctrl+[` | Collapse all sections |
|
| `Ctrl+[` | Collapse all sections |
|
||||||
|
|
||||||
|
### Dashboard Data Sync
|
||||||
|
|
||||||
|
The dashboard automatically syncs data between documentation files:
|
||||||
|
|
||||||
|
- **Deliverables → TODOs**: When loading a project dashboard, deliverables from `milestones.md` tables are synced to `todos.md`. If a deliverable doesn't exist as a todo (matched by text + milestone ID), it's added with the appropriate `@M#` tag and priority.
|
||||||
|
- **TODOs → Milestone Progress**: The milestone progress bar is calculated from linked todos (items tagged with `@M#`), not from the deliverables table.
|
||||||
|
|
||||||
|
This means you can define major work items in milestone deliverable tables, and they'll automatically appear in your todo list for tracking.
|
||||||
|
|
||||||
## CLI Scripts
|
## CLI Scripts
|
||||||
|
|
||||||
### `bin/new-project`
|
### `bin/new-project`
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ from PySide6.QtWidgets import (
|
||||||
QMenu,
|
QMenu,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QProgressBar,
|
QProgressBar,
|
||||||
|
QPushButton,
|
||||||
QTextEdit,
|
QTextEdit,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QWidget,
|
QWidget,
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ from development_hub.services.git_service import GitService
|
||||||
from development_hub.services.health_checker import HealthChecker
|
from development_hub.services.health_checker import HealthChecker
|
||||||
from development_hub.models.health import HealthStatus
|
from development_hub.models.health import HealthStatus
|
||||||
from development_hub.parsers.todos_parser import TodosParser
|
from development_hub.parsers.todos_parser import TodosParser
|
||||||
|
from development_hub.models.todo import Todo, TodoList
|
||||||
from development_hub.parsers.goals_parser import MilestonesParser, GoalsParser
|
from development_hub.parsers.goals_parser import MilestonesParser, GoalsParser
|
||||||
from development_hub.widgets.progress_bar import MilestoneProgressBar
|
from development_hub.widgets.progress_bar import MilestoneProgressBar
|
||||||
from development_hub.widgets.stat_card import StatCardRow
|
from development_hub.widgets.stat_card import StatCardRow
|
||||||
|
|
@ -1107,6 +1108,19 @@ class ProjectDashboard(QWidget):
|
||||||
if todos_path.exists():
|
if todos_path.exists():
|
||||||
parser = TodosParser(todos_path)
|
parser = TodosParser(todos_path)
|
||||||
todo_list = parser.parse()
|
todo_list = parser.parse()
|
||||||
|
else:
|
||||||
|
# Create empty TodoList if file doesn't exist
|
||||||
|
todo_list = TodoList(source_file=str(todos_path))
|
||||||
|
|
||||||
|
# Sync deliverables from milestones to todos (adds missing items)
|
||||||
|
if self._milestones_list and todo_list:
|
||||||
|
synced = self._sync_deliverables_to_todos(
|
||||||
|
self._milestones_list, todo_list, todos_path
|
||||||
|
)
|
||||||
|
if synced:
|
||||||
|
# Reload todo_list after sync to get fresh data
|
||||||
|
parser = TodosParser(todos_path)
|
||||||
|
todo_list = parser.parse()
|
||||||
|
|
||||||
# Create MilestoneWidget for each milestone (keep original file order)
|
# Create MilestoneWidget for each milestone (keep original file order)
|
||||||
first_active_shown = False
|
first_active_shown = False
|
||||||
|
|
@ -3141,6 +3155,81 @@ generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||||
if changed:
|
if changed:
|
||||||
self._save_milestones()
|
self._save_milestones()
|
||||||
|
|
||||||
|
def _sync_deliverables_to_todos(
|
||||||
|
self, milestones: list, todo_list, todos_path
|
||||||
|
) -> bool:
|
||||||
|
"""Sync milestone deliverables to todos.
|
||||||
|
|
||||||
|
For each deliverable in a milestone, check if a matching todo exists.
|
||||||
|
If not, create a new todo with the appropriate milestone tag and status.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
milestones: List of Milestone objects
|
||||||
|
todo_list: TodoList to add deliverables to
|
||||||
|
todos_path: Path to todos.md for saving
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if any todos were added, False otherwise
|
||||||
|
"""
|
||||||
|
from development_hub.models.goal import DeliverableStatus
|
||||||
|
|
||||||
|
if not milestones or not todo_list:
|
||||||
|
return False
|
||||||
|
|
||||||
|
added_count = 0
|
||||||
|
|
||||||
|
for milestone in milestones:
|
||||||
|
if not milestone.deliverables:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get existing todos for this milestone
|
||||||
|
existing_todos = todo_list.get_by_milestone(milestone.id)
|
||||||
|
existing_texts = {t.text.lower().strip() for t in existing_todos}
|
||||||
|
|
||||||
|
for deliverable in milestone.deliverables:
|
||||||
|
deliverable_text = deliverable.name.strip()
|
||||||
|
deliverable_text_lower = deliverable_text.lower()
|
||||||
|
|
||||||
|
# Check if this deliverable already exists as a todo
|
||||||
|
if deliverable_text_lower in existing_texts:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Map deliverable status to todo properties
|
||||||
|
if deliverable.status == DeliverableStatus.DONE:
|
||||||
|
completed = True
|
||||||
|
priority = "high"
|
||||||
|
else:
|
||||||
|
completed = False
|
||||||
|
# In Progress gets high priority, Not Started gets medium
|
||||||
|
if deliverable.status == DeliverableStatus.IN_PROGRESS:
|
||||||
|
priority = "high"
|
||||||
|
else:
|
||||||
|
priority = "medium"
|
||||||
|
|
||||||
|
# Create new todo
|
||||||
|
new_todo = Todo(
|
||||||
|
text=deliverable_text,
|
||||||
|
completed=completed,
|
||||||
|
priority=priority,
|
||||||
|
milestone=milestone.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mark complete with today's date if done
|
||||||
|
if completed:
|
||||||
|
new_todo.mark_complete()
|
||||||
|
|
||||||
|
todo_list.add_todo(new_todo)
|
||||||
|
added_count += 1
|
||||||
|
|
||||||
|
# Save if any todos were added
|
||||||
|
if added_count > 0 and todos_path:
|
||||||
|
parser = TodosParser(todos_path)
|
||||||
|
parser._frontmatter = parser.frontmatter # Preserve frontmatter
|
||||||
|
parser.save(todo_list)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def _on_milestone_todo_toggled(self, todo, completed):
|
def _on_milestone_todo_toggled(self, todo, completed):
|
||||||
"""Handle todo toggle from within milestone widget.
|
"""Handle todo toggle from within milestone widget.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue