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:
1 parent
ec977c24dd
commit
8f924b793a
16 files changed
+2622
-163
No files matched your search
@@ -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!({
|
||||
|
||||
Reference in new issue
Block a user