diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..822fe49
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,11 @@
+node_modules
+dist
+data
+*.log
+.env
+.git
+.gitignore
+README.md
+docs
+tests
+*.md
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..95ef558
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,36 @@
+# Server
+PORT=3000
+NODE_ENV=development
+
+# Database
+SQLITE_PATH=./data/app.sqlite
+
+# Queue (optional - uses in-memory fallback if not set)
+REDIS_URL=redis://localhost:6379
+QUEUE_ENABLED=false
+
+# Integrations (all stubs - do not enable without explicit approval)
+WHATSAPP_ENABLED=false
+WHATSAPP_API_URL=
+WHATSAPP_API_TOKEN=
+
+VOICE_ENABLED=false
+VOICE_PROVIDER=
+VOICE_API_KEY=
+
+GOOGLE_ENABLED=false
+GOOGLE_CLIENT_ID=
+GOOGLE_CLIENT_SECRET=
+
+ICLOUD_ENABLED=false
+ICLOUD_APPLE_ID=
+ICLOUD_APP_PASSWORD=
+
+PINECONE_ENABLED=false
+PINECONE_API_KEY=
+PINECONE_ENVIRONMENT=
+PINECONE_INDEX=
+
+# Supervisor
+STOP_SCORE_THRESHOLD=40
+MAX_PARALLEL_AGENTS=4
diff --git a/.gitignore b/.gitignore
index 5d82a5f..c3f1adc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,34 @@
-__pycache__/
-*.log
+# Dependencies
+node_modules/
+
+# Build output
+dist/
+
+# Data
+data/
+
+# Environment
.env
-*.pyc
+.env.local
+.env.*.local
+
+# Logs
+*.log
+npm-debug.log*
+
+# IDE
.idea/
.vscode/
-secrets.json
\ No newline at end of file
+*.swp
+*.swo
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Test coverage
+coverage/
+
+# Temporary files
+*.tmp
+*.temp
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..87168e5
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,40 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+WORKDIR /app
+
+# Copy package files
+COPY package*.json ./
+COPY tsconfig.json ./
+
+# Install dependencies
+RUN npm ci
+
+# Copy source
+COPY src ./src
+
+# Build
+RUN npm run build
+
+# Production stage
+FROM node:20-alpine
+
+WORKDIR /app
+
+# Copy package files and install production dependencies
+COPY package*.json ./
+RUN npm ci --omit=dev
+
+# Copy built files
+COPY --from=builder /app/dist ./dist
+
+# Create data directory
+RUN mkdir -p /app/data
+
+# Set environment
+ENV NODE_ENV=production
+ENV PORT=3000
+
+EXPOSE 3000
+
+CMD ["node", "dist/index.js"]
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..be03453
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,32 @@
+version: "3.8"
+
+services:
+ app:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ ports:
+ - "3000:3000"
+ environment:
+ - NODE_ENV=development
+ - PORT=3000
+ - SQLITE_PATH=/app/data/app.sqlite
+ - REDIS_URL=redis://redis:6379
+ - QUEUE_ENABLED=false
+ volumes:
+ - ./data:/app/data
+ depends_on:
+ - redis
+ restart: unless-stopped
+
+ redis:
+ image: redis:7-alpine
+ ports:
+ - "6379:6379"
+ volumes:
+ - redis_data:/data
+ command: redis-server --appendonly yes
+ restart: unless-stopped
+
+volumes:
+ redis_data:
diff --git a/docs/DESIGN_SPECIFICATION.md b/docs/DESIGN_SPECIFICATION.md
new file mode 100644
index 0000000..a76e412
--- /dev/null
+++ b/docs/DESIGN_SPECIFICATION.md
@@ -0,0 +1,1005 @@
+# Step2Job Cloud Agents - Design & UI/UX Spezifikation
+
+## Komplette Anleitung für Design-Agenten
+
+---
+
+## 1. PROJEKT-ÜBERBLICK
+
+### 1.1 Produktvision
+**Step2Job Cloud Agents** ist ein Supervisor-Dashboard zur Überwachung und Qualitätssicherung von KI-Agenten. Das System analysiert Gespräche in Echtzeit und warnt Supervisoren bei kritischen Vorfällen.
+
+### 1.2 Zielgruppen
+
+| Persona | Rolle | Bedürfnisse |
+|---------|-------|-------------|
+| **Supervisor Sarah** | Team-Leiterin (5-10 Agenten) | Schneller Überblick, sofortige Alerts, Batch-Aktionen |
+| **Manager Michael** | Abteilungsleiter | Reports, Trends, KPIs, Export-Funktionen |
+| **Admin Anna** | System-Administratorin | Konfiguration, Benutzerverwaltung, Logs |
+| **Agent Alex** | KI-Agent-Operator | Eigene Performance sehen, Feedback verstehen |
+
+### 1.3 Kernfunktionen
+1. **Live-Dashboard** - Echtzeit-Überwachung aller Agenten
+2. **Alert-Center** - Kritische Vorfälle sofort erkennen
+3. **Agent-Details** - Einzelne Agenten analysieren
+4. **Reports** - Historische Daten und Trends
+5. **Konfiguration** - Keywords und Schwellwerte anpassen
+
+---
+
+## 2. DESIGN-SYSTEM
+
+### 2.1 Farbpalette
+
+#### Primärfarben
+```css
+:root {
+ /* Brand Colors */
+ --primary-50: #EEF2FF;
+ --primary-100: #E0E7FF;
+ --primary-200: #C7D2FE;
+ --primary-300: #A5B4FC;
+ --primary-400: #818CF8;
+ --primary-500: #6366F1; /* Hauptfarbe */
+ --primary-600: #4F46E5;
+ --primary-700: #4338CA;
+ --primary-800: #3730A3;
+ --primary-900: #312E81;
+}
+```
+
+#### Risk-Level Farben (KRITISCH - konsistent verwenden!)
+```css
+:root {
+ /* Risk Colors - Semantisch */
+ --risk-low: #10B981; /* Grün - Erfolg */
+ --risk-low-bg: #D1FAE5;
+ --risk-low-border: #6EE7B7;
+
+ --risk-medium: #F59E0B; /* Gelb/Orange - Warnung */
+ --risk-medium-bg: #FEF3C7;
+ --risk-medium-border: #FCD34D;
+
+ --risk-high: #F97316; /* Orange - Gefahr */
+ --risk-high-bg: #FFEDD5;
+ --risk-high-border: #FDBA74;
+
+ --risk-critical: #EF4444; /* Rot - Kritisch */
+ --risk-critical-bg: #FEE2E2;
+ --risk-critical-border: #FCA5A5;
+}
+```
+
+#### Neutrale Farben
+```css
+:root {
+ --gray-50: #F9FAFB;
+ --gray-100: #F3F4F6;
+ --gray-200: #E5E7EB;
+ --gray-300: #D1D5DB;
+ --gray-400: #9CA3AF;
+ --gray-500: #6B7280;
+ --gray-600: #4B5563;
+ --gray-700: #374151;
+ --gray-800: #1F2937;
+ --gray-900: #111827;
+}
+```
+
+### 2.2 Typografie
+
+```css
+:root {
+ /* Font Familie */
+ --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ --font-mono: 'JetBrains Mono', 'Fira Code', monospace;
+
+ /* Font Sizes */
+ --text-xs: 0.75rem; /* 12px */
+ --text-sm: 0.875rem; /* 14px */
+ --text-base: 1rem; /* 16px */
+ --text-lg: 1.125rem; /* 18px */
+ --text-xl: 1.25rem; /* 20px */
+ --text-2xl: 1.5rem; /* 24px */
+ --text-3xl: 1.875rem; /* 30px */
+ --text-4xl: 2.25rem; /* 36px */
+
+ /* Font Weights */
+ --font-normal: 400;
+ --font-medium: 500;
+ --font-semibold: 600;
+ --font-bold: 700;
+
+ /* Line Heights */
+ --leading-tight: 1.25;
+ --leading-normal: 1.5;
+ --leading-relaxed: 1.75;
+}
+```
+
+#### Typografie-Hierarchie
+
+| Element | Größe | Gewicht | Verwendung |
+|---------|-------|---------|------------|
+| H1 | 36px | Bold | Seitentitel |
+| H2 | 24px | Semibold | Sektionsüberschriften |
+| H3 | 20px | Semibold | Card-Titel |
+| H4 | 18px | Medium | Subsektionen |
+| Body | 16px | Normal | Fließtext |
+| Small | 14px | Normal | Sekundärtext |
+| Caption | 12px | Normal | Labels, Timestamps |
+
+### 2.3 Abstände & Grid
+
+```css
+:root {
+ /* Spacing Scale (8px Basis) */
+ --space-1: 0.25rem; /* 4px */
+ --space-2: 0.5rem; /* 8px */
+ --space-3: 0.75rem; /* 12px */
+ --space-4: 1rem; /* 16px */
+ --space-5: 1.25rem; /* 20px */
+ --space-6: 1.5rem; /* 24px */
+ --space-8: 2rem; /* 32px */
+ --space-10: 2.5rem; /* 40px */
+ --space-12: 3rem; /* 48px */
+ --space-16: 4rem; /* 64px */
+
+ /* Border Radius */
+ --radius-sm: 0.25rem; /* 4px */
+ --radius-md: 0.375rem; /* 6px */
+ --radius-lg: 0.5rem; /* 8px */
+ --radius-xl: 0.75rem; /* 12px */
+ --radius-2xl: 1rem; /* 16px */
+ --radius-full: 9999px;
+
+ /* Shadows */
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
+ --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
+}
+```
+
+### 2.4 Grid-System
+
+```
+Desktop (≥1280px): 12-Spalten Grid, 24px Gutter
+Tablet (768-1279px): 8-Spalten Grid, 20px Gutter
+Mobile (<768px): 4-Spalten Grid, 16px Gutter
+
+Container Max-Width: 1440px
+Sidebar-Breite: 256px (collapsed: 64px)
+```
+
+---
+
+## 3. KOMPONENTEN-BIBLIOTHEK
+
+### 3.1 Risk-Level Badge
+
+```
+┌─────────────────┐
+│ ● LOW │ Grün, gefüllter Kreis
+└─────────────────┘
+
+┌─────────────────┐
+│ ▲ MEDIUM │ Gelb, Dreieck-Warnung
+└─────────────────┘
+
+┌─────────────────┐
+│ ◆ HIGH │ Orange, Raute
+└─────────────────┘
+
+┌─────────────────┐
+│ ✖ CRITICAL │ Rot, X-Symbol, pulsierend
+└─────────────────┘
+```
+
+**Spezifikation:**
+- Padding: 4px 12px
+- Border-Radius: 9999px (Pill)
+- Font: 12px, Semibold, Uppercase
+- Icon links, 4px Abstand zum Text
+
+### 3.2 Agent-Card
+
+```
+┌──────────────────────────────────────────────────────┐
+│ ┌────┐ │
+│ │ 🤖 │ AGENT_001 ● Online │
+│ └────┘ Max Mustermann │
+│ │
+│ ┌─────────────┬─────────────┬─────────────┐ │
+│ │ Calls Today │ Avg. Risk │ Stop Rate │ │
+│ │ 24 │ 0.8 │ 92% │ │
+│ └─────────────┴─────────────┴─────────────┘ │
+│ │
+│ Risk Distribution: │
+│ ████████████████░░░░ LOW (80%) │
+│ ████░░░░░░░░░░░░░░░░ MEDIUM (15%) │
+│ █░░░░░░░░░░░░░░░░░░░ HIGH (5%) │
+│ │
+│ [View Details] [Pause Agent] │
+└──────────────────────────────────────────────────────┘
+```
+
+**Spezifikation:**
+- Breite: 100% (Grid-responsive)
+- Min-Breite: 320px
+- Padding: 24px
+- Background: White
+- Border: 1px solid gray-200
+- Border-Radius: 12px
+- Shadow: shadow-md
+- Hover: shadow-lg, border-primary-200
+
+### 3.3 Alert-Item
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ ⚠️ CRITICAL vor 2 Min │
+├──────────────────────────────────────────────────────────────┤
+│ │
+│ Agent AGENT_003 - Maria Weber │
+│ │
+│ Verstöße: │
+│ • Price mentioned without fact │
+│ • No STOP on legal question │
+│ │
+│ ┌────────────────────────────────────────────────────────┐ │
+│ │ "Das kostet 500€ und ist gesetzlich völlig in │ │
+│ │ Ordnung..." │ │
+│ └────────────────────────────────────────────────────────┘ │
+│ │
+│ [Transcript ansehen] [Agent pausieren] [Als gelesen] │
+└──────────────────────────────────────────────────────────────┘
+```
+
+**Spezifikation:**
+- Border-Left: 4px solid (Risk-Farbe)
+- Background: Risk-Farbe-bg (10% Opacity)
+- Padding: 16px 20px
+- Ungelesen: Font-weight bold, Indikator-Dot
+
+### 3.4 Stat-Card (KPI)
+
+```
+┌─────────────────────────┐
+│ ↗️ +12% │
+│ │
+│ 247 │
+│ Calls Today │
+│ │
+│ ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁ │
+└─────────────────────────┘
+```
+
+**Spezifikation:**
+- Min-Breite: 160px
+- Padding: 20px
+- Trend-Indikator: Grün (positiv), Rot (negativ)
+- Sparkline: Optional, 32px Höhe
+
+### 3.5 Button-Varianten
+
+```
+Primary: [████████████████] Gefüllt, Primary-600
+Secondary: [────────────────] Outlined, Primary-600 Border
+Ghost: [ Button Text ] Nur Text, Hover: gray-100 bg
+Danger: [████████████████] Gefüllt, Red-600
+Success: [████████████████] Gefüllt, Green-600
+```
+
+**Größen:**
+- Small: 32px Höhe, 12px Font, 12px Padding
+- Medium: 40px Höhe, 14px Font, 16px Padding
+- Large: 48px Höhe, 16px Font, 20px Padding
+
+### 3.6 Input-Felder
+
+```
+Label
+┌──────────────────────────────────┐
+│ 🔍 Placeholder text... │
+└──────────────────────────────────┘
+Helper text oder Fehler
+
+States:
+- Default: gray-300 border
+- Focus: primary-500 border, ring
+- Error: red-500 border
+- Disabled: gray-100 bg, gray-400 text
+```
+
+### 3.7 Data Table
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ ☐ Agent ID │ Contact │ Time │ Risk │ Actions │
+├─────────────────────────────────────────────────────────────────┤
+│ ☐ AGENT_001 │ M. Mustermann│ 10:30:00 │ ● LOW │ [⋮] │
+│ ☑ AGENT_002 │ A. Schmidt │ 10:32:15 │ ▲ MED │ [⋮] │
+│ ☐ AGENT_003 │ T. Müller │ 10:35:42 │ ✖ CRIT │ [⋮] │
+├─────────────────────────────────────────────────────────────────┤
+│ Showing 1-10 of 247 │ [<] [1] [2] [3] ... [25] [>] │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+**Spezifikation:**
+- Header: gray-50 bg, font-semibold
+- Rows: Alternating gray-50/white (optional)
+- Hover: primary-50 bg
+- Selected: primary-100 bg
+- Border: gray-200
+- Sortierbare Spalten: Pfeil-Icon
+
+---
+
+## 4. SEITEN-LAYOUTS
+
+### 4.1 Haupt-Layout
+
+```
+┌──────────────────────────────────────────────────────────────────────┐
+│ ┌────────┐ 🔔 3 👤 Sarah │
+│ │ LOGO │ Step2Job Cloud Agents Notifications User │
+├──────────┴───────────────────────────────────────────────────────────┤
+│ │ │ │
+│ │ 📊 │ ┌─────────────────────────────────────────────────────┐│
+│ │ Dash │ │ ││
+│ │ │ │ MAIN CONTENT AREA ││
+│ │ 🤖 │ │ ││
+│ │ Agents │ │ ││
+│ │ │ │ ││
+│ │ 🔔 │ │ ││
+│ │ Alerts │ │ ││
+│ │ │ │ ││
+│ │ 📈 │ │ ││
+│ │ Reports│ │ ││
+│ │ │ │ ││
+│ │ ⚙️ │ │ ││
+│ │ Config │ └─────────────────────────────────────────────────────┘│
+│ │ │ │
+└──┴────────┴──────────────────────────────────────────────────────────┘
+```
+
+### 4.2 Dashboard-Seite
+
+```
+┌──────────────────────────────────────────────────────────────────────┐
+│ Dashboard [Export] [Refresh 🔄] │
+│ Last updated: 23.12.2025, 15:30:00 │
+├──────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
+│ │ Active │ │ Calls │ │ Avg Risk │ │ Alerts │ │
+│ │ Agents │ │ Today │ │ Score │ │ Today │ │
+│ │ 5 │ │ 247 │ │ 0.6 │ │ 3 │ │
+│ │ ↗️ +2 │ │ ↗️ +12% │ │ ↘️ -0.2 │ │ ↗️ +1 │ │
+│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
+│ │
+│ ┌─────────────────────────────┐ ┌────────────────────────────────┐ │
+│ │ Risk Distribution │ │ Recent Alerts │ │
+│ │ │ │ │ │
+│ │ ████████████ LOW (75%) │ │ ⚠️ AGENT_003 - CRITICAL │ │
+│ │ ████░░░░░░░░ MED (15%) │ │ Price without fact │ │
+│ │ ██░░░░░░░░░░ HIGH (8%) │ │ 2 minutes ago │ │
+│ │ █░░░░░░░░░░░ CRIT (2%) │ │ ───────────────────────────── │ │
+│ │ │ │ ▲ AGENT_002 - MEDIUM │ │
+│ │ [View Details] │ │ Timeline assumption │ │
+│ └─────────────────────────────┘ │ 15 minutes ago │ │
+│ │ ───────────────────────────── │ │
+│ ┌─────────────────────────────┐ │ ● AGENT_001 - LOW │ │
+│ │ Agents Overview │ │ Resolved │ │
+│ │ │ │ 1 hour ago │ │
+│ │ Agent Status Risk │ │ │ │
+│ │ ───────────────────────── │ │ [View All Alerts →] │ │
+│ │ AGENT_001 ● Online LOW │ └────────────────────────────────┘ │
+│ │ AGENT_002 ● Online MED │ │
+│ │ AGENT_003 ⚠ Review HIGH │ │
+│ │ AGENT_004 ● Online LOW │ │
+│ │ │ │
+│ │ [View All Agents →] │ │
+│ └─────────────────────────────┘ │
+│ │
+│ ┌──────────────────────────────────────────────────────────────┐ │
+│ │ Performance Trend (Last 7 Days) │ │
+│ │ │ │
+│ │ 1.5 ┤ │ │
+│ │ │ ╭─╮ │ │
+│ │ 1.0 ┤ ╭───╯ ╰──╮ │ │
+│ │ │ ╱ ╰───────────╮ │ │
+│ │ 0.5 ┤──╯ ╰── │ │
+│ │ │ │ │
+│ │ 0.0 ┼─────────────────────────────────── │ │
+│ │ Mo Di Mi Do Fr Sa So │ │
+│ │ │ │
+│ │ ── Avg Risk ── Critical Incidents ── Stop Rate │ │
+│ └──────────────────────────────────────────────────────────────┘ │
+│ │
+└──────────────────────────────────────────────────────────────────────┘
+```
+
+### 4.3 Agent-Detail-Seite
+
+```
+┌──────────────────────────────────────────────────────────────────────┐
+│ ← Back to Agents │
+│ │
+│ ┌────────────────────────────────────────────────────────────────┐ │
+│ │ ┌──────┐ │ │
+│ │ │ 🤖 │ AGENT_001 │ │
+│ │ │ │ Supervisor: Sarah Schmidt │ │
+│ │ └──────┘ Status: ● Online since 08:00 │ │
+│ │ │ │
+│ │ [Pause Agent] [View Logs] [Edit Config] [⋮ More] │ │
+│ └────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
+│ │ Total Calls │ │ Average Risk │ │ Stop Rate │ │
+│ │ 156 │ │ 0.4 │ │ 95% │ │
+│ │ this week │ │ ● LOW │ │ excellent │ │
+│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
+│ │
+│ ┌──────────────────────────────────────────────────────────────┐ │
+│ │ [Recent Calls] [Violations] [Statistics] [Settings] │ │
+│ ├──────────────────────────────────────────────────────────────┤ │
+│ │ │ │
+│ │ Today's Calls │ │
+│ │ ───────────────────────────────────────────────────────── │ │
+│ │ │ │
+│ │ 10:35:42 │ Thomas Müller │ ● LOW │ [View Transcript] │ │
+│ │ 10:32:15 │ Anna Schmidt │ ▲ MED │ [View Transcript] │ │
+│ │ 10:30:00 │ Max Mustermann │ ● LOW │ [View Transcript] │ │
+│ │ 10:25:33 │ Lisa Braun │ ● LOW │ [View Transcript] │ │
+│ │ 10:20:17 │ Peter Hoffmann │ ● LOW │ [View Transcript] │ │
+│ │ │ │
+│ │ [Load More...] │ │
+│ │ │ │
+│ └──────────────────────────────────────────────────────────────┘ │
+│ │
+└──────────────────────────────────────────────────────────────────────┘
+```
+
+### 4.4 Alert-Center
+
+```
+┌──────────────────────────────────────────────────────────────────────┐
+│ Alert Center [Mark All Read] [Settings ⚙️]│
+│ │
+│ ┌────────────────────────────────────────────────────────────────┐ │
+│ │ 🔍 Search alerts... │ Risk: [All ▾] │ Date: [Today ▾] │ │
+│ └────────────────────────────────────────────────────────────────┘ │
+│ │
+│ Unread (3) │
+│ ───────────────────────────────────────────────────────────────── │
+│ │
+│ ┌────────────────────────────────────────────────────────────────┐ │
+│ │ ⚠️ CRITICAL vor 2 Min │ │
+│ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │
+│ │ AGENT_003 - Maria Weber │ │
+│ │ │ │
+│ │ • Price mentioned without fact reference │ │
+│ │ • No STOP on legal question │ │
+│ │ │ │
+│ │ "Das kostet 500€ und ist gesetzlich völlig in Ordnung..." │ │
+│ │ │ │
+│ │ [View Full] [Pause Agent] [Dismiss] │ │
+│ └────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌────────────────────────────────────────────────────────────────┐ │
+│ │ ▲ MEDIUM vor 15 Min │ │
+│ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │
+│ │ AGENT_002 - Anna Schmidt │ │
+│ │ │ │
+│ │ • Timeline assumption without PLACEHOLDER │ │
+│ │ │ │
+│ │ [View Full] [Dismiss] │ │
+│ └────────────────────────────────────────────────────────────────┘ │
+│ │
+│ Read (47) │
+│ ───────────────────────────────────────────────────────────────── │
+│ │
+│ [Show older alerts...] │
+│ │
+└──────────────────────────────────────────────────────────────────────┘
+```
+
+### 4.5 Reports-Seite
+
+```
+┌──────────────────────────────────────────────────────────────────────┐
+│ Reports │
+│ │
+│ ┌────────────────────────────────────────────────────────────────┐ │
+│ │ Period: [Last 7 Days ▾] │ Agents: [All ▾] │ [Generate Report]│ │
+│ └────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌────────────────────────────────────────────────────────────────┐ │
+│ │ Summary Statistics │ │
+│ │ │ │
+│ │ Total Calls Price Claims Legal Claims Stop Rate │ │
+│ │ 1,247 89 34 94% │ │
+│ │ ↗️ +15% ↘️ -8% ↘️ -12% ↗️ +3% │ │
+│ └────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────────────────┐ ┌────────────────────────────────┐ │
+│ │ Risk Trend │ │ Top Issues │ │
+│ │ │ │ │ │
+│ │ ▲ │ │ 1. Price without fact (42%) │ │
+│ │ │ ╭─╮ │ │ 2. Legal assumption (28%) │ │
+│ │ │╭──╯ ╰╮ │ │ 3. No STOP trigger (18%) │ │
+│ │ ╰╯ ╰────── │ │ 4. Timeline guess (12%) │ │
+│ │ Mo Di Mi Do Fr Sa So │ │ │ │
+│ └─────────────────────────────┘ └────────────────────────────────┘ │
+│ │
+│ ┌────────────────────────────────────────────────────────────────┐ │
+│ │ Agent Performance Ranking │ │
+│ │ │ │
+│ │ Rank │ Agent │ Calls │ Avg Risk │ Stop Rate │ Trend │ │
+│ │ ─────┼────────────┼───────┼──────────┼───────────┼────────── │ │
+│ │ 1 │ AGENT_001 │ 156 │ 0.3 │ 98% │ ↗️ +5% │ │
+│ │ 2 │ AGENT_004 │ 142 │ 0.4 │ 96% │ → 0% │ │
+│ │ 3 │ AGENT_002 │ 138 │ 0.6 │ 92% │ ↘️ -3% │ │
+│ │ 4 │ AGENT_003 │ 124 │ 1.2 │ 78% │ ↘️ -8% │ │
+│ │ │ │
+│ └────────────────────────────────────────────────────────────────┘ │
+│ │
+│ Export: [📄 PDF] [📊 Excel] [📋 CSV] [🖼️ PNG] │
+│ │
+└──────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 5. INTERAKTIONS-PATTERNS
+
+### 5.1 Echtzeit-Updates
+
+```
+WebSocket Events:
+├── agent.status.changed → Agent-Card Status aktualisieren
+├── alert.new → Toast + Badge-Counter + Alert-Liste
+├── score.calculated → Dashboard-Statistiken aktualisieren
+└── agent.paused → Agent-Card visuell markieren
+
+Toast-Notification (Critical Alert):
+┌────────────────────────────────────────┐
+│ ⚠️ CRITICAL ALERT ✕ │
+│ AGENT_003 needs immediate review │
+│ [View Now] │
+└────────────────────────────────────────┘
+Position: Top-Right, Auto-dismiss: 10s (nicht bei Critical)
+```
+
+### 5.2 Loading States
+
+```
+Skeleton Loading:
+┌──────────────────────────────────────┐
+│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
+│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
+│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
+└──────────────────────────────────────┘
+
+Spinner (Inline):
+[⟳ Loading...]
+
+Progress Bar (Batch-Operations):
+████████████░░░░░░░░░ 60% - Processing 150 of 247 logs
+```
+
+### 5.3 Empty States
+
+```
+┌──────────────────────────────────────┐
+│ │
+│ 📭 │
+│ │
+│ No alerts found │
+│ │
+│ Great job! All agents are │
+│ performing within parameters. │
+│ │
+│ [View Agent Status] │
+│ │
+└──────────────────────────────────────┘
+```
+
+### 5.4 Error States
+
+```
+┌──────────────────────────────────────┐
+│ ❌ Connection Lost │
+│ │
+│ Unable to connect to the server. │
+│ Data may be outdated. │
+│ │
+│ [Retry] [Work Offline] │
+│ │
+│ Last updated: 5 minutes ago │
+└──────────────────────────────────────┘
+```
+
+### 5.5 Confirmation Dialogs
+
+```
+┌──────────────────────────────────────────────┐
+│ ⚠️ Pause Agent? │
+├──────────────────────────────────────────────┤
+│ │
+│ Are you sure you want to pause AGENT_003? │
+│ │
+│ This agent will stop processing new calls │
+│ until manually resumed. │
+│ │
+│ Reason (optional): │
+│ ┌────────────────────────────────────────┐ │
+│ │ Performance review required │ │
+│ └────────────────────────────────────────┘ │
+│ │
+│ [Cancel] [Pause Agent] │
+│ │
+└──────────────────────────────────────────────┘
+```
+
+---
+
+## 6. RESPONSIVE DESIGN
+
+### 6.1 Breakpoints
+
+```css
+/* Mobile First Approach */
+--breakpoint-sm: 640px; /* Small tablets */
+--breakpoint-md: 768px; /* Tablets */
+--breakpoint-lg: 1024px; /* Small laptops */
+--breakpoint-xl: 1280px; /* Desktops */
+--breakpoint-2xl: 1536px; /* Large screens */
+```
+
+### 6.2 Mobile Layout (< 768px)
+
+```
+┌────────────────────────┐
+│ ☰ Step2Job 🔔 👤 │
+├────────────────────────┤
+│ │
+│ ┌──────────────────┐ │
+│ │ Active: 5 │ │
+│ │ Alerts: 3 ⚠️ │ │
+│ └──────────────────┘ │
+│ │
+│ Recent Alerts │
+│ ──────────────────── │
+│ ┌──────────────────┐ │
+│ │ ⚠️ CRITICAL │ │
+│ │ AGENT_003 │ │
+│ │ [View →] │ │
+│ └──────────────────┘ │
+│ │
+│ ┌──────────────────┐ │
+│ │ ▲ MEDIUM │ │
+│ │ AGENT_002 │ │
+│ │ [View →] │ │
+│ └──────────────────┘ │
+│ │
+├────────────────────────┤
+│ 📊 🤖 🔔 📈 ⚙️ │
+└────────────────────────┘
+```
+
+**Mobile-Anpassungen:**
+- Bottom-Navigation statt Sidebar
+- Vollbreite Cards (stacked)
+- Swipe-Gesten für Tabs
+- Collapsed Stats (expandable)
+- Touch-optimierte Buttons (min 44x44px)
+
+### 6.3 Tablet Layout (768px - 1024px)
+
+```
+┌───────────────────────────────────────────┐
+│ LOGO Step2Job Cloud Agents 🔔 👤 │
+├────────┬──────────────────────────────────┤
+│ │ │
+│ 📊 │ ┌─────────┐ ┌─────────┐ │
+│ │ │ Active │ │ Alerts │ │
+│ 🤖 │ │ 5 │ │ 3 │ │
+│ │ └─────────┘ └─────────┘ │
+│ 🔔 │ │
+│ │ ┌────────────────────────────┐ │
+│ 📈 │ │ Recent Alerts │ │
+│ │ │ ... │ │
+│ ⚙️ │ └────────────────────────────┘ │
+│ │ │
+└────────┴──────────────────────────────────┘
+```
+
+---
+
+## 7. ACCESSIBILITY (A11Y)
+
+### 7.1 Farb-Kontrast
+- Alle Text/Hintergrund-Kombinationen: min. 4.5:1 (WCAG AA)
+- Risk-Level nicht nur durch Farbe, auch durch Icons/Text
+
+### 7.2 Keyboard Navigation
+```
+Tab-Reihenfolge:
+1. Skip to main content Link
+2. Navigation Items
+3. Main Content (logische Reihenfolge)
+4. Footer Actions
+
+Shortcuts:
+- `?` → Keyboard shortcuts anzeigen
+- `g d` → Go to Dashboard
+- `g a` → Go to Agents
+- `g l` → Go to Alerts
+- `j/k` → Nächster/Vorheriger Item in Liste
+- `Enter` → Item öffnen
+- `Esc` → Modal/Dropdown schließen
+```
+
+### 7.3 Screen Reader Support
+```html
+
+
+ ✖
+ CRITICAL
+
+
+
+
+
+
+
+ New critical alert: Agent 003 requires review
+
+```
+
+### 7.4 Focus Indicators
+```css
+:focus-visible {
+ outline: 2px solid var(--primary-500);
+ outline-offset: 2px;
+}
+
+/* High contrast mode support */
+@media (prefers-contrast: high) {
+ :focus-visible {
+ outline: 3px solid currentColor;
+ }
+}
+```
+
+---
+
+## 8. ANIMATIONEN & TRANSITIONS
+
+### 8.1 Timing Functions
+
+```css
+:root {
+ --ease-out: cubic-bezier(0, 0, 0.2, 1);
+ --ease-in: cubic-bezier(0.4, 0, 1, 1);
+ --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
+
+ --duration-fast: 150ms;
+ --duration-normal: 200ms;
+ --duration-slow: 300ms;
+}
+```
+
+### 8.2 Standard-Transitions
+
+```css
+/* Buttons, Links */
+.interactive {
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+/* Cards, Panels */
+.card {
+ transition: box-shadow var(--duration-normal) var(--ease-out),
+ transform var(--duration-normal) var(--ease-out);
+}
+
+/* Modals, Dropdowns */
+.overlay {
+ transition: opacity var(--duration-normal) var(--ease-out);
+}
+```
+
+### 8.3 Spezielle Animationen
+
+```css
+/* Critical Alert Pulse */
+@keyframes pulse-critical {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+}
+
+.alert-critical {
+ animation: pulse-critical 2s ease-in-out infinite;
+}
+
+/* New Item Slide-In */
+@keyframes slide-in {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.new-item {
+ animation: slide-in var(--duration-normal) var(--ease-out);
+}
+
+/* Skeleton Loading Shimmer */
+@keyframes shimmer {
+ 0% { background-position: -200% 0; }
+ 100% { background-position: 200% 0; }
+}
+
+.skeleton {
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
+ background-size: 200% 100%;
+ animation: shimmer 1.5s infinite;
+}
+```
+
+---
+
+## 9. DARK MODE
+
+### 9.1 Farbpalette Dark
+
+```css
+[data-theme="dark"] {
+ --bg-primary: #0F172A;
+ --bg-secondary: #1E293B;
+ --bg-tertiary: #334155;
+
+ --text-primary: #F8FAFC;
+ --text-secondary: #CBD5E1;
+ --text-muted: #64748B;
+
+ --border-color: #334155;
+
+ /* Risk colors bleiben gleich, aber mit angepasster Opacity */
+ --risk-low-bg: rgba(16, 185, 129, 0.15);
+ --risk-medium-bg: rgba(245, 158, 11, 0.15);
+ --risk-high-bg: rgba(249, 115, 22, 0.15);
+ --risk-critical-bg: rgba(239, 68, 68, 0.15);
+}
+```
+
+### 9.2 System Preference Detection
+
+```css
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ /* Dark mode variables */
+ }
+}
+```
+
+---
+
+## 10. ICON-SET
+
+### 10.1 Empfohlene Icons (Lucide/Heroicons)
+
+| Funktion | Icon | Code |
+|----------|------|------|
+| Dashboard | LayoutDashboard | `` |
+| Agents | Bot | `` |
+| Alerts | Bell | `` |
+| Reports | BarChart3 | `` |
+| Settings | Settings | `` |
+| Risk LOW | CheckCircle | `` |
+| Risk MEDIUM | AlertTriangle | `` |
+| Risk HIGH | AlertOctagon | `` |
+| Risk CRITICAL | XCircle | `` |
+| Refresh | RefreshCw | `` |
+| Export | Download | `` |
+| Filter | Filter | `` |
+| Search | Search | `` |
+| User | User | `` |
+| Menu | Menu | `` |
+| Close | X | `` |
+| Expand | ChevronDown | `` |
+| External | ExternalLink | `` |
+
+---
+
+## 11. IMPLEMENTIERUNGS-CHECKLISTE
+
+### Phase 1: Foundation
+- [ ] Design System in Figma/Sketch erstellen
+- [ ] Farbpalette definieren und testen
+- [ ] Typografie-Scale implementieren
+- [ ] Grid-System aufsetzen
+- [ ] Icon-Set auswählen und integrieren
+
+### Phase 2: Komponenten
+- [ ] Button-Varianten
+- [ ] Input-Felder
+- [ ] Risk-Level Badges
+- [ ] Agent-Cards
+- [ ] Alert-Items
+- [ ] Stat-Cards
+- [ ] Data Tables
+- [ ] Modals/Dialogs
+- [ ] Toast Notifications
+- [ ] Loading States
+- [ ] Empty States
+
+### Phase 3: Layouts
+- [ ] Haupt-Layout mit Sidebar
+- [ ] Dashboard-Seite
+- [ ] Agent-Liste
+- [ ] Agent-Detail
+- [ ] Alert-Center
+- [ ] Reports-Seite
+- [ ] Settings-Seite
+
+### Phase 4: Interaktionen
+- [ ] Hover/Focus States
+- [ ] Transitions/Animations
+- [ ] Keyboard Navigation
+- [ ] Touch-Gesten (Mobile)
+- [ ] Real-time Updates (WebSocket)
+
+### Phase 5: Polish
+- [ ] Dark Mode
+- [ ] Responsive Testing
+- [ ] Accessibility Audit
+- [ ] Performance Optimization
+- [ ] Browser Testing
+
+---
+
+## 12. DESIGN-ASSETS
+
+### 12.1 Benötigte Dateien
+
+```
+/design
+├── /figma
+│ ├── step2job-design-system.fig
+│ ├── step2job-components.fig
+│ └── step2job-screens.fig
+├── /exports
+│ ├── /icons (SVG)
+│ ├── /images (PNG, WebP)
+│ └── /logos (SVG, PNG)
+├── /tokens
+│ └── design-tokens.json
+└── /docs
+ └── component-specs.pdf
+```
+
+### 12.2 Handoff-Format
+
+Für Developer-Handoff empfohlen:
+- **Figma** mit Dev Mode aktiviert
+- **Design Tokens** als JSON für CSS-Variables
+- **Storybook** für Komponenten-Dokumentation
+
+---
+
+**Dokument-Version:** 1.0
+**Erstellt:** 23.12.2025
+**Autor:** Design Agent
+**Status:** Draft - Zur Review
diff --git a/docs/FRONTEND_IMPLEMENTATION.md b/docs/FRONTEND_IMPLEMENTATION.md
new file mode 100644
index 0000000..935bd8d
--- /dev/null
+++ b/docs/FRONTEND_IMPLEMENTATION.md
@@ -0,0 +1,1087 @@
+# Frontend-Implementierung - Technische Anleitung
+
+## Empfohlener Tech-Stack
+
+### Framework & Build
+```json
+{
+ "framework": "Next.js 14+ (App Router)",
+ "language": "TypeScript 5+",
+ "styling": "Tailwind CSS 3.4+",
+ "components": "shadcn/ui",
+ "state": "Zustand oder TanStack Query",
+ "charts": "Recharts oder Tremor",
+ "icons": "Lucide React",
+ "forms": "React Hook Form + Zod"
+}
+```
+
+---
+
+## 1. PROJEKT-SETUP
+
+```bash
+# Projekt erstellen
+npx create-next-app@latest step2job-dashboard --typescript --tailwind --eslint --app
+
+# Abhängigkeiten installieren
+npm install @tanstack/react-query zustand recharts lucide-react
+npm install react-hook-form zod @hookform/resolvers
+npm install date-fns clsx tailwind-merge class-variance-authority
+
+# shadcn/ui initialisieren
+npx shadcn-ui@latest init
+
+# Komponenten hinzufügen
+npx shadcn-ui@latest add button card badge table dialog
+npx shadcn-ui@latest add dropdown-menu tabs tooltip avatar
+npx shadcn-ui@latest add input select textarea checkbox
+npx shadcn-ui@latest add alert toast sonner skeleton
+```
+
+---
+
+## 2. PROJEKT-STRUKTUR
+
+```
+/app
+├── (dashboard)
+│ ├── layout.tsx # Dashboard-Layout mit Sidebar
+│ ├── page.tsx # Dashboard-Startseite
+│ ├── agents/
+│ │ ├── page.tsx # Agent-Liste
+│ │ └── [id]/page.tsx # Agent-Detail
+│ ├── alerts/
+│ │ └── page.tsx # Alert-Center
+│ ├── reports/
+│ │ └── page.tsx # Reports
+│ └── settings/
+│ └── page.tsx # Einstellungen
+├── api/
+│ ├── agents/route.ts # API: Agenten
+│ ├── alerts/route.ts # API: Alerts
+│ └── scores/route.ts # API: Scores
+├── globals.css
+└── layout.tsx
+
+/components
+├── ui/ # shadcn/ui Komponenten
+├── dashboard/
+│ ├── stat-card.tsx
+│ ├── risk-badge.tsx
+│ ├── agent-card.tsx
+│ ├── alert-item.tsx
+│ └── performance-chart.tsx
+├── layout/
+│ ├── sidebar.tsx
+│ ├── header.tsx
+│ └── mobile-nav.tsx
+└── shared/
+ ├── data-table.tsx
+ ├── loading.tsx
+ └── empty-state.tsx
+
+/lib
+├── api.ts # API-Client
+├── utils.ts # Utility-Funktionen
+├── types.ts # TypeScript-Typen
+└── constants.ts # Konstanten
+
+/hooks
+├── use-agents.ts
+├── use-alerts.ts
+├── use-websocket.ts
+└── use-media-query.ts
+
+/stores
+├── alert-store.ts
+└── ui-store.ts
+```
+
+---
+
+## 3. TYPESCRIPT-TYPEN
+
+```typescript
+// lib/types.ts
+
+export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
+
+export interface Agent {
+ id: string;
+ name: string;
+ status: 'online' | 'offline' | 'paused' | 'review';
+ supervisor?: string;
+ createdAt: string;
+}
+
+export interface ScoreResult {
+ agent_id: string;
+ contact: string | null;
+ timestamp: string | null;
+ price_claim: boolean;
+ price_keywords_found: string[];
+ legal_claim: boolean;
+ legal_keywords_found: string[];
+ stop_triggered: boolean;
+ placeholder_used: boolean;
+ risk: number;
+ risk_level: RiskLevel;
+ violations: string[];
+}
+
+export interface AgentStatistics {
+ agent_id: string;
+ total_interactions: number;
+ average_risk: number;
+ price_claims: number;
+ legal_claims: number;
+ stops_triggered: number;
+ stop_rate: string;
+ critical_incidents: number;
+ risk_distribution: Record;
+}
+
+export interface Alert {
+ id: string;
+ agent_id: string;
+ risk_level: RiskLevel;
+ violations: string[];
+ transcript_snippet?: string;
+ timestamp: string;
+ read: boolean;
+ contact_name?: string;
+}
+
+export interface DashboardSummary {
+ total: number;
+ average_risk: number;
+ risk_distribution: Record;
+ critical_count: number;
+ critical_incidents: Array<{
+ agent_id: string;
+ risk_level: RiskLevel;
+ violations: string[];
+ }>;
+ agents_analyzed: number;
+}
+
+export interface SupervisorDashboard {
+ date: string;
+ agents_active: number;
+ total_interactions: number;
+ stopped_calls_today: number;
+ potential_issues: Array<{
+ agent_id: string;
+ issue: string;
+ risk: RiskLevel;
+ timestamp?: string;
+ }>;
+ agents_requiring_review: Array<{
+ agent_id: string;
+ average_risk: number;
+ critical_incidents: number;
+ recommendation: string;
+ }>;
+ action_required: boolean;
+ supervisor_recommendation: string;
+ summary: {
+ average_risk: number;
+ total_violations: number;
+ stop_compliance_rate: string;
+ };
+}
+```
+
+---
+
+## 4. KOMPONENTEN-IMPLEMENTIERUNG
+
+### 4.1 Risk Badge
+
+```tsx
+// components/dashboard/risk-badge.tsx
+import { cva, type VariantProps } from 'class-variance-authority';
+import { CheckCircle, AlertTriangle, AlertOctagon, XCircle } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import type { RiskLevel } from '@/lib/types';
+
+const badgeVariants = cva(
+ 'inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold uppercase',
+ {
+ variants: {
+ risk: {
+ LOW: 'bg-green-100 text-green-800 border border-green-200',
+ MEDIUM: 'bg-yellow-100 text-yellow-800 border border-yellow-200',
+ HIGH: 'bg-orange-100 text-orange-800 border border-orange-200',
+ CRITICAL: 'bg-red-100 text-red-800 border border-red-200 animate-pulse',
+ },
+ },
+ defaultVariants: {
+ risk: 'LOW',
+ },
+ }
+);
+
+const icons: Record> = {
+ LOW: CheckCircle,
+ MEDIUM: AlertTriangle,
+ HIGH: AlertOctagon,
+ CRITICAL: XCircle,
+};
+
+interface RiskBadgeProps extends VariantProps {
+ level: RiskLevel;
+ showIcon?: boolean;
+ className?: string;
+}
+
+export function RiskBadge({ level, showIcon = true, className }: RiskBadgeProps) {
+ const Icon = icons[level];
+
+ return (
+
+ {showIcon && }
+ {level}
+
+ );
+}
+```
+
+### 4.2 Stat Card
+
+```tsx
+// components/dashboard/stat-card.tsx
+import { Card, CardContent } from '@/components/ui/card';
+import { TrendingUp, TrendingDown, Minus } from 'lucide-react';
+import { cn } from '@/lib/utils';
+
+interface StatCardProps {
+ title: string;
+ value: string | number;
+ trend?: number;
+ trendLabel?: string;
+ icon?: React.ReactNode;
+ className?: string;
+}
+
+export function StatCard({
+ title,
+ value,
+ trend,
+ trendLabel,
+ icon,
+ className,
+}: StatCardProps) {
+ const getTrendIcon = () => {
+ if (!trend) return ;
+ if (trend > 0) return ;
+ return ;
+ };
+
+ const getTrendColor = () => {
+ if (!trend) return 'text-gray-500';
+ return trend > 0 ? 'text-green-600' : 'text-red-600';
+ };
+
+ return (
+
+
+
+
{title}
+ {icon &&
{icon}
}
+
+
+
{value}
+ {trend !== undefined && (
+
+ {getTrendIcon()}
+ {Math.abs(trend)}%
+
+ )}
+
+ {trendLabel && (
+ {trendLabel}
+ )}
+
+
+ );
+}
+```
+
+### 4.3 Agent Card
+
+```tsx
+// components/dashboard/agent-card.tsx
+import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Avatar, AvatarFallback } from '@/components/ui/avatar';
+import { RiskBadge } from './risk-badge';
+import { Bot, Pause, Eye } from 'lucide-react';
+import type { Agent, AgentStatistics } from '@/lib/types';
+
+interface AgentCardProps {
+ agent: Agent;
+ stats?: AgentStatistics;
+ onViewDetails?: () => void;
+ onPause?: () => void;
+}
+
+export function AgentCard({ agent, stats, onViewDetails, onPause }: AgentCardProps) {
+ const getStatusColor = () => {
+ switch (agent.status) {
+ case 'online': return 'bg-green-500';
+ case 'offline': return 'bg-gray-400';
+ case 'paused': return 'bg-yellow-500';
+ case 'review': return 'bg-red-500';
+ default: return 'bg-gray-400';
+ }
+ };
+
+ const riskLevel = stats?.average_risk
+ ? stats.average_risk <= 0 ? 'LOW'
+ : stats.average_risk <= 1 ? 'MEDIUM'
+ : stats.average_risk <= 2 ? 'HIGH'
+ : 'CRITICAL'
+ : 'LOW';
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
{agent.id}
+
{agent.name}
+
+
+
+
+ {agent.status}
+
+
+
+
+
+ {stats && (
+ <>
+
+
+
+ {stats.total_interactions}
+
+
Calls Today
+
+
+
+ {stats.average_risk.toFixed(1)}
+
+
Avg. Risk
+
+
+
{stats.stop_rate}
+
Stop Rate
+
+
+
+
+
Risk Distribution
+
+ {(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'] as const).map((level) => {
+ const count = stats.risk_distribution[level] || 0;
+ const total = Object.values(stats.risk_distribution).reduce((a, b) => a + b, 0);
+ const percentage = total > 0 ? (count / total) * 100 : 0;
+
+ return (
+
+
{level}
+
+
+ {Math.round(percentage)}%
+
+
+ );
+ })}
+
+
+ >
+ )}
+
+
+
+
+
+
+
+ );
+}
+```
+
+### 4.4 Alert Item
+
+```tsx
+// components/dashboard/alert-item.tsx
+import { Card, CardContent } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { RiskBadge } from './risk-badge';
+import { Eye, Pause, Check, Clock } from 'lucide-react';
+import { formatDistanceToNow } from 'date-fns';
+import { de } from 'date-fns/locale';
+import type { Alert } from '@/lib/types';
+import { cn } from '@/lib/utils';
+
+interface AlertItemProps {
+ alert: Alert;
+ onView?: () => void;
+ onPauseAgent?: () => void;
+ onDismiss?: () => void;
+}
+
+export function AlertItem({ alert, onView, onPauseAgent, onDismiss }: AlertItemProps) {
+ const borderColor = {
+ LOW: 'border-l-green-500',
+ MEDIUM: 'border-l-yellow-500',
+ HIGH: 'border-l-orange-500',
+ CRITICAL: 'border-l-red-500',
+ }[alert.risk_level];
+
+ const bgColor = {
+ LOW: 'bg-green-50/50',
+ MEDIUM: 'bg-yellow-50/50',
+ HIGH: 'bg-orange-50/50',
+ CRITICAL: 'bg-red-50/50',
+ }[alert.risk_level];
+
+ return (
+
+
+
+
+
+
+ {formatDistanceToNow(new Date(alert.timestamp), {
+ addSuffix: true,
+ locale: de,
+ })}
+
+
+
+
+
+ {alert.agent_id}
+ {alert.contact_name && (
+ - {alert.contact_name}
+ )}
+
+
+
+ {alert.violations.length > 0 && (
+
+ {alert.violations.map((violation, i) => (
+ -
+ •
+ {violation}
+
+ ))}
+
+ )}
+
+ {alert.transcript_snippet && (
+
+ "{alert.transcript_snippet}"
+
+ )}
+
+
+
+ {alert.risk_level === 'CRITICAL' && (
+
+ )}
+
+
+
+
+ );
+}
+```
+
+### 4.5 Performance Chart
+
+```tsx
+// components/dashboard/performance-chart.tsx
+'use client';
+
+import {
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+} from 'recharts';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+
+interface DataPoint {
+ date: string;
+ avgRisk: number;
+ criticalIncidents: number;
+ stopRate: number;
+}
+
+interface PerformanceChartProps {
+ data: DataPoint[];
+ title?: string;
+}
+
+export function PerformanceChart({
+ data,
+ title = 'Performance Trend',
+}: PerformanceChartProps) {
+ return (
+
+
+ {title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+```
+
+---
+
+## 5. LAYOUT-KOMPONENTEN
+
+### 5.1 Sidebar
+
+```tsx
+// components/layout/sidebar.tsx
+'use client';
+
+import Link from 'next/link';
+import { usePathname } from 'next/navigation';
+import {
+ LayoutDashboard,
+ Bot,
+ Bell,
+ BarChart3,
+ Settings,
+ ChevronLeft,
+} from 'lucide-react';
+import { cn } from '@/lib/utils';
+import { Button } from '@/components/ui/button';
+import { useState } from 'react';
+
+const navigation = [
+ { name: 'Dashboard', href: '/', icon: LayoutDashboard },
+ { name: 'Agents', href: '/agents', icon: Bot },
+ { name: 'Alerts', href: '/alerts', icon: Bell },
+ { name: 'Reports', href: '/reports', icon: BarChart3 },
+ { name: 'Settings', href: '/settings', icon: Settings },
+];
+
+export function Sidebar() {
+ const pathname = usePathname();
+ const [collapsed, setCollapsed] = useState(false);
+
+ return (
+
+ );
+}
+```
+
+### 5.2 Header
+
+```tsx
+// components/layout/header.tsx
+'use client';
+
+import { Bell, User, Search } from 'lucide-react';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import { Avatar, AvatarFallback } from '@/components/ui/avatar';
+import { useAlertStore } from '@/stores/alert-store';
+
+export function Header() {
+ const unreadCount = useAlertStore((state) => state.unreadCount);
+
+ return (
+
+ );
+}
+```
+
+---
+
+## 6. HOOKS & STATE
+
+### 6.1 Alert Store (Zustand)
+
+```typescript
+// stores/alert-store.ts
+import { create } from 'zustand';
+import type { Alert } from '@/lib/types';
+
+interface AlertState {
+ alerts: Alert[];
+ unreadCount: number;
+ addAlert: (alert: Alert) => void;
+ markAsRead: (id: string) => void;
+ markAllAsRead: () => void;
+ dismissAlert: (id: string) => void;
+}
+
+export const useAlertStore = create((set) => ({
+ alerts: [],
+ unreadCount: 0,
+
+ addAlert: (alert) =>
+ set((state) => ({
+ alerts: [alert, ...state.alerts],
+ unreadCount: state.unreadCount + 1,
+ })),
+
+ markAsRead: (id) =>
+ set((state) => ({
+ alerts: state.alerts.map((a) =>
+ a.id === id ? { ...a, read: true } : a
+ ),
+ unreadCount: Math.max(0, state.unreadCount - 1),
+ })),
+
+ markAllAsRead: () =>
+ set((state) => ({
+ alerts: state.alerts.map((a) => ({ ...a, read: true })),
+ unreadCount: 0,
+ })),
+
+ dismissAlert: (id) =>
+ set((state) => ({
+ alerts: state.alerts.filter((a) => a.id !== id),
+ unreadCount: state.alerts.find((a) => a.id === id && !a.read)
+ ? state.unreadCount - 1
+ : state.unreadCount,
+ })),
+}));
+```
+
+### 6.2 useAgents Hook
+
+```typescript
+// hooks/use-agents.ts
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { api } from '@/lib/api';
+import type { Agent, AgentStatistics } from '@/lib/types';
+
+export function useAgents() {
+ return useQuery({
+ queryKey: ['agents'],
+ queryFn: () => api.get('/api/agents'),
+ refetchInterval: 30000, // Refresh every 30s
+ });
+}
+
+export function useAgentStats(agentId: string) {
+ return useQuery({
+ queryKey: ['agent-stats', agentId],
+ queryFn: () => api.get(`/api/agents/${agentId}/stats`),
+ enabled: !!agentId,
+ });
+}
+
+export function usePauseAgent() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (agentId: string) =>
+ api.post(`/api/agents/${agentId}/pause`),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['agents'] });
+ },
+ });
+}
+```
+
+### 6.3 useWebSocket Hook
+
+```typescript
+// hooks/use-websocket.ts
+import { useEffect, useRef, useCallback } from 'react';
+import { useAlertStore } from '@/stores/alert-store';
+import type { Alert } from '@/lib/types';
+
+interface WebSocketMessage {
+ type: 'alert.new' | 'agent.status' | 'score.update';
+ payload: any;
+}
+
+export function useWebSocket(url: string) {
+ const wsRef = useRef(null);
+ const addAlert = useAlertStore((state) => state.addAlert);
+
+ const connect = useCallback(() => {
+ const ws = new WebSocket(url);
+
+ ws.onopen = () => {
+ console.log('WebSocket connected');
+ };
+
+ ws.onmessage = (event) => {
+ const message: WebSocketMessage = JSON.parse(event.data);
+
+ switch (message.type) {
+ case 'alert.new':
+ addAlert(message.payload as Alert);
+ // Show toast notification
+ if (message.payload.risk_level === 'CRITICAL') {
+ // Trigger toast
+ }
+ break;
+ case 'agent.status':
+ // Update agent status in query cache
+ break;
+ case 'score.update':
+ // Update dashboard stats
+ break;
+ }
+ };
+
+ ws.onclose = () => {
+ console.log('WebSocket disconnected, reconnecting...');
+ setTimeout(connect, 3000);
+ };
+
+ wsRef.current = ws;
+ }, [url, addAlert]);
+
+ useEffect(() => {
+ connect();
+ return () => {
+ wsRef.current?.close();
+ };
+ }, [connect]);
+
+ return wsRef.current;
+}
+```
+
+---
+
+## 7. API-CLIENT
+
+```typescript
+// lib/api.ts
+const BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
+
+class ApiClient {
+ private baseUrl: string;
+
+ constructor(baseUrl: string) {
+ this.baseUrl = baseUrl;
+ }
+
+ private async request(
+ endpoint: string,
+ options: RequestInit = {}
+ ): Promise {
+ const url = `${this.baseUrl}${endpoint}`;
+
+ const response = await fetch(url, {
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers,
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error(`API Error: ${response.status}`);
+ }
+
+ return response.json();
+ }
+
+ get(endpoint: string): Promise {
+ return this.request(endpoint);
+ }
+
+ post(endpoint: string, data?: any): Promise {
+ return this.request(endpoint, {
+ method: 'POST',
+ body: data ? JSON.stringify(data) : undefined,
+ });
+ }
+
+ put(endpoint: string, data: any): Promise {
+ return this.request(endpoint, {
+ method: 'PUT',
+ body: JSON.stringify(data),
+ });
+ }
+
+ delete(endpoint: string): Promise {
+ return this.request(endpoint, { method: 'DELETE' });
+ }
+}
+
+export const api = new ApiClient(BASE_URL);
+```
+
+---
+
+## 8. TAILWIND KONFIGURATION
+
+```typescript
+// tailwind.config.ts
+import type { Config } from 'tailwindcss';
+
+const config: Config = {
+ darkMode: ['class'],
+ content: [
+ './pages/**/*.{ts,tsx}',
+ './components/**/*.{ts,tsx}',
+ './app/**/*.{ts,tsx}',
+ ],
+ theme: {
+ extend: {
+ colors: {
+ primary: {
+ 50: '#EEF2FF',
+ 100: '#E0E7FF',
+ 200: '#C7D2FE',
+ 300: '#A5B4FC',
+ 400: '#818CF8',
+ 500: '#6366F1',
+ 600: '#4F46E5',
+ 700: '#4338CA',
+ 800: '#3730A3',
+ 900: '#312E81',
+ },
+ risk: {
+ low: '#10B981',
+ medium: '#F59E0B',
+ high: '#F97316',
+ critical: '#EF4444',
+ },
+ },
+ animation: {
+ 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
+ },
+ },
+ },
+ plugins: [require('tailwindcss-animate')],
+};
+
+export default config;
+```
+
+---
+
+## 9. NÄCHSTE SCHRITTE
+
+1. **Phase 1**: Projekt-Setup und Design-System
+2. **Phase 2**: Basis-Komponenten implementieren
+3. **Phase 3**: Layout und Navigation
+4. **Phase 4**: Dashboard-Seite
+5. **Phase 5**: Agent-Management
+6. **Phase 6**: Alert-System mit WebSocket
+7. **Phase 7**: Reports und Export
+8. **Phase 8**: Testing und Optimierung
+
+---
+
+**Dokument-Version:** 1.0
+**Erstellt:** 23.12.2025
diff --git a/docs/architecture/decision-flow.md b/docs/architecture/decision-flow.md
new file mode 100644
index 0000000..9c4c0f6
--- /dev/null
+++ b/docs/architecture/decision-flow.md
@@ -0,0 +1,157 @@
+# Decision Flow
+
+## STOP Score Calculation
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ INPUT: Work Submission │
+│ - Description │
+│ - Claims │
+│ - Artefacts │
+│ - Test status │
+│ - Schema status │
+│ - Deploy config status │
+└─────────────────────────────┬───────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ REASON DETECTION │
+│ │
+│ PRICING_WITHOUT_FACT → +40 points │
+│ LEGAL_STATEMENT → +40 points │
+│ UNPROVEN_CLAIM → +30 points │
+│ CROSS_LAYER_MISMATCH → +25 points │
+│ MISSING_SQL_OR_SCHEMA → +25 points │
+│ COST_OR_LOAD_RISK → +20 points │
+│ MISSING_TESTS → +15 points │
+│ MISSING_DEPLOY_CONFIG → +15 points │
+│ │
+│ Maximum Score: 100 │
+└─────────────────────────────┬───────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ SEVERITY MAPPING │
+│ │
+│ 0-19: LOW │
+│ 20-44: MEDIUM │
+│ 45-69: HIGH │
+│ 70-100: CRITICAL │
+│ │
+│ STOP Required: score >= 40 │
+└─────────────────────────────┬───────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ FINAL DECISION │
+│ │
+│ score < 40 → APPROVED │
+│ score >= 40 → STOP_REQUIRED │
+│ │
+│ Final Status: │
+│ - COMPLETE: No issues │
+│ - COMPLETE_WITH_GAPS: Issues but not blocking │
+│ - STOP_REQUIRED: Must address before proceeding │
+└─────────────────────────────────────────────────────────────┘
+```
+
+## Decision Examples
+
+### Example 1: Clean Submission
+```
+Input:
+ - Description: "Added user login endpoint"
+ - Artefacts: [src/api/auth.ts, tests/auth.test.ts]
+ - hasTests: true
+ - hasSchema: true
+ - hasDeployConfig: true
+
+Detected Reasons: []
+Score: 0
+Severity: LOW
+
+Decision: APPROVED
+Final Status: COMPLETE
+```
+
+### Example 2: Missing Tests
+```
+Input:
+ - Description: "Added payment processing"
+ - Artefacts: [src/api/payments.ts]
+ - hasTests: false
+ - hasSchema: true
+ - hasDeployConfig: false
+
+Detected Reasons: [MISSING_TESTS, MISSING_DEPLOY_CONFIG]
+Score: 30
+Severity: MEDIUM
+
+Decision: APPROVED
+Final Status: COMPLETE_WITH_GAPS
+Required Action: Add missing: Tests, Deploy config
+```
+
+### Example 3: Pricing Claim
+```
+Input:
+ - Description: "Set price to $99/month for premium tier"
+ - Artefacts: [src/pricing.ts]
+ - hasTests: true
+ - hasSchema: true
+ - hasDeployConfig: true
+
+Detected Reasons: [PRICING_WITHOUT_FACT]
+Score: 40
+Severity: HIGH
+
+Decision: STOP_REQUIRED
+Final Status: STOP_REQUIRED
+Required Action: STOP: Address PRICING_WITHOUT_FACT before proceeding
+```
+
+### Example 4: Multiple Issues
+```
+Input:
+ - Description: "Deployed the API with legal disclaimer"
+ - Artefacts: []
+ - hasTests: false
+ - hasSchema: false
+ - hasDeployConfig: false
+
+Detected Reasons: [
+ UNPROVEN_CLAIM (deployed),
+ LEGAL_STATEMENT,
+ MISSING_TESTS,
+ MISSING_SQL_OR_SCHEMA,
+ MISSING_DEPLOY_CONFIG
+]
+Score: 100 (capped)
+Severity: CRITICAL
+
+Decision: STOP_REQUIRED
+Final Status: STOP_REQUIRED
+Required Action: STOP: Address all issues before proceeding
+```
+
+## Escalation Path
+
+```
+Cloud Assistant
+ │
+ │ submits work
+ ▼
+Engineering Lead Supervisor
+ │
+ │ STOP_REQUIRED?
+ ├──────────────────┐
+ │ Yes │ No
+ ▼ ▼
+Meta Supervisor Continue
+ │
+ │ Cross-system?
+ ├──────────────────┐
+ │ Yes │ No
+ ▼ ▼
+Human Review Log & Monitor
+```
diff --git a/docs/architecture/system-overview.md b/docs/architecture/system-overview.md
new file mode 100644
index 0000000..ef02084
--- /dev/null
+++ b/docs/architecture/system-overview.md
@@ -0,0 +1,139 @@
+# System Architecture Overview
+
+## Core Components
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ META SUPERVISOR │
+│ (Routing + Monitoring + Aggregation) │
+└─────────────────────────────┬───────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────────┐
+│ ENGINEERING LEAD SUPERVISOR │
+│ (Plan + Delegate + Verify + STOP) │
+│ │
+│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌───────────┐ │
+│ │ STOP │ │ Artefact │ │ Cross- │ │ Risk │ │
+│ │ Scorer │ │ Verifier │ │ Layer │ │ Assessor │ │
+│ │ │ │ │ │ Check │ │ │ │
+│ └────────────┘ └────────────┘ └────────────┘ └───────────┘ │
+└─────────────────────────────┬───────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────────┐
+│ CLOUD ASSISTANT │
+│ (Execute + Report + Evidence) │
+│ │
+│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌───────────┐ │
+│ │ Code │ │ Config │ │ Test │ │ Evidence │ │
+│ │ Writer │ │ Manager │ │ Runner │ │ Collector │ │
+│ │ │ │ │ │ │ │ │ │
+│ └────────────┘ └────────────┘ └────────────┘ └───────────┘ │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+## Data Flow
+
+1. **Task Submission** → API receives task
+2. **Routing** → Meta Supervisor routes to appropriate system
+3. **Planning** → Engineering Lead plans execution
+4. **Execution** → Cloud Assistant executes with evidence
+5. **Verification** → Engineering Lead verifies artefacts
+6. **Decision** → APPROVED or STOP_REQUIRED
+7. **Audit** → Decision logged for review
+
+## Key Principles
+
+### 1. Evidence-Based Verification
+- No claim accepted without proof
+- Every artefact must be verifiable
+- File paths, test outputs, configs required
+
+### 2. STOP is Success
+- When truth or safety at risk, STOP is correct
+- STOP decisions are never overridden by lower layers
+- Escalation path: Assistant → Lead → Meta → Human
+
+### 3. Cross-Layer Consistency
+- Frontend ↔ Backend alignment required
+- Backend ↔ Database schema match
+- Claims ↔ Artefacts correspondence
+
+## Component Details
+
+### Meta Supervisor
+- Routes tasks to systems
+- Monitors health metrics
+- Aggregates costs and outcomes
+- Does NOT make business decisions
+
+### Engineering Lead Supervisor
+- Plans implementation approach
+- Delegates to Cloud Assistant
+- Verifies completed work
+- Makes APPROVE/STOP decisions
+
+### Cloud Assistant
+- Executes code changes
+- Collects evidence
+- Reports status honestly
+- Never declares "done" without proof
+
+## Integration Points
+
+All integrations are **STUBS** until explicitly approved:
+
+| Integration | Status | Notes |
+|-------------|--------|-------|
+| WhatsApp | STUB | Requires approval |
+| Voice | STUB | Requires approval |
+| Google | STUB | OAuth not configured |
+| iCloud | STUB | Requires approval |
+| Pinecone | STUB | Vector DB not connected |
+
+## Database Schema
+
+```sql
+-- Tasks table
+CREATE TABLE tasks (
+ id TEXT PRIMARY KEY,
+ title TEXT NOT NULL,
+ description TEXT,
+ priority TEXT DEFAULT 'medium',
+ status TEXT DEFAULT 'pending',
+ assignee TEXT NOT NULL,
+ created_at TEXT NOT NULL,
+ updated_at TEXT,
+ stop_score INTEGER
+);
+
+-- Audit entries table
+CREATE TABLE audit_entries (
+ id TEXT PRIMARY KEY,
+ task_id TEXT,
+ decision TEXT NOT NULL,
+ final_status TEXT NOT NULL,
+ risk_level TEXT NOT NULL,
+ stop_score INTEGER NOT NULL,
+ verified_artefacts TEXT NOT NULL,
+ missing_invalid_parts TEXT NOT NULL,
+ required_next_action TEXT NOT NULL,
+ created_at TEXT NOT NULL
+);
+```
+
+## API Endpoints
+
+| Endpoint | Method | Description |
+|----------|--------|-------------|
+| `/` | GET | Service info |
+| `/health` | GET | Health check |
+| `/health/ready` | GET | Readiness probe |
+| `/health/live` | GET | Liveness probe |
+| `/api/tasks` | GET | List tasks |
+| `/api/tasks` | POST | Create task |
+| `/api/tasks/:id` | GET | Get task |
+| `/api/audit` | GET | List audit entries |
+| `/api/audit/:id` | GET | Get audit entry |
+| `/api/audit/stats/stop-scores` | GET | Stop score statistics |
diff --git a/docs/assistants/cloud_assistant.system.md b/docs/assistants/cloud_assistant.system.md
new file mode 100644
index 0000000..256e94e
--- /dev/null
+++ b/docs/assistants/cloud_assistant.system.md
@@ -0,0 +1,50 @@
+ROLE: CLOUD_ASSISTANT (SUPERVISED)
+AUTHORITY: SUBORDINATE TO ENGINEERING_LEAD_SUPERVISOR
+MODE: EXECUTE + REPORT + EVIDENCE
+
+You execute engineering tasks (code, files, configs, integrations) under supervision.
+You NEVER declare "done" without artefacts.
+You ALWAYS provide evidence: file paths, snippets, commands, test output references.
+
+## Rules
+
+1. **No hallucinations** - If you cannot verify, state `UNKNOWN` and request supervisor decision
+2. **No pricing/legal statements** - Treat as `UNKNOWN`
+3. **Evidence required for claims** - e.g., "scraped 10 portals" must include exact list and code paths
+4. **Always include**:
+ - What changed
+ - Where (file paths)
+ - How to run
+ - How to test
+ - Known gaps
+
+## Evidence Types
+
+- File paths with line numbers
+- Code snippets
+- Command outputs
+- Test results
+- Configuration values
+- Schema definitions
+
+## Output Format
+
+```
+Status Proposal: COMPLETE | COMPLETE_WITH_GAPS | STOP_REQUIRED
+Evidence:
+ - [artefact 1]
+ - [artefact 2]
+Risks/Gaps:
+ - [risk 1]
+ - [gap 1]
+Next Steps:
+ - [step 1]
+ - [step 2]
+```
+
+## Interaction with Supervisor
+
+- Submit work with evidence
+- Accept STOP decisions without argument
+- Request clarification when requirements are ambiguous
+- Report blockers immediately
diff --git a/docs/facts/company.md b/docs/facts/company.md
new file mode 100644
index 0000000..b2c61c1
--- /dev/null
+++ b/docs/facts/company.md
@@ -0,0 +1,6 @@
+# Company FACTS
+
+- Name: UNKNOWN
+- Business type: UNKNOWN
+- Target customers: UNKNOWN
+- Languages: German, English, Bosnian, Serbian (planned)
diff --git a/docs/facts/countries.md b/docs/facts/countries.md
new file mode 100644
index 0000000..4413998
--- /dev/null
+++ b/docs/facts/countries.md
@@ -0,0 +1,3 @@
+# Countries FACTS
+
+- Primary country: UNKNOWN
diff --git a/docs/facts/pricing.md b/docs/facts/pricing.md
new file mode 100644
index 0000000..bb90a68
--- /dev/null
+++ b/docs/facts/pricing.md
@@ -0,0 +1,3 @@
+# Pricing FACTS
+
+- Pricing: UNKNOWN (requires explicit approval)
diff --git a/docs/facts/products.md b/docs/facts/products.md
new file mode 100644
index 0000000..4d04921
--- /dev/null
+++ b/docs/facts/products.md
@@ -0,0 +1,3 @@
+# Products FACTS
+
+- Offered services: Cloud-based supervised AI system with an Engineering Lead Supervisor and a Cloud Assistant for controlled multi-agent workflows.
diff --git a/docs/policies/default-policy.yaml b/docs/policies/default-policy.yaml
new file mode 100644
index 0000000..5d7f442
--- /dev/null
+++ b/docs/policies/default-policy.yaml
@@ -0,0 +1,24 @@
+policy_id: default
+version: "1.0.0"
+
+rules:
+ pricing:
+ allowed: false
+ on_violation: STOP_REQUIRED
+
+ legal:
+ allowed: false
+ on_violation: STOP_REQUIRED
+
+ arena_parallel:
+ allowed: true
+ max_parallel_agents: 4
+
+ integrations_live:
+ allowed: false
+ comment: "All integrations are stubs until explicitly enabled"
+
+enforcement:
+ on_violation: STOP_REQUIRED
+ escalation: human_review
+ logging: required
diff --git a/docs/runbooks/live-runbook.md b/docs/runbooks/live-runbook.md
new file mode 100644
index 0000000..06488b9
--- /dev/null
+++ b/docs/runbooks/live-runbook.md
@@ -0,0 +1,58 @@
+# Live Runbook (Incident / Failure)
+
+## Golden Rule
+
+**STOP is success when safety or truth is at risk.**
+
+## Triggers → Actions
+
+| Trigger | Action |
+|---------|--------|
+| Pricing/legal mentioned | STOP_REQUIRED, escalate to human |
+| Claims without proof | STOP_REQUIRED, demand artefacts |
+| Queue overload / degraded health | Switch to DEGRADED mode, throttle tasks |
+| Cost spike | Disable parallelization, enforce budgets |
+| Integration failure | Fallback to stub, log error, continue |
+| Database unavailable | Switch to in-memory mode, alert |
+
+## Evidence Policy
+
+**No evidence = not done.**
+
+All completed work must have:
+- File paths
+- Test results
+- Command outputs
+- Configuration proof
+
+## Escalation Path
+
+1. Assistant → Engineering Lead Supervisor
+2. Engineering Lead → Meta Supervisor
+3. Meta Supervisor → Human Review
+
+## Recovery Procedures
+
+### Queue Recovery
+```bash
+# Check queue status
+npm run queue:status
+
+# Clear stuck jobs
+npm run queue:clear-failed
+
+# Restart workers
+npm run queue:restart
+```
+
+### Database Recovery
+```bash
+# Check DB health
+npm run db:health
+
+# Run migrations
+npm run db:migrate
+
+# Backup before recovery
+npm run db:backup
+```
diff --git a/docs/runbooks/production-launch-checklist.md b/docs/runbooks/production-launch-checklist.md
new file mode 100644
index 0000000..b81c8c4
--- /dev/null
+++ b/docs/runbooks/production-launch-checklist.md
@@ -0,0 +1,40 @@
+# Production Launch Checklist
+
+## Before Go-Live
+
+- [ ] Supervisor prompts committed and versioned
+- [ ] Facts reviewed (pricing remains UNKNOWN)
+- [ ] Policy active and enforced
+- [ ] Logging enabled (structured JSON)
+- [ ] STOP scoring enabled
+- [ ] Basic API health endpoint works
+- [ ] DB migrations in place
+- [ ] Queue adapter works (in-memory fallback ok)
+- [ ] All integration stubs return graceful errors
+- [ ] Environment variables documented
+
+## First Live Run
+
+- [ ] 1–5 tasks only
+- [ ] No live messaging/voice integrations
+- [ ] Review logs + stop scores
+- [ ] Verify evidence collection
+- [ ] Check error handling paths
+- [ ] Monitor memory/CPU usage
+
+## Scaling Phase
+
+- [ ] KPI review (completion rate, stop rate)
+- [ ] Cost review (API calls, compute)
+- [ ] Supervisor approval for increased load
+- [ ] Enable parallel processing (max 4 agents)
+- [ ] Set up alerting thresholds
+
+## Integration Enablement
+
+For each integration:
+- [ ] Security review completed
+- [ ] Credentials rotated from dev
+- [ ] Rate limits configured
+- [ ] Fallback behavior tested
+- [ ] Monitoring in place
diff --git a/docs/supervisor/engineering_lead_supervisor.system.md b/docs/supervisor/engineering_lead_supervisor.system.md
new file mode 100644
index 0000000..aee4f72
--- /dev/null
+++ b/docs/supervisor/engineering_lead_supervisor.system.md
@@ -0,0 +1,46 @@
+ROLE: ENGINEERING_LEAD_SUPERVISOR
+AUTHORITY: ABSOLUTE
+MODE: EXECUTION + CONTROL + AUDIT
+
+You are the leading Engineering Supervisor (Tech Lead + Architect + QA + Auditor).
+You do NOT primarily write code. You PLAN, DELEGATE, VERIFY, and STOP.
+
+## Core Laws
+
+- No work accepted without verifiable artefacts (files, schemas, tests, configs)
+- No statements trusted without proof
+- No layer may be skipped: UI/UX, Frontend, Backend/API, Data/DB, Tests, Deploy, Docs
+- Cross-layer consistency required (FE↔BE, BE↔DB, claims↔artefacts)
+- If unclear/missing/inconsistent/unproven → STOP_REQUIRED
+
+## Forbidden Acceptance
+
+Never accept claims like:
+- "implemented"
+- "connected"
+- "scraped 10 portals"
+- "deployed"
+
+...without verifiable evidence.
+
+## Result States
+
+Exactly one of:
+- `COMPLETE`
+- `COMPLETE_WITH_GAPS`
+- `STOP_REQUIRED`
+
+## Output Format
+
+```
+Decision: APPROVED | STOP_REQUIRED
+Final Status: COMPLETE | COMPLETE_WITH_GAPS | STOP_REQUIRED
+Risk Level: LOW | MEDIUM | HIGH | CRITICAL
+Verified Artefacts: [list]
+Missing/Invalid Parts: [list]
+Required Next Action: [explicit instruction]
+```
+
+## Final Law
+
+**If work cannot be proven → it does not exist.**
diff --git a/docs/supervisor/meta_supervisor.system.md b/docs/supervisor/meta_supervisor.system.md
new file mode 100644
index 0000000..a3c96f6
--- /dev/null
+++ b/docs/supervisor/meta_supervisor.system.md
@@ -0,0 +1,39 @@
+ROLE: META_SUPERVISOR
+AUTHORITY: ROUTING + MONITORING ONLY
+
+You route tasks to the correct supervisor repo/system.
+You do not make business decisions and do not inject facts across boundaries.
+You never override STOP decisions.
+You aggregate health, cost, stop scores, and task outcomes.
+
+## Responsibilities
+
+1. **Routing**: Direct tasks to appropriate supervisor/system
+2. **Monitoring**: Track health, costs, and outcomes
+3. **Aggregation**: Collect metrics across systems
+4. **Escalation**: Forward STOP decisions without override
+
+## Boundaries
+
+- NO business logic decisions
+- NO fact injection across system boundaries
+- NO overriding of STOP_REQUIRED decisions
+- NO direct task execution
+
+## Output Format
+
+```
+Route Decision: [target_system]
+Reason: [why this target]
+Constraints: [any limits or conditions]
+Monitoring Notes: [health/cost/status observations]
+```
+
+## Health Aggregation
+
+Track and report:
+- Task completion rates
+- STOP frequency and reasons
+- Cost accumulation
+- Queue depths
+- Error rates
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..684c6af
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1961 @@
+{
+ "name": "code-cloud-agents",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "code-cloud-agents",
+ "version": "0.1.0",
+ "dependencies": {
+ "better-sqlite3": "^11.0.0",
+ "express": "^4.19.2",
+ "zod": "^3.23.8"
+ },
+ "devDependencies": {
+ "@types/better-sqlite3": "^7.6.11",
+ "@types/express": "^4.17.21",
+ "@types/node": "^20.12.12",
+ "tsx": "^4.16.2",
+ "typescript": "^5.5.4"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
+ "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
+ "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
+ "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
+ "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
+ "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
+ "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
+ "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
+ "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
+ "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
+ "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
+ "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
+ "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
+ "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
+ "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
+ "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
+ "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
+ "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
+ "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
+ "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
+ "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
+ "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@types/better-sqlite3": {
+ "version": "7.6.13",
+ "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
+ "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.6",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/express": {
+ "version": "4.17.25",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
+ "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "^1"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "4.19.7",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz",
+ "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/http-errors": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.27",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz",
+ "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "1.15.10",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz",
+ "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "<1"
+ }
+ },
+ "node_modules/@types/serve-static/node_modules/@types/send": {
+ "version": "0.17.6",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
+ "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/better-sqlite3": {
+ "version": "11.10.0",
+ "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
+ "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "bindings": "^1.5.0",
+ "prebuild-install": "^7.1.1"
+ }
+ },
+ "node_modules/bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.4",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+ "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.14.0",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "license": "ISC"
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
+ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.2",
+ "@esbuild/android-arm": "0.27.2",
+ "@esbuild/android-arm64": "0.27.2",
+ "@esbuild/android-x64": "0.27.2",
+ "@esbuild/darwin-arm64": "0.27.2",
+ "@esbuild/darwin-x64": "0.27.2",
+ "@esbuild/freebsd-arm64": "0.27.2",
+ "@esbuild/freebsd-x64": "0.27.2",
+ "@esbuild/linux-arm": "0.27.2",
+ "@esbuild/linux-arm64": "0.27.2",
+ "@esbuild/linux-ia32": "0.27.2",
+ "@esbuild/linux-loong64": "0.27.2",
+ "@esbuild/linux-mips64el": "0.27.2",
+ "@esbuild/linux-ppc64": "0.27.2",
+ "@esbuild/linux-riscv64": "0.27.2",
+ "@esbuild/linux-s390x": "0.27.2",
+ "@esbuild/linux-x64": "0.27.2",
+ "@esbuild/netbsd-arm64": "0.27.2",
+ "@esbuild/netbsd-x64": "0.27.2",
+ "@esbuild/openbsd-arm64": "0.27.2",
+ "@esbuild/openbsd-x64": "0.27.2",
+ "@esbuild/openharmony-arm64": "0.27.2",
+ "@esbuild/sunos-x64": "0.27.2",
+ "@esbuild/win32-arm64": "0.27.2",
+ "@esbuild/win32-ia32": "0.27.2",
+ "@esbuild/win32-x64": "0.27.2"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/expand-template": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+ "license": "(MIT OR WTFPL)",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "~0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "~6.14.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "~2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "license": "MIT"
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "license": "MIT"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
+ "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/github-from-package": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+ "license": "MIT"
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp-classic": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/napi-build-utils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
+ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-abi": {
+ "version": "3.85.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz",
+ "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==",
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
+ },
+ "node_modules/prebuild-install": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
+ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "expand-template": "^2.0.3",
+ "github-from-package": "0.0.0",
+ "minimist": "^1.2.3",
+ "mkdirp-classic": "^0.5.3",
+ "napi-build-utils": "^2.0.0",
+ "node-abi": "^3.3.0",
+ "pump": "^3.0.0",
+ "rc": "^1.2.7",
+ "simple-get": "^4.0.0",
+ "tar-fs": "^2.0.0",
+ "tunnel-agent": "^0.6.0"
+ },
+ "bin": {
+ "prebuild-install": "bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/pump": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
+ "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/simple-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/simple-get": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
+ "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decompress-response": "^6.0.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tar-fs": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
+ "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "chownr": "^1.1.1",
+ "mkdirp-classic": "^0.5.2",
+ "pump": "^3.0.0",
+ "tar-stream": "^2.1.4"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..5fd189d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "code-cloud-agents",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "description": "Supervised AI system with Engineering Lead Supervisor and Cloud Assistant",
+ "scripts": {
+ "dev": "tsx watch src/index.ts",
+ "build": "tsc -p tsconfig.json",
+ "start": "node dist/index.js",
+ "test": "node --test tests/**/*.test.ts",
+ "db:migrate": "tsx src/db/migrate.ts",
+ "db:health": "tsx src/db/health.ts",
+ "queue:status": "tsx src/queue/status.ts",
+ "lint": "echo 'No linter configured yet'"
+ },
+ "dependencies": {
+ "better-sqlite3": "^11.0.0",
+ "express": "^4.19.2",
+ "zod": "^3.23.8"
+ },
+ "devDependencies": {
+ "@types/better-sqlite3": "^7.6.11",
+ "@types/express": "^4.17.21",
+ "@types/node": "^20.12.12",
+ "tsx": "^4.16.2",
+ "typescript": "^5.5.4"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+}
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..e906f7f
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,825 @@
+
+
+
+
+
+ Code Cloud Agents - Supervisor Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Version: 0.1.0
+
Supervisor: ENGINEERING_LEAD_SUPERVISOR
+
Mode: SUPERVISED
+
Stop Threshold: 40
+
+
+
+
+
+
+
+ | PRICING_WITHOUT_FACT | +40 |
+ | LEGAL_STATEMENT | +40 |
+ | UNPROVEN_CLAIM | +30 |
+ | CROSS_LAYER_MISMATCH | +25 |
+ | MISSING_SQL_OR_SCHEMA | +25 |
+ | COST_OR_LOAD_RISK | +20 |
+ | MISSING_TESTS | +15 |
+ | MISSING_DEPLOY_CONFIG | +15 |
+
+
+ Score >= 40 = STOP_REQUIRED (Task wird blockiert)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Task:
+
+
+
+
+
+
+
+
+
Task:
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/api/audit.ts b/src/api/audit.ts
new file mode 100644
index 0000000..2a2e075
--- /dev/null
+++ b/src/api/audit.ts
@@ -0,0 +1,51 @@
+/**
+ * Audit Router - View audit logs and supervisor decisions
+ */
+
+import { Router } from "express";
+import type { Database } from "../db/database.js";
+
+export function createAuditRouter(db: Database): Router {
+ const router = Router();
+
+ // List audit entries
+ router.get("/", (req, res) => {
+ try {
+ const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);
+ const entries = db.listAuditEntries(limit);
+ res.json({ entries, count: entries.length });
+ } catch (error) {
+ console.error("Failed to list audit entries:", error);
+ res.status(500).json({ error: "Internal server error" });
+ }
+ });
+
+ // Get audit entry by ID
+ router.get("/:id", (req, res) => {
+ try {
+ const entry = db.getAuditEntry(req.params.id);
+
+ if (!entry) {
+ return res.status(404).json({ error: "Audit entry not found" });
+ }
+
+ res.json(entry);
+ } catch (error) {
+ console.error("Failed to get audit entry:", error);
+ res.status(500).json({ error: "Internal server error" });
+ }
+ });
+
+ // Get stop score statistics
+ router.get("/stats/stop-scores", (_req, res) => {
+ try {
+ const stats = db.getStopScoreStats();
+ res.json(stats);
+ } catch (error) {
+ console.error("Failed to get stop score stats:", error);
+ res.status(500).json({ error: "Internal server error" });
+ }
+ });
+
+ return router;
+}
diff --git a/src/api/enforcement.ts b/src/api/enforcement.ts
new file mode 100644
index 0000000..2a29d09
--- /dev/null
+++ b/src/api/enforcement.ts
@@ -0,0 +1,166 @@
+/**
+ * Enforcement Router - Human approval endpoints for blocked tasks
+ *
+ * These endpoints require human intervention.
+ * No automation can approve a STOP_REQUIRED task.
+ */
+
+import { Router } from "express";
+import { z } from "zod";
+import type { EnforcementGate } from "../audit/enforcementGate.js";
+import { verifyClaims, generateVerificationReport } from "../audit/claimVerifier.js";
+
+const ApproveSchema = z.object({
+ approver: z.string().min(1, "Approver identity required"),
+ reason: z.string().min(10, "Reason must be at least 10 characters"),
+ acknowledged_risks: z.array(z.string()).min(1, "Must acknowledge at least one risk"),
+});
+
+const RejectSchema = z.object({
+ rejector: z.string().min(1, "Rejector identity required"),
+ reason: z.string().min(10, "Reason must be at least 10 characters"),
+});
+
+const VerifyClaimsSchema = z.object({
+ content: z.string().min(1),
+ artefacts: z.array(z.string()),
+});
+
+export function createEnforcementRouter(gate: EnforcementGate): Router {
+ const router = Router();
+
+ /**
+ * GET /enforcement/blocked
+ * List all currently blocked tasks awaiting human approval
+ */
+ router.get("/blocked", (_req, res) => {
+ const pending = gate.getAllPendingApprovals();
+ res.json({
+ blocked_count: pending.length,
+ tasks: pending,
+ message: pending.length > 0
+ ? "⚠️ Tasks blocked. Human approval required."
+ : "✅ No blocked tasks.",
+ });
+ });
+
+ /**
+ * GET /enforcement/blocked/:taskId
+ * Get details of a specific blocked task
+ */
+ router.get("/blocked/:taskId", (req, res) => {
+ const pending = gate.getPendingApproval(req.params.taskId);
+
+ if (!pending) {
+ return res.status(404).json({
+ error: "Task not found or not blocked",
+ });
+ }
+
+ res.json({
+ ...pending,
+ message: "⚠️ This task is BLOCKED. Human approval required to proceed.",
+ required_fields: {
+ approver: "Your identity (email/name)",
+ reason: "Why you are approving despite STOP (min 10 chars)",
+ acknowledged_risks: "List of risks you are accepting",
+ },
+ });
+ });
+
+ /**
+ * POST /enforcement/approve/:taskId
+ * Approve a blocked task - REQUIRES human action
+ */
+ router.post("/approve/:taskId", (req, res) => {
+ try {
+ const parsed = ApproveSchema.safeParse(req.body);
+
+ if (!parsed.success) {
+ return res.status(400).json({
+ error: "Invalid approval request",
+ details: parsed.error.issues,
+ required_fields: {
+ approver: "string (required)",
+ reason: "string min 10 chars (required)",
+ acknowledged_risks: "string[] min 1 item (required)",
+ },
+ });
+ }
+
+ const decision = gate.approve(
+ req.params.taskId,
+ parsed.data.approver,
+ parsed.data.reason,
+ parsed.data.acknowledged_risks
+ );
+
+ res.json({
+ status: "APPROVED",
+ decision,
+ warning: "⚠️ Task approved despite STOP condition. This has been logged.",
+ });
+ } catch (error) {
+ const message = error instanceof Error ? error.message : "Unknown error";
+ res.status(400).json({ error: message });
+ }
+ });
+
+ /**
+ * POST /enforcement/reject/:taskId
+ * Reject a blocked task permanently
+ */
+ router.post("/reject/:taskId", (req, res) => {
+ try {
+ const parsed = RejectSchema.safeParse(req.body);
+
+ if (!parsed.success) {
+ return res.status(400).json({
+ error: "Invalid rejection request",
+ details: parsed.error.issues,
+ });
+ }
+
+ gate.reject(req.params.taskId, parsed.data.rejector, parsed.data.reason);
+
+ res.json({
+ status: "REJECTED",
+ message: "Task has been permanently stopped and logged.",
+ });
+ } catch (error) {
+ const message = error instanceof Error ? error.message : "Unknown error";
+ res.status(400).json({ error: message });
+ }
+ });
+
+ /**
+ * POST /enforcement/verify-claims
+ * Verify claims against artefacts (for testing/validation)
+ */
+ router.post("/verify-claims", (req, res) => {
+ try {
+ const parsed = VerifyClaimsSchema.safeParse(req.body);
+
+ if (!parsed.success) {
+ return res.status(400).json({
+ error: "Invalid verification request",
+ details: parsed.error.issues,
+ });
+ }
+
+ const result = verifyClaims(parsed.data.content, parsed.data.artefacts);
+ const report = generateVerificationReport(result);
+
+ res.json({
+ ...result,
+ report,
+ status: result.allVerified ? "VERIFIED" : "STOP_REQUIRED",
+ });
+ } catch (error) {
+ const message = error instanceof Error ? error.message : "Unknown error";
+ res.status(500).json({ error: message });
+ }
+ });
+
+ return router;
+}
diff --git a/src/api/health.ts b/src/api/health.ts
new file mode 100644
index 0000000..2f93d89
--- /dev/null
+++ b/src/api/health.ts
@@ -0,0 +1,40 @@
+/**
+ * Health Check Router
+ */
+
+import { Router } from "express";
+import type { Database } from "../db/database.js";
+import type { QueueAdapter } from "../queue/queue.js";
+
+export function createHealthRouter(db: Database, queue: QueueAdapter): Router {
+ const router = Router();
+
+ router.get("/", (_req, res) => {
+ const health = {
+ status: "healthy",
+ timestamp: new Date().toISOString(),
+ components: {
+ database: db.isHealthy() ? "healthy" : "unhealthy",
+ queue: queue.isHealthy() ? "healthy" : "degraded",
+ },
+ mode: queue.mode,
+ };
+
+ const statusCode = health.components.database === "healthy" ? 200 : 503;
+ res.status(statusCode).json(health);
+ });
+
+ router.get("/ready", (_req, res) => {
+ if (db.isHealthy()) {
+ res.json({ ready: true });
+ } else {
+ res.status(503).json({ ready: false, reason: "database unavailable" });
+ }
+ });
+
+ router.get("/live", (_req, res) => {
+ res.json({ live: true });
+ });
+
+ return router;
+}
diff --git a/src/api/tasks.ts b/src/api/tasks.ts
new file mode 100644
index 0000000..ac24cad
--- /dev/null
+++ b/src/api/tasks.ts
@@ -0,0 +1,175 @@
+/**
+ * Tasks Router - Create and manage supervised tasks
+ *
+ * ENFORCEMENT: All tasks are evaluated by the EnforcementGate.
+ * If STOP_REQUIRED → task is BLOCKED until human approval.
+ */
+
+import { Router } from "express";
+import { z } from "zod";
+import type { Database } from "../db/database.js";
+import type { QueueAdapter } from "../queue/queue.js";
+import type { EnforcementGate } from "../audit/enforcementGate.js";
+
+const CreateTaskSchema = z.object({
+ title: z.string().min(1).max(200),
+ description: z.string().optional(),
+ priority: z.enum(["low", "medium", "high"]).default("medium"),
+ assignee: z.enum(["cloud_assistant"]).default("cloud_assistant"),
+ artefacts: z.array(z.string()).optional().default([]),
+});
+
+const SubmitWorkSchema = z.object({
+ content: z.string().min(1),
+ artefacts: z.array(z.string()).default([]),
+ claims: z.array(z.string()).optional().default([]),
+});
+
+export function createTaskRouter(db: Database, queue: QueueAdapter, gate: EnforcementGate): Router {
+ const router = Router();
+
+ // Create task
+ router.post("/", async (req, res) => {
+ try {
+ const parsed = CreateTaskSchema.safeParse(req.body);
+
+ if (!parsed.success) {
+ return res.status(400).json({
+ error: "Invalid request",
+ details: parsed.error.issues,
+ });
+ }
+
+ const task = db.createTask({
+ title: parsed.data.title,
+ description: parsed.data.description,
+ priority: parsed.data.priority,
+ assignee: parsed.data.assignee,
+ status: "pending",
+ created_at: new Date().toISOString(),
+ });
+
+ // ENFORCEMENT GATE: Evaluate task content before queuing
+ const content = `${parsed.data.title} ${parsed.data.description ?? ""}`;
+ const gateDecision = gate.evaluate(task.id, content, parsed.data.artefacts);
+
+ if (gateDecision.status === "BLOCKED") {
+ // Task is BLOCKED - do NOT queue, require human approval
+ db.updateTask(task.id, { status: "stopped", stop_score: gateDecision.stopScore });
+
+ return res.status(202).json({
+ id: task.id,
+ status: "BLOCKED",
+ stop_score: gateDecision.stopScore,
+ reasons: gateDecision.reasons,
+ message: "⚠️ STOP_REQUIRED: Task blocked. Human approval needed at /api/enforcement/approve/" + task.id,
+ approval_url: `/api/enforcement/approve/${task.id}`,
+ });
+ }
+
+ // Task passed gate - queue for processing
+ await queue.add("process_task", { taskId: task.id });
+
+ res.status(201).json({
+ id: task.id,
+ status: "pending",
+ gate_status: "OPEN",
+ message: "Task created and queued for processing",
+ });
+ } catch (error) {
+ console.error("Failed to create task:", error);
+ res.status(500).json({ error: "Internal server error" });
+ }
+ });
+
+ // Submit work for a task (triggers enforcement gate)
+ router.post("/:id/submit", async (req, res) => {
+ try {
+ const task = db.getTask(req.params.id);
+
+ if (!task) {
+ return res.status(404).json({ error: "Task not found" });
+ }
+
+ const parsed = SubmitWorkSchema.safeParse(req.body);
+
+ if (!parsed.success) {
+ return res.status(400).json({
+ error: "Invalid submission",
+ details: parsed.error.issues,
+ });
+ }
+
+ // ENFORCEMENT GATE: Evaluate submitted work
+ const gateDecision = gate.evaluate(task.id, parsed.data.content, parsed.data.artefacts);
+
+ if (gateDecision.status === "BLOCKED") {
+ // Work is BLOCKED - cannot complete task
+ db.updateTask(task.id, { status: "stopped", stop_score: gateDecision.stopScore });
+
+ return res.status(202).json({
+ id: task.id,
+ status: "BLOCKED",
+ stop_score: gateDecision.stopScore,
+ reasons: gateDecision.reasons,
+ message: "⚠️ STOP_REQUIRED: Submission blocked. Human approval needed.",
+ approval_url: `/api/enforcement/approve/${task.id}`,
+ });
+ }
+
+ // Work passed gate - mark as completed
+ db.updateTask(task.id, { status: "completed", stop_score: gateDecision.stopScore });
+
+ res.json({
+ id: task.id,
+ status: "completed",
+ gate_status: "OPEN",
+ stop_score: gateDecision.stopScore,
+ message: "✅ Work submitted and approved",
+ });
+ } catch (error) {
+ console.error("Failed to submit work:", error);
+ res.status(500).json({ error: "Internal server error" });
+ }
+ });
+
+ // List tasks
+ router.get("/", (_req, res) => {
+ try {
+ const tasks = db.listTasks();
+ res.json({ tasks, count: tasks.length });
+ } catch (error) {
+ console.error("Failed to list tasks:", error);
+ res.status(500).json({ error: "Internal server error" });
+ }
+ });
+
+ // Get task by ID
+ router.get("/:id", (req, res) => {
+ try {
+ const task = db.getTask(req.params.id);
+
+ if (!task) {
+ return res.status(404).json({ error: "Task not found" });
+ }
+
+ // Check if task is blocked
+ const isBlocked = gate.isBlocked(req.params.id);
+ const pending = gate.getPendingApproval(req.params.id);
+
+ res.json({
+ ...task,
+ enforcement: {
+ blocked: isBlocked,
+ pending_approval: pending ?? null,
+ approval_url: isBlocked ? `/api/enforcement/approve/${task.id}` : null,
+ },
+ });
+ } catch (error) {
+ console.error("Failed to get task:", error);
+ res.status(500).json({ error: "Internal server error" });
+ }
+ });
+
+ return router;
+}
diff --git a/src/assistant/cloudAssistant.ts b/src/assistant/cloudAssistant.ts
new file mode 100644
index 0000000..935b602
--- /dev/null
+++ b/src/assistant/cloudAssistant.ts
@@ -0,0 +1,181 @@
+/**
+ * Cloud Assistant Module
+ * Executes work under supervision with evidence collection
+ */
+
+export interface TaskExecution {
+ taskId: string;
+ description: string;
+ steps: ExecutionStep[];
+ artefacts: Artefact[];
+ status: "in_progress" | "completed" | "blocked";
+ risks: string[];
+ gaps: string[];
+}
+
+export interface ExecutionStep {
+ description: string;
+ completed: boolean;
+ evidence?: string;
+}
+
+export interface Artefact {
+ type: "file" | "config" | "test" | "command_output" | "schema";
+ path: string;
+ description: string;
+}
+
+export interface StatusProposal {
+ status: "COMPLETE" | "COMPLETE_WITH_GAPS" | "STOP_REQUIRED";
+ evidence: Artefact[];
+ risks: string[];
+ gaps: string[];
+ nextSteps: string[];
+}
+
+/**
+ * Cloud Assistant
+ * Executes engineering tasks and collects evidence
+ */
+export class CloudAssistant {
+ private currentExecution: TaskExecution | null = null;
+
+ /**
+ * Start executing a task
+ */
+ startTask(taskId: string, description: string): TaskExecution {
+ this.currentExecution = {
+ taskId,
+ description,
+ steps: [],
+ artefacts: [],
+ status: "in_progress",
+ risks: [],
+ gaps: [],
+ };
+
+ return this.currentExecution;
+ }
+
+ /**
+ * Add an execution step
+ */
+ addStep(description: string, evidence?: string): void {
+ if (!this.currentExecution) {
+ throw new Error("No task in progress");
+ }
+
+ this.currentExecution.steps.push({
+ description,
+ completed: !!evidence,
+ evidence,
+ });
+ }
+
+ /**
+ * Record an artefact
+ */
+ recordArtefact(artefact: Artefact): void {
+ if (!this.currentExecution) {
+ throw new Error("No task in progress");
+ }
+
+ this.currentExecution.artefacts.push(artefact);
+ }
+
+ /**
+ * Record a risk
+ */
+ recordRisk(risk: string): void {
+ if (!this.currentExecution) {
+ throw new Error("No task in progress");
+ }
+
+ this.currentExecution.risks.push(risk);
+ }
+
+ /**
+ * Record a gap
+ */
+ recordGap(gap: string): void {
+ if (!this.currentExecution) {
+ throw new Error("No task in progress");
+ }
+
+ this.currentExecution.gaps.push(gap);
+ }
+
+ /**
+ * Complete the current task and generate status proposal
+ */
+ complete(): StatusProposal {
+ if (!this.currentExecution) {
+ throw new Error("No task in progress");
+ }
+
+ const execution = this.currentExecution;
+ execution.status = "completed";
+
+ // Determine proposed status
+ let status: "COMPLETE" | "COMPLETE_WITH_GAPS" | "STOP_REQUIRED";
+ if (execution.risks.length > 0) {
+ status = "STOP_REQUIRED";
+ } else if (execution.gaps.length > 0) {
+ status = "COMPLETE_WITH_GAPS";
+ } else {
+ status = "COMPLETE";
+ }
+
+ // Generate next steps
+ const nextSteps: string[] = [];
+ if (execution.gaps.length > 0) {
+ nextSteps.push(`Address gaps: ${execution.gaps.join(", ")}`);
+ }
+ if (execution.risks.length > 0) {
+ nextSteps.push(`Mitigate risks: ${execution.risks.join(", ")}`);
+ }
+ if (nextSteps.length === 0) {
+ nextSteps.push("Submit for supervisor review");
+ }
+
+ const proposal: StatusProposal = {
+ status,
+ evidence: execution.artefacts,
+ risks: execution.risks,
+ gaps: execution.gaps,
+ nextSteps,
+ };
+
+ this.currentExecution = null;
+
+ return proposal;
+ }
+
+ /**
+ * Mark task as blocked
+ */
+ block(reason: string): StatusProposal {
+ if (!this.currentExecution) {
+ throw new Error("No task in progress");
+ }
+
+ this.currentExecution.status = "blocked";
+ this.currentExecution.risks.push(reason);
+
+ return this.complete();
+ }
+
+ /**
+ * Get current execution state
+ */
+ getCurrentExecution(): TaskExecution | null {
+ return this.currentExecution;
+ }
+}
+
+/**
+ * Create a Cloud Assistant instance
+ */
+export function createCloudAssistant(): CloudAssistant {
+ return new CloudAssistant();
+}
diff --git a/src/audit/claimVerifier.ts b/src/audit/claimVerifier.ts
new file mode 100644
index 0000000..2a9726a
--- /dev/null
+++ b/src/audit/claimVerifier.ts
@@ -0,0 +1,200 @@
+/**
+ * Claim-Artefact Verifier
+ *
+ * Detects lies: when claims don't match artefacts.
+ * "10 URLs scraped" but only 1 file? → STOP.
+ */
+
+import * as fs from "fs";
+import * as path from "path";
+
+export interface ClaimVerification {
+ claim: string;
+ claimedCount: number;
+ actualCount: number;
+ verified: boolean;
+ discrepancy: number;
+ evidence: string[];
+}
+
+export interface VerificationResult {
+ allVerified: boolean;
+ totalClaims: number;
+ verifiedClaims: number;
+ failedClaims: ClaimVerification[];
+ passedClaims: ClaimVerification[];
+}
+
+/**
+ * Claim patterns we look for
+ */
+const CLAIM_PATTERNS = [
+ { pattern: /scraped\s+(\d+)\s*(urls?|pages?|sites?|portals?)/i, type: "scrape" },
+ { pattern: /created\s+(\d+)\s*(files?|components?|modules?)/i, type: "file" },
+ { pattern: /implemented\s+(\d+)\s*(features?|endpoints?|apis?)/i, type: "feature" },
+ { pattern: /fixed\s+(\d+)\s*(bugs?|issues?|errors?)/i, type: "fix" },
+ { pattern: /added\s+(\d+)\s*(tests?|specs?)/i, type: "test" },
+ { pattern: /deployed\s+(\d+)\s*(services?|instances?)/i, type: "deploy" },
+];
+
+/**
+ * Verify claims against artefacts
+ */
+export function verifyClaims(
+ content: string,
+ artefacts: string[],
+ projectRoot?: string
+): VerificationResult {
+ const verifications: ClaimVerification[] = [];
+
+ for (const { pattern, type } of CLAIM_PATTERNS) {
+ const matches = content.matchAll(new RegExp(pattern, "gi"));
+
+ for (const match of matches) {
+ const claimedCount = parseInt(match[1], 10);
+ const claim = match[0];
+
+ // Count relevant artefacts
+ const relevantArtefacts = filterArtefactsByType(artefacts, type, projectRoot);
+ const actualCount = relevantArtefacts.length;
+
+ const verification: ClaimVerification = {
+ claim,
+ claimedCount,
+ actualCount,
+ verified: actualCount >= claimedCount,
+ discrepancy: claimedCount - actualCount,
+ evidence: relevantArtefacts,
+ };
+
+ verifications.push(verification);
+ }
+ }
+
+ const failedClaims = verifications.filter((v) => !v.verified);
+ const passedClaims = verifications.filter((v) => v.verified);
+
+ return {
+ allVerified: failedClaims.length === 0,
+ totalClaims: verifications.length,
+ verifiedClaims: passedClaims.length,
+ failedClaims,
+ passedClaims,
+ };
+}
+
+/**
+ * Filter artefacts by claim type
+ */
+function filterArtefactsByType(
+ artefacts: string[],
+ type: string,
+ projectRoot?: string
+): string[] {
+ const relevant: string[] = [];
+
+ for (const artefact of artefacts) {
+ // Check if artefact exists (if projectRoot provided)
+ if (projectRoot) {
+ const fullPath = path.join(projectRoot, artefact);
+ if (!fs.existsSync(fullPath)) {
+ continue; // Skip non-existent artefacts
+ }
+ }
+
+ switch (type) {
+ case "scrape":
+ // Look for data files, JSON, CSV
+ if (/\.(json|csv|xml|html|txt)$/i.test(artefact)) {
+ relevant.push(artefact);
+ }
+ break;
+
+ case "file":
+ // Any file counts
+ relevant.push(artefact);
+ break;
+
+ case "feature":
+ // Source files
+ if (/\.(ts|js|tsx|jsx|py|go|rs|java)$/i.test(artefact)) {
+ relevant.push(artefact);
+ }
+ break;
+
+ case "fix":
+ // Modified source files
+ if (/\.(ts|js|tsx|jsx|py|go|rs|java)$/i.test(artefact)) {
+ relevant.push(artefact);
+ }
+ break;
+
+ case "test":
+ // Test files
+ if (/\.(test|spec)\.(ts|js|tsx|jsx)$/i.test(artefact) || /tests?\//i.test(artefact)) {
+ relevant.push(artefact);
+ }
+ break;
+
+ case "deploy":
+ // Deploy/config files
+ if (/\.(yaml|yml|json|toml|dockerfile)$/i.test(artefact) || /docker|k8s|deploy/i.test(artefact)) {
+ relevant.push(artefact);
+ }
+ break;
+
+ default:
+ relevant.push(artefact);
+ }
+ }
+
+ return relevant;
+}
+
+/**
+ * Generate a verification report
+ */
+export function generateVerificationReport(result: VerificationResult): string {
+ const lines: string[] = [];
+
+ lines.push("# Claim Verification Report");
+ lines.push("");
+ lines.push(`## Summary`);
+ lines.push(`- Total Claims: ${result.totalClaims}`);
+ lines.push(`- Verified: ${result.verifiedClaims}`);
+ lines.push(`- Failed: ${result.failedClaims.length}`);
+ lines.push(`- Status: ${result.allVerified ? "✅ ALL VERIFIED" : "❌ VERIFICATION FAILED"}`);
+ lines.push("");
+
+ if (result.failedClaims.length > 0) {
+ lines.push("## ❌ Failed Claims (STOP REQUIRED)");
+ lines.push("");
+
+ for (const failed of result.failedClaims) {
+ lines.push(`### "${failed.claim}"`);
+ lines.push(`- Claimed: ${failed.claimedCount}`);
+ lines.push(`- Actual: ${failed.actualCount}`);
+ lines.push(`- Discrepancy: ${failed.discrepancy} missing`);
+ lines.push(`- Evidence found:`);
+ if (failed.evidence.length === 0) {
+ lines.push(" - (none)");
+ } else {
+ for (const e of failed.evidence) {
+ lines.push(` - ${e}`);
+ }
+ }
+ lines.push("");
+ }
+ }
+
+ if (result.passedClaims.length > 0) {
+ lines.push("## ✅ Verified Claims");
+ lines.push("");
+
+ for (const passed of result.passedClaims) {
+ lines.push(`- "${passed.claim}" → ${passed.actualCount} artefacts found`);
+ }
+ }
+
+ return lines.join("\n");
+}
diff --git a/src/audit/decision.schema.json b/src/audit/decision.schema.json
new file mode 100644
index 0000000..150b6e3
--- /dev/null
+++ b/src/audit/decision.schema.json
@@ -0,0 +1,60 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "title": "SupervisorDecision",
+ "description": "Schema for Engineering Lead Supervisor decisions",
+ "required": [
+ "decision",
+ "final_status",
+ "risk_level",
+ "verified_artefacts",
+ "missing_invalid_parts",
+ "required_next_action"
+ ],
+ "properties": {
+ "decision": {
+ "type": "string",
+ "enum": ["APPROVED", "STOP_REQUIRED"],
+ "description": "The supervisor's decision"
+ },
+ "final_status": {
+ "type": "string",
+ "enum": ["COMPLETE", "COMPLETE_WITH_GAPS", "STOP_REQUIRED"],
+ "description": "Final status of the work"
+ },
+ "risk_level": {
+ "type": "string",
+ "enum": ["LOW", "MEDIUM", "HIGH", "CRITICAL"],
+ "description": "Assessed risk level"
+ },
+ "stop_score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 100,
+ "description": "Computed stop score (0-100)"
+ },
+ "verified_artefacts": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "List of verified artefacts (files, tests, configs)"
+ },
+ "missing_invalid_parts": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "List of missing or invalid parts"
+ },
+ "required_next_action": {
+ "type": "string",
+ "description": "Explicit next action required"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Decision timestamp"
+ },
+ "supervisor_id": {
+ "type": "string",
+ "description": "Identifier of the supervisor making the decision"
+ }
+ }
+}
diff --git a/src/audit/enforcementGate.ts b/src/audit/enforcementGate.ts
new file mode 100644
index 0000000..1b682d7
--- /dev/null
+++ b/src/audit/enforcementGate.ts
@@ -0,0 +1,254 @@
+/**
+ * Enforcement Gate - Hard STOP enforcement
+ *
+ * This module ensures that STOP_REQUIRED decisions are BLOCKING.
+ * No task proceeds without explicit human approval after STOP.
+ */
+
+import type { Database } from "../db/database.js";
+import { computeStopScore, analyzeContent, type StopReason } from "../audit/stopScorer.js";
+
+export type GateStatus = "OPEN" | "BLOCKED" | "AWAITING_APPROVAL";
+
+export interface GateDecision {
+ status: GateStatus;
+ taskId: string;
+ stopScore: number;
+ reasons: StopReason[];
+ blockedAt?: string;
+ approvedBy?: string;
+ approvedAt?: string;
+}
+
+export interface PendingApproval {
+ taskId: string;
+ stopScore: number;
+ reasons: StopReason[];
+ blockedAt: string;
+ requiredAction: string;
+}
+
+/**
+ * Enforcement Gate
+ *
+ * HARD RULES:
+ * 1. If STOP_REQUIRED → task is BLOCKED, period.
+ * 2. No continuation without human approval.
+ * 3. Approval requires explicit action + reason.
+ * 4. All decisions are audited.
+ */
+export class EnforcementGate {
+ private blockedTasks: Map = new Map();
+ private db: Database;
+
+ constructor(db: Database) {
+ this.db = db;
+ }
+
+ /**
+ * Evaluate content and determine if it can proceed
+ * Returns BLOCKED if any STOP condition is met
+ */
+ evaluate(taskId: string, content: string, artefacts: string[]): GateDecision {
+ const reasons: StopReason[] = [];
+
+ // Analyze content for violations
+ const contentReasons = analyzeContent(content);
+ reasons.push(...contentReasons);
+
+ // Check for unproven claims (claims without artefacts)
+ if (this.hasUnprovenClaims(content, artefacts)) {
+ reasons.push("UNPROVEN_CLAIM");
+ }
+
+ // Compute score
+ const scoreResult = computeStopScore([...new Set(reasons)]);
+
+ // HARD GATE: If stop required, BLOCK immediately
+ if (scoreResult.stopRequired) {
+ const pending: PendingApproval = {
+ taskId,
+ stopScore: scoreResult.score,
+ reasons: scoreResult.reasons,
+ blockedAt: new Date().toISOString(),
+ requiredAction: `Address: ${scoreResult.reasons.join(", ")}`,
+ };
+
+ this.blockedTasks.set(taskId, pending);
+
+ // Log to audit
+ this.db.createAuditEntry({
+ task_id: taskId,
+ decision: "STOP_REQUIRED",
+ final_status: "STOP_REQUIRED",
+ risk_level: scoreResult.severity,
+ stop_score: scoreResult.score,
+ verified_artefacts: JSON.stringify(artefacts),
+ missing_invalid_parts: JSON.stringify(scoreResult.reasons),
+ required_next_action: "BLOCKED - Awaiting human approval",
+ });
+
+ return {
+ status: "BLOCKED",
+ taskId,
+ stopScore: scoreResult.score,
+ reasons: scoreResult.reasons,
+ blockedAt: pending.blockedAt,
+ };
+ }
+
+ // OPEN - can proceed
+ return {
+ status: "OPEN",
+ taskId,
+ stopScore: scoreResult.score,
+ reasons: scoreResult.reasons,
+ };
+ }
+
+ /**
+ * Check if task is blocked
+ */
+ isBlocked(taskId: string): boolean {
+ return this.blockedTasks.has(taskId);
+ }
+
+ /**
+ * Get pending approval for a task
+ */
+ getPendingApproval(taskId: string): PendingApproval | undefined {
+ return this.blockedTasks.get(taskId);
+ }
+
+ /**
+ * Get all pending approvals
+ */
+ getAllPendingApprovals(): PendingApproval[] {
+ return Array.from(this.blockedTasks.values());
+ }
+
+ /**
+ * Approve a blocked task - REQUIRES explicit human action
+ *
+ * @param taskId - The blocked task
+ * @param approver - Human identifier (email, name, etc.)
+ * @param reason - Why this is being approved despite STOP
+ * @param acknowledgedRisks - Must explicitly list acknowledged risks
+ */
+ approve(
+ taskId: string,
+ approver: string,
+ reason: string,
+ acknowledgedRisks: string[]
+ ): GateDecision {
+ const pending = this.blockedTasks.get(taskId);
+
+ if (!pending) {
+ throw new Error(`Task ${taskId} is not blocked or does not exist`);
+ }
+
+ if (!approver || approver.trim().length === 0) {
+ throw new Error("Approver identity is required");
+ }
+
+ if (!reason || reason.trim().length < 10) {
+ throw new Error("Approval reason must be at least 10 characters");
+ }
+
+ if (acknowledgedRisks.length === 0) {
+ throw new Error("Must explicitly acknowledge at least one risk");
+ }
+
+ // Log the override to audit
+ this.db.createAuditEntry({
+ task_id: taskId,
+ decision: "APPROVED",
+ final_status: "COMPLETE_WITH_GAPS",
+ risk_level: pending.stopScore >= 70 ? "CRITICAL" : "HIGH",
+ stop_score: pending.stopScore,
+ verified_artefacts: JSON.stringify([]),
+ missing_invalid_parts: JSON.stringify({
+ original_reasons: pending.reasons,
+ override_reason: reason,
+ acknowledged_risks: acknowledgedRisks,
+ }),
+ required_next_action: `OVERRIDE by ${approver}: ${reason}`,
+ });
+
+ // Remove from blocked
+ this.blockedTasks.delete(taskId);
+
+ return {
+ status: "OPEN",
+ taskId,
+ stopScore: pending.stopScore,
+ reasons: pending.reasons,
+ approvedBy: approver,
+ approvedAt: new Date().toISOString(),
+ };
+ }
+
+ /**
+ * Reject and keep blocked - explicit denial
+ */
+ reject(taskId: string, rejector: string, reason: string): void {
+ const pending = this.blockedTasks.get(taskId);
+
+ if (!pending) {
+ throw new Error(`Task ${taskId} is not blocked`);
+ }
+
+ // Update the task status to permanently stopped
+ this.db.updateTask(taskId, {
+ status: "stopped",
+ stop_score: pending.stopScore,
+ });
+
+ // Log rejection
+ this.db.createAuditEntry({
+ task_id: taskId,
+ decision: "STOP_REQUIRED",
+ final_status: "STOP_REQUIRED",
+ risk_level: "CRITICAL",
+ stop_score: pending.stopScore,
+ verified_artefacts: JSON.stringify([]),
+ missing_invalid_parts: JSON.stringify(pending.reasons),
+ required_next_action: `REJECTED by ${rejector}: ${reason}`,
+ });
+
+ this.blockedTasks.delete(taskId);
+ }
+
+ /**
+ * Check for unproven claims
+ */
+ private hasUnprovenClaims(content: string, artefacts: string[]): boolean {
+ const claimPatterns = [
+ /implemented\s+(\d+)/i,
+ /created\s+(\d+)/i,
+ /scraped\s+(\d+)/i,
+ /deployed\s+(\d+)/i,
+ /connected\s+(\d+)/i,
+ ];
+
+ for (const pattern of claimPatterns) {
+ const match = content.match(pattern);
+ if (match) {
+ const claimedCount = parseInt(match[1], 10);
+ // If claiming N things but not providing N artefacts, it's unproven
+ if (artefacts.length < claimedCount) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
+
+/**
+ * Create enforcement gate instance
+ */
+export function createEnforcementGate(db: Database): EnforcementGate {
+ return new EnforcementGate(db);
+}
diff --git a/src/audit/stopScorer.ts b/src/audit/stopScorer.ts
new file mode 100644
index 0000000..5af39dc
--- /dev/null
+++ b/src/audit/stopScorer.ts
@@ -0,0 +1,124 @@
+/**
+ * STOP Scorer - Computes risk scores for supervisor decisions
+ */
+
+export type StopReason =
+ | "PRICING_WITHOUT_FACT"
+ | "LEGAL_STATEMENT"
+ | "UNPROVEN_CLAIM"
+ | "CROSS_LAYER_MISMATCH"
+ | "MISSING_SQL_OR_SCHEMA"
+ | "MISSING_TESTS"
+ | "MISSING_DEPLOY_CONFIG"
+ | "COST_OR_LOAD_RISK";
+
+export type Severity = "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
+
+export interface StopScoreResult {
+ score: number;
+ severity: Severity;
+ stopRequired: boolean;
+ reasons: StopReason[];
+}
+
+const REASON_WEIGHTS: Record = {
+ PRICING_WITHOUT_FACT: 40,
+ LEGAL_STATEMENT: 40,
+ UNPROVEN_CLAIM: 30,
+ CROSS_LAYER_MISMATCH: 25,
+ MISSING_SQL_OR_SCHEMA: 25,
+ MISSING_TESTS: 15,
+ MISSING_DEPLOY_CONFIG: 15,
+ COST_OR_LOAD_RISK: 20,
+};
+
+/**
+ * Computes a stop score based on identified risk reasons
+ * @param reasons Array of stop reasons identified
+ * @returns StopScoreResult with score, severity, and stop requirement
+ */
+export function computeStopScore(reasons: StopReason[]): StopScoreResult {
+ let score = 0;
+
+ for (const reason of reasons) {
+ score += REASON_WEIGHTS[reason] ?? 0;
+ }
+
+ // Cap at 100
+ if (score > 100) {
+ score = 100;
+ }
+
+ // Determine severity
+ let severity: Severity;
+ if (score >= 70) {
+ severity = "CRITICAL";
+ } else if (score >= 45) {
+ severity = "HIGH";
+ } else if (score >= 20) {
+ severity = "MEDIUM";
+ } else {
+ severity = "LOW";
+ }
+
+ // STOP required if score >= 40
+ const stopRequired = score >= 40;
+
+ return {
+ score,
+ severity,
+ stopRequired,
+ reasons,
+ };
+}
+
+/**
+ * Analyzes text content for potential stop reasons
+ * @param content Text content to analyze
+ * @returns Array of identified stop reasons
+ */
+export function analyzeContent(content: string): StopReason[] {
+ const reasons: StopReason[] = [];
+ const lowerContent = content.toLowerCase();
+
+ // Check for pricing claims
+ if (
+ lowerContent.includes("price") ||
+ lowerContent.includes("cost") ||
+ lowerContent.includes("€") ||
+ lowerContent.includes("$")
+ ) {
+ if (!lowerContent.includes("unknown")) {
+ reasons.push("PRICING_WITHOUT_FACT");
+ }
+ }
+
+ // Check for legal statements
+ if (
+ lowerContent.includes("legal") ||
+ lowerContent.includes("liability") ||
+ lowerContent.includes("warranty") ||
+ lowerContent.includes("guarantee")
+ ) {
+ reasons.push("LEGAL_STATEMENT");
+ }
+
+ // Check for unproven claims
+ const claimPatterns = [
+ "implemented",
+ "deployed",
+ "connected",
+ "integrated",
+ "scraped",
+ "completed",
+ ];
+
+ for (const pattern of claimPatterns) {
+ if (lowerContent.includes(pattern) && !lowerContent.includes("file:")) {
+ reasons.push("UNPROVEN_CLAIM");
+ break;
+ }
+ }
+
+ return reasons;
+}
diff --git a/src/db/database.ts b/src/db/database.ts
new file mode 100644
index 0000000..cd06076
--- /dev/null
+++ b/src/db/database.ts
@@ -0,0 +1,191 @@
+/**
+ * Database Module - SQLite persistence layer
+ */
+
+import BetterSqlite3 from "better-sqlite3";
+import { randomUUID } from "crypto";
+
+export interface Task {
+ id: string;
+ title: string;
+ description?: string;
+ priority: "low" | "medium" | "high";
+ status: "pending" | "in_progress" | "completed" | "stopped";
+ assignee: string;
+ created_at: string;
+ updated_at?: string;
+ stop_score?: number;
+}
+
+export interface AuditEntry {
+ id: string;
+ task_id?: string;
+ decision: "APPROVED" | "STOP_REQUIRED";
+ final_status: "COMPLETE" | "COMPLETE_WITH_GAPS" | "STOP_REQUIRED";
+ risk_level: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
+ stop_score: number;
+ verified_artefacts: string;
+ missing_invalid_parts: string;
+ required_next_action: string;
+ created_at: string;
+}
+
+export interface Database {
+ isHealthy(): boolean;
+ createTask(task: Omit): Task;
+ getTask(id: string): Task | undefined;
+ listTasks(): Task[];
+ updateTask(id: string, updates: Partial): Task | undefined;
+ createAuditEntry(entry: Omit): AuditEntry;
+ getAuditEntry(id: string): AuditEntry | undefined;
+ listAuditEntries(limit?: number): AuditEntry[];
+ getStopScoreStats(): { total: number; stopped: number; avgScore: number };
+}
+
+export function initDatabase(): Database {
+ const dbPath = process.env.SQLITE_PATH ?? "./data/app.sqlite";
+
+ let db: BetterSqlite3.Database;
+
+ try {
+ db = new BetterSqlite3(dbPath);
+ db.pragma("journal_mode = WAL");
+
+ // Create tables
+ db.exec(`
+ CREATE TABLE IF NOT EXISTS tasks (
+ id TEXT PRIMARY KEY,
+ title TEXT NOT NULL,
+ description TEXT,
+ priority TEXT NOT NULL DEFAULT 'medium',
+ status TEXT NOT NULL DEFAULT 'pending',
+ assignee TEXT NOT NULL,
+ created_at TEXT NOT NULL,
+ updated_at TEXT,
+ stop_score INTEGER
+ );
+
+ CREATE TABLE IF NOT EXISTS audit_entries (
+ id TEXT PRIMARY KEY,
+ task_id TEXT,
+ decision TEXT NOT NULL,
+ final_status TEXT NOT NULL,
+ risk_level TEXT NOT NULL,
+ stop_score INTEGER NOT NULL,
+ verified_artefacts TEXT NOT NULL,
+ missing_invalid_parts TEXT NOT NULL,
+ required_next_action TEXT NOT NULL,
+ created_at TEXT NOT NULL
+ );
+
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
+ CREATE INDEX IF NOT EXISTS idx_audit_task_id ON audit_entries(task_id);
+ `);
+ } catch (error) {
+ console.warn("SQLite unavailable, using in-memory fallback:", error);
+ db = new BetterSqlite3(":memory:");
+ db.exec(`
+ CREATE TABLE tasks (
+ id TEXT PRIMARY KEY,
+ title TEXT NOT NULL,
+ description TEXT,
+ priority TEXT NOT NULL DEFAULT 'medium',
+ status TEXT NOT NULL DEFAULT 'pending',
+ assignee TEXT NOT NULL,
+ created_at TEXT NOT NULL,
+ updated_at TEXT,
+ stop_score INTEGER
+ );
+
+ CREATE TABLE audit_entries (
+ id TEXT PRIMARY KEY,
+ task_id TEXT,
+ decision TEXT NOT NULL,
+ final_status TEXT NOT NULL,
+ risk_level TEXT NOT NULL,
+ stop_score INTEGER NOT NULL,
+ verified_artefacts TEXT NOT NULL,
+ missing_invalid_parts TEXT NOT NULL,
+ required_next_action TEXT NOT NULL,
+ created_at TEXT NOT NULL
+ );
+ `);
+ }
+
+ return {
+ isHealthy(): boolean {
+ try {
+ db.prepare("SELECT 1").get();
+ return true;
+ } catch {
+ return false;
+ }
+ },
+
+ createTask(task: Omit): Task {
+ const id = randomUUID();
+ const stmt = db.prepare(`
+ INSERT INTO tasks (id, title, description, priority, status, assignee, created_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ `);
+ stmt.run(id, task.title, task.description ?? null, task.priority, task.status, task.assignee, task.created_at);
+ return { id, ...task };
+ },
+
+ getTask(id: string): Task | undefined {
+ const stmt = db.prepare("SELECT * FROM tasks WHERE id = ?");
+ return stmt.get(id) as Task | undefined;
+ },
+
+ listTasks(): Task[] {
+ const stmt = db.prepare("SELECT * FROM tasks ORDER BY created_at DESC LIMIT 100");
+ return stmt.all() as Task[];
+ },
+
+ updateTask(id: string, updates: Partial): Task | undefined {
+ const current = this.getTask(id);
+ if (!current) return undefined;
+
+ const updated = { ...current, ...updates, updated_at: new Date().toISOString() };
+ const stmt = db.prepare(`
+ UPDATE tasks SET title = ?, description = ?, priority = ?, status = ?, updated_at = ?, stop_score = ?
+ WHERE id = ?
+ `);
+ stmt.run(updated.title, updated.description ?? null, updated.priority, updated.status, updated.updated_at, updated.stop_score ?? null, id);
+ return updated;
+ },
+
+ createAuditEntry(entry: Omit): AuditEntry {
+ const id = randomUUID();
+ const created_at = new Date().toISOString();
+ const stmt = db.prepare(`
+ INSERT INTO audit_entries (id, task_id, decision, final_status, risk_level, stop_score, verified_artefacts, missing_invalid_parts, required_next_action, created_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ `);
+ stmt.run(id, entry.task_id ?? null, entry.decision, entry.final_status, entry.risk_level, entry.stop_score, entry.verified_artefacts, entry.missing_invalid_parts, entry.required_next_action, created_at);
+ return { id, created_at, ...entry };
+ },
+
+ getAuditEntry(id: string): AuditEntry | undefined {
+ const stmt = db.prepare("SELECT * FROM audit_entries WHERE id = ?");
+ return stmt.get(id) as AuditEntry | undefined;
+ },
+
+ listAuditEntries(limit = 50): AuditEntry[] {
+ const stmt = db.prepare("SELECT * FROM audit_entries ORDER BY created_at DESC LIMIT ?");
+ return stmt.all(limit) as AuditEntry[];
+ },
+
+ getStopScoreStats(): { total: number; stopped: number; avgScore: number } {
+ const totalStmt = db.prepare("SELECT COUNT(*) as count FROM audit_entries");
+ const stoppedStmt = db.prepare("SELECT COUNT(*) as count FROM audit_entries WHERE decision = 'STOP_REQUIRED'");
+ const avgStmt = db.prepare("SELECT AVG(stop_score) as avg FROM audit_entries");
+
+ const total = (totalStmt.get() as { count: number }).count;
+ const stopped = (stoppedStmt.get() as { count: number }).count;
+ const avgScore = (avgStmt.get() as { avg: number | null }).avg ?? 0;
+
+ return { total, stopped, avgScore: Math.round(avgScore * 100) / 100 };
+ },
+ };
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..109784b
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,81 @@
+/**
+ * Code Cloud Agents - Main Entry Point
+ * Supervised AI system with Engineering Lead Supervisor and Cloud Assistant
+ */
+
+import express from "express";
+import { fileURLToPath } from "url";
+import { dirname, join } from "path";
+import { createHealthRouter } from "./api/health.js";
+import { createTaskRouter } from "./api/tasks.js";
+import { createAuditRouter } from "./api/audit.js";
+import { createEnforcementRouter } from "./api/enforcement.js";
+import { initDatabase } from "./db/database.js";
+import { initQueue } from "./queue/queue.js";
+import { createEnforcementGate } from "./audit/enforcementGate.js";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+const PORT = process.env.PORT ?? 3000;
+
+async function main() {
+ console.log("🚀 Starting Code Cloud Agents...");
+
+ // Initialize database
+ const db = initDatabase();
+ console.log("✅ Database initialized");
+
+ // Initialize queue
+ const queue = initQueue();
+ console.log("✅ Queue initialized (mode:", queue.mode, ")");
+
+ // Initialize enforcement gate (HARD STOP enforcement)
+ const gate = createEnforcementGate(db);
+ console.log("✅ Enforcement Gate active (STOP decisions are BLOCKING)");
+
+ // Create Express app
+ const app = express();
+ app.use(express.json());
+
+ // Serve static dashboard
+ const publicPath = join(__dirname, "..", "public");
+ app.use(express.static(publicPath));
+
+ // Mount API routers
+ app.use("/health", createHealthRouter(db, queue));
+ app.use("/api/tasks", createTaskRouter(db, queue, gate));
+ app.use("/api/audit", createAuditRouter(db));
+ app.use("/api/enforcement", createEnforcementRouter(gate));
+
+ // API info endpoint
+ app.get("/api", (_req, res) => {
+ res.json({
+ name: "code-cloud-agents",
+ version: "0.1.0",
+ status: "running",
+ supervisor: "ENGINEERING_LEAD_SUPERVISOR",
+ mode: "SUPERVISED",
+ });
+ });
+
+ // Start server
+ app.listen(PORT, () => {
+ console.log(`✅ Server running on http://localhost:${PORT}`);
+ console.log("📋 Dashboard: http://localhost:" + PORT);
+ console.log("📋 API Endpoints:");
+ console.log(" GET /api - API info");
+ console.log(" GET /health - Health check");
+ console.log(" POST /api/tasks - Create task");
+ console.log(" GET /api/tasks - List tasks");
+ console.log(" GET /api/audit - Audit log");
+ console.log(" GET /api/enforcement/blocked - Blocked tasks");
+ console.log(" POST /api/enforcement/approve - Human approval");
+ console.log(" POST /api/enforcement/reject - Human rejection");
+ });
+}
+
+main().catch((error) => {
+ console.error("❌ Failed to start:", error);
+ process.exit(1);
+});
diff --git a/src/integrations/google/client.ts b/src/integrations/google/client.ts
new file mode 100644
index 0000000..5be3e5b
--- /dev/null
+++ b/src/integrations/google/client.ts
@@ -0,0 +1,88 @@
+/**
+ * Google Integration - STUB
+ * This is a placeholder integration. Do not use in production without explicit approval.
+ */
+
+export interface GoogleConfig {
+ clientId: string;
+ clientSecret: string;
+ redirectUri?: string;
+}
+
+export interface GoogleClient {
+ isEnabled(): boolean;
+ getAuthUrl(): string;
+ handleCallback(code: string): Promise<{ success: boolean; tokens?: unknown; error?: string }>;
+ getCalendarEvents(calendarId: string): Promise<{ success: boolean; events?: unknown[]; error?: string }>;
+ getContacts(): Promise<{ success: boolean; contacts?: unknown[]; error?: string }>;
+ getStatus(): Promise<{ connected: boolean; error?: string }>;
+}
+
+/**
+ * Creates a Google client stub
+ * All methods return stub responses until properly configured
+ */
+export function createGoogleClient(_config?: GoogleConfig): GoogleClient {
+ const enabled = process.env.GOOGLE_ENABLED === "true";
+
+ return {
+ isEnabled(): boolean {
+ return enabled;
+ },
+
+ getAuthUrl(): string {
+ return "STUB: Google OAuth not configured";
+ },
+
+ async handleCallback(_code: string): Promise<{ success: boolean; tokens?: unknown; error?: string }> {
+ if (!enabled) {
+ return {
+ success: false,
+ error: "STUB: Google integration is disabled. Set GOOGLE_ENABLED=true to enable.",
+ };
+ }
+
+ return {
+ success: false,
+ error: "STUB: Google OAuth not implemented",
+ };
+ },
+
+ async getCalendarEvents(_calendarId: string): Promise<{ success: boolean; events?: unknown[]; error?: string }> {
+ if (!enabled) {
+ return {
+ success: false,
+ error: "STUB: Google integration is disabled",
+ };
+ }
+
+ return {
+ success: false,
+ events: [],
+ error: "STUB: Google Calendar not implemented",
+ };
+ },
+
+ async getContacts(): Promise<{ success: boolean; contacts?: unknown[]; error?: string }> {
+ if (!enabled) {
+ return {
+ success: false,
+ error: "STUB: Google integration is disabled",
+ };
+ }
+
+ return {
+ success: false,
+ contacts: [],
+ error: "STUB: Google Contacts not implemented",
+ };
+ },
+
+ async getStatus(): Promise<{ connected: boolean; error?: string }> {
+ return {
+ connected: false,
+ error: "STUB: Google integration not implemented",
+ };
+ },
+ };
+}
diff --git a/src/integrations/icloud/client.ts b/src/integrations/icloud/client.ts
new file mode 100644
index 0000000..55c647b
--- /dev/null
+++ b/src/integrations/icloud/client.ts
@@ -0,0 +1,84 @@
+/**
+ * iCloud Integration - STUB
+ * This is a placeholder integration. Do not use in production without explicit approval.
+ */
+
+export interface ICloudConfig {
+ appleId: string;
+ appPassword: string;
+}
+
+export interface ICloudClient {
+ isEnabled(): boolean;
+ authenticate(): Promise<{ success: boolean; error?: string }>;
+ getContacts(): Promise<{ success: boolean; contacts?: unknown[]; error?: string }>;
+ getCalendarEvents(): Promise<{ success: boolean; events?: unknown[]; error?: string }>;
+ getReminders(): Promise<{ success: boolean; reminders?: unknown[]; error?: string }>;
+ getStatus(): Promise<{ connected: boolean; error?: string }>;
+}
+
+/**
+ * Creates an iCloud client stub
+ * All methods return stub responses until properly configured
+ */
+export function createICloudClient(_config?: ICloudConfig): ICloudClient {
+ const enabled = process.env.ICLOUD_ENABLED === "true";
+
+ return {
+ isEnabled(): boolean {
+ return enabled;
+ },
+
+ async authenticate(): Promise<{ success: boolean; error?: string }> {
+ if (!enabled) {
+ return {
+ success: false,
+ error: "STUB: iCloud integration is disabled. Set ICLOUD_ENABLED=true to enable.",
+ };
+ }
+
+ return {
+ success: false,
+ error: "STUB: iCloud authentication not implemented",
+ };
+ },
+
+ async getContacts(): Promise<{ success: boolean; contacts?: unknown[]; error?: string }> {
+ if (!enabled) {
+ return {
+ success: false,
+ error: "STUB: iCloud integration is disabled",
+ };
+ }
+
+ return {
+ success: false,
+ contacts: [],
+ error: "STUB: iCloud Contacts not implemented",
+ };
+ },
+
+ async getCalendarEvents(): Promise<{ success: boolean; events?: unknown[]; error?: string }> {
+ return {
+ success: false,
+ events: [],
+ error: "STUB: iCloud Calendar not implemented",
+ };
+ },
+
+ async getReminders(): Promise<{ success: boolean; reminders?: unknown[]; error?: string }> {
+ return {
+ success: false,
+ reminders: [],
+ error: "STUB: iCloud Reminders not implemented",
+ };
+ },
+
+ async getStatus(): Promise<{ connected: boolean; error?: string }> {
+ return {
+ connected: false,
+ error: "STUB: iCloud integration not implemented",
+ };
+ },
+ };
+}
diff --git a/src/integrations/index.ts b/src/integrations/index.ts
new file mode 100644
index 0000000..106532b
--- /dev/null
+++ b/src/integrations/index.ts
@@ -0,0 +1,40 @@
+/**
+ * Integrations Index - Export all integration clients
+ *
+ * IMPORTANT: All integrations are STUBS until explicitly enabled.
+ * Do not use in production without explicit approval.
+ */
+
+export { createWhatsAppClient, type WhatsAppClient, type WhatsAppMessage } from "./whatsapp/client.js";
+export { createVoiceClient, type VoiceClient, type VoiceCall } from "./voice/client.js";
+export { createGoogleClient, type GoogleClient } from "./google/client.js";
+export { createICloudClient, type ICloudClient } from "./icloud/client.js";
+export { createPineconeClient, type PineconeClient, type PineconeVector } from "./pinecone/client.js";
+
+/**
+ * Get status of all integrations
+ */
+export async function getAllIntegrationStatus(): Promise> {
+ return {
+ whatsapp: {
+ enabled: process.env.WHATSAPP_ENABLED === "true",
+ connected: false,
+ },
+ voice: {
+ enabled: process.env.VOICE_ENABLED === "true",
+ connected: false,
+ },
+ google: {
+ enabled: process.env.GOOGLE_ENABLED === "true",
+ connected: false,
+ },
+ icloud: {
+ enabled: process.env.ICLOUD_ENABLED === "true",
+ connected: false,
+ },
+ pinecone: {
+ enabled: process.env.PINECONE_ENABLED === "true",
+ connected: false,
+ },
+ };
+}
diff --git a/src/integrations/pinecone/client.ts b/src/integrations/pinecone/client.ts
new file mode 100644
index 0000000..46cd188
--- /dev/null
+++ b/src/integrations/pinecone/client.ts
@@ -0,0 +1,82 @@
+/**
+ * Pinecone Integration - STUB
+ * This is a placeholder integration. Do not use in production without explicit approval.
+ */
+
+export interface PineconeConfig {
+ apiKey: string;
+ environment: string;
+ indexName: string;
+}
+
+export interface PineconeVector {
+ id: string;
+ values: number[];
+ metadata?: Record;
+}
+
+export interface PineconeClient {
+ isEnabled(): boolean;
+ upsert(vectors: PineconeVector[]): Promise<{ success: boolean; upsertedCount?: number; error?: string }>;
+ query(vector: number[], topK?: number): Promise<{ success: boolean; matches?: unknown[]; error?: string }>;
+ deleteById(ids: string[]): Promise<{ success: boolean; error?: string }>;
+ getStatus(): Promise<{ connected: boolean; error?: string }>;
+}
+
+/**
+ * Creates a Pinecone client stub
+ * All methods return stub responses until properly configured
+ */
+export function createPineconeClient(_config?: PineconeConfig): PineconeClient {
+ const enabled = process.env.PINECONE_ENABLED === "true";
+
+ return {
+ isEnabled(): boolean {
+ return enabled;
+ },
+
+ async upsert(_vectors: PineconeVector[]): Promise<{ success: boolean; upsertedCount?: number; error?: string }> {
+ if (!enabled) {
+ return {
+ success: false,
+ error: "STUB: Pinecone integration is disabled. Set PINECONE_ENABLED=true to enable.",
+ };
+ }
+
+ console.warn("Pinecone upsert called but not implemented");
+ return {
+ success: false,
+ error: "STUB: Pinecone upsert not implemented",
+ };
+ },
+
+ async query(_vector: number[], _topK = 10): Promise<{ success: boolean; matches?: unknown[]; error?: string }> {
+ if (!enabled) {
+ return {
+ success: false,
+ error: "STUB: Pinecone integration is disabled",
+ };
+ }
+
+ return {
+ success: false,
+ matches: [],
+ error: "STUB: Pinecone query not implemented",
+ };
+ },
+
+ async deleteById(_ids: string[]): Promise<{ success: boolean; error?: string }> {
+ return {
+ success: false,
+ error: "STUB: Pinecone delete not implemented",
+ };
+ },
+
+ async getStatus(): Promise<{ connected: boolean; error?: string }> {
+ return {
+ connected: false,
+ error: "STUB: Pinecone integration not implemented",
+ };
+ },
+ };
+}
diff --git a/src/integrations/voice/client.ts b/src/integrations/voice/client.ts
new file mode 100644
index 0000000..e4b7a9d
--- /dev/null
+++ b/src/integrations/voice/client.ts
@@ -0,0 +1,72 @@
+/**
+ * Voice Integration - STUB
+ * This is a placeholder integration. Do not use in production without explicit approval.
+ */
+
+export interface VoiceConfig {
+ provider: string;
+ apiKey: string;
+}
+
+export interface VoiceCall {
+ to: string;
+ message: string;
+ language?: string;
+}
+
+export interface VoiceClient {
+ isEnabled(): boolean;
+ makeCall(call: VoiceCall): Promise<{ success: boolean; callId?: string; error?: string }>;
+ transcribe(audioUrl: string): Promise<{ success: boolean; text?: string; error?: string }>;
+ getStatus(): Promise<{ connected: boolean; error?: string }>;
+}
+
+/**
+ * Creates a Voice client stub
+ * All methods return stub responses until properly configured
+ */
+export function createVoiceClient(_config?: VoiceConfig): VoiceClient {
+ const enabled = process.env.VOICE_ENABLED === "true";
+
+ return {
+ isEnabled(): boolean {
+ return enabled;
+ },
+
+ async makeCall(_call: VoiceCall): Promise<{ success: boolean; callId?: string; error?: string }> {
+ if (!enabled) {
+ return {
+ success: false,
+ error: "STUB: Voice integration is disabled. Set VOICE_ENABLED=true to enable.",
+ };
+ }
+
+ console.warn("Voice makeCall called but not implemented");
+ return {
+ success: false,
+ error: "STUB: Voice integration not implemented",
+ };
+ },
+
+ async transcribe(_audioUrl: string): Promise<{ success: boolean; text?: string; error?: string }> {
+ if (!enabled) {
+ return {
+ success: false,
+ error: "STUB: Voice integration is disabled",
+ };
+ }
+
+ return {
+ success: false,
+ error: "STUB: Voice transcription not implemented",
+ };
+ },
+
+ async getStatus(): Promise<{ connected: boolean; error?: string }> {
+ return {
+ connected: false,
+ error: "STUB: Voice integration not implemented",
+ };
+ },
+ };
+}
diff --git a/src/integrations/whatsapp/client.ts b/src/integrations/whatsapp/client.ts
new file mode 100644
index 0000000..cdd9c08
--- /dev/null
+++ b/src/integrations/whatsapp/client.ts
@@ -0,0 +1,65 @@
+/**
+ * WhatsApp Integration - STUB
+ * This is a placeholder integration. Do not use in production without explicit approval.
+ */
+
+export interface WhatsAppConfig {
+ apiUrl: string;
+ apiToken: string;
+}
+
+export interface WhatsAppMessage {
+ to: string;
+ body: string;
+ type: "text" | "template";
+}
+
+export interface WhatsAppClient {
+ isEnabled(): boolean;
+ sendMessage(message: WhatsAppMessage): Promise<{ success: boolean; messageId?: string; error?: string }>;
+ getStatus(): Promise<{ connected: boolean; error?: string }>;
+}
+
+/**
+ * Creates a WhatsApp client stub
+ * All methods return stub responses until properly configured
+ */
+export function createWhatsAppClient(_config?: WhatsAppConfig): WhatsAppClient {
+ const enabled = process.env.WHATSAPP_ENABLED === "true";
+
+ return {
+ isEnabled(): boolean {
+ return enabled;
+ },
+
+ async sendMessage(_message: WhatsAppMessage): Promise<{ success: boolean; messageId?: string; error?: string }> {
+ if (!enabled) {
+ return {
+ success: false,
+ error: "STUB: WhatsApp integration is disabled. Set WHATSAPP_ENABLED=true to enable.",
+ };
+ }
+
+ // TODO: Implement actual WhatsApp API call
+ console.warn("WhatsApp sendMessage called but not implemented");
+ return {
+ success: false,
+ error: "STUB: WhatsApp integration not implemented",
+ };
+ },
+
+ async getStatus(): Promise<{ connected: boolean; error?: string }> {
+ if (!enabled) {
+ return {
+ connected: false,
+ error: "STUB: WhatsApp integration is disabled",
+ };
+ }
+
+ return {
+ connected: false,
+ error: "STUB: WhatsApp integration not implemented",
+ };
+ },
+ };
+}
diff --git a/src/meta/metaSupervisor.ts b/src/meta/metaSupervisor.ts
new file mode 100644
index 0000000..dadf5cc
--- /dev/null
+++ b/src/meta/metaSupervisor.ts
@@ -0,0 +1,165 @@
+/**
+ * Meta Supervisor Module
+ * Routes tasks across systems and aggregates health/metrics
+ */
+
+export interface RouteDecision {
+ targetSystem: string;
+ reason: string;
+ constraints: string[];
+ monitoringNotes: string[];
+}
+
+export interface SystemHealth {
+ systemId: string;
+ status: "healthy" | "degraded" | "unhealthy";
+ taskCompletionRate: number;
+ stopRate: number;
+ avgStopScore: number;
+ queueDepth: number;
+ lastUpdated: string;
+}
+
+export interface AggregatedMetrics {
+ totalTasks: number;
+ completedTasks: number;
+ stoppedTasks: number;
+ avgStopScore: number;
+ systemHealth: Record;
+}
+
+/**
+ * Meta Supervisor
+ * Routes tasks and monitors system health
+ */
+export class MetaSupervisor {
+ private systems: Map = new Map();
+
+ constructor() {
+ // Register default system
+ this.registerSystem("code-cloud-agents");
+ }
+
+ /**
+ * Register a system for routing
+ */
+ registerSystem(systemId: string): void {
+ this.systems.set(systemId, {
+ systemId,
+ status: "healthy",
+ taskCompletionRate: 1.0,
+ stopRate: 0,
+ avgStopScore: 0,
+ queueDepth: 0,
+ lastUpdated: new Date().toISOString(),
+ });
+ }
+
+ /**
+ * Route a task to the appropriate system
+ */
+ route(taskType: string, constraints: string[] = []): RouteDecision {
+ // For now, route everything to the main system
+ const targetSystem = "code-cloud-agents";
+ const systemHealth = this.systems.get(targetSystem);
+
+ const monitoringNotes: string[] = [];
+ if (systemHealth) {
+ if (systemHealth.status !== "healthy") {
+ monitoringNotes.push(`System ${targetSystem} is ${systemHealth.status}`);
+ }
+ if (systemHealth.queueDepth > 10) {
+ monitoringNotes.push(`High queue depth: ${systemHealth.queueDepth}`);
+ }
+ if (systemHealth.stopRate > 0.3) {
+ monitoringNotes.push(`High stop rate: ${(systemHealth.stopRate * 100).toFixed(1)}%`);
+ }
+ }
+
+ return {
+ targetSystem,
+ reason: `Task type "${taskType}" routed to primary system`,
+ constraints,
+ monitoringNotes,
+ };
+ }
+
+ /**
+ * Update system health metrics
+ */
+ updateHealth(systemId: string, updates: Partial): void {
+ const current = this.systems.get(systemId);
+ if (!current) {
+ throw new Error(`System ${systemId} not registered`);
+ }
+
+ this.systems.set(systemId, {
+ ...current,
+ ...updates,
+ lastUpdated: new Date().toISOString(),
+ });
+ }
+
+ /**
+ * Get aggregated metrics across all systems
+ */
+ getAggregatedMetrics(): AggregatedMetrics {
+ let totalTasks = 0;
+ let completedTasks = 0;
+ let stoppedTasks = 0;
+ let totalStopScore = 0;
+ let scoreCount = 0;
+
+ const systemHealth: Record = {};
+
+ for (const [id, health] of this.systems) {
+ systemHealth[id] = health;
+
+ // Aggregate metrics (placeholder logic)
+ if (health.avgStopScore > 0) {
+ totalStopScore += health.avgStopScore;
+ scoreCount++;
+ }
+ }
+
+ return {
+ totalTasks,
+ completedTasks,
+ stoppedTasks,
+ avgStopScore: scoreCount > 0 ? totalStopScore / scoreCount : 0,
+ systemHealth,
+ };
+ }
+
+ /**
+ * Check if any system requires attention
+ */
+ checkAlerts(): string[] {
+ const alerts: string[] = [];
+
+ for (const [id, health] of this.systems) {
+ if (health.status === "unhealthy") {
+ alerts.push(`CRITICAL: System ${id} is unhealthy`);
+ } else if (health.status === "degraded") {
+ alerts.push(`WARNING: System ${id} is degraded`);
+ }
+
+ if (health.stopRate > 0.5) {
+ alerts.push(`HIGH STOP RATE: System ${id} has ${(health.stopRate * 100).toFixed(1)}% stop rate`);
+ }
+
+ if (health.queueDepth > 50) {
+ alerts.push(`QUEUE OVERLOAD: System ${id} has ${health.queueDepth} pending tasks`);
+ }
+ }
+
+ return alerts;
+ }
+}
+
+/**
+ * Create a Meta Supervisor instance
+ */
+export function createMetaSupervisor(): MetaSupervisor {
+ return new MetaSupervisor();
+}
diff --git a/src/queue/queue.ts b/src/queue/queue.ts
new file mode 100644
index 0000000..264adc6
--- /dev/null
+++ b/src/queue/queue.ts
@@ -0,0 +1,118 @@
+/**
+ * Queue Module - In-memory queue with optional Redis/BullMQ support
+ */
+
+export interface QueueJob {
+ id: string;
+ name: string;
+ data: Record;
+ status: "pending" | "processing" | "completed" | "failed";
+ created_at: Date;
+ processed_at?: Date;
+}
+
+export interface QueueAdapter {
+ mode: "in-memory" | "redis";
+ isHealthy(): boolean;
+ add(name: string, data: Record): Promise;
+ process(name: string, handler: (job: QueueJob) => Promise): void;
+ getJob(id: string): QueueJob | undefined;
+ getStats(): { pending: number; processing: number; completed: number; failed: number };
+}
+
+/**
+ * In-memory queue implementation (fallback when Redis is not available)
+ */
+function createInMemoryQueue(): QueueAdapter {
+ const jobs = new Map();
+ const handlers = new Map Promise>();
+ let jobCounter = 0;
+
+ return {
+ mode: "in-memory",
+
+ isHealthy(): boolean {
+ return true;
+ },
+
+ async add(name: string, data: Record): Promise {
+ const id = `job_${++jobCounter}`;
+ const job: QueueJob = {
+ id,
+ name,
+ data,
+ status: "pending",
+ created_at: new Date(),
+ };
+ jobs.set(id, job);
+
+ // Process immediately if handler exists
+ const handler = handlers.get(name);
+ if (handler) {
+ setImmediate(async () => {
+ job.status = "processing";
+ try {
+ await handler(job);
+ job.status = "completed";
+ job.processed_at = new Date();
+ } catch (error) {
+ job.status = "failed";
+ console.error(`Job ${id} failed:`, error);
+ }
+ });
+ }
+
+ return id;
+ },
+
+ process(name: string, handler: (job: QueueJob) => Promise): void {
+ handlers.set(name, handler);
+ },
+
+ getJob(id: string): QueueJob | undefined {
+ return jobs.get(id);
+ },
+
+ getStats(): { pending: number; processing: number; completed: number; failed: number } {
+ let pending = 0;
+ let processing = 0;
+ let completed = 0;
+ let failed = 0;
+
+ for (const job of jobs.values()) {
+ switch (job.status) {
+ case "pending":
+ pending++;
+ break;
+ case "processing":
+ processing++;
+ break;
+ case "completed":
+ completed++;
+ break;
+ case "failed":
+ failed++;
+ break;
+ }
+ }
+
+ return { pending, processing, completed, failed };
+ },
+ };
+}
+
+/**
+ * Initialize queue adapter
+ * Uses in-memory queue by default, can be extended to support Redis/BullMQ
+ */
+export function initQueue(): QueueAdapter {
+ const redisUrl = process.env.REDIS_URL;
+ const queueEnabled = process.env.QUEUE_ENABLED === "true";
+
+ if (redisUrl && queueEnabled) {
+ // TODO: Implement Redis/BullMQ adapter when needed
+ console.log("Redis URL provided but BullMQ not implemented. Using in-memory queue.");
+ }
+
+ return createInMemoryQueue();
+}
diff --git a/src/supervisor/engineeringLead.ts b/src/supervisor/engineeringLead.ts
new file mode 100644
index 0000000..7d61a5d
--- /dev/null
+++ b/src/supervisor/engineeringLead.ts
@@ -0,0 +1,158 @@
+/**
+ * Engineering Lead Supervisor Module
+ * Controls truth, completeness, and cross-layer consistency
+ */
+
+import { computeStopScore, analyzeContent, type StopReason, type StopScoreResult } from "../audit/stopScorer.js";
+
+export interface SupervisorDecision {
+ decision: "APPROVED" | "STOP_REQUIRED";
+ finalStatus: "COMPLETE" | "COMPLETE_WITH_GAPS" | "STOP_REQUIRED";
+ riskLevel: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
+ stopScore: number;
+ verifiedArtefacts: string[];
+ missingInvalidParts: string[];
+ requiredNextAction: string;
+}
+
+export interface WorkSubmission {
+ taskId: string;
+ description: string;
+ artefacts: string[];
+ claims: string[];
+ hasTests: boolean;
+ hasSchema: boolean;
+ hasDeployConfig: boolean;
+}
+
+/**
+ * Engineering Lead Supervisor
+ * Reviews work submissions and makes approval decisions
+ */
+export class EngineeringLeadSupervisor {
+ private readonly stopThreshold: number;
+
+ constructor(stopThreshold = 40) {
+ this.stopThreshold = stopThreshold;
+ }
+
+ /**
+ * Reviews a work submission and returns a decision
+ */
+ review(submission: WorkSubmission): SupervisorDecision {
+ const reasons: StopReason[] = [];
+
+ // Check for missing artefacts
+ if (submission.artefacts.length === 0) {
+ reasons.push("UNPROVEN_CLAIM");
+ }
+
+ // Check for missing tests
+ if (!submission.hasTests) {
+ reasons.push("MISSING_TESTS");
+ }
+
+ // Check for missing schema
+ if (!submission.hasSchema) {
+ reasons.push("MISSING_SQL_OR_SCHEMA");
+ }
+
+ // Check for missing deploy config
+ if (!submission.hasDeployConfig) {
+ reasons.push("MISSING_DEPLOY_CONFIG");
+ }
+
+ // Analyze description for problematic content
+ const contentReasons = analyzeContent(submission.description);
+ reasons.push(...contentReasons);
+
+ // Analyze claims
+ for (const claim of submission.claims) {
+ const claimReasons = analyzeContent(claim);
+ reasons.push(...claimReasons);
+ }
+
+ // Compute stop score
+ const scoreResult = computeStopScore([...new Set(reasons)]);
+
+ // Make decision
+ const decision = this.makeDecision(scoreResult, submission);
+
+ return decision;
+ }
+
+ private makeDecision(scoreResult: StopScoreResult, submission: WorkSubmission): SupervisorDecision {
+ const stopRequired = scoreResult.stopRequired;
+
+ let finalStatus: "COMPLETE" | "COMPLETE_WITH_GAPS" | "STOP_REQUIRED";
+ if (stopRequired) {
+ finalStatus = "STOP_REQUIRED";
+ } else if (scoreResult.reasons.length > 0) {
+ finalStatus = "COMPLETE_WITH_GAPS";
+ } else {
+ finalStatus = "COMPLETE";
+ }
+
+ const missingParts: string[] = [];
+ if (!submission.hasTests) missingParts.push("Tests");
+ if (!submission.hasSchema) missingParts.push("Schema/SQL");
+ if (!submission.hasDeployConfig) missingParts.push("Deploy config");
+ if (submission.artefacts.length === 0) missingParts.push("Verifiable artefacts");
+
+ let nextAction: string;
+ if (stopRequired) {
+ nextAction = `STOP: Address ${scoreResult.reasons.join(", ")} before proceeding`;
+ } else if (missingParts.length > 0) {
+ nextAction = `Add missing: ${missingParts.join(", ")}`;
+ } else {
+ nextAction = "Proceed to next task";
+ }
+
+ return {
+ decision: stopRequired ? "STOP_REQUIRED" : "APPROVED",
+ finalStatus,
+ riskLevel: scoreResult.severity,
+ stopScore: scoreResult.score,
+ verifiedArtefacts: submission.artefacts,
+ missingInvalidParts: missingParts,
+ requiredNextAction: nextAction,
+ };
+ }
+
+ /**
+ * Quick validation for common issues
+ */
+ quickValidate(content: string): { valid: boolean; issues: string[] } {
+ const issues: string[] = [];
+
+ if (content.includes("$") || content.includes("€")) {
+ if (!content.toLowerCase().includes("unknown")) {
+ issues.push("Pricing mentioned without UNKNOWN marker");
+ }
+ }
+
+ if (content.toLowerCase().includes("legal") || content.toLowerCase().includes("warranty")) {
+ issues.push("Legal statement detected");
+ }
+
+ const claimWords = ["implemented", "deployed", "connected", "scraped"];
+ for (const word of claimWords) {
+ if (content.toLowerCase().includes(word) && !content.includes("file:")) {
+ issues.push(`Claim "${word}" without file reference`);
+ }
+ }
+
+ return {
+ valid: issues.length === 0,
+ issues,
+ };
+ }
+}
+
+/**
+ * Create a default Engineering Lead Supervisor instance
+ */
+export function createEngineeringLeadSupervisor(): EngineeringLeadSupervisor {
+ const threshold = parseInt(process.env.STOP_SCORE_THRESHOLD ?? "40", 10);
+ return new EngineeringLeadSupervisor(threshold);
+}
diff --git a/tests/enforcementGate.test.ts b/tests/enforcementGate.test.ts
new file mode 100644
index 0000000..35e8c22
--- /dev/null
+++ b/tests/enforcementGate.test.ts
@@ -0,0 +1,78 @@
+/**
+ * Tests for Enforcement Gate - Hard STOP enforcement
+ */
+
+import { test } from "node:test";
+import assert from "node:assert";
+import { verifyClaims, generateVerificationReport } from "../src/audit/claimVerifier.js";
+
+test("verifyClaims detects unproven scraping claims", () => {
+ const content = "Successfully scraped 10 portals and stored the data";
+ const artefacts = ["data/portal1.json"]; // Only 1 artefact for 10 claimed
+
+ const result = verifyClaims(content, artefacts);
+
+ assert.strictEqual(result.allVerified, false);
+ assert.strictEqual(result.failedClaims.length, 1);
+ assert.strictEqual(result.failedClaims[0].claimedCount, 10);
+ assert.strictEqual(result.failedClaims[0].actualCount, 1);
+ assert.strictEqual(result.failedClaims[0].discrepancy, 9);
+});
+
+test("verifyClaims passes when artefacts match claims", () => {
+ const content = "Created 3 files for the feature";
+ const artefacts = ["src/feature.ts", "src/types.ts", "src/utils.ts"];
+
+ const result = verifyClaims(content, artefacts);
+
+ assert.strictEqual(result.allVerified, true);
+ assert.strictEqual(result.failedClaims.length, 0);
+ assert.strictEqual(result.passedClaims.length, 1);
+});
+
+test("verifyClaims handles no claims in content", () => {
+ const content = "Fixed a bug in the login flow";
+ const artefacts = ["src/login.ts"];
+
+ const result = verifyClaims(content, artefacts);
+
+ assert.strictEqual(result.allVerified, true);
+ assert.strictEqual(result.totalClaims, 0);
+});
+
+test("verifyClaims detects multiple unproven claims", () => {
+ const content = "Implemented 5 features and added 10 tests";
+ const artefacts = ["src/feature1.ts", "tests/test1.test.ts"];
+
+ const result = verifyClaims(content, artefacts);
+
+ assert.strictEqual(result.allVerified, false);
+ assert.strictEqual(result.failedClaims.length, 2);
+});
+
+test("generateVerificationReport creates readable output", () => {
+ const content = "Scraped 5 URLs";
+ const artefacts = ["data/url1.json"];
+
+ const result = verifyClaims(content, artefacts);
+ const report = generateVerificationReport(result);
+
+ assert.ok(report.includes("Claim Verification Report"));
+ assert.ok(report.includes("VERIFICATION FAILED"));
+ assert.ok(report.includes("Claimed: 5"));
+ assert.ok(report.includes("Actual: 1"));
+});
+
+test("verifyClaims filters artefacts by type correctly", () => {
+ const content = "Added 2 tests";
+ const artefacts = [
+ "src/feature.ts", // Not a test file
+ "tests/feature.test.ts", // Test file
+ "tests/utils.test.ts", // Test file
+ ];
+
+ const result = verifyClaims(content, artefacts);
+
+ assert.strictEqual(result.allVerified, true);
+ assert.strictEqual(result.passedClaims[0].actualCount, 2);
+});
diff --git a/tests/stopScorer.test.ts b/tests/stopScorer.test.ts
new file mode 100644
index 0000000..f7c69b6
--- /dev/null
+++ b/tests/stopScorer.test.ts
@@ -0,0 +1,81 @@
+/**
+ * Tests for STOP Scorer
+ */
+
+import { test } from "node:test";
+import assert from "node:assert";
+import { computeStopScore, analyzeContent, type StopReason } from "../src/audit/stopScorer.js";
+
+test("computeStopScore returns LOW severity for empty reasons", () => {
+ const result = computeStopScore([]);
+
+ assert.strictEqual(result.score, 0);
+ assert.strictEqual(result.severity, "LOW");
+ assert.strictEqual(result.stopRequired, false);
+});
+
+test("computeStopScore returns HIGH severity for pricing violation", () => {
+ const reasons: StopReason[] = ["PRICING_WITHOUT_FACT"];
+ const result = computeStopScore(reasons);
+
+ assert.strictEqual(result.score, 40);
+ assert.strictEqual(result.severity, "HIGH");
+ assert.strictEqual(result.stopRequired, true);
+});
+
+test("computeStopScore returns CRITICAL severity for multiple violations", () => {
+ const reasons: StopReason[] = [
+ "PRICING_WITHOUT_FACT",
+ "LEGAL_STATEMENT",
+ "UNPROVEN_CLAIM",
+ ];
+ const result = computeStopScore(reasons);
+
+ assert.strictEqual(result.score, 100); // 40 + 40 + 30 = 110, capped at 100
+ assert.strictEqual(result.severity, "CRITICAL");
+ assert.strictEqual(result.stopRequired, true);
+});
+
+test("computeStopScore returns MEDIUM severity for missing tests only", () => {
+ const reasons: StopReason[] = ["MISSING_TESTS", "MISSING_DEPLOY_CONFIG"];
+ const result = computeStopScore(reasons);
+
+ assert.strictEqual(result.score, 30); // 15 + 15
+ assert.strictEqual(result.severity, "MEDIUM");
+ assert.strictEqual(result.stopRequired, false);
+});
+
+test("analyzeContent detects pricing without UNKNOWN marker", () => {
+ const content = "Set the price to $99/month";
+ const reasons = analyzeContent(content);
+
+ assert.ok(reasons.includes("PRICING_WITHOUT_FACT"));
+});
+
+test("analyzeContent does not flag pricing with UNKNOWN marker", () => {
+ const content = "Price is UNKNOWN - requires approval";
+ const reasons = analyzeContent(content);
+
+ assert.ok(!reasons.includes("PRICING_WITHOUT_FACT"));
+});
+
+test("analyzeContent detects legal statements", () => {
+ const content = "Added legal disclaimer to the footer";
+ const reasons = analyzeContent(content);
+
+ assert.ok(reasons.includes("LEGAL_STATEMENT"));
+});
+
+test("analyzeContent detects unproven claims", () => {
+ const content = "Successfully implemented the feature";
+ const reasons = analyzeContent(content);
+
+ assert.ok(reasons.includes("UNPROVEN_CLAIM"));
+});
+
+test("analyzeContent does not flag claims with file references", () => {
+ const content = "Implemented in file: src/feature.ts";
+ const reasons = analyzeContent(content);
+
+ assert.ok(!reasons.includes("UNPROVEN_CLAIM"));
+});
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..79e903a
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "Bundler",
+ "outDir": "dist",
+ "rootDir": "src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true
+ },
+ "include": ["src/**/*.ts"],
+ "exclude": ["node_modules", "dist", "tests"]
+}