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:
rob 2026-01-12 20:14:38 -04:00
parent e885ccc409
commit a6937463d0
3 changed files with 99 additions and 0 deletions

View File

@ -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`

View File

@ -14,6 +14,7 @@ from PySide6.QtWidgets import (
QMenu, QMenu,
QMessageBox, QMessageBox,
QProgressBar, QProgressBar,
QPushButton,
QTextEdit, QTextEdit,
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,

View File

@ -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.