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

+42 -5
View File
@@ -287,8 +287,8 @@ impl McpTool for LogCodeChangeHandler {
author: req.author,
session_id: req.session_id,
vcs_type: detected_vcs,
revision: effective_rev,
branch: effective_branch,
revision: effective_rev.clone(),
branch: effective_branch.clone(),
repository_root: req.repository_root,
});
if ledger.len() > 500 {
@@ -300,9 +300,42 @@ impl McpTool for LogCodeChangeHandler {
&format!("Modified {}", req.file_path),
Some(&description),
);
let recon = crate::handlers::reconciliation::reconcile_commit_or_code_change(
&state,
&description,
Some(&req.file_path),
effective_rev.as_deref(),
effective_branch.as_deref(),
)
.await;
let mut recon_notes = Vec::new();
if !recon.implemented_adrs.is_empty() {
recon_notes.push(format!("Implemented ADRs: {}", recon.implemented_adrs.join(", ")));
}
if !recon.resolved_tech_debts.is_empty() {
recon_notes.push(format!("Resolved TechDebt: {}", recon.resolved_tech_debts.join(", ")));
}
if !recon.completed_tasks.is_empty() {
recon_notes.push(format!("Completed Tasks: {}", recon.completed_tasks.join(", ")));
}
if !recon.unblocked_tasks.is_empty() {
recon_notes.push(format!("Unblocked Tasks: {}", recon.unblocked_tasks.join(", ")));
}
if !recon.updated_milestones.is_empty() {
recon_notes.push(format!("Updated Milestones: {}", recon.updated_milestones.join(", ")));
}
let recon_suffix = if recon_notes.is_empty() {
String::new()
} else {
format!(" [{}]", recon_notes.join(" | "))
};
Ok(format!(
"Logged code change for {}: {}",
req.file_path, description
"Logged code change for {}: {}{}",
req.file_path, description, recon_suffix
))
}
}
@@ -1170,7 +1203,11 @@ impl McpTool for GetProjectHealthHandler {
let active_milestones = state.project.milestones.read_with(|milestones| {
milestones
.iter()
.filter(|m| m.namespace == req.namespace && m.status != "done")
.filter(|m| {
m.namespace == req.namespace
&& !m.status.eq_ignore_ascii_case("done")
&& !m.status.eq_ignore_ascii_case("completed")
})
.count()
});
let report = serde_json::json!({