- 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
95 lines
3.2 KiB
Python
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()
|