Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5d00fe2
docs: add Phase 2 route audit and gap analysis
Feb 1, 2026
3df0382
feat(server): add v2 discovery router delegating to core
Feb 1, 2026
f2de19a
docs: update gap analysis with discovery extraction progress
Feb 1, 2026
92b3b2b
feat(server): add v2 task execution router delegating to core
Feb 1, 2026
297a8b9
docs: update gap analysis with task execution progress
Feb 1, 2026
6be58d5
feat(api): add v2 checkpoints router delegating to core
Feb 1, 2026
1a95dd1
feat(api): add v2 schedule router delegating to core
Feb 1, 2026
8fb52bb
feat(api): add v2 templates router delegating to core
Feb 1, 2026
1026aa1
docs: update gap analysis with completed HIGH priority items
Feb 1, 2026
569446f
feat(api): add v2 projects router delegating to core
Feb 1, 2026
00c12c4
docs: update gap analysis with projects/session extraction
Feb 1, 2026
7829aca
feat(api): add v2 git and review routers delegating to core
Feb 1, 2026
d66b4c6
feat(api): establish route consistency standards for v2 API
Feb 1, 2026
9a9c056
feat(api): add v2 routers for blockers, PRD CRUD, and task management
Feb 2, 2026
397391d
feat(api): add SSE streaming and run status endpoints for task output
Feb 2, 2026
c82d86b
test(ui): add integration tests for v2 routers
Feb 2, 2026
eed8465
docs: update Phase 2 documentation with progress and developer guide
Feb 2, 2026
d694382
feat(api): add 6 missing v2 routers completing Phase 2 server layer (…
Feb 2, 2026
3426edc
fix(api): address code review feedback for Phase 2 PR
Feb 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions codeframe/core/checkpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,135 @@ def _row_to_checkpoint(row: tuple) -> Checkpoint:
snapshot=json.loads(row[3]) if row[3] else {},
created_at=datetime.fromisoformat(row[4]),
)


# ============================================================================
# Checkpoint Diff (Route Delegation Helper)
# ============================================================================


@dataclass
class TaskDiff:
"""Difference in a single task between checkpoints."""

task_id: str
title: str
old_status: Optional[str]
new_status: Optional[str]
change_type: str # "added", "removed", "status_changed", "unchanged"


@dataclass
class CheckpointDiff:
"""Difference between two checkpoints.

Attributes:
checkpoint_a: First checkpoint (older)
checkpoint_b: Second checkpoint (newer)
task_diffs: List of task differences
summary: Summary counts of changes
"""

checkpoint_a_id: str
checkpoint_a_name: str
checkpoint_b_id: str
checkpoint_b_name: str
task_diffs: list[TaskDiff]
summary: dict[str, int]


def diff(
workspace: Workspace,
checkpoint_id_a: str,
checkpoint_id_b: str,
) -> CheckpointDiff:
"""Compare two checkpoints and return the differences.

Compares task statuses between two checkpoints to show what changed.

Args:
workspace: Target workspace
checkpoint_id_a: First checkpoint ID (typically older)
checkpoint_id_b: Second checkpoint ID (typically newer)

Returns:
CheckpointDiff with task differences and summary

Raises:
ValueError: If either checkpoint not found

Example:
diff_result = diff(workspace, "abc123", "def456")
for task_diff in diff_result.task_diffs:
if task_diff.change_type == "status_changed":
print(f"{task_diff.title}: {task_diff.old_status} -> {task_diff.new_status}")
"""
# Load both checkpoints
checkpoint_a = get(workspace, checkpoint_id_a)
if not checkpoint_a:
raise ValueError(f"Checkpoint not found: {checkpoint_id_a}")

checkpoint_b = get(workspace, checkpoint_id_b)
if not checkpoint_b:
raise ValueError(f"Checkpoint not found: {checkpoint_id_b}")

# Extract task data from snapshots
tasks_a = {t["id"]: t for t in checkpoint_a.snapshot.get("tasks", [])}
tasks_b = {t["id"]: t for t in checkpoint_b.snapshot.get("tasks", [])}

# Find all task IDs
all_task_ids = set(tasks_a.keys()) | set(tasks_b.keys())

# Compare tasks
task_diffs = []
summary = {"added": 0, "removed": 0, "status_changed": 0, "unchanged": 0}

for task_id in sorted(all_task_ids):
task_a = tasks_a.get(task_id)
task_b = tasks_b.get(task_id)

if task_a is None:
# Task added in checkpoint B
task_diffs.append(TaskDiff(
task_id=task_id,
title=task_b.get("title", "Unknown"),
old_status=None,
new_status=task_b.get("status"),
change_type="added",
))
summary["added"] += 1

elif task_b is None:
# Task removed in checkpoint B
task_diffs.append(TaskDiff(
task_id=task_id,
title=task_a.get("title", "Unknown"),
old_status=task_a.get("status"),
new_status=None,
change_type="removed",
))
summary["removed"] += 1

elif task_a.get("status") != task_b.get("status"):
# Status changed
task_diffs.append(TaskDiff(
task_id=task_id,
title=task_b.get("title", task_a.get("title", "Unknown")),
old_status=task_a.get("status"),
new_status=task_b.get("status"),
change_type="status_changed",
))
summary["status_changed"] += 1

else:
# Unchanged (only include if requested, skip for brevity)
summary["unchanged"] += 1

return CheckpointDiff(
checkpoint_a_id=checkpoint_a.id,
checkpoint_a_name=checkpoint_a.name,
checkpoint_b_id=checkpoint_b.id,
checkpoint_b_name=checkpoint_b.name,
task_diffs=task_diffs,
summary=summary,
)
Loading
Loading