name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
env:
NODE_OPTIONS: --max-old-space-size=4096
jobs:
# Pre-flight checks that don't require containers
preflight:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Create test directories
run: mkdir -p tmp/test-brain development/reports/coverage
- name: Security audit
run: npm run audit:check
- name: Check formatting
run: npm run format:check
- name: Run linting (strict - zero warnings)
run: npm run lint:strict
- name: Run type checking
run: npm run typecheck
# Local tests: Unit + Integration (parallel execution, all deps mocked)
local-tests:
needs: preflight
runs-on: ubuntu-latest
timeout-minutes: 5
strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Create test directories
run: mkdir -p tmp/test-brain development/reports/coverage
- name: Run Local Tests (Unit + Integration)
run: npm run test:coverage
env:
NODE_ENV: test
- name: Upload coverage
if: matrix.node-version == '20.x'
uses: actions/upload-artifact@v4
with:
name: local-tests-coverage
path: development/reports/coverage/
retention-days: 7
if-no-files-found: warn
# E2E tests: Real PostgreSQL + Real Ollama (sequential execution)
e2e-tests:
needs: preflight
runs-on: ubuntu-latest
timeout-minutes: 15
services:
postgres:
image: pgvector/pgvector:pg16
env:
POSTGRES_USER: thoughtmcp_test
POSTGRES_PASSWORD: test_password
POSTGRES_DB: thoughtmcp_test
ports:
- 5433:5432
options: >-
--health-cmd "pg_isready -U thoughtmcp_test -d thoughtmcp_test"
--health-interval 5s
--health-timeout 3s
--health-retries 10
--health-start-period 5s
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Create test directories
run: mkdir -p tmp/test-brain development/reports/coverage
- name: Wait for PostgreSQL
run: |
echo "Waiting for PostgreSQL to be ready..."
for i in {1..30}; do
if pg_isready -h localhost -p 5433 -U thoughtmcp_test; then
echo "PostgreSQL is ready!"
break
fi
echo "Waiting... ($i/30)"
sleep 2
done
- name: Initialize database schema
run: |
echo "Initializing database schema..."
PGPASSWORD=test_password psql -h localhost -p 5433 -U thoughtmcp_test -d thoughtmcp_test -f scripts/db/init.sql
PGPASSWORD=test_password psql -h localhost -p 5433 -U thoughtmcp_test -d thoughtmcp_test -f scripts/db/enable-pgvector.sql
echo "Database schema initialized successfully"
- name: Cache Docker images
uses: actions/cache@v4
with:
path: /tmp/docker-images
key: docker-ollama-${{ runner.os }}-${{ hashFiles('docker-compose.test.yml') }}
restore-keys: |
docker-ollama-${{ runner.os }}-
- name: Load cached Docker images
run: |
if [ -f /tmp/docker-images/ollama.tar ]; then
echo "Loading cached Ollama image..."
docker load -i /tmp/docker-images/ollama.tar || true
fi
- name: Start Ollama container
run: |
docker run -d --name ollama-test \
-p 11435:11434 \
ollama/ollama:latest
echo "Waiting for Ollama to be ready..."
for i in {1..30}; do
if docker exec ollama-test ollama list 2>/dev/null; then
echo "Ollama is ready!"
break
fi
echo "Waiting... ($i/30)"
sleep 2
done
- name: Run E2E Tests
run: npm run test:e2e
env:
NODE_ENV: test
DB_HOST: localhost
DB_PORT: "5433"
DB_USER: thoughtmcp_test
DB_PASSWORD: test_password
DB_NAME: thoughtmcp_test
OLLAMA_HOST: http://localhost:11435
# Skip container management - using GitHub Actions services
SKIP_CONTAINER_MANAGEMENT: "true"
- name: Save Docker images to cache
run: |
mkdir -p /tmp/docker-images
docker save ollama/ollama:latest -o /tmp/docker-images/ollama.tar || true
- name: Stop Ollama container
if: always()
run: docker stop ollama-test && docker rm ollama-test || true
# Finalize: Build and validate
finalize:
needs: [local-tests, e2e-tests]
runs-on: ubuntu-latest
timeout-minutes: 10
if: always()
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Create directories
run: mkdir -p development/reports/coverage
- name: Download coverage artifact
uses: actions/download-artifact@v4
with:
name: local-tests-coverage
path: development/reports/coverage/
continue-on-error: true
- name: Build project
run: npm run build:quick
- name: Verify build artifacts
run: |
required_files=(
"dist/index.js"
"dist/index.d.ts"
"dist/server/mcp-server.d.ts"
"dist/reasoning/orchestrator.d.ts"
"dist/utils/index.js"
)
for file in "${required_files[@]}"; do
if [ ! -f "$file" ]; then
echo "Build artifact missing: $file"
exit 1
fi
done
echo "All build artifacts verified"
- name: Test MCP server startup
run: |
timeout 10s node dist/index.js --help || true
echo "MCP server startup test completed"
- name: Validate package.json
run: |
node -e "
const pkg = require('./package.json');
const required = ['name', 'version', 'description', 'main', 'scripts', 'keywords', 'author', 'license'];
const missing = required.filter(field => !pkg[field]);
if (missing.length > 0) {
console.error('Missing required package.json fields:', missing);
process.exit(1);
}
if (!pkg.scripts.build || !pkg.scripts.test || !pkg.scripts.lint) {
console.error('Missing required npm scripts');
process.exit(1);
}
if (!pkg.engines || !pkg.engines.node) {
console.error('Missing Node.js engine specification');
process.exit(1);
}
console.log('package.json validation passed');
"
- name: Test package installation
run: |
npm pack
PACKAGE_NAME=$(ls *.tgz | head -1)
echo "Package created: $PACKAGE_NAME"
mkdir -p /tmp/test-install
cd /tmp/test-install
npm init -y
npm install $GITHUB_WORKSPACE/$PACKAGE_NAME
node --input-type=module -e "
try {
const pkg = await import('thoughtmcp');
console.log('Package installation and import successful');
console.log('Available exports:', Object.keys(pkg));
} catch (error) {
console.error('Package import failed:', error.message);
process.exit(1);
}
"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
directory: ./development/reports/coverage/
fail_ci_if_error: false
verbose: true
- name: Generate CI summary
run: |
echo "## CI Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Test Results" >> $GITHUB_STEP_SUMMARY
echo "| Category | Description | Status |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Local Tests | Unit + Integration (mocked deps) | ${{ needs.local-tests.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| E2E Tests | Real PostgreSQL + Ollama | ${{ needs.e2e-tests.result }} |" >> $GITHUB_STEP_SUMMARY
# Security job
security:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run security audit
run: npm run audit:check
- name: Check for sensitive files
run: |
if find . -name "*.env*" -not -path "./node_modules/*" -not -name "*.example" | grep -q .; then
echo "Warning: Found potential environment files"
find . -name "*.env*" -not -path "./node_modules/*" -not -name "*.example"
fi
- name: Scan for secrets
run: |
if grep -r -i "password\|secret\|key\|token" --include="*.ts" --include="*.js" --include="*.json" src/ | grep -v "// " | grep -v "test" | grep -q .; then
echo "Warning: Potential secrets found in source code"
grep -r -i "password\|secret\|key\|token" --include="*.ts" --include="*.js" --include="*.json" src/ | grep -v "// " | grep -v "test" || true
fi
# Compatibility job
compatibility:
needs: finalize
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18.x, 20.x, 22.x]
exclude:
- os: windows-latest
node-version: 18.x
- os: macos-latest
node-version: 18.x
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Create test directories
run: mkdir -p tmp/test-brain
- name: Build project
run: npm run build:quick
- name: Validate build
run: |
npm run typecheck
echo "✅ TypeScript compilation successful on ${{ matrix.os }}"