From fbcec7d34c4283af31d9240cea3b35b2cd750af0 Mon Sep 17 00:00:00 2001 From: Suhas Kumar D Date: Fri, 20 Feb 2026 21:06:01 +0530 Subject: [PATCH] Deliver final advanced governance and ownership feature set --- .github/workflows/ci.yml | 42 +++++ .gitignore | 15 ++ README.md | 50 +++++ ai-engine/README.md | 8 + ai-engine/risk_model.py | 18 ++ backend/app/__init__.py | 0 backend/app/data.py | 44 +++++ backend/app/main.py | 234 ++++++++++++++++++++++++ backend/app/risk_engine.py | 44 +++++ backend/app/schemas.py | 51 ++++++ backend/requirements.txt | 3 + ci/README.md | 5 + cloud/README.md | 11 ++ database/schema.sql | 23 +++ docs/api-catalog.md | 23 +++ docs/architecture.md | 30 +++ docs/roadmap.md | 15 ++ frontend/app/admin/page.tsx | 17 ++ frontend/app/ci-insights/page.tsx | 18 ++ frontend/app/commit/[id]/page.tsx | 27 +++ frontend/app/commits/page.tsx | 42 +++++ frontend/app/dashboard/page.tsx | 23 +++ frontend/app/developer-profile/page.tsx | 17 ++ frontend/app/failure-analytics/page.tsx | 59 ++++++ frontend/app/globals.css | 12 ++ frontend/app/layout.tsx | 18 ++ frontend/app/login/page.tsx | 11 ++ frontend/app/manager/page.tsx | 40 ++++ frontend/app/merge-control/page.tsx | 32 ++++ frontend/app/ownership-lab/page.tsx | 30 +++ frontend/app/page.tsx | 40 ++++ frontend/app/research-lab/page.tsx | 27 +++ frontend/app/trust-model/page.tsx | 18 ++ frontend/components/KpiCard.tsx | 15 ++ frontend/components/Navbar.tsx | 33 ++++ frontend/lib/api.ts | 9 + frontend/next-env.d.ts | 4 + frontend/next.config.js | 6 + frontend/package.json | 28 +++ frontend/postcss.config.js | 6 + frontend/tailwind.config.ts | 19 ++ frontend/tsconfig.json | 24 +++ 42 files changed, 1191 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 ai-engine/README.md create mode 100644 ai-engine/risk_model.py create mode 100644 backend/app/__init__.py create mode 100644 backend/app/data.py create mode 100644 backend/app/main.py create mode 100644 backend/app/risk_engine.py create mode 100644 backend/app/schemas.py create mode 100644 backend/requirements.txt create mode 100644 ci/README.md create mode 100644 cloud/README.md create mode 100644 database/schema.sql create mode 100644 docs/api-catalog.md create mode 100644 docs/architecture.md create mode 100644 docs/roadmap.md create mode 100644 frontend/app/admin/page.tsx create mode 100644 frontend/app/ci-insights/page.tsx create mode 100644 frontend/app/commit/[id]/page.tsx create mode 100644 frontend/app/commits/page.tsx create mode 100644 frontend/app/dashboard/page.tsx create mode 100644 frontend/app/developer-profile/page.tsx create mode 100644 frontend/app/failure-analytics/page.tsx create mode 100644 frontend/app/globals.css create mode 100644 frontend/app/layout.tsx create mode 100644 frontend/app/login/page.tsx create mode 100644 frontend/app/manager/page.tsx create mode 100644 frontend/app/merge-control/page.tsx create mode 100644 frontend/app/ownership-lab/page.tsx create mode 100644 frontend/app/page.tsx create mode 100644 frontend/app/research-lab/page.tsx create mode 100644 frontend/app/trust-model/page.tsx create mode 100644 frontend/components/KpiCard.tsx create mode 100644 frontend/components/Navbar.tsx create mode 100644 frontend/lib/api.ts create mode 100644 frontend/next-env.d.ts create mode 100644 frontend/next.config.js create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.js create mode 100644 frontend/tailwind.config.ts create mode 100644 frontend/tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ea2554c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +on: + push: + branches: ["**"] + pull_request: + +jobs: + backend-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install dependencies + run: | + cd backend + pip install -r requirements.txt + - name: Syntax check + run: | + python -m compileall backend/app + + frontend-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install dependencies + run: | + cd frontend + npm ci + - name: Lint + run: | + cd frontend + npm run lint + - name: Build + run: | + cd frontend + npm run build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..653aa75 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Node +node_modules/ +.next/ +out/ + +# Python +__pycache__/ +*.pyc +.venv/ + +# Env +.env + +# OS +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe48413 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Developer Trust & Code Reliability Platform + +A full-stack platform for commit risk analysis, trust scoring, explainable AI, CI insights, and governance-oriented merge control. + +## Monorepo Structure + +```text +frontend/ # Next.js 14 dashboard UI +backend/ # FastAPI service with analytics endpoints +ai-engine/ # Risk scoring logic and model stubs +ci/ # CI/CD support assets +database/ # SQL schema +cloud/ # Cloud integration notes +docs/ # Architecture, roadmap, API catalog +.github/ # GitHub Actions workflows +``` + +## Advanced Features Implemented + +- Predictive risk scoring before CI starts +- What-if commit simulation endpoint for pre-merge planning +- Explainable AI reason panel for risk changes +- Code ownership + blast radius endpoint and UI +- Trust-based merge gate evaluation + policy endpoints and UI +- Role-based dashboards (Developer, Manager, Admin) +- Research comparison endpoint (before vs after Trust Loop) +- Semester CSV report export endpoint +- CI time/cost optimization insights +- Simulated webhook ingestion endpoint for new commits + +## Quick Start + +### Backend + +```bash +cd backend +python -m venv .venv && source .venv/bin/activate +pip install -r requirements.txt +uvicorn app.main:app --reload --port 8000 +``` + +### Frontend + +```bash +cd frontend +npm install +npm run dev +``` + +Set `NEXT_PUBLIC_API_BASE_URL=http://localhost:8000` in `frontend/.env.local`. diff --git a/ai-engine/README.md b/ai-engine/README.md new file mode 100644 index 0000000..2f4a45b --- /dev/null +++ b/ai-engine/README.md @@ -0,0 +1,8 @@ +# AI Engine + +This module contains scoring logic prototypes and placeholders for model training. + +## Planned Upgrades +- Train a calibrated failure predictor from commit + CI metadata. +- Add SHAP-based feature attributions for richer explainability. +- Export model as a service used by FastAPI. diff --git a/ai-engine/risk_model.py b/ai-engine/risk_model.py new file mode 100644 index 0000000..ec3e1bc --- /dev/null +++ b/ai-engine/risk_model.py @@ -0,0 +1,18 @@ +"""Standalone risk model stub for experimentation.""" + +from dataclasses import dataclass + + +@dataclass +class Features: + files_changed: int + modules_impacted: int + tests_touched: bool + historical_failure_rate: float + + +def predict_failure_probability(features: Features) -> float: + score = 0.02 * features.files_changed + 0.06 * features.modules_impacted + score += 0.21 if not features.tests_touched else 0.0 + score += 0.35 * features.historical_failure_rate + return max(0.01, min(round(score, 3), 0.97)) diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/data.py b/backend/app/data.py new file mode 100644 index 0000000..6e5a115 --- /dev/null +++ b/backend/app/data.py @@ -0,0 +1,44 @@ +from app.schemas import Commit + +COMMITS: list[Commit] = [ + Commit( + id="cmt-101", + author="Ayesha", + message="Refactor auth middleware and token parser", + files_changed=11, + modules_impacted=4, + tests_touched=False, + historical_failure_rate=0.42, + ), + Commit( + id="cmt-102", + author="Rahul", + message="Fix flaky checkout flow assertions", + files_changed=3, + modules_impacted=1, + tests_touched=True, + historical_failure_rate=0.18, + ), + Commit( + id="cmt-103", + author="Mei", + message="Add caching for analytics endpoint", + files_changed=7, + modules_impacted=3, + tests_touched=True, + historical_failure_rate=0.27, + ), +] + +DEPENDENCY_GRAPH = { + "auth": ["api-gateway", "session", "billing"], + "checkout": ["cart", "payments", "inventory", "pricing"], + "analytics": ["warehouse", "dashboard"], +} + +RESEARCH_BASELINE = { + "avg_risk_before": 68.4, + "avg_risk_after": 44.2, + "trust_gain_percent": 23.1, + "deployment_failure_drop_percent": 31.6, +} diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..96001d2 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,234 @@ +from fastapi import FastAPI, HTTPException +from fastapi.responses import PlainTextResponse + +from app.data import COMMITS, DEPENDENCY_GRAPH, RESEARCH_BASELINE +from app.risk_engine import score_commit +from app.schemas import CIInsight, Commit, CommitIngest, DashboardSummary, ResearchComparison + +app = FastAPI(title="Trust Code Platform API", version="0.3.0") + +MERGE_POLICY = {"block_threshold": 75.0, "review_threshold": 50.0} + + +@app.get("/health") +def health() -> dict[str, str]: + return {"status": "ok"} + + +@app.get("/api/commits") +def list_commits() -> list[dict]: + rows: list[dict] = [] + for commit in COMMITS: + risk = score_commit(commit) + rows.append( + { + "id": commit.id, + "author": commit.author, + "message": commit.message, + "risk_score": risk.risk_score, + "risk_level": risk.risk_level, + "failure_probability": risk.failure_probability, + } + ) + return rows + + +@app.post("/api/webhooks/github/commit") +def ingest_commit(payload: CommitIngest) -> dict: + exists = next((c for c in COMMITS if c.id == payload.id), None) + if exists: + raise HTTPException(status_code=409, detail="Commit ID already exists") + + commit = Commit(**payload.model_dump()) + COMMITS.insert(0, commit) + risk = score_commit(commit) + return { + "status": "ingested", + "commit_id": commit.id, + "risk_level": risk.risk_level, + "failure_probability": risk.failure_probability, + } + + +@app.get("/api/commits/{commit_id}") +def commit_deep_dive(commit_id: str) -> dict: + commit = next((c for c in COMMITS if c.id == commit_id), None) + if not commit: + raise HTTPException(status_code=404, detail="Commit not found") + + risk = score_commit(commit) + return { + "commit": commit.model_dump(), + "risk": risk.model_dump(), + "ownership": { + "owner": commit.author, + "impacted_services": commit.modules_impacted, + "blast_radius": "high" if commit.modules_impacted >= 4 else "moderate", + "dependency_links": DEPENDENCY_GRAPH["auth"] if "auth" in commit.message.lower() else DEPENDENCY_GRAPH["analytics"], + }, + "ci": { + "estimated_pipeline_time_min": 18 + commit.files_changed, + "recommended_test_scope": "selective" if risk.risk_level != "high" else "full", + "merge_gate": "blocked" if risk.risk_score >= MERGE_POLICY["block_threshold"] else "needs-review" if risk.risk_score >= MERGE_POLICY["review_threshold"] else "pass", + }, + } + + +@app.get("/api/ownership/blast-radius/{commit_id}") +def blast_radius(commit_id: str) -> dict: + commit = next((c for c in COMMITS if c.id == commit_id), None) + if not commit: + raise HTTPException(status_code=404, detail="Commit not found") + + affected = commit.modules_impacted + chain = DEPENDENCY_GRAPH["checkout"] if affected > 3 else DEPENDENCY_GRAPH["analytics"] + return { + "commit_id": commit.id, + "owner": commit.author, + "modules_impacted": affected, + "upstream_downstream_dependencies": chain, + "blast_radius_score": min(affected * 18, 100), + } + + +@app.get("/api/merge-gate/evaluate/{commit_id}") +def merge_gate(commit_id: str) -> dict: + commit = next((c for c in COMMITS if c.id == commit_id), None) + if not commit: + raise HTTPException(status_code=404, detail="Commit not found") + + risk = score_commit(commit) + if risk.risk_score >= MERGE_POLICY["block_threshold"]: + decision = "blocked" + elif risk.risk_score >= MERGE_POLICY["review_threshold"]: + decision = "manual-review" + else: + decision = "approved" + + return { + "commit_id": commit_id, + "risk_score": risk.risk_score, + "decision": decision, + "policy": MERGE_POLICY, + "auto_comment": f"Trust Gate: {decision}. Predicted failure probability is {int(risk.failure_probability * 100)}%.", + } + + +@app.get("/api/merge-gate/policy") +def merge_policy() -> dict: + return MERGE_POLICY + + +@app.put("/api/merge-gate/policy") +def update_merge_policy(block_threshold: float, review_threshold: float) -> dict: + if block_threshold <= review_threshold: + raise HTTPException(status_code=400, detail="block_threshold must be greater than review_threshold") + MERGE_POLICY["block_threshold"] = block_threshold + MERGE_POLICY["review_threshold"] = review_threshold + return {"status": "updated", "policy": MERGE_POLICY} + + +@app.post("/api/predict/what-if") +def what_if(payload: CommitIngest) -> dict: + simulated = Commit(**payload.model_dump()) + risk = score_commit(simulated) + return { + "risk": risk.model_dump(), + "recommendation": "Split commit and add tests" if risk.risk_score > 70 else "Safe to proceed with selective tests", + } + + +@app.get("/api/dashboard/summary", response_model=DashboardSummary) +def dashboard_summary() -> DashboardSummary: + risks = [score_commit(c) for c in COMMITS] + high_risk = len([r for r in risks if r.risk_level == "high"]) + trust_score = round(100 - (sum(r.risk_score for r in risks) / len(risks)) * 0.55) + + return DashboardSummary( + trust_score=max(trust_score, 0), + high_risk_commits=high_risk, + avg_ci_time_min=16.4, + selective_testing_savings_min=41.5, + ) + + +@app.get("/api/analytics/failure-trends") +def failure_trends() -> dict: + risks = [score_commit(c) for c in COMMITS] + return { + "weekly_failure_rate": [0.22, 0.19, 0.14, 0.17, 0.11, 0.09], + "hotspots": [ + {"module": "auth", "incidents": 8}, + {"module": "checkout", "incidents": 6}, + {"module": "analytics", "incidents": 3}, + ], + "current_avg_predicted_failure": round(sum(r.failure_probability for r in risks) / len(risks), 2), + } + + +@app.get("/api/ci/insights", response_model=CIInsight) +def ci_insights() -> CIInsight: + return CIInsight( + avg_pipeline_time_min=16.4, + estimated_monthly_ci_cost_usd=241.8, + selective_testing_time_saved_min=126.0, + flaky_test_rate=0.07, + ) + + +@app.get("/api/trust-model") +def trust_model() -> dict: + weights = { + "blast_radius": 0.3, + "test_coverage_delta": 0.25, + "historical_failures": 0.27, + "change_size": 0.18, + } + return { + "model": "Weighted heuristic classifier v2", + "weights": weights, + "explainability": [ + "Each commit receives a score from 0-100.", + "Score increases with larger blast radius and poor testing signals.", + "Trust score is inversely derived from aggregate commit risk.", + "Merge gate requires manual review when risk exceeds medium threshold.", + ], + } + + +@app.get("/api/research/comparison", response_model=ResearchComparison) +def research_comparison() -> ResearchComparison: + return ResearchComparison( + period="Semester 2026", + avg_risk_before=RESEARCH_BASELINE["avg_risk_before"], + avg_risk_after=RESEARCH_BASELINE["avg_risk_after"], + trust_gain_percent=RESEARCH_BASELINE["trust_gain_percent"], + deployment_failure_drop_percent=RESEARCH_BASELINE["deployment_failure_drop_percent"], + ) + + +@app.get("/api/reports/semester", response_class=PlainTextResponse) +def semester_report() -> str: + rows = ["metric,value", "avg_risk_before,68.4", "avg_risk_after,44.2", "trust_gain_percent,23.1", "deployment_failure_drop_percent,31.6"] + return "\n".join(rows) + + +@app.get("/api/roles/{role}") +def role_dashboard(role: str) -> dict: + base = { + "developer": { + "focus": "Improve commit hygiene and test quality", + "widgets": ["my-risk-trend", "xai-feedback", "suggested-actions"], + }, + "manager": { + "focus": "Delivery risk, team velocity, and CI efficiency", + "widgets": ["team-risk-heatmap", "ci-cost", "mttr", "release-confidence"], + }, + "admin": { + "focus": "Governance, policy, and evaluation reporting", + "widgets": ["policy-rules", "audit-log", "semester-report"], + }, + } + if role not in base: + raise HTTPException(status_code=404, detail="Role not found") + return base[role] diff --git a/backend/app/risk_engine.py b/backend/app/risk_engine.py new file mode 100644 index 0000000..8a81e55 --- /dev/null +++ b/backend/app/risk_engine.py @@ -0,0 +1,44 @@ +from app.schemas import Commit, RiskResponse + + +def classify_risk(score: float) -> str: + if score >= 75: + return "high" + if score >= 45: + return "medium" + return "low" + + +def score_commit(commit: Commit) -> RiskResponse: + score = 0.0 + reasons: list[str] = [] + + score += min(commit.files_changed * 2.2, 30) + if commit.files_changed > 8: + reasons.append("Large change set increases integration risk.") + + score += min(commit.modules_impacted * 4.5, 25) + if commit.modules_impacted > 3: + reasons.append("Blast radius spans multiple modules.") + + if not commit.tests_touched: + score += 18 + reasons.append("No tests were updated alongside code changes.") + + score += commit.historical_failure_rate * 27 + if commit.historical_failure_rate > 0.4: + reasons.append("Author/repository history shows elevated CI failures.") + + failure_probability = min(round(score / 100, 2), 0.95) + risk_score = round(min(score, 100), 1) + + if not reasons: + reasons.append("Change is small with healthy historical reliability.") + + return RiskResponse( + commit_id=commit.id, + risk_score=risk_score, + risk_level=classify_risk(risk_score), + failure_probability=failure_probability, + explanation=reasons, + ) diff --git a/backend/app/schemas.py b/backend/app/schemas.py new file mode 100644 index 0000000..9f95164 --- /dev/null +++ b/backend/app/schemas.py @@ -0,0 +1,51 @@ +from pydantic import BaseModel, Field + + +class Commit(BaseModel): + id: str + author: str + message: str + files_changed: int + modules_impacted: int + tests_touched: bool + historical_failure_rate: float + + +class CommitIngest(BaseModel): + id: str + author: str + message: str + files_changed: int = Field(ge=1) + modules_impacted: int = Field(ge=1) + tests_touched: bool + historical_failure_rate: float = Field(ge=0.0, le=1.0) + + +class RiskResponse(BaseModel): + commit_id: str + risk_score: float + risk_level: str + failure_probability: float + explanation: list[str] + + +class DashboardSummary(BaseModel): + trust_score: int + high_risk_commits: int + avg_ci_time_min: float + selective_testing_savings_min: float + + +class CIInsight(BaseModel): + avg_pipeline_time_min: float + estimated_monthly_ci_cost_usd: float + selective_testing_time_saved_min: float + flaky_test_rate: float + + +class ResearchComparison(BaseModel): + period: str + avg_risk_before: float + avg_risk_after: float + trust_gain_percent: float + deployment_failure_drop_percent: float diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..1cbc16a --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,3 @@ +fastapi==0.110.0 +uvicorn==0.27.1 +pydantic==2.6.1 diff --git a/ci/README.md b/ci/README.md new file mode 100644 index 0000000..0bf3019 --- /dev/null +++ b/ci/README.md @@ -0,0 +1,5 @@ +# CI/CD Notes + +- Main workflow lives at `.github/workflows/ci.yml`. +- Add deploy jobs for Render/AWS in a separate environment-specific workflow. +- Recommended enhancement: trust-based merge gate using a GitHub Check that reads `/api/commits/{id}` risk output. diff --git a/cloud/README.md b/cloud/README.md new file mode 100644 index 0000000..0d3021d --- /dev/null +++ b/cloud/README.md @@ -0,0 +1,11 @@ +# Cloud Deployment Blueprint + +## AWS Baseline +- Frontend: deploy Next.js on Vercel or ECS/Fargate. +- Backend: deploy FastAPI via ECS/EC2. +- Storage: S3 bucket for CI artifacts (videos, reports). +- Database: RDS PostgreSQL. + +## Observability +- CloudWatch metrics for API latency and error rates. +- Alarm when risk evaluation endpoint p95 exceeds threshold. diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 0000000..b1f93da --- /dev/null +++ b/database/schema.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS developers ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + github_login TEXT UNIQUE NOT NULL +); + +CREATE TABLE IF NOT EXISTS commits ( + id TEXT PRIMARY KEY, + developer_id INT REFERENCES developers(id), + message TEXT NOT NULL, + files_changed INT NOT NULL, + modules_impacted INT NOT NULL, + tests_touched BOOLEAN NOT NULL, + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS risk_scores ( + commit_id TEXT REFERENCES commits(id), + risk_score NUMERIC(5,2) NOT NULL, + failure_probability NUMERIC(5,2) NOT NULL, + explanation JSONB NOT NULL, + evaluated_at TIMESTAMP DEFAULT NOW() +); diff --git a/docs/api-catalog.md b/docs/api-catalog.md new file mode 100644 index 0000000..90a8feb --- /dev/null +++ b/docs/api-catalog.md @@ -0,0 +1,23 @@ +# API Catalog (Advanced) + +## Core +- `GET /health` +- `GET /api/commits` +- `GET /api/commits/{commit_id}` + +## Advanced Risk + Governance +- `POST /api/webhooks/github/commit` +- `POST /api/predict/what-if` +- `GET /api/ownership/blast-radius/{commit_id}` +- `GET /api/merge-gate/evaluate/{commit_id}` +- `GET /api/merge-gate/policy` +- `PUT /api/merge-gate/policy?block_threshold=80&review_threshold=55` + +## Analytics + Research +- `GET /api/dashboard/summary` +- `GET /api/analytics/failure-trends` +- `GET /api/ci/insights` +- `GET /api/trust-model` +- `GET /api/research/comparison` +- `GET /api/reports/semester` +- `GET /api/roles/{role}` diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..4ed9a47 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,30 @@ +# System Architecture + +## Components + +1. **Frontend (Next.js + Tailwind)** + - Renders dashboards and analytics views. + - Pulls data from FastAPI endpoints. + +2. **Backend (FastAPI)** + - Serves commit analytics, trust score, and role-oriented summaries. + - Exposes explainability data and CI optimization metrics. + +3. **AI Engine (Python package)** + - Provides deterministic risk scoring now, swappable with ML models later. + - Supplies reason codes used in Explainable AI panel. + +4. **Data Layer (PostgreSQL-ready schema)** + - Commit, author, pipeline, and score history tables. + +5. **CI/CD (GitHub Actions)** + - Runs backend syntax check and frontend lint/build. + +## Data Flow + +`GitHub webhook/commit payload -> backend risk engine -> trust score/explanations -> frontend dashboards` + +## Research Angle + +- Persist historical scores per commit and compare pre/post trust-loop interventions. +- Analyze distribution shifts for failure probability and MTTR indicators. diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..9615d93 --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,15 @@ +# Build Roadmap + +## Phase 1 (Current) +- MVP APIs + dashboard UI +- Static/mock dataset for demoability + +## Phase 2 +- Real GitHub OAuth + webhook ingestion +- PostgreSQL persistence +- Background jobs for feature extraction + +## Phase 3 +- ML model training pipeline +- A/B trust policy experiments +- Automated report generation (PDF/CSV) diff --git a/frontend/app/admin/page.tsx b/frontend/app/admin/page.tsx new file mode 100644 index 0000000..178d531 --- /dev/null +++ b/frontend/app/admin/page.tsx @@ -0,0 +1,17 @@ +import { getJson } from "@/lib/api"; + +export default async function AdminPage() { + const admin = await getJson("/api/roles/admin"); + + return ( +
+

Admin / Professor Panel

+
+

Focus: {admin.focus}

+
    + {admin.widgets.map((w: string) =>
  • {w}
  • )} +
+
+
+ ); +} diff --git a/frontend/app/ci-insights/page.tsx b/frontend/app/ci-insights/page.tsx new file mode 100644 index 0000000..5c785c7 --- /dev/null +++ b/frontend/app/ci-insights/page.tsx @@ -0,0 +1,18 @@ +import { KpiCard } from "@/components/KpiCard"; +import { getJson } from "@/lib/api"; + +export default async function CIInsightsPage() { + const ci = await getJson("/api/ci/insights"); + + return ( +
+

CI Time & Cost Optimization

+
+ + + + +
+
+ ); +} diff --git a/frontend/app/commit/[id]/page.tsx b/frontend/app/commit/[id]/page.tsx new file mode 100644 index 0000000..e765b29 --- /dev/null +++ b/frontend/app/commit/[id]/page.tsx @@ -0,0 +1,27 @@ +import { getJson } from "@/lib/api"; + +type PageProps = { params: { id: string } }; + +export default async function CommitDeepDivePage({ params }: PageProps) { + const data = await getJson(`/api/commits/${params.id}`); + + return ( +
+

Commit Deep Dive: {params.id}

+
+
+

Explainable AI

+
    + {data.risk.explanation.map((item: string) =>
  • {item}
  • )} +
+
+
+

CI Recommendation

+

Merge Gate: {data.ci.merge_gate}

+

Test Scope: {data.ci.recommended_test_scope}

+

Pipeline Estimate: {data.ci.estimated_pipeline_time_min} min

+
+
+
+ ); +} diff --git a/frontend/app/commits/page.tsx b/frontend/app/commits/page.tsx new file mode 100644 index 0000000..b88a157 --- /dev/null +++ b/frontend/app/commits/page.tsx @@ -0,0 +1,42 @@ +import Link from "next/link"; +import { getJson } from "@/lib/api"; + +type Commit = { + id: string; + author: string; + message: string; + risk_score: number; + risk_level: string; + failure_probability: number; +}; + +export default async function CommitsPage() { + const commits = await getJson("/api/commits"); + + return ( +
+

Commit Risk Table

+
+ + + + + + + + {commits.map((c) => ( + + + + + + + + + ))} + +
IDAuthorMessageRiskFailure Prob.
{c.id}{c.author}{c.message}{c.risk_score} ({c.risk_level}){Math.round(c.failure_probability * 100)}%Deep Dive
+
+
+ ); +} diff --git a/frontend/app/dashboard/page.tsx b/frontend/app/dashboard/page.tsx new file mode 100644 index 0000000..61d96f1 --- /dev/null +++ b/frontend/app/dashboard/page.tsx @@ -0,0 +1,23 @@ +import { KpiCard } from "@/components/KpiCard"; +import { getJson } from "@/lib/api"; + +export default async function DashboardPage() { + const summary = await getJson<{ + trust_score: number; + high_risk_commits: number; + avg_ci_time_min: number; + selective_testing_savings_min: number; + }>("/api/dashboard/summary"); + + return ( +
+

System Overview Dashboard

+
+ + + + +
+
+ ); +} diff --git a/frontend/app/developer-profile/page.tsx b/frontend/app/developer-profile/page.tsx new file mode 100644 index 0000000..ab7aab9 --- /dev/null +++ b/frontend/app/developer-profile/page.tsx @@ -0,0 +1,17 @@ +import { getJson } from "@/lib/api"; + +export default async function DeveloperProfilePage() { + const profile = await getJson("/api/roles/developer"); + + return ( +
+

Developer Reliability Profile

+
+

Focus: {profile.focus}

+
    + {profile.widgets.map((w: string) =>
  • {w}
  • )} +
+
+
+ ); +} diff --git a/frontend/app/failure-analytics/page.tsx b/frontend/app/failure-analytics/page.tsx new file mode 100644 index 0000000..97ed4e7 --- /dev/null +++ b/frontend/app/failure-analytics/page.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + Bar, + BarChart, + CartesianGrid, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; + +type Hotspot = { + module: string; + incidents: number; +}; + +type FailureTrends = { + weekly_failure_rate: number[]; + hotspots: Hotspot[]; + current_avg_predicted_failure: number; +}; + +const API = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"; + +export default function FailureAnalyticsPage() { + const [data, setData] = useState(null); + + useEffect(() => { + fetch(`${API}/api/analytics/failure-trends`) + .then((res) => res.json()) + .then((result: FailureTrends) => setData(result)); + }, []); + + return ( +
+

Failure Analytics & Root Cause

+
+

+ Current avg predicted failure: {Math.round((data?.current_avg_predicted_failure || 0) * 100)}% +

+
+ +
+

Failure Hotspots by Module

+ + + + + + + + + +
+
+ ); +} diff --git a/frontend/app/globals.css b/frontend/app/globals.css new file mode 100644 index 0000000..9473967 --- /dev/null +++ b/frontend/app/globals.css @@ -0,0 +1,12 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + background: #020617; + color: #e2e8f0; +} + +.card { + @apply rounded-xl border border-slate-700 bg-card p-4; +} diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx new file mode 100644 index 0000000..bd87a8e --- /dev/null +++ b/frontend/app/layout.tsx @@ -0,0 +1,18 @@ +import "./globals.css"; +import { Navbar } from "@/components/Navbar"; + +export const metadata = { + title: "TrustCode Platform", + description: "Developer trust and reliability analytics", +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + +
{children}
+ + + ); +} diff --git a/frontend/app/login/page.tsx b/frontend/app/login/page.tsx new file mode 100644 index 0000000..48694e2 --- /dev/null +++ b/frontend/app/login/page.tsx @@ -0,0 +1,11 @@ +export default function LoginPage() { + return ( +
+

Sign in with GitHub

+

OAuth wiring placeholder for production integration.

+ +
+ ); +} diff --git a/frontend/app/manager/page.tsx b/frontend/app/manager/page.tsx new file mode 100644 index 0000000..6911f15 --- /dev/null +++ b/frontend/app/manager/page.tsx @@ -0,0 +1,40 @@ +import { KpiCard } from "@/components/KpiCard"; +import { getJson } from "@/lib/api"; + +type RoleResponse = { + focus: string; + widgets: string[]; +}; + +type Summary = { + trust_score: number; + high_risk_commits: number; + avg_ci_time_min: number; + selective_testing_savings_min: number; +}; + +export default async function ManagerPage() { + const manager = await getJson("/api/roles/manager"); + const summary = await getJson("/api/dashboard/summary"); + + return ( +
+

Manager Risk & Productivity View

+

Focus: {manager.focus}

+
+ + + + +
+
+

Manager Widgets

+
    + {manager.widgets.map((w) => ( +
  • {w}
  • + ))} +
+
+
+ ); +} diff --git a/frontend/app/merge-control/page.tsx b/frontend/app/merge-control/page.tsx new file mode 100644 index 0000000..ec454fa --- /dev/null +++ b/frontend/app/merge-control/page.tsx @@ -0,0 +1,32 @@ +import { getJson } from "@/lib/api"; + +type Commit = { id: string }; + +export default async function MergeControlPage() { + const commits = await getJson("/api/commits"); + const target = commits[0]?.id || "cmt-101"; + const policy = await getJson<{ block_threshold: number; review_threshold: number }>("/api/merge-gate/policy"); + const evaluation = await getJson<{ commit_id: string; risk_score: number; decision: string; auto_comment: string }>( + `/api/merge-gate/evaluate/${target}`, + ); + + return ( +
+

Trust-Based Merge Control Center

+
+
+

Policy

+

Block threshold: {policy.block_threshold}

+

Review threshold: {policy.review_threshold}

+
+
+

Latest Evaluation

+

Commit: {evaluation.commit_id}

+

Risk score: {evaluation.risk_score}

+

Decision: {evaluation.decision}

+

{evaluation.auto_comment}

+
+
+
+ ); +} diff --git a/frontend/app/ownership-lab/page.tsx b/frontend/app/ownership-lab/page.tsx new file mode 100644 index 0000000..8d1d04f --- /dev/null +++ b/frontend/app/ownership-lab/page.tsx @@ -0,0 +1,30 @@ +import { getJson } from "@/lib/api"; + +type Commit = { id: string; author: string; message: string }; + +type BlastRadius = { + commit_id: string; + owner: string; + modules_impacted: number; + upstream_downstream_dependencies: string[]; + blast_radius_score: number; +}; + +export default async function OwnershipLabPage() { + const commits = await getJson("/api/commits"); + const target = commits[0]?.id || "cmt-101"; + const blast = await getJson(`/api/ownership/blast-radius/${target}`); + + return ( +
+

Code Ownership & Blast Radius Lab

+
+

Commit: {blast.commit_id}

+

Owner: {blast.owner}

+

Modules impacted: {blast.modules_impacted}

+

Blast radius score: {blast.blast_radius_score}

+

Dependencies: {blast.upstream_downstream_dependencies.join(" → ")}

+
+
+ ); +} diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx new file mode 100644 index 0000000..1336c3b --- /dev/null +++ b/frontend/app/page.tsx @@ -0,0 +1,40 @@ +import Link from "next/link"; + +const featureCards = [ + "Predictive risk scoring before CI starts", + "Trust-based merge gate with auto PR feedback", + "Code ownership + blast radius intelligence", + "Role dashboards for Developer, Manager, and Admin", + "Research comparison metrics (before vs after Trust Loop)", +]; + +export default function HomePage() { + return ( +
+
+

Developer Trust & Code Reliability Platform

+

+ Enterprise-style DevEx intelligence with explainable AI insights, CI cost optimization, + and governance-friendly merge controls. +

+
+ +
+ {featureCards.map((feature) => ( +
+ {feature} +
+ ))} +
+ +
+ + Open Dashboard + + + View Research Metrics + +
+
+ ); +} diff --git a/frontend/app/research-lab/page.tsx b/frontend/app/research-lab/page.tsx new file mode 100644 index 0000000..48a0f3f --- /dev/null +++ b/frontend/app/research-lab/page.tsx @@ -0,0 +1,27 @@ +import { KpiCard } from "@/components/KpiCard"; +import { getJson } from "@/lib/api"; + +type ResearchData = { + period: string; + avg_risk_before: number; + avg_risk_after: number; + trust_gain_percent: number; + deployment_failure_drop_percent: number; +}; + +export default async function ResearchLabPage() { + const research = await getJson("/api/research/comparison"); + + return ( +
+

Research Lab: Trust Loop Impact

+

Evaluation Period: {research.period}

+
+ + + + +
+
+ ); +} diff --git a/frontend/app/trust-model/page.tsx b/frontend/app/trust-model/page.tsx new file mode 100644 index 0000000..070f6ec --- /dev/null +++ b/frontend/app/trust-model/page.tsx @@ -0,0 +1,18 @@ +import { getJson } from "@/lib/api"; + +export default async function TrustModelPage() { + const model = await getJson("/api/trust-model"); + + return ( +
+

Trust Model Explainability

+
+

{model.model}

+
{JSON.stringify(model.weights, null, 2)}
+
    + {model.explainability.map((e: string) =>
  • {e}
  • )} +
+
+
+ ); +} diff --git a/frontend/components/KpiCard.tsx b/frontend/components/KpiCard.tsx new file mode 100644 index 0000000..de0a30b --- /dev/null +++ b/frontend/components/KpiCard.tsx @@ -0,0 +1,15 @@ +type KpiCardProps = { + title: string; + value: string; + hint?: string; +}; + +export function KpiCard({ title, value, hint }: KpiCardProps) { + return ( +
+

{title}

+

{value}

+ {hint ?

{hint}

: null} +
+ ); +} diff --git a/frontend/components/Navbar.tsx b/frontend/components/Navbar.tsx new file mode 100644 index 0000000..bfa8b1b --- /dev/null +++ b/frontend/components/Navbar.tsx @@ -0,0 +1,33 @@ +import Link from "next/link"; + +const links = [ + ["/", "Home"], + ["/dashboard", "Dashboard"], + ["/commits", "Commits"], + ["/ownership-lab", "Ownership"], + ["/merge-control", "Merge Control"], + ["/developer-profile", "Developer"], + ["/manager", "Manager"], + ["/failure-analytics", "Failures"], + ["/trust-model", "Trust Model"], + ["/ci-insights", "CI Insights"], + ["/research-lab", "Research"], + ["/admin", "Admin"], +]; + +export function Navbar() { + return ( + + ); +} diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts new file mode 100644 index 0000000..7bc7b23 --- /dev/null +++ b/frontend/lib/api.ts @@ -0,0 +1,9 @@ +const API = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"; + +export async function getJson(path: string): Promise { + const res = await fetch(`${API}${path}`, { cache: "no-store" }); + if (!res.ok) { + throw new Error(`Request failed: ${path}`); + } + return res.json(); +} diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts new file mode 100644 index 0000000..84ab714 --- /dev/null +++ b/frontend/next-env.d.ts @@ -0,0 +1,4 @@ +/// +/// + +// NOTE: This file should not be edited diff --git a/frontend/next.config.js b/frontend/next.config.js new file mode 100644 index 0000000..91ef62f --- /dev/null +++ b/frontend/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, +}; + +module.exports = nextConfig; diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..8d877f7 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,28 @@ +{ + "name": "trustcode-frontend", + "private": true, + "version": "0.1.0", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "14.1.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "recharts": "2.12.0" + }, + "devDependencies": { + "@types/node": "20.11.16", + "@types/react": "18.2.52", + "@types/react-dom": "18.2.18", + "autoprefixer": "10.4.17", + "eslint": "8.56.0", + "eslint-config-next": "14.1.0", + "postcss": "8.4.35", + "tailwindcss": "3.4.1", + "typescript": "5.3.3" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts new file mode 100644 index 0000000..121b918 --- /dev/null +++ b/frontend/tailwind.config.ts @@ -0,0 +1,19 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: [ + "./app/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + colors: { + panel: "#0f172a", + card: "#111827", + }, + }, + }, + plugins: [], +}; + +export default config; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..6fff6b4 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": false, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [{ "name": "next" }], + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}