From 4e2116aa1c6800451d909f6f7017a81aa3d7f136 Mon Sep 17 00:00:00 2001 From: shibinsp Date: Sat, 14 Mar 2026 20:26:18 +0530 Subject: [PATCH] docs: update README and add project overview Excel report - Rewrite README with professional structure: badges, live demo, feature categories, tech stack, architecture diagram, getting started guide, E2E test results, CI/CD pipeline, project structure, API endpoints, security measures, and RBAC matrix - Create docs/reports/TaskPulse_Project_Overview.xlsx with 6 sheets: Features (46), E2E Test Results (113), Security (19), Tech Stack (45), API Endpoints (80), Architecture (23) - Update Playwright config for deployed app testing via E2E_BASE_URL - Add test:e2e:deployed npm script Fixes #17 Co-Authored-By: Beeax --- Frontend/e2e/helpers.ts | 6 +- Frontend/package.json | 3 +- Frontend/playwright.config.ts | 4 +- README.md | 671 +++++++++---------- docs/reports/TaskPulse_Project_Overview.xlsx | Bin 0 -> 24661 bytes 5 files changed, 336 insertions(+), 348 deletions(-) create mode 100644 docs/reports/TaskPulse_Project_Overview.xlsx diff --git a/Frontend/e2e/helpers.ts b/Frontend/e2e/helpers.ts index f86640f..22800f0 100644 --- a/Frontend/e2e/helpers.ts +++ b/Frontend/e2e/helpers.ts @@ -1,9 +1,9 @@ import { type Page, expect } from '@playwright/test'; -/** Demo credentials from seed data */ +/** Demo credentials — use deployed Supabase credentials when E2E_BASE_URL is set */ export const DEMO_USER = { - email: 'admin@acme.com', - password: 'demo123', + email: process.env.E2E_BASE_URL ? 'admin@taskpulse.demo' : 'admin@acme.com', + password: process.env.E2E_BASE_URL ? 'TaskPulse2024' : 'demo123', name: 'Admin', }; diff --git a/Frontend/package.json b/Frontend/package.json index d7e6ab6..f5f59a4 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -10,7 +10,8 @@ "preview": "vite preview", "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui", - "test:e2e:headed": "playwright test --headed" + "test:e2e:headed": "playwright test --headed", + "test:e2e:deployed": "E2E_BASE_URL=https://relaxed-gates.vercel.app playwright test" }, "dependencies": { "@gsap/react": "^2.1.2", diff --git a/Frontend/playwright.config.ts b/Frontend/playwright.config.ts index f2cf9f7..8c18e69 100644 --- a/Frontend/playwright.config.ts +++ b/Frontend/playwright.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ expect: { timeout: 10_000 }, use: { - baseURL: 'http://localhost:5173', + baseURL: process.env.E2E_BASE_URL || 'http://localhost:5173', trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', @@ -47,7 +47,7 @@ export default defineConfig({ }, ], - webServer: { + webServer: process.env.E2E_BASE_URL ? undefined : { command: 'npm run dev', url: 'http://localhost:5173', reuseExistingServer: true, diff --git a/README.md b/README.md index 78a7abf..811bcaa 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- TaskPulse AI + TaskPulse AI

TaskPulse AI

@@ -15,457 +15,444 @@

- Features • - Quick Start • - Architecture • - API Docs • - Contributing + + CI/CD + + + Vercel + + Python + React + TypeScript + FastAPI + Vite

- Python - FastAPI - React - TypeScript - Vite - Docker + Live Demo • + Features • + Tech Stack • + Getting Started • + API Docs • + Contributing

--- -## The Problem - -Traditional task management tools are **passive observers**. They track what's happening but don't actively help employees complete their work. When someone gets stuck: - -- ❌ They waste hours before asking for help -- ❌ Managers only find out during weekly standups -- ❌ Projects slip without early warning -- ❌ Valuable knowledge stays siloed in individuals +## Live Demo -## The Solution +**Production**: [https://relaxed-gates.vercel.app](https://relaxed-gates.vercel.app) -**TaskPulse AI** is an **active task completion engine** that: - -- ✅ Proactively checks in with employees every 3 hours -- ✅ Uses AI to unblock stuck workers instantly -- ✅ Predicts delivery risks before they become problems -- ✅ Builds organizational knowledge that helps everyone +| Email | Password | Role | +|-------|----------|------| +| `admin@taskpulse.demo` | `TaskPulse2024` | Super Admin | +| `orgadmin@taskpulse.demo` | `TaskPulse2024` | Org Admin | +| `manager@taskpulse.demo` | `TaskPulse2024` | Manager | +| `lead@taskpulse.demo` | `TaskPulse2024` | Team Lead | +| `dev@taskpulse.demo` | `TaskPulse2024` | Employee | +| `viewer@taskpulse.demo` | `TaskPulse2024` | Viewer | --- ## Features -### 🔄 Smart Check-In Engine - -Intelligent, proactive engagement that catches blockers early. - -| Feature | Description | -|---------|-------------| -| **3-Hour Loops** | Configurable check-in intervals per team or task type | -| **Friction Detection** | AI identifies when progress is slower than expected | -| **Auto-Escalation** | Missed check-ins trigger manager notifications | -| **Silent Mode** | No interruptions when everything's on track | - -### 🧠 AI Unblock Engine - -RAG-powered assistance with **zero hallucination** guarantee. - -``` -Employee: "I'm stuck on the OAuth integration with our legacy system" +### Task Management +- **Full CRUD** with priority, status, assignments, due dates, and subtasks +- **Kanban Board** / **List** / **Grid** views with drag-and-drop +- **AI Task Decomposition** - break complex tasks into subtasks automatically +- **Bulk Operations** - update multiple tasks at once +- **Task Dependencies** - link related tasks together +- **Comments & History** - full audit trail on every task + +### Smart Check-In Engine +- **3-Hour Loops** - configurable proactive check-in intervals +- **Friction Detection** - AI identifies when progress stalls +- **Auto-Escalation** - missed check-ins notify managers +- **Manager Feed** - centralized view of team responses + +### AI Command Center +- **AI Unblock Engine** - RAG-powered help with blockers +- **Knowledge Base** - upload and search internal docs +- **Conversational Task Creation** - create tasks via natural language +- **Multi-Provider** - OpenAI, Anthropic, Mistral, Kimi, Ollama support + +### Prediction & Analytics +- **Delivery Forecasts** - P25/P50/P90 completion estimates +- **Velocity Trends** - team performance over time +- **Productivity Heatmap** - hourly productivity patterns +- **Dashboard Metrics** - real-time task statistics + +### Skill Graph +- **Skill Tracking** - catalog and rate team skills +- **Gap Analysis** - identify missing skills for roles +- **Learning Paths** - personalized development plans +- **Team Composition** - visualize collective strengths + +### Workforce Intelligence +- **Performance Scoring** - multi-factor employee metrics +- **Manager Rankings** - leadership effectiveness comparison +- **Org Health Index** - company-wide health dashboard +- **Attrition Risk** - identify at-risk employees +- **Restructuring Simulator** - model team changes before committing + +### Automation Detection +- **Pattern Recognition** - AI finds repetitive workflows +- **Agent Creation** - one-click automation for common tasks +- **Shadow Mode** - test automations safely before activation +- **ROI Dashboard** - track hours saved and cost reduction + +### Integrations +- **OAuth Flows** - connect Slack, Jira, and other tools +- **Webhooks** - real-time event notifications +- **API Keys** - programmatic access management -TaskPulse AI: Based on your codebase and past solutions: -├── Similar issue solved by @john (PR #234) - 3 weeks ago -├── Internal wiki: "Legacy OAuth Migration Guide" -├── Suggested teammate: @sarah (95% skill match) -└── Estimated unblock time: 45 minutes with pairing -``` - -- **Knowledge Base Integration** - Upload docs, wikis, and past solutions -- **Skill-Adaptive Responses** - Adjusts explanation complexity to user level -- **Smart Teammate Matching** - Suggests the right person to help - -### 📊 Prediction Engine +--- -Machine learning-powered forecasting that sees problems coming. +## Tech Stack -| Prediction Type | What It Does | -|----------------|--------------| -| **Delivery Forecast** | P25, P50, P90 completion estimates | -| **Risk Cascade** | Shows how one delay affects downstream tasks | -| **Velocity Trends** | Team performance over time | -| **Hiring Forecast** | Predicts when you'll need more capacity | +| Layer | Technology | +|-------|------------| +| **Frontend** | React 19, TypeScript 5.9, Vite 7, Tailwind CSS, Radix UI/shadcn | +| **State** | Zustand 5, TanStack React Query | +| **Backend** | FastAPI, Python 3.11+, SQLAlchemy 2.0 (async) | +| **Database** | SQLite (dev) / PostgreSQL + Supabase (prod) | +| **Auth** | JWT + bcrypt + Google OAuth 2.0 + Supabase Auth | +| **AI** | OpenAI, Anthropic, Mistral, Kimi, Ollama, Mock | +| **Testing** | Playwright (E2E), pytest (unit) | +| **CI/CD** | GitHub Actions + Vercel | +| **Infra** | Docker, Docker Compose | -### 🎯 Skill Graph +--- -Automatic skill inference that grows with your organization. +## Architecture ``` -┌─────────────────────────────────────────────────┐ -│ Developer: Sarah Chen │ -├─────────────────────────────────────────────────┤ -│ ████████████████████░░░░ React (85%) │ -│ ██████████████░░░░░░░░░░ TypeScript (70%) │ -│ ████████████████████████ Python (95%) │ -│ ████████░░░░░░░░░░░░░░░░ DevOps (40%) │ -├─────────────────────────────────────────────────┤ -│ 📈 Learning Velocity: +15% this quarter │ -│ 🎯 Recommended: "Advanced Kubernetes" course │ -└─────────────────────────────────────────────────┘ + ┌──────────────────────────────┐ + │ Vercel (Production) │ + │ relaxed-gates.vercel.app │ + └──────────────┬───────────────┘ + │ + ┌───────────────────────────┼───────────────────────────┐ + │ │ │ + ┌──────────▼──────────┐ ┌──────────▼──────────┐ ┌──────────▼──────────┐ + │ Frontend (Vite) │ │ API (Serverless) │ │ Supabase Auth │ + │ React 19 + TS │───▶│ FastAPI + Python │ │ OAuth + JWT │ + │ Port: 5173 │ │ Port: 8000 │ │ │ + └─────────────────────┘ └──────────┬───────────┘ └─────────────────────┘ + │ + ┌─────────────────────┼─────────────────────┐ + │ │ │ + ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ + │ PostgreSQL │ │ AI Providers │ │ Supabase │ + │ (Supabase) │ │ OpenAI/etc │ │ Storage │ + └─────────────┘ └─────────────┘ └─────────────┘ ``` -### 👥 Workforce Intelligence - -Data-driven insights for better organizational decisions. - -- **Performance Scoring** - Objective, multi-factor employee metrics -- **Manager Effectiveness** - How well leaders enable their teams -- **Org Health Index** - Company-wide productivity dashboard -- **Restructuring Simulator** - Model team changes before making them - -### 🤖 Automation Detection - -Find and eliminate repetitive work automatically. - -1. **Pattern Recognition** - AI identifies repetitive task sequences -2. **Agent Creation** - One-click automation for common workflows -3. **Shadow Mode** - Test automations safely before deployment -4. **ROI Dashboard** - Track hours saved and cost reduction - --- -## Quick Start +## Getting Started ### Prerequisites - Python 3.11+ - Node.js 18+ -- npm or yarn +- npm -### Local Development - -**1. Clone the repository** +### 1. Clone ```bash -git clone https://github.com/shibinsp/taskpulse-ai.git -cd taskpulse-ai +git clone https://github.com/shibinsp/task_assistant_AI.git +cd task_assistant_AI ``` -**2. Backend Setup** +### 2. Backend Setup ```bash cd backend - -# Install dependencies +python3 -m venv venv +source venv/bin/activate pip install -r requirements.txt -# Or install core dependencies -pip install fastapi uvicorn sqlalchemy aiosqlite pydantic-settings python-jose[cryptography] passlib bcrypt python-multipart greenlet - -# Configure environment (already set up) -# Edit backend/.env if needed +# Configure environment +cp .env.example .env +# Edit .env with your settings # Start the server -python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload +python run.py +# Or: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload ``` -**3. Frontend Setup** +### 3. Frontend Setup ```bash -cd frontend - -# Install dependencies +cd Frontend npm install - -# Start development server npm run dev ``` -**4. Access the Application** - -- **Frontend**: http://localhost:3000 -- **Backend API**: http://localhost:8000 -- **API Docs**: http://localhost:8000/docs - -### Quick Start Script +### 4. Seed Demo Data ```bash -# From project root -./start.sh +cd backend +python scripts/seed_data.py ``` -This will start both backend and frontend services automatically. +### 5. Access ---- +| Service | URL | +|---------|-----| +| Frontend | http://localhost:5173 | +| Backend API | http://localhost:8000 | +| API Docs (Swagger) | http://localhost:8000/docs | +| API Docs (ReDoc) | http://localhost:8000/redoc | -## Architecture - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Frontend (React) │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ │ -│ │Dashboard │ │ Tasks │ │ Check-ins│ │ Skills & Reports │ │ -│ └──────────┘ └──────────┘ └──────────┘ └──────────────────────┘ │ -└─────────────────────────────┬───────────────────────────────────┘ - │ REST API (Vite Proxy) -┌─────────────────────────────▼───────────────────────────────────┐ -│ Backend (FastAPI) │ -│ ┌──────────────────────────────────────────────────────────────┐│ -│ │ API Layer (v1) ││ -│ │ Auth │ Tasks │ Check-ins │ Skills │ Predictions │ Reports ││ -│ └──────────────────────────────────────────────────────────────┘│ -│ ┌──────────────────────────────────────────────────────────────┐│ -│ │ Service Layer ││ -│ │ AI Service │ Prediction │ Skill Graph │ Automation │ Notif ││ -│ └──────────────────────────────────────────────────────────────┘│ -│ ┌──────────────────────────────────────────────────────────────┐│ -│ │ Data Layer ││ -│ │ SQLAlchemy ORM │ Async SQLite/PostgreSQL ││ -│ └──────────────────────────────────────────────────────────────┘│ -└──────────────────────────────────────────────────────────────────┘ - │ - ┌─────────────────────┼─────────────────────┐ - ▼ ▼ ▼ - ┌─────────┐ ┌──────────┐ ┌──────────┐ - │ AI │ │ Database │ │ Cache │ - │ Provider│ │ SQLite │ │ (Future)│ - └─────────┘ └──────────┘ └──────────┘ -``` - -### Tech Stack - -| Layer | Technology | -|-------|------------| -| **Frontend** | React 18, TypeScript, Vite, Zustand, Axios | -| **Backend** | FastAPI, Python 3.11+, SQLAlchemy, Pydantic | -| **Database** | SQLite (dev) / PostgreSQL (prod) | -| **AI** | OpenAI / Anthropic Claude / Mock (configurable) | -| **Deployment** | Docker, Docker Compose (coming soon) | +**Default login**: `admin@acme.com` / `demo123` --- -## API Documentation - -Interactive API documentation is available at: - -- **Swagger UI**: http://localhost:8000/docs -- **ReDoc**: http://localhost:8000/redoc - -### Key Endpoints - -
-Authentication - -```http -POST /api/v1/auth/register # Create new account -POST /api/v1/auth/login # Get access tokens -POST /api/v1/auth/refresh # Refresh access token -GET /api/v1/auth/me # Get current user -``` -
+## E2E Testing -
-Tasks +13 Playwright test suites covering 113 test cases. -```http -GET /api/v1/tasks # List all tasks -POST /api/v1/tasks # Create new task -GET /api/v1/tasks/{id} # Get task details -PUT /api/v1/tasks/{id} # Update task -DELETE /api/v1/tasks/{id} # Delete task -POST /api/v1/tasks/{id}/decompose # AI task breakdown -``` -
+```bash +cd Frontend -
-Check-ins +# Run against local dev server +npm run test:e2e -```http -GET /api/v1/checkins # List check-ins -POST /api/v1/checkins # Create check-in -GET /api/v1/checkins/{id} # Get check-in details -``` -
+# Run against deployed app +npm run test:e2e:deployed -
-AI & Predictions +# Run with visible browser +npm run test:e2e:headed -```http -POST /api/v1/ai/unblock # Get AI help for blockers -GET /api/v1/predictions/tasks/{id} # Delivery forecast -GET /api/v1/predictions/team/{id}/velocity # Velocity trends -``` -
+# Interactive UI mode +npm run test:e2e:ui -
-Workforce & Reports - -```http -GET /api/v1/workforce/scores # Employee performance -GET /api/v1/workforce/org-health # Organization metrics -POST /api/v1/workforce/simulate # Restructuring simulation -GET /api/v1/reports/dashboard # Dashboard data -POST /api/v1/reports/generate # Generate report +# View HTML report +npx playwright show-report ``` -
---- - -## Role-Based Access Control - -| Role | Capabilities | -|------|--------------| -| **Super Admin** | Full system access, multi-org management | -| **Org Admin** | Organization settings, all users | -| **Manager** | Team oversight, reports, escalations | -| **Team Lead** | Team task management, check-ins | -| **Employee** | Personal tasks, respond to check-ins | -| **Viewer** | Read-only dashboard access | +### Latest Results (Deployed App) + +| Suite | Tests | Status | +|-------|-------|--------| +| Landing Page | 7 | Pass | +| Authentication | 8 | Pass | +| Dashboard | 9 | 8 Pass / 1 Fail | +| Task Creation | 6 | Pass | +| AI Task Creator | 6 | Pass | +| Task Management | 5 | Pass | +| Navigation | 7 | Pass | +| Settings | 3 | Pass | +| Task Detail | 11 | Pass | +| Filters & Sort | 12 | Pass | +| Check-Ins | 13 | Pass | +| AI Command | 9 | Pass | +| Settings Logic | 15 | Pass | +| **Total** | **113** | **110 Pass / 1 Fail / 2 Skip** | --- -## Configuration - -### Environment Variables +## CI/CD Pipeline -| Variable | Description | Default | -|----------|-------------|---------| -| `ENVIRONMENT` | Environment mode | `development` | -| `DATABASE_URL` | Database connection | `sqlite+aiosqlite:///./taskpulse.db` | -| `SECRET_KEY` | JWT signing key | (auto-generated for dev) | -| `AI_PROVIDER` | AI backend | `mock` | -| `OPENAI_API_KEY` | OpenAI API key | - | -| `ANTHROPIC_API_KEY` | Anthropic API key | - | +Every push to `main` triggers automatic deployment: -### AI Provider Setup - -**Mock (Default)** - No API keys needed, great for development. - -**OpenAI:** -```env -AI_PROVIDER=openai -OPENAI_API_KEY=sk-your-key ``` - -**Anthropic Claude:** -```env -AI_PROVIDER=anthropic -ANTHROPIC_API_KEY=sk-ant-your-key +Push to main + │ + ├── Detect Changes (frontend/backend) + │ + ├── Frontend: Lint & Build (ESLint + tsc + Vite) + │ + ├── Backend: Unit Tests (pytest) + │ + └── Deploy to Vercel Production ``` +- **Preview deploys** on every PR with auto-comment +- **Manual trigger** via GitHub Actions UI (`workflow_dispatch`) +- **Concurrency control** — new pushes cancel in-progress runs + --- ## Project Structure ``` -taskpulse-ai/ +task_assistant_AI/ +├── Frontend/ +│ ├── src/ +│ │ ├── pages/ # 19 route pages +│ │ ├── components/ # UI components (shadcn + custom) +│ │ ├── services/ # 17 API service modules +│ │ ├── store/ # Zustand stores (auth, theme, ui) +│ │ ├── types/ # TypeScript types + mappers +│ │ ├── hooks/ # React Query hooks +│ │ └── lib/ # API client, Supabase, utils +│ ├── e2e/ # 13 Playwright spec files +│ ├── playwright.config.ts +│ └── package.json ├── backend/ │ ├── app/ -│ │ ├── api/v1/ # REST API endpoints -│ │ ├── core/ # Security, middleware -│ │ ├── models/ # Database models -│ │ ├── schemas/ # Pydantic schemas -│ │ ├── services/ # Business logic -│ │ ├── agents/ # AI agents -│ │ └── utils/ # Helpers -│ ├── tests/ # Test suite -│ ├── scripts/ # Seed data, utilities -│ ├── .env # Environment config +│ │ ├── api/v1/ # 18 route modules +│ │ ├── core/ # Security, middleware, permissions +│ │ ├── models/ # 12 SQLAlchemy models +│ │ ├── schemas/ # Pydantic request/response schemas +│ │ ├── services/ # 17 business logic services +│ │ └── agents/ # 5 AI agents +│ ├── tests/ # pytest test suite +│ ├── scripts/ # Seed data scripts │ └── requirements.txt -├── frontend/ -│ ├── src/ -│ │ ├── pages/ # Route pages -│ │ ├── services/ # API clients -│ │ ├── store/ # State management -│ │ ├── types/ # TypeScript types -│ │ └── lib/ # Utilities -│ ├── package.json -│ └── vite.config.ts -├── start.sh # Quick start script -├── INTEGRATION.md # Integration guide -└── README.md +├── api/ +│ └── index.py # Vercel serverless entry point +├── .github/workflows/ +│ └── ci-cd.yml # CI/CD pipeline +├── docs/ +│ ├── reports/ # Test & audit reports +│ └── screenshots/ # UI screenshots +├── vercel.json # Vercel deploy config +├── docker-compose.yml # Docker services +└── CLAUDE.md # Development rules ``` --- -## Development +## API Endpoints + +
+Authentication -### Running Tests +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/api/v1/auth/register` | Create new account | +| `POST` | `/api/v1/auth/login` | Get access tokens | +| `GET` | `/api/v1/auth/me` | Get current user | +| `POST` | `/api/v1/auth/google` | Google OAuth login | +| `POST` | `/api/v1/auth/refresh` | Refresh access token | +| `POST` | `/api/v1/auth/logout` | Logout current session | +| `POST` | `/api/v1/auth/change-password` | Change password | +| `GET` | `/api/v1/auth/sessions` | List active sessions | -```bash -# Backend tests -cd backend -pytest +
-# With coverage -pytest --cov=app --cov-report=html -``` +
+Tasks -### Code Quality +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/v1/tasks` | List tasks (filterable) | +| `POST` | `/api/v1/tasks` | Create task | +| `GET` | `/api/v1/tasks/{id}` | Get task details | +| `PUT` | `/api/v1/tasks/{id}` | Update task | +| `DELETE` | `/api/v1/tasks/{id}` | Delete task | +| `POST` | `/api/v1/tasks/{id}/decompose` | AI decomposition | +| `POST` | `/api/v1/tasks/{id}/subtasks` | Create subtask | +| `POST` | `/api/v1/tasks/{id}/comments` | Add comment | +| `GET` | `/api/v1/tasks/{id}/history` | Task change history | +| `POST` | `/api/v1/tasks/bulk/update` | Bulk update | -```bash -# Backend linting -cd backend -flake8 app/ -black app/ +
-# Frontend linting -cd frontend -npm run lint -``` +
+Check-Ins + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/v1/checkins` | List check-ins | +| `GET` | `/api/v1/checkins/pending` | Pending check-ins | +| `POST` | `/api/v1/checkins/{id}/respond` | Submit response | +| `POST` | `/api/v1/checkins/{id}/skip` | Skip check-in | +| `POST` | `/api/v1/checkins/{id}/escalate` | Escalate blocker | +| `GET` | `/api/v1/checkins/config` | Get config | +| `GET` | `/api/v1/checkins/statistics` | Check-in stats | ---- +
-## Roadmap - -- [x] **Core Task Management** - CRUD operations, status tracking -- [x] **Authentication & Authorization** - JWT-based auth, RBAC -- [x] **Check-in System** - Proactive engagement loops -- [x] **AI Unblock Engine** - RAG-powered assistance -- [x] **Skill Graph** - Automatic skill inference -- [x] **Prediction Engine** - ML-powered forecasting -- [ ] **Slack/Teams Integration** - Check-ins where your team works -- [ ] **Mobile App** - iOS and Android native apps -- [ ] **Advanced Analytics** - Custom report builder -- [ ] **SSO/SAML** - Enterprise authentication -- [ ] **Multi-language Support** - i18n for global teams -- [ ] **API Webhooks** - External integrations +
+AI & Chat ---- +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/api/v1/ai/unblock` | AI unblock assistance | +| `GET` | `/api/v1/ai/documents` | Knowledge base docs | +| `POST` | `/api/v1/chat` | Send chat message | +| `GET` | `/api/v1/chat/conversations` | List conversations | -## Contributing +
-We welcome contributions! Here's how to get started: +
+Skills, Predictions, Workforce + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/v1/skills` | Skill catalog | +| `GET` | `/api/v1/skills/{userId}/graph` | Skill graph | +| `GET` | `/api/v1/skills/{userId}/gaps` | Skill gaps | +| `GET` | `/api/v1/predictions/tasks/{id}` | Delivery forecast | +| `GET` | `/api/v1/predictions/team/{id}/velocity` | Velocity forecast | +| `GET` | `/api/v1/workforce/scores` | Performance scores | +| `GET` | `/api/v1/workforce/org-health` | Org health index | +| `POST` | `/api/v1/workforce/simulations` | Restructuring sim | -1. **Fork** the repository -2. **Create** a feature branch (`git checkout -b feature/amazing-feature`) -3. **Commit** your changes (`git commit -m 'Add amazing feature'`) -4. **Push** to the branch (`git push origin feature/amazing-feature`) -5. **Open** a Pull Request +
+ +
+Automation & Integrations + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/v1/automation/patterns` | Detected patterns | +| `POST` | `/api/v1/automation/agents` | Create agent | +| `GET` | `/api/v1/automation/roi` | ROI dashboard | +| `GET` | `/api/v1/integrations` | List integrations | +| `POST` | `/api/v1/integrations` | Create integration | +| `POST` | `/api/v1/integrations/{id}/test` | Test connection | +| `GET` | `/api/v1/webhooks` | List webhooks | + +
+ +--- + +## Security + +- **JWT Authentication** with refresh token rotation +- **bcrypt Password Hashing** with complexity requirements (8+ chars, uppercase, etc.) +- **Role-Based Access Control** (6 roles: Super Admin to Viewer) +- **Permission Guards** on all API endpoints +- **Secret Redaction** in API responses (SMTP passwords, tokens) +- **CORS Protection** with configurable origins +- **Rate Limiting** on authentication endpoints +- **Input Validation** via Pydantic schemas +- **SQL Injection Prevention** via SQLAlchemy ORM +- **XSS Protection** via React's built-in escaping --- -## License +## Role-Based Access Control -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +| Role | Tasks | Check-Ins | Team | Reports | Admin | +|------|-------|-----------|------|---------|-------| +| **Super Admin** | Full | Full | Full | Full | Full | +| **Org Admin** | Full | Full | Full | Full | Org Settings | +| **Manager** | Team | Team | View | Full | - | +| **Team Lead** | Team | Team | View | Team | - | +| **Employee** | Own | Own | - | Own | - | +| **Viewer** | Read | Read | - | Read | - | --- -## Support +## Contributing + +All changes follow the **Issue -> Branch -> PR** workflow: -- **Documentation**: See [INTEGRATION.md](INTEGRATION.md) for detailed setup -- **Issues**: Report bugs on [GitHub Issues](https://github.com/shibinsp/taskpulse-ai/issues) -- **Discussions**: Join our [GitHub Discussions](https://github.com/shibinsp/taskpulse-ai/discussions) +1. **Create a GitHub Issue** describing the change +2. **Create a branch**: `fix/issue-42-description` or `feat/issue-43-feature` +3. **Implement** the fix/feature +4. **Create a PR** with `Fixes #` in the body +5. **CI passes** -> **Merge** to `main` -> **Auto-deploy** to Vercel ---

- Built with passion for productive teams everywhere. -
-
- - Stars - - - Forks - + Built by shibinsp • Powered by Beeax

diff --git a/docs/reports/TaskPulse_Project_Overview.xlsx b/docs/reports/TaskPulse_Project_Overview.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b6587bcc01d4753050f798391e2ffa29ac70af64 GIT binary patch literal 24661 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%0D6)+z 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 a|LDn7