Files
Riz Ashraf 8f924b793a 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
2026-10-07 19:00:44 +01:00

95 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""
Deterministic Git Reconciliation Hook for MCP Memory
Automatically syncs git commit events to mcp-memory to transition ADRs, resolve tech debts,
complete tasks, cascade-unblock dependent tasks, and recalculate milestone statuses.
"""
import os
import sys
import json
import subprocess
import urllib.request
import urllib.error
def run_git(cmd):
try:
res = subprocess.run(["git"] + cmd, capture_output=True, text=True, check=True)
return res.stdout.strip()
except Exception:
return ""
def main():
port = os.environ.get("MCP_PORT", "3000")
# 1. Quick check if server is running
ping_url = f"http://127.0.0.1:{port}/ping"
try:
with urllib.request.urlopen(ping_url, timeout=1.0) as resp:
if resp.status != 200:
sys.exit(0)
except Exception:
# Server not running, silently exit
sys.exit(0)
# 2. Extract git metadata
commit = run_git(["rev-parse", "HEAD"])
branch = run_git(["branch", "--show-current"])
author = run_git(["log", "-1", "--pretty=format:%an"])
message = run_git(["log", "-1", "--pretty=format:%B"])
# Get changed files in this commit
files_raw = run_git(["diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"])
files = [f.strip() for f in files_raw.splitlines() if f.strip()]
if not commit or not message:
sys.exit(0)
payload = {
"commit": commit,
"branch": branch,
"author": author,
"message": message,
"files": files,
}
# 3. Post to reconciliation endpoint
reconcile_url = f"http://127.0.0.1:{port}/api/git/commit"
req_data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(
reconcile_url,
data=req_data,
headers={"Content-Type": "application/json"},
method="POST"
)
try:
with urllib.request.urlopen(req, timeout=3.0) as resp:
if resp.status == 200:
body = json.loads(resp.read().decode("utf-8"))
res = body.get("reconciliation", {})
adrs = res.get("implemented_adrs", [])
debts = res.get("resolved_tech_debts", [])
tasks = res.get("completed_tasks", [])
unblocked = res.get("unblocked_tasks", [])
milestones = res.get("updated_milestones", [])
if any([adrs, debts, tasks, unblocked, milestones]):
print("\033[36m[MCP Memory Auto-Reconciled]\033[0m")
if adrs:
print(f" \033[32m✔ Implemented ADRs:\033[0m {', '.join(adrs)}")
if debts:
print(f" \033[32m✔ Resolved Tech Debts:\033[0m {', '.join(debts)}")
if tasks:
print(f" \033[32m✔ Completed Tasks:\033[0m {', '.join(tasks)}")
if unblocked:
print(f" \033[33m⚡ Unblocked Dependent Tasks:\033[0m {', '.join(unblocked)}")
if milestones:
print(f" \033[35m🎯 Updated Milestones:\033[0m {', '.join(milestones)}")
except Exception:
# Never break commit flow on failure
pass
if __name__ == "__main__":
main()