feat(reconciliation): implement deterministic state reconciliation engine and post-commit hook

- Implement ReconciliationEngine in server/src/handlers/reconciliation.rs
- Auto-transition ADRs to implemented, resolve tech debts, and cascade task unblocking
- Ingest git commit events via POST /api/git/commit and scripts/git-reconcile.py post-commit hook
- Add universal pagination, search filtering, and keyboard navigation to dashboard
- Implement non-destructive task TTL expiry sweeper and gate verification
- Implements: ADR-0102, ADR-0103
This commit is contained in:
Riz Ashraf committed 2026-10-07 19:00:44 +01:00
1 parent ec977c24dd
commit 8f924b793a
16 files changed
+2622 -163

No files matched your search

+15
View File
@@ -192,6 +192,10 @@ impl McpTool for TasksHandler {
session_id: None,
..Default::default()
});
if target_status == "completed" {
crate::handlers::reconciliation::cascade_unblock_tasks(&state);
crate::handlers::reconciliation::recalculate_milestones(&state);
}
Ok("Task status updated.".to_string())
} else {
Err(crate::error::AppError::Internal(
@@ -452,11 +456,13 @@ impl McpTool for MilestonesHandler {
.as_deref()
.and_then(|s| s.parse::<u64>().ok()),
description: req.description,
task_ids: req.task_ids.unwrap_or_default(),
deliverables: req.deliverables.unwrap_or_default(),
repo_name: req.repo_name,
..Default::default()
})
});
crate::handlers::reconciliation::recalculate_milestones(&state);
Ok("Milestone added".to_string())
}
MilestoneAction::Update => {
@@ -471,12 +477,21 @@ impl McpTool for MilestonesHandler {
for m in ms.iter_mut() {
if m.id == id {
m.status = status.clone();
if status.eq_ignore_ascii_case("completed") || status.eq_ignore_ascii_case("done") {
if m.completed_at.is_none() {
m.completed_at = Some(crate::handlers::utils::now_secs());
}
}
if let Some(task_ids) = &req.task_ids {
m.task_ids = task_ids.clone();
}
found = true;
break;
}
}
});
if found {
crate::handlers::reconciliation::recalculate_milestones(&state);
Ok("Milestone updated".to_string())
} else {
Err(crate::error::AppError::Internal(