feat: add new icon components including Envelope, ChatCircle, HardDrives, WarningCircle, CheckCircle, XCircle, TrendUp, ShieldWarning, LockKey, Crown, Sparkle, BookOpen, Tree, Broom, Export, UploadSimple, Funnel, FunnelSimple, MapTrifold, PushPinSimple, Buildings, GithubLogo, and GoogleLogo

This commit is contained in:
2025-12-30 12:21:15 +00:00
parent 29061af4b1
commit cfa1e5bfee
2132 changed files with 206959 additions and 206933 deletions
File diff suppressed because it is too large Load Diff
+57 -57
View File
@@ -1,57 +1,57 @@
name: CLI Build
on:
push:
branches: [ main, develop ]
paths:
- 'frontends/cli/**'
- '.github/workflows/ci/cli.yml'
pull_request:
branches: [ main, develop ]
paths:
- 'frontends/cli/**'
- '.github/workflows/ci/cli.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
build:
name: Build MetaBuilder CLI
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build python3-pip libssl-dev
- name: Install Conan
run: |
python3 -m pip install --upgrade pip
python3 -m pip install conan
- name: Detect Conan profile
run: conan profile detect --force
- name: Install Conan dependencies
run: |
mkdir -p frontends/cli/build
conan install frontends/cli \
--output-folder frontends/cli/build \
--build missing
- name: Configure CLI with CMake
run: |
cmake -S frontends/cli -B frontends/cli/build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=frontends/cli/build/conan_toolchain.cmake
- name: Build CLI executable
run: cmake --build frontends/cli/build
- name: Run help command to verify binary
run: frontends/cli/build/bin/metabuilder-cli --help
name: CLI Build
on:
push:
branches: [ main, develop ]
paths:
- 'frontends/cli/**'
- '.github/workflows/ci/cli.yml'
pull_request:
branches: [ main, develop ]
paths:
- 'frontends/cli/**'
- '.github/workflows/ci/cli.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
build:
name: Build MetaBuilder CLI
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build python3-pip libssl-dev
- name: Install Conan
run: |
python3 -m pip install --upgrade pip
python3 -m pip install conan
- name: Detect Conan profile
run: conan profile detect --force
- name: Install Conan dependencies
run: |
mkdir -p frontends/cli/build
conan install frontends/cli \
--output-folder frontends/cli/build \
--build missing
- name: Configure CLI with CMake
run: |
cmake -S frontends/cli -B frontends/cli/build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=frontends/cli/build/conan_toolchain.cmake
- name: Build CLI executable
run: cmake --build frontends/cli/build
- name: Run help command to verify binary
run: frontends/cli/build/bin/metabuilder-cli --help
+308 -308
View File
@@ -1,308 +1,308 @@
name: C++ Build & Test
on:
push:
branches: [ main, develop ]
paths:
- 'dbal/production/**'
- 'dbal/shared/tools/cpp-build-assistant.cjs'
- '.github/workflows/cpp-build.yml'
pull_request:
branches: [ main, develop ]
paths:
- 'dbal/production/**'
- 'dbal/shared/tools/cpp-build-assistant.cjs'
- '.github/workflows/cpp-build.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
check-implementation:
name: Check C++ Implementation Status
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
has_sources: ${{ steps.check.outputs.has_sources }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Check if C++ sources exist
id: check
run: |
if [ -d "dbal/production/src" ] && [ "$(find dbal/production/src -name '*.cpp' | wc -l)" -gt 0 ]; then
echo "has_sources=true" >> $GITHUB_OUTPUT
echo "✓ C++ source files found"
else
echo "has_sources=false" >> $GITHUB_OUTPUT
echo "⚠ C++ implementation not yet available - skipping build"
fi
build-linux:
name: Build on Linux
runs-on: ubuntu-latest
needs: check-implementation
if: needs.check-implementation.outputs.has_sources == 'true'
strategy:
matrix:
build_type: [Release, Debug]
compiler:
- { cc: gcc, cxx: g++ }
- { cc: clang, cxx: clang++ }
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build ${{ matrix.compiler.cxx }}
pip install conan
- name: Setup Conan profile
run: conan profile detect --force
- name: Check C++ dependencies
run: bun run cpp:check
- name: Initialize Conanfile
run: bun run cpp:init
- name: Install Conan dependencies
env:
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
CC: ${{ matrix.compiler.cc }}
CXX: ${{ matrix.compiler.cxx }}
run: bun run cpp:install
- name: Configure CMake
env:
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
CC: ${{ matrix.compiler.cc }}
CXX: ${{ matrix.compiler.cxx }}
run: |
if [ "${{ matrix.build_type }}" = "Debug" ]; then
bun run cpp:build -- configure --debug
else
bun run cpp:configure
fi
- name: Build C++ project
env:
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
run: bun run cpp:build
- name: Run C++ tests
run: bun run cpp:test
- name: Upload build artifacts
if: matrix.build_type == 'Release' && matrix.compiler.cxx == 'g++'
uses: actions/upload-artifact@v4
with:
name: dbal-daemon-linux
path: |
dbal/production/build/dbal_daemon
dbal/production/build/*.so
retention-days: 7
build-macos:
name: Build on macOS
runs-on: macos-latest
needs: check-implementation
if: needs.check-implementation.outputs.has_sources == 'true'
strategy:
matrix:
build_type: [Release, Debug]
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install system dependencies
run: |
brew install cmake ninja conan
- name: Setup Conan profile
run: conan profile detect --force
- name: Check C++ dependencies
run: bun run cpp:check
- name: Full C++ build
env:
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
run: |
if [ "${{ matrix.build_type }}" = "Debug" ]; then
node dbal/shared/tools/cpp-build-assistant.cjs full --debug
else
bun run cpp:full
fi
- name: Run C++ tests
run: bun run cpp:test
- name: Upload build artifacts
if: matrix.build_type == 'Release'
uses: actions/upload-artifact@v4
with:
name: dbal-daemon-macos
path: |
dbal/production/build/dbal_daemon
dbal/production/build/*.dylib
retention-days: 7
build-windows:
name: Build on Windows
runs-on: windows-latest
needs: check-implementation
if: needs.check-implementation.outputs.has_sources == 'true'
strategy:
matrix:
build_type: [Release, Debug]
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install system dependencies
run: |
choco install cmake ninja -y
pip install conan
- name: Setup Conan profile
run: conan profile detect --force
- name: Check C++ dependencies
run: bun run cpp:check
- name: Full C++ build
env:
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
shell: bash
run: |
if [ "${{ matrix.build_type }}" = "Debug" ]; then
node dbal/shared/tools/cpp-build-assistant.cjs full --debug
else
bun run cpp:full
fi
- name: Run C++ tests
run: bun run cpp:test
- name: Upload build artifacts
if: matrix.build_type == 'Release'
uses: actions/upload-artifact@v4
with:
name: dbal-daemon-windows
path: |
dbal/production/build/dbal_daemon.exe
dbal/production/build/*.dll
retention-days: 7
code-quality:
name: C++ Code Quality
runs-on: ubuntu-latest
needs: check-implementation
if: needs.check-implementation.outputs.has_sources == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build cppcheck clang-format
pip install conan
- name: Setup Conan
run: conan profile detect --force
- name: Configure project
run: bun run cpp:full
- name: Run cppcheck
run: |
cppcheck --enable=all --inconclusive --error-exitcode=1 \
--suppress=missingIncludeSystem \
-I dbal/production/include \
dbal/production/src/
continue-on-error: true
- name: Check formatting
run: |
find dbal/production/src dbal/production/include -name '*.cpp' -o -name '*.hpp' | \
xargs clang-format --dry-run --Werror
continue-on-error: true
integration:
name: Integration Test
runs-on: ubuntu-latest
needs: [check-implementation, build-linux]
if: needs.check-implementation.outputs.has_sources == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Download Linux build
uses: actions/download-artifact@v4
with:
name: dbal-daemon-linux
path: dbal/production/build/
- name: Make daemon executable
run: chmod +x dbal/production/build/dbal_daemon
- name: Run integration tests
run: |
# Start C++ daemon
./dbal/production/build/dbal_daemon &
DAEMON_PID=$!
sleep 2
# Run TypeScript integration tests
bun run test:unit
# Cleanup
kill $DAEMON_PID
continue-on-error: true
name: C++ Build & Test
on:
push:
branches: [ main, develop ]
paths:
- 'dbal/production/**'
- 'dbal/shared/tools/cpp-build-assistant.cjs'
- '.github/workflows/cpp-build.yml'
pull_request:
branches: [ main, develop ]
paths:
- 'dbal/production/**'
- 'dbal/shared/tools/cpp-build-assistant.cjs'
- '.github/workflows/cpp-build.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
check-implementation:
name: Check C++ Implementation Status
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
has_sources: ${{ steps.check.outputs.has_sources }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Check if C++ sources exist
id: check
run: |
if [ -d "dbal/production/src" ] && [ "$(find dbal/production/src -name '*.cpp' | wc -l)" -gt 0 ]; then
echo "has_sources=true" >> $GITHUB_OUTPUT
echo "✓ C++ source files found"
else
echo "has_sources=false" >> $GITHUB_OUTPUT
echo "⚠ C++ implementation not yet available - skipping build"
fi
build-linux:
name: Build on Linux
runs-on: ubuntu-latest
needs: check-implementation
if: needs.check-implementation.outputs.has_sources == 'true'
strategy:
matrix:
build_type: [Release, Debug]
compiler:
- { cc: gcc, cxx: g++ }
- { cc: clang, cxx: clang++ }
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build ${{ matrix.compiler.cxx }}
pip install conan
- name: Setup Conan profile
run: conan profile detect --force
- name: Check C++ dependencies
run: bun run cpp:check
- name: Initialize Conanfile
run: bun run cpp:init
- name: Install Conan dependencies
env:
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
CC: ${{ matrix.compiler.cc }}
CXX: ${{ matrix.compiler.cxx }}
run: bun run cpp:install
- name: Configure CMake
env:
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
CC: ${{ matrix.compiler.cc }}
CXX: ${{ matrix.compiler.cxx }}
run: |
if [ "${{ matrix.build_type }}" = "Debug" ]; then
bun run cpp:build -- configure --debug
else
bun run cpp:configure
fi
- name: Build C++ project
env:
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
run: bun run cpp:build
- name: Run C++ tests
run: bun run cpp:test
- name: Upload build artifacts
if: matrix.build_type == 'Release' && matrix.compiler.cxx == 'g++'
uses: actions/upload-artifact@v4
with:
name: dbal-daemon-linux
path: |
dbal/production/build/dbal_daemon
dbal/production/build/*.so
retention-days: 7
build-macos:
name: Build on macOS
runs-on: macos-latest
needs: check-implementation
if: needs.check-implementation.outputs.has_sources == 'true'
strategy:
matrix:
build_type: [Release, Debug]
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install system dependencies
run: |
brew install cmake ninja conan
- name: Setup Conan profile
run: conan profile detect --force
- name: Check C++ dependencies
run: bun run cpp:check
- name: Full C++ build
env:
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
run: |
if [ "${{ matrix.build_type }}" = "Debug" ]; then
node dbal/shared/tools/cpp-build-assistant.cjs full --debug
else
bun run cpp:full
fi
- name: Run C++ tests
run: bun run cpp:test
- name: Upload build artifacts
if: matrix.build_type == 'Release'
uses: actions/upload-artifact@v4
with:
name: dbal-daemon-macos
path: |
dbal/production/build/dbal_daemon
dbal/production/build/*.dylib
retention-days: 7
build-windows:
name: Build on Windows
runs-on: windows-latest
needs: check-implementation
if: needs.check-implementation.outputs.has_sources == 'true'
strategy:
matrix:
build_type: [Release, Debug]
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install system dependencies
run: |
choco install cmake ninja -y
pip install conan
- name: Setup Conan profile
run: conan profile detect --force
- name: Check C++ dependencies
run: bun run cpp:check
- name: Full C++ build
env:
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
shell: bash
run: |
if [ "${{ matrix.build_type }}" = "Debug" ]; then
node dbal/shared/tools/cpp-build-assistant.cjs full --debug
else
bun run cpp:full
fi
- name: Run C++ tests
run: bun run cpp:test
- name: Upload build artifacts
if: matrix.build_type == 'Release'
uses: actions/upload-artifact@v4
with:
name: dbal-daemon-windows
path: |
dbal/production/build/dbal_daemon.exe
dbal/production/build/*.dll
retention-days: 7
code-quality:
name: C++ Code Quality
runs-on: ubuntu-latest
needs: check-implementation
if: needs.check-implementation.outputs.has_sources == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build cppcheck clang-format
pip install conan
- name: Setup Conan
run: conan profile detect --force
- name: Configure project
run: bun run cpp:full
- name: Run cppcheck
run: |
cppcheck --enable=all --inconclusive --error-exitcode=1 \
--suppress=missingIncludeSystem \
-I dbal/production/include \
dbal/production/src/
continue-on-error: true
- name: Check formatting
run: |
find dbal/production/src dbal/production/include -name '*.cpp' -o -name '*.hpp' | \
xargs clang-format --dry-run --Werror
continue-on-error: true
integration:
name: Integration Test
runs-on: ubuntu-latest
needs: [check-implementation, build-linux]
if: needs.check-implementation.outputs.has_sources == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Download Linux build
uses: actions/download-artifact@v4
with:
name: dbal-daemon-linux
path: dbal/production/build/
- name: Make daemon executable
run: chmod +x dbal/production/build/dbal_daemon
- name: Run integration tests
run: |
# Start C++ daemon
./dbal/production/build/dbal_daemon &
DAEMON_PID=$!
sleep 2
# Run TypeScript integration tests
bun run test:unit
# Cleanup
kill $DAEMON_PID
continue-on-error: true
+199 -199
View File
@@ -1,199 +1,199 @@
name: Stub Implementation Detection
on:
pull_request:
branches: [ main, master, develop ]
types: [opened, synchronize, reopened]
push:
branches: [ main, master, develop ]
workflow_dispatch:
schedule:
- cron: '0 0 * * 1' # Weekly on Monday
permissions:
contents: read
pull-requests: write
checks: write
jobs:
detect-stubs:
name: Detect Stub Implementations
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontends/nextjs
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.4'
- name: Cache Bun dependencies
uses: actions/cache@v4
with:
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
path: |
frontends/nextjs/node_modules
~/.bun
restore-keys: bun-deps-${{ runner.os }}-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Generate Prisma Client
run: bun run db:generate
env:
DATABASE_URL: file:./dev.db
# Pattern-based stub detection
- name: Detect stub patterns
id: detect-patterns
run: bunx tsx ../../tools/detect-stub-implementations.ts > stub-patterns.json
continue-on-error: true
# Implementation completeness analysis
- name: Analyze implementation completeness
id: analyze-completeness
run: bunx tsx ../../tools/analyze-implementation-completeness.ts > implementation-analysis.json
continue-on-error: true
# Generate detailed report
- name: Generate stub report
id: generate-report
run: bunx tsx ../../tools/generate-stub-report.ts > stub-report.md
continue-on-error: true
# Check for unimplemented TODOs in changed files (PR only)
- name: Check changed files for stubs
if: github.event_name == 'pull_request'
id: check-changed
run: |
git diff origin/${{ github.base_ref }}...HEAD -- 'src/**/*.{ts,tsx}' | \
grep -E '^\+.*(TODO|FIXME|not implemented|stub|placeholder|mock)' | \
tee changed-stubs.txt || true
STUB_COUNT=$(wc -l < changed-stubs.txt)
echo "stub_count=$STUB_COUNT" >> $GITHUB_OUTPUT
continue-on-error: true
# Post PR comment with findings
- name: Post stub detection comment
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let comment = '## 🔍 Stub Implementation Detection Report\n\n';
try {
const patternData = JSON.parse(fs.readFileSync('stub-patterns.json', 'utf8'));
const completenessData = JSON.parse(fs.readFileSync('implementation-analysis.json', 'utf8'));
// Summary table
comment += '### Summary\n\n';
comment += `**Pattern-Based Stubs**: ${patternData.totalStubsFound}\n`;
comment += `**Low Completeness Items**: ${completenessData.bySeverity.high + completenessData.bySeverity.medium}\n`;
comment += `**Average Completeness**: ${completenessData.averageCompleteness}%\n\n`;
// Severity breakdown
if (patternData.totalStubsFound > 0) {
comment += '### Severity Breakdown (Patterns)\n\n';
comment += `| Severity | Count |\n`;
comment += `|----------|-------|\n`;
comment += `| 🔴 Critical | ${patternData.bySeverity.high} |\n`;
comment += `| 🟠 Medium | ${patternData.bySeverity.medium} |\n`;
comment += `| 🟡 Low | ${patternData.bySeverity.low} |\n\n`;
}
// Type breakdown
if (Object.values(patternData.byType).some(v => v > 0)) {
comment += '### Issue Types\n\n';
for (const [type, count] of Object.entries(patternData.byType)) {
if (count > 0) {
comment += `- **${type}**: ${count}\n`;
}
}
comment += '\n';
}
// Critical issues
if (patternData.criticalIssues && patternData.criticalIssues.length > 0) {
comment += '### 🔴 Critical Issues Found\n\n';
comment += '<details><summary>Click to expand</summary>\n\n';
comment += `| File | Line | Function | Type |\n`;
comment += `|------|------|----------|------|\n`;
patternData.criticalIssues.slice(0, 10).forEach(issue => {
comment += `| ${issue.file} | ${issue.line} | \`${issue.function}\` | ${issue.type} |\n`;
});
comment += '\n</details>\n\n';
}
// Recommendations
comment += '### 📋 Recommendations\n\n';
comment += '- [ ] Review all critical stubs before merging\n';
comment += '- [ ] Replace TODO comments with GitHub issues\n';
comment += '- [ ] Implement placeholder functions before production\n';
comment += '- [ ] Run `bun run test:check-functions` to ensure coverage\n';
comment += '- [ ] Use type system to force implementation (avoid `any` types)\n\n';
// Artifacts info
comment += '### 📁 Detailed Reports\n\n';
comment += 'Full analysis available in artifacts:\n';
comment += '- `stub-patterns.json` - Pattern-based detection results\n';
comment += '- `implementation-analysis.json` - Completeness scoring\n';
comment += '- `stub-report.md` - Detailed markdown report\n';
} catch (e) {
comment += '⚠️ Could not generate detailed report. Check logs for errors.\n';
}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
# Upload detailed reports
- name: Upload stub detection reports
uses: actions/upload-artifact@v4
if: always()
with:
name: stub-detection-reports
path: |
stub-patterns.json
implementation-analysis.json
stub-report.md
changed-stubs.txt
retention-days: 30
# Create check run with summary
- name: Create check run
uses: actions/github-script@v7
if: always()
with:
script: |
const fs = require('fs');
let summary = '';
try {
const data = JSON.parse(fs.readFileSync('stub-patterns.json', 'utf8'));
summary = `Found ${data.totalStubsFound} stub implementations (${data.bySeverity.high} high severity)`;
} catch (e) {
summary = 'Stub detection completed. See artifacts for details.';
}
github.rest.checks.create({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'Stub Implementation Detection',
head_sha: context.sha,
status: 'completed',
conclusion: 'neutral',
summary: summary
});
name: Stub Implementation Detection
on:
pull_request:
branches: [ main, master, develop ]
types: [opened, synchronize, reopened]
push:
branches: [ main, master, develop ]
workflow_dispatch:
schedule:
- cron: '0 0 * * 1' # Weekly on Monday
permissions:
contents: read
pull-requests: write
checks: write
jobs:
detect-stubs:
name: Detect Stub Implementations
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontends/nextjs
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.4'
- name: Cache Bun dependencies
uses: actions/cache@v4
with:
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
path: |
frontends/nextjs/node_modules
~/.bun
restore-keys: bun-deps-${{ runner.os }}-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Generate Prisma Client
run: bun run db:generate
env:
DATABASE_URL: file:./dev.db
# Pattern-based stub detection
- name: Detect stub patterns
id: detect-patterns
run: bunx tsx ../../tools/detect-stub-implementations.ts > stub-patterns.json
continue-on-error: true
# Implementation completeness analysis
- name: Analyze implementation completeness
id: analyze-completeness
run: bunx tsx ../../tools/analyze-implementation-completeness.ts > implementation-analysis.json
continue-on-error: true
# Generate detailed report
- name: Generate stub report
id: generate-report
run: bunx tsx ../../tools/generate-stub-report.ts > stub-report.md
continue-on-error: true
# Check for unimplemented TODOs in changed files (PR only)
- name: Check changed files for stubs
if: github.event_name == 'pull_request'
id: check-changed
run: |
git diff origin/${{ github.base_ref }}...HEAD -- 'src/**/*.{ts,tsx}' | \
grep -E '^\+.*(TODO|FIXME|not implemented|stub|placeholder|mock)' | \
tee changed-stubs.txt || true
STUB_COUNT=$(wc -l < changed-stubs.txt)
echo "stub_count=$STUB_COUNT" >> $GITHUB_OUTPUT
continue-on-error: true
# Post PR comment with findings
- name: Post stub detection comment
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let comment = '## 🔍 Stub Implementation Detection Report\n\n';
try {
const patternData = JSON.parse(fs.readFileSync('stub-patterns.json', 'utf8'));
const completenessData = JSON.parse(fs.readFileSync('implementation-analysis.json', 'utf8'));
// Summary table
comment += '### Summary\n\n';
comment += `**Pattern-Based Stubs**: ${patternData.totalStubsFound}\n`;
comment += `**Low Completeness Items**: ${completenessData.bySeverity.high + completenessData.bySeverity.medium}\n`;
comment += `**Average Completeness**: ${completenessData.averageCompleteness}%\n\n`;
// Severity breakdown
if (patternData.totalStubsFound > 0) {
comment += '### Severity Breakdown (Patterns)\n\n';
comment += `| Severity | Count |\n`;
comment += `|----------|-------|\n`;
comment += `| 🔴 Critical | ${patternData.bySeverity.high} |\n`;
comment += `| 🟠 Medium | ${patternData.bySeverity.medium} |\n`;
comment += `| 🟡 Low | ${patternData.bySeverity.low} |\n\n`;
}
// Type breakdown
if (Object.values(patternData.byType).some(v => v > 0)) {
comment += '### Issue Types\n\n';
for (const [type, count] of Object.entries(patternData.byType)) {
if (count > 0) {
comment += `- **${type}**: ${count}\n`;
}
}
comment += '\n';
}
// Critical issues
if (patternData.criticalIssues && patternData.criticalIssues.length > 0) {
comment += '### 🔴 Critical Issues Found\n\n';
comment += '<details><summary>Click to expand</summary>\n\n';
comment += `| File | Line | Function | Type |\n`;
comment += `|------|------|----------|------|\n`;
patternData.criticalIssues.slice(0, 10).forEach(issue => {
comment += `| ${issue.file} | ${issue.line} | \`${issue.function}\` | ${issue.type} |\n`;
});
comment += '\n</details>\n\n';
}
// Recommendations
comment += '### 📋 Recommendations\n\n';
comment += '- [ ] Review all critical stubs before merging\n';
comment += '- [ ] Replace TODO comments with GitHub issues\n';
comment += '- [ ] Implement placeholder functions before production\n';
comment += '- [ ] Run `bun run test:check-functions` to ensure coverage\n';
comment += '- [ ] Use type system to force implementation (avoid `any` types)\n\n';
// Artifacts info
comment += '### 📁 Detailed Reports\n\n';
comment += 'Full analysis available in artifacts:\n';
comment += '- `stub-patterns.json` - Pattern-based detection results\n';
comment += '- `implementation-analysis.json` - Completeness scoring\n';
comment += '- `stub-report.md` - Detailed markdown report\n';
} catch (e) {
comment += '⚠️ Could not generate detailed report. Check logs for errors.\n';
}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
# Upload detailed reports
- name: Upload stub detection reports
uses: actions/upload-artifact@v4
if: always()
with:
name: stub-detection-reports
path: |
stub-patterns.json
implementation-analysis.json
stub-report.md
changed-stubs.txt
retention-days: 30
# Create check run with summary
- name: Create check run
uses: actions/github-script@v7
if: always()
with:
script: |
const fs = require('fs');
let summary = '';
try {
const data = JSON.parse(fs.readFileSync('stub-patterns.json', 'utf8'));
summary = `Found ${data.totalStubsFound} stub implementations (${data.bySeverity.high} high severity)`;
} catch (e) {
summary = 'Stub detection completed. See artifacts for details.';
}
github.rest.checks.create({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'Stub Implementation Detection',
head_sha: context.sha,
status: 'completed',
conclusion: 'neutral',
summary: summary
});
+360 -360
View File
@@ -1,360 +1,360 @@
name: Development Assistance
on:
pull_request:
types: [opened, synchronize, ready_for_review]
issue_comment:
types: [created]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
code-quality-feedback:
name: Continuous Quality Feedback
runs-on: ubuntu-latest
if: |
github.event_name == 'pull_request' && !github.event.pull_request.draft
defaults:
run:
working-directory: frontends/nextjs
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Analyze code metrics (no redundant checks)
id: quality
run: |
# Note: Lint/build/tests are handled by gated-ci.yml
# This job only collects metrics for architectural feedback
# Count TypeScript files and their sizes
TOTAL_TS_FILES=$(find src -name "*.ts" -o -name "*.tsx" 2>/dev/null | wc -l)
LARGE_FILES=$(find src -name "*.ts" -o -name "*.tsx" -exec wc -l {} \; 2>/dev/null | awk '$1 > 150 {print $2}' | wc -l)
echo "total_ts_files=$TOTAL_TS_FILES" >> $GITHUB_OUTPUT
echo "large_files=$LARGE_FILES" >> $GITHUB_OUTPUT
# Check for declarative vs imperative balance
JSON_FILES=$(find src packages -name "*.json" 2>/dev/null | wc -l)
LUA_SCRIPTS=$(find src packages -name "*.lua" 2>/dev/null | wc -l)
echo "json_files=$JSON_FILES" >> $GITHUB_OUTPUT
echo "lua_scripts=$LUA_SCRIPTS" >> $GITHUB_OUTPUT
- name: Check architectural compliance
id: architecture
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
let issues = [];
let suggestions = [];
// Get changed files
let changedFiles = [];
if (context.eventName === 'pull_request') {
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
changedFiles = files.map(f => f.filename);
}
// Check for hardcoded components outside ui/
const hardcodedComponents = changedFiles.filter(f =>
f.endsWith('.tsx') &&
f.includes('src/components/') &&
!f.includes('src/components/ui/') &&
!f.includes('src/components/shared/') &&
!['RenderComponent', 'FieldRenderer', 'GenericPage'].some(g => f.includes(g))
);
if (hardcodedComponents.length > 0) {
suggestions.push(`Consider if these components could be declarative: ${hardcodedComponents.join(', ')}`);
}
// Check for database changes without seed data
const schemaChanged = changedFiles.some(f => f.includes('schema.prisma'));
const seedChanged = changedFiles.some(f => f.includes('seed'));
if (schemaChanged && !seedChanged) {
suggestions.push('Database schema changed but no seed data updates detected. Consider updating seed data.');
}
// Check for new routes without PageRoutes table updates
const routeFiles = changedFiles.filter(f => f.includes('Route') || f.includes('route'));
if (routeFiles.length > 0) {
suggestions.push('Route changes detected. Ensure PageRoutes table is updated for dynamic routing.');
}
// Check for large TypeScript files
const largeFiles = parseInt('${{ steps.quality.outputs.large_files }}');
if (largeFiles > 0) {
issues.push(`${largeFiles} TypeScript files exceed 150 lines. Consider breaking them into smaller components.`);
}
return { issues, suggestions };
- name: Provide development feedback
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const analysis = JSON.parse('${{ steps.architecture.outputs.result }}');
const totalFiles = parseInt('${{ steps.quality.outputs.total_ts_files }}');
const largeFiles = parseInt('${{ steps.quality.outputs.large_files }}');
const jsonFiles = parseInt('${{ steps.quality.outputs.json_files }}');
const luaScripts = parseInt('${{ steps.quality.outputs.lua_scripts }}');
let comment = `## 💻 Development Quality Feedback\n\n`;
comment += `### 📊 Code Metrics\n\n`;
comment += `- TypeScript files: ${totalFiles}\n`;
comment += `- Files >150 LOC: ${largeFiles} ${largeFiles > 0 ? '⚠️' : '✅'}\n`;
comment += `- JSON config files: ${jsonFiles}\n`;
comment += `- Lua scripts: ${luaScripts}\n`;
comment += `- Declarative ratio: ${((jsonFiles + luaScripts) / Math.max(totalFiles, 1) * 100).toFixed(1)}%\n\n`;
if (analysis.issues.length > 0) {
comment += `### ⚠️ Architectural Issues\n\n`;
analysis.issues.forEach(issue => comment += `- ${issue}\n`);
comment += '\n';
}
if (analysis.suggestions.length > 0) {
comment += `### 💡 Suggestions\n\n`;
analysis.suggestions.forEach(suggestion => comment += `- ${suggestion}\n`);
comment += '\n';
}
comment += `### 🎯 Project Goals Reminder\n\n`;
comment += `- **Declarative First:** Prefer JSON + Lua over TypeScript\n`;
comment += `- **Component Size:** Keep files under 150 LOC\n`;
comment += `- **Generic Renderers:** Use RenderComponent for dynamic components\n`;
comment += `- **Database-Driven:** Store configuration in database, not code\n`;
comment += `- **Package-Based:** Organize features as importable packages\n\n`;
comment += `**@copilot** can help refactor code to better align with these principles.\n\n`;
comment += `📖 See [Architecture Guidelines](/.github/copilot-instructions.md)`;
// Check if we already commented
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('Development Quality Feedback')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: comment
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}
copilot-interaction:
name: Handle Copilot Mentions
runs-on: ubuntu-latest
if: |
github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '@copilot')
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Parse Copilot request
uses: actions/github-script@v7
with:
script: |
const comment = context.payload.comment.body.toLowerCase();
const issue = context.payload.issue;
let response = `## 🤖 Copilot Assistance\n\n`;
// Determine what the user is asking for
if (comment.includes('implement') || comment.includes('fix this')) {
response += `To implement this with Copilot assistance:\n\n`;
response += `1. **Create a branch:** \`git checkout -b feature/issue-${issue.number}\`\n`;
response += `2. **Use Copilot in your IDE** to generate code with context from:\n`;
response += ` - [Copilot Instructions](/.github/copilot-instructions.md)\n`;
response += ` - [PRD.md](/PRD.md)\n`;
response += ` - Existing package structure in \`/packages/\`\n`;
response += `3. **Follow the architectural principles:**\n`;
response += ` - Declarative over imperative\n`;
response += ` - Database-driven configuration\n`;
response += ` - Generic renderers vs hardcoded components\n`;
response += `4. **Test your changes:** \`bun run lint && bun run test:e2e\`\n`;
response += `5. **Create a PR** - The automated workflows will review it\n\n`;
}
if (comment.includes('review') || comment.includes('check')) {
response += `Copilot can review this through:\n\n`;
response += `- **Automated Code Review** workflow (runs on PRs)\n`;
response += `- **Development Assistance** workflow (runs on pushes)\n`;
response += `- **Planning & Design** workflow (runs on feature requests)\n\n`;
response += `Create a PR to trigger comprehensive review!\n\n`;
}
if (comment.includes('architecture') || comment.includes('design')) {
response += `### 🏗️ Architectural Guidance\n\n`;
response += `MetaBuilder follows these principles:\n\n`;
response += `1. **5-Level Architecture:** User → Admin → God → SuperGod levels\n`;
response += `2. **Multi-Tenant:** Isolated tenant instances with independent configs\n`;
response += `3. **Declarative Components:** JSON config + Lua scripts, not TSX\n`;
response += `4. **Package System:** Self-contained, importable feature bundles\n`;
response += `5. **Database-First:** All config in Prisma, not hardcoded\n\n`;
response += `📖 Full details: [PRD.md](/PRD.md)\n\n`;
}
if (comment.includes('test') || comment.includes('e2e')) {
response += `### 🧪 Testing with Copilot\n\n`;
response += `\`\`\`bash\n`;
response += `# Run E2E tests\n`;
response += `bun run test:e2e\n\n`;
response += `# Run with UI\n`;
response += `bun run test:e2e:ui\n\n`;
response += `# Run linter\n`;
response += `bun run lint\n`;
response += `\`\`\`\n\n`;
response += `Use Copilot in your IDE to:\n`;
response += `- Generate test cases based on user stories\n`;
response += `- Write Playwright selectors and assertions\n`;
response += `- Create mock data for tests\n\n`;
}
if (comment.includes('help') || (!comment.includes('implement') && !comment.includes('review') && !comment.includes('architecture') && !comment.includes('test'))) {
response += `### 🆘 How to Use Copilot\n\n`;
response += `Mention **@copilot** in comments with:\n\n`;
response += `- \`@copilot implement this\` - Get implementation guidance\n`;
response += `- \`@copilot review this\` - Request code review\n`;
response += `- \`@copilot architecture\` - Get architectural guidance\n`;
response += `- \`@copilot test this\` - Get testing guidance\n`;
response += `- \`@copilot fix this issue\` - Request automated fix\n\n`;
response += `**In your IDE:**\n`;
response += `- Use GitHub Copilot with context from [Copilot Instructions](/.github/copilot-instructions.md)\n`;
response += `- Reference the [PRD](/PRD.md) when prompting\n`;
response += `- Follow patterns from existing packages in \`/packages/\`\n\n`;
}
response += `---\n`;
response += `*This is an automated response. For detailed Copilot assistance, use the extension in your IDE with project context.*`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: response
});
suggest-refactoring:
name: Suggest Refactoring Opportunities
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && !github.event.pull_request.draft
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Analyze refactoring opportunities
uses: actions/github-script@v7
with:
script: |
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
let opportunities = [];
// Look for opportunities in changed files
for (const file of files) {
const patch = file.patch || '';
// Check for repeated code patterns
if (patch.split('\n').length > 100) {
opportunities.push({
file: file.filename,
type: 'Size',
suggestion: 'Large changeset - consider breaking into smaller PRs or extracting common utilities'
});
}
// Check for hardcoded values
if (patch.match(/['"][A-Z_]{3,}['"]\s*:/)) {
opportunities.push({
file: file.filename,
type: 'Configuration',
suggestion: 'Hardcoded constants detected - consider moving to database configuration'
});
}
// Check for new TSX components
if (file.filename.includes('components/') && file.filename.endsWith('.tsx') && file.status === 'added') {
opportunities.push({
file: file.filename,
type: 'Architecture',
suggestion: 'New component added - could this be implemented declaratively with JSON + Lua?'
});
}
// Check for inline styles or complex class strings
if (patch.includes('style={{') || patch.match(/className="[^"]{50,}"/)) {
opportunities.push({
file: file.filename,
type: 'Styling',
suggestion: 'Complex styling detected - consider extracting to theme configuration'
});
}
}
if (opportunities.length > 0) {
let comment = `## 🔄 Refactoring Opportunities\n\n`;
comment += `**@copilot** identified potential improvements:\n\n`;
const grouped = {};
opportunities.forEach(opp => {
if (!grouped[opp.type]) grouped[opp.type] = [];
grouped[opp.type].push(opp);
});
for (const [type, opps] of Object.entries(grouped)) {
comment += `### ${type}\n\n`;
opps.forEach(opp => {
comment += `- **${opp.file}**: ${opp.suggestion}\n`;
});
comment += '\n';
}
comment += `---\n`;
comment += `These are suggestions, not requirements. Consider them as part of continuous improvement.\n\n`;
comment += `Use **@copilot** in your IDE to help implement these refactorings.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}
name: Development Assistance
on:
pull_request:
types: [opened, synchronize, ready_for_review]
issue_comment:
types: [created]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
code-quality-feedback:
name: Continuous Quality Feedback
runs-on: ubuntu-latest
if: |
github.event_name == 'pull_request' && !github.event.pull_request.draft
defaults:
run:
working-directory: frontends/nextjs
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Analyze code metrics (no redundant checks)
id: quality
run: |
# Note: Lint/build/tests are handled by gated-ci.yml
# This job only collects metrics for architectural feedback
# Count TypeScript files and their sizes
TOTAL_TS_FILES=$(find src -name "*.ts" -o -name "*.tsx" 2>/dev/null | wc -l)
LARGE_FILES=$(find src -name "*.ts" -o -name "*.tsx" -exec wc -l {} \; 2>/dev/null | awk '$1 > 150 {print $2}' | wc -l)
echo "total_ts_files=$TOTAL_TS_FILES" >> $GITHUB_OUTPUT
echo "large_files=$LARGE_FILES" >> $GITHUB_OUTPUT
# Check for declarative vs imperative balance
JSON_FILES=$(find src packages -name "*.json" 2>/dev/null | wc -l)
LUA_SCRIPTS=$(find src packages -name "*.lua" 2>/dev/null | wc -l)
echo "json_files=$JSON_FILES" >> $GITHUB_OUTPUT
echo "lua_scripts=$LUA_SCRIPTS" >> $GITHUB_OUTPUT
- name: Check architectural compliance
id: architecture
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
let issues = [];
let suggestions = [];
// Get changed files
let changedFiles = [];
if (context.eventName === 'pull_request') {
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
changedFiles = files.map(f => f.filename);
}
// Check for hardcoded components outside ui/
const hardcodedComponents = changedFiles.filter(f =>
f.endsWith('.tsx') &&
f.includes('src/components/') &&
!f.includes('src/components/ui/') &&
!f.includes('src/components/shared/') &&
!['RenderComponent', 'FieldRenderer', 'GenericPage'].some(g => f.includes(g))
);
if (hardcodedComponents.length > 0) {
suggestions.push(`Consider if these components could be declarative: ${hardcodedComponents.join(', ')}`);
}
// Check for database changes without seed data
const schemaChanged = changedFiles.some(f => f.includes('schema.prisma'));
const seedChanged = changedFiles.some(f => f.includes('seed'));
if (schemaChanged && !seedChanged) {
suggestions.push('Database schema changed but no seed data updates detected. Consider updating seed data.');
}
// Check for new routes without PageRoutes table updates
const routeFiles = changedFiles.filter(f => f.includes('Route') || f.includes('route'));
if (routeFiles.length > 0) {
suggestions.push('Route changes detected. Ensure PageRoutes table is updated for dynamic routing.');
}
// Check for large TypeScript files
const largeFiles = parseInt('${{ steps.quality.outputs.large_files }}');
if (largeFiles > 0) {
issues.push(`${largeFiles} TypeScript files exceed 150 lines. Consider breaking them into smaller components.`);
}
return { issues, suggestions };
- name: Provide development feedback
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const analysis = JSON.parse('${{ steps.architecture.outputs.result }}');
const totalFiles = parseInt('${{ steps.quality.outputs.total_ts_files }}');
const largeFiles = parseInt('${{ steps.quality.outputs.large_files }}');
const jsonFiles = parseInt('${{ steps.quality.outputs.json_files }}');
const luaScripts = parseInt('${{ steps.quality.outputs.lua_scripts }}');
let comment = `## 💻 Development Quality Feedback\n\n`;
comment += `### 📊 Code Metrics\n\n`;
comment += `- TypeScript files: ${totalFiles}\n`;
comment += `- Files >150 LOC: ${largeFiles} ${largeFiles > 0 ? '⚠️' : '✅'}\n`;
comment += `- JSON config files: ${jsonFiles}\n`;
comment += `- Lua scripts: ${luaScripts}\n`;
comment += `- Declarative ratio: ${((jsonFiles + luaScripts) / Math.max(totalFiles, 1) * 100).toFixed(1)}%\n\n`;
if (analysis.issues.length > 0) {
comment += `### ⚠️ Architectural Issues\n\n`;
analysis.issues.forEach(issue => comment += `- ${issue}\n`);
comment += '\n';
}
if (analysis.suggestions.length > 0) {
comment += `### 💡 Suggestions\n\n`;
analysis.suggestions.forEach(suggestion => comment += `- ${suggestion}\n`);
comment += '\n';
}
comment += `### 🎯 Project Goals Reminder\n\n`;
comment += `- **Declarative First:** Prefer JSON + Lua over TypeScript\n`;
comment += `- **Component Size:** Keep files under 150 LOC\n`;
comment += `- **Generic Renderers:** Use RenderComponent for dynamic components\n`;
comment += `- **Database-Driven:** Store configuration in database, not code\n`;
comment += `- **Package-Based:** Organize features as importable packages\n\n`;
comment += `**@copilot** can help refactor code to better align with these principles.\n\n`;
comment += `📖 See [Architecture Guidelines](/.github/copilot-instructions.md)`;
// Check if we already commented
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('Development Quality Feedback')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: comment
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}
copilot-interaction:
name: Handle Copilot Mentions
runs-on: ubuntu-latest
if: |
github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '@copilot')
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Parse Copilot request
uses: actions/github-script@v7
with:
script: |
const comment = context.payload.comment.body.toLowerCase();
const issue = context.payload.issue;
let response = `## 🤖 Copilot Assistance\n\n`;
// Determine what the user is asking for
if (comment.includes('implement') || comment.includes('fix this')) {
response += `To implement this with Copilot assistance:\n\n`;
response += `1. **Create a branch:** \`git checkout -b feature/issue-${issue.number}\`\n`;
response += `2. **Use Copilot in your IDE** to generate code with context from:\n`;
response += ` - [Copilot Instructions](/.github/copilot-instructions.md)\n`;
response += ` - [PRD.md](/PRD.md)\n`;
response += ` - Existing package structure in \`/packages/\`\n`;
response += `3. **Follow the architectural principles:**\n`;
response += ` - Declarative over imperative\n`;
response += ` - Database-driven configuration\n`;
response += ` - Generic renderers vs hardcoded components\n`;
response += `4. **Test your changes:** \`bun run lint && bun run test:e2e\`\n`;
response += `5. **Create a PR** - The automated workflows will review it\n\n`;
}
if (comment.includes('review') || comment.includes('check')) {
response += `Copilot can review this through:\n\n`;
response += `- **Automated Code Review** workflow (runs on PRs)\n`;
response += `- **Development Assistance** workflow (runs on pushes)\n`;
response += `- **Planning & Design** workflow (runs on feature requests)\n\n`;
response += `Create a PR to trigger comprehensive review!\n\n`;
}
if (comment.includes('architecture') || comment.includes('design')) {
response += `### 🏗️ Architectural Guidance\n\n`;
response += `MetaBuilder follows these principles:\n\n`;
response += `1. **5-Level Architecture:** User → Admin → God → SuperGod levels\n`;
response += `2. **Multi-Tenant:** Isolated tenant instances with independent configs\n`;
response += `3. **Declarative Components:** JSON config + Lua scripts, not TSX\n`;
response += `4. **Package System:** Self-contained, importable feature bundles\n`;
response += `5. **Database-First:** All config in Prisma, not hardcoded\n\n`;
response += `📖 Full details: [PRD.md](/PRD.md)\n\n`;
}
if (comment.includes('test') || comment.includes('e2e')) {
response += `### 🧪 Testing with Copilot\n\n`;
response += `\`\`\`bash\n`;
response += `# Run E2E tests\n`;
response += `bun run test:e2e\n\n`;
response += `# Run with UI\n`;
response += `bun run test:e2e:ui\n\n`;
response += `# Run linter\n`;
response += `bun run lint\n`;
response += `\`\`\`\n\n`;
response += `Use Copilot in your IDE to:\n`;
response += `- Generate test cases based on user stories\n`;
response += `- Write Playwright selectors and assertions\n`;
response += `- Create mock data for tests\n\n`;
}
if (comment.includes('help') || (!comment.includes('implement') && !comment.includes('review') && !comment.includes('architecture') && !comment.includes('test'))) {
response += `### 🆘 How to Use Copilot\n\n`;
response += `Mention **@copilot** in comments with:\n\n`;
response += `- \`@copilot implement this\` - Get implementation guidance\n`;
response += `- \`@copilot review this\` - Request code review\n`;
response += `- \`@copilot architecture\` - Get architectural guidance\n`;
response += `- \`@copilot test this\` - Get testing guidance\n`;
response += `- \`@copilot fix this issue\` - Request automated fix\n\n`;
response += `**In your IDE:**\n`;
response += `- Use GitHub Copilot with context from [Copilot Instructions](/.github/copilot-instructions.md)\n`;
response += `- Reference the [PRD](/PRD.md) when prompting\n`;
response += `- Follow patterns from existing packages in \`/packages/\`\n\n`;
}
response += `---\n`;
response += `*This is an automated response. For detailed Copilot assistance, use the extension in your IDE with project context.*`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: response
});
suggest-refactoring:
name: Suggest Refactoring Opportunities
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && !github.event.pull_request.draft
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Analyze refactoring opportunities
uses: actions/github-script@v7
with:
script: |
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
let opportunities = [];
// Look for opportunities in changed files
for (const file of files) {
const patch = file.patch || '';
// Check for repeated code patterns
if (patch.split('\n').length > 100) {
opportunities.push({
file: file.filename,
type: 'Size',
suggestion: 'Large changeset - consider breaking into smaller PRs or extracting common utilities'
});
}
// Check for hardcoded values
if (patch.match(/['"][A-Z_]{3,}['"]\s*:/)) {
opportunities.push({
file: file.filename,
type: 'Configuration',
suggestion: 'Hardcoded constants detected - consider moving to database configuration'
});
}
// Check for new TSX components
if (file.filename.includes('components/') && file.filename.endsWith('.tsx') && file.status === 'added') {
opportunities.push({
file: file.filename,
type: 'Architecture',
suggestion: 'New component added - could this be implemented declaratively with JSON + Lua?'
});
}
// Check for inline styles or complex class strings
if (patch.includes('style={{') || patch.match(/className="[^"]{50,}"/)) {
opportunities.push({
file: file.filename,
type: 'Styling',
suggestion: 'Complex styling detected - consider extracting to theme configuration'
});
}
}
if (opportunities.length > 0) {
let comment = `## 🔄 Refactoring Opportunities\n\n`;
comment += `**@copilot** identified potential improvements:\n\n`;
const grouped = {};
opportunities.forEach(opp => {
if (!grouped[opp.type]) grouped[opp.type] = [];
grouped[opp.type].push(opp);
});
for (const [type, opps] of Object.entries(grouped)) {
comment += `### ${type}\n\n`;
opps.forEach(opp => {
comment += `- **${opp.file}**: ${opp.suggestion}\n`;
});
comment += '\n';
}
comment += `---\n`;
comment += `These are suggestions, not requirements. Consider them as part of continuous improvement.\n\n`;
comment += `Use **@copilot** in your IDE to help implement these refactorings.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+182 -182
View File
@@ -1,182 +1,182 @@
name: Issue Triage and Auto-Fix
on:
issues:
types: [opened, labeled]
permissions:
contents: write
issues: write
pull-requests: write
jobs:
triage-issue:
name: Triage and Label Issues
runs-on: ubuntu-latest
if: github.event.action == 'opened'
steps:
- name: Analyze and label issue
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const title = issue.title.toLowerCase();
const body = (issue.body || '').toLowerCase();
const text = title + ' ' + body;
let labels = [];
// Categorize by type
if (text.match(/bug|error|crash|broken|fail/)) {
labels.push('bug');
}
if (text.match(/feature|enhancement|add|new|implement/)) {
labels.push('enhancement');
}
if (text.match(/document|readme|docs|guide/)) {
labels.push('documentation');
}
if (text.match(/test|testing|spec|e2e/)) {
labels.push('testing');
}
if (text.match(/security|vulnerability|exploit|xss|sql/)) {
labels.push('security');
}
if (text.match(/performance|slow|optimize|speed/)) {
labels.push('performance');
}
// Categorize by priority
if (text.match(/critical|urgent|asap|blocker/)) {
labels.push('priority: high');
} else if (text.match(/minor|low|nice to have/)) {
labels.push('priority: low');
} else {
labels.push('priority: medium');
}
// Check if it's a good first issue
if (text.match(/beginner|easy|simple|starter/) || labels.length <= 2) {
labels.push('good first issue');
}
// Check if AI can help
if (labels.includes('bug') || labels.includes('documentation') || labels.includes('testing')) {
labels.push('ai-fixable');
}
// Add labels
if (labels.length > 0) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: labels
});
} catch (e) {
console.log('Some labels may not exist:', e.message);
}
}
// Post welcome comment
const aiHelpText = labels.includes('ai-fixable')
? '\n\n🤖 This issue appears to be something AI can help with! A fix may be automatically attempted.'
: '';
const comment = '👋 Thank you for opening this issue!\n\n' +
'This issue has been automatically labeled as: ' + labels.join(', ') +
aiHelpText + '\n\n' +
'A maintainer will review this issue soon. In the meantime, please make sure you have provided:\n' +
'- A clear description of the issue\n' +
'- Steps to reproduce (for bugs)\n' +
'- Expected vs actual behavior\n' +
'- Any relevant error messages or screenshots\n\n' +
'Copilot may be able to help with this issue.';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
attempt-auto-fix:
name: Attempt Automated Fix
runs-on: ubuntu-latest
if: |
(github.event.action == 'labeled' && github.event.label.name == 'ai-fixable') ||
(github.event.action == 'labeled' && github.event.label.name == 'auto-fix')
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Analyze issue and suggest fix
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const labelList = issue.labels.map(l => l.name).join(', ');
const comment = '🤖 **AI-Assisted Fix Attempt**\n\n' +
'I have analyzed this issue and here are my suggestions:\n\n' +
'**Issue Type:** ' + labelList + '\n\n' +
'**Suggested Actions:**\n' +
'1. Review the issue description carefully\n' +
'2. Check for similar issues in the repository history\n' +
'3. Consider using Copilot to help implement the fix\n\n' +
'**To request an automated fix:**\n' +
'- Add the auto-fix label to this issue\n' +
'- Ensure the issue description clearly explains:\n' +
' - What needs to be fixed\n' +
' - Where the issue is located (file/line if known)\n' +
' - Expected behavior\n\n' +
'**Note:** Complex issues may require human review before implementation.\n\n' +
'Would you like me to attempt an automated fix? If so, please confirm by commenting "Copilot fix this issue".';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
create-fix-pr:
name: Create Fix PR
runs-on: ubuntu-latest
if: github.event.action == 'labeled' && github.event.label.name == 'create-pr'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Create fix branch and PR
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const branchName = 'auto-fix/issue-' + issue.number;
const comment = '🤖 **Automated Fix PR Creation**\n\n' +
'I have created a branch ' + branchName + ' for this fix.\n\n' +
'**Next Steps:**\n' +
'1. A developer or Copilot will work on the fix in this branch\n' +
'2. A pull request will be created automatically\n' +
'3. The PR will be linked to this issue\n\n' +
'**Branch:** ' + branchName + '\n\n' +
'To work on this fix:\n' +
'git fetch origin\n' +
'git checkout ' + branchName + '\n\n' +
'This issue will be automatically closed when the PR is merged.';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
name: Issue Triage and Auto-Fix
on:
issues:
types: [opened, labeled]
permissions:
contents: write
issues: write
pull-requests: write
jobs:
triage-issue:
name: Triage and Label Issues
runs-on: ubuntu-latest
if: github.event.action == 'opened'
steps:
- name: Analyze and label issue
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const title = issue.title.toLowerCase();
const body = (issue.body || '').toLowerCase();
const text = title + ' ' + body;
let labels = [];
// Categorize by type
if (text.match(/bug|error|crash|broken|fail/)) {
labels.push('bug');
}
if (text.match(/feature|enhancement|add|new|implement/)) {
labels.push('enhancement');
}
if (text.match(/document|readme|docs|guide/)) {
labels.push('documentation');
}
if (text.match(/test|testing|spec|e2e/)) {
labels.push('testing');
}
if (text.match(/security|vulnerability|exploit|xss|sql/)) {
labels.push('security');
}
if (text.match(/performance|slow|optimize|speed/)) {
labels.push('performance');
}
// Categorize by priority
if (text.match(/critical|urgent|asap|blocker/)) {
labels.push('priority: high');
} else if (text.match(/minor|low|nice to have/)) {
labels.push('priority: low');
} else {
labels.push('priority: medium');
}
// Check if it's a good first issue
if (text.match(/beginner|easy|simple|starter/) || labels.length <= 2) {
labels.push('good first issue');
}
// Check if AI can help
if (labels.includes('bug') || labels.includes('documentation') || labels.includes('testing')) {
labels.push('ai-fixable');
}
// Add labels
if (labels.length > 0) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: labels
});
} catch (e) {
console.log('Some labels may not exist:', e.message);
}
}
// Post welcome comment
const aiHelpText = labels.includes('ai-fixable')
? '\n\n🤖 This issue appears to be something AI can help with! A fix may be automatically attempted.'
: '';
const comment = '👋 Thank you for opening this issue!\n\n' +
'This issue has been automatically labeled as: ' + labels.join(', ') +
aiHelpText + '\n\n' +
'A maintainer will review this issue soon. In the meantime, please make sure you have provided:\n' +
'- A clear description of the issue\n' +
'- Steps to reproduce (for bugs)\n' +
'- Expected vs actual behavior\n' +
'- Any relevant error messages or screenshots\n\n' +
'Copilot may be able to help with this issue.';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
attempt-auto-fix:
name: Attempt Automated Fix
runs-on: ubuntu-latest
if: |
(github.event.action == 'labeled' && github.event.label.name == 'ai-fixable') ||
(github.event.action == 'labeled' && github.event.label.name == 'auto-fix')
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Analyze issue and suggest fix
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const labelList = issue.labels.map(l => l.name).join(', ');
const comment = '🤖 **AI-Assisted Fix Attempt**\n\n' +
'I have analyzed this issue and here are my suggestions:\n\n' +
'**Issue Type:** ' + labelList + '\n\n' +
'**Suggested Actions:**\n' +
'1. Review the issue description carefully\n' +
'2. Check for similar issues in the repository history\n' +
'3. Consider using Copilot to help implement the fix\n\n' +
'**To request an automated fix:**\n' +
'- Add the auto-fix label to this issue\n' +
'- Ensure the issue description clearly explains:\n' +
' - What needs to be fixed\n' +
' - Where the issue is located (file/line if known)\n' +
' - Expected behavior\n\n' +
'**Note:** Complex issues may require human review before implementation.\n\n' +
'Would you like me to attempt an automated fix? If so, please confirm by commenting "Copilot fix this issue".';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
create-fix-pr:
name: Create Fix PR
runs-on: ubuntu-latest
if: github.event.action == 'labeled' && github.event.label.name == 'create-pr'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Create fix branch and PR
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const branchName = 'auto-fix/issue-' + issue.number;
const comment = '🤖 **Automated Fix PR Creation**\n\n' +
'I have created a branch ' + branchName + ' for this fix.\n\n' +
'**Next Steps:**\n' +
'1. A developer or Copilot will work on the fix in this branch\n' +
'2. A pull request will be created automatically\n' +
'3. The PR will be linked to this issue\n\n' +
'**Branch:** ' + branchName + '\n\n' +
'To work on this fix:\n' +
'git fetch origin\n' +
'git checkout ' + branchName + '\n\n' +
'This issue will be automatically closed when the PR is merged.';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
+202 -202
View File
@@ -1,202 +1,202 @@
name: Auto Merge
on:
pull_request_review:
types: [submitted]
check_suite:
types: [completed]
workflow_run:
workflows: ["CI/CD", "Enterprise Gated CI/CD Pipeline"]
types: [completed]
permissions:
contents: write
pull-requests: write
jobs:
auto-merge:
name: Auto Merge PR
runs-on: ubuntu-latest
if: >
${{
(github.event_name == 'pull_request_review' && github.event.review.state == 'approved') ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
}}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Check PR status and merge
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// Get PR number from event
let prNumber;
if (context.payload.pull_request) {
prNumber = context.payload.pull_request.number;
} else if (context.payload.workflow_run) {
// Get PR from workflow run
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}`
});
if (prs.length === 0) {
console.log('No open PR found for this branch');
return;
}
prNumber = prs[0].number;
} else {
console.log('Could not determine PR number');
return;
}
console.log(`Checking PR #${prNumber}`);
// Get PR details
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
if (pr.state !== 'open') {
console.log('PR is not open');
return;
}
if (pr.draft) {
console.log('PR is still in draft');
return;
}
// Check if PR is approved
const { data: reviews } = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
const latestReviews = {};
for (const review of reviews) {
latestReviews[review.user.login] = review.state;
}
const hasApproval = Object.values(latestReviews).includes('APPROVED');
const hasRequestChanges = Object.values(latestReviews).includes('CHANGES_REQUESTED');
if (!hasApproval) {
console.log('PR has not been approved yet');
return;
}
if (hasRequestChanges) {
console.log('PR has requested changes');
return;
}
// Check CI status - support both old and new gated workflows
const { data: checks } = await github.rest.checks.listForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: pr.head.sha
});
// Required checks for old CI/CD workflow
const legacyRequiredChecks = ['Lint Code', 'Build Application', 'E2E Tests'];
// Required gate checks for new Enterprise Gated CI/CD Pipeline
const gatedRequiredChecks = [
'Gate 1: Code Quality - Passed ✅',
'Gate 2: Testing - Passed ✅',
'Gate 3: Build & Package - Passed ✅'
];
const checkStatuses = {};
for (const check of checks.check_runs) {
checkStatuses[check.name] = check.conclusion;
}
console.log('Check statuses:', checkStatuses);
// Check if using new gated workflow or old workflow
const hasGatedChecks = gatedRequiredChecks.some(checkName =>
checkStatuses[checkName] !== undefined
);
const requiredChecks = hasGatedChecks ? gatedRequiredChecks : legacyRequiredChecks;
console.log('Using checks:', hasGatedChecks ? 'Enterprise Gated' : 'Legacy');
// Wait for all required checks to pass
const allChecksPassed = requiredChecks.every(checkName =>
checkStatuses[checkName] === 'success' || checkStatuses[checkName] === 'skipped'
);
if (!allChecksPassed) {
console.log('Not all required checks have passed');
// Check if any checks failed
const anyChecksFailed = Object.values(checkStatuses).some(status =>
status === 'failure'
);
if (anyChecksFailed) {
console.log('Some checks failed, not merging');
return;
}
console.log('Checks are still running, will retry later');
return;
}
console.log('All conditions met, merging PR');
// Add comment before merging
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: '✅ All checks passed and PR is approved! Auto-merging and cleaning up branch.'
});
try {
// Merge the PR
await github.rest.pulls.merge({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
merge_method: 'squash',
commit_title: `${pr.title} (#${prNumber})`,
commit_message: pr.body || ''
});
console.log('PR merged successfully');
// Delete the branch
try {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${pr.head.ref}`
});
console.log(`Branch ${pr.head.ref} deleted successfully`);
} catch (deleteError) {
console.log('Could not delete branch:', deleteError.message);
// Don't fail the workflow if branch deletion fails
}
} catch (mergeError) {
console.error('Failed to merge PR:', mergeError.message);
// Post comment about merge failure
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `❌ Auto-merge failed: ${mergeError.message}\n\nPlease merge manually.`
});
}
name: Auto Merge
on:
pull_request_review:
types: [submitted]
check_suite:
types: [completed]
workflow_run:
workflows: ["CI/CD", "Enterprise Gated CI/CD Pipeline"]
types: [completed]
permissions:
contents: write
pull-requests: write
jobs:
auto-merge:
name: Auto Merge PR
runs-on: ubuntu-latest
if: >
${{
(github.event_name == 'pull_request_review' && github.event.review.state == 'approved') ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
}}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Check PR status and merge
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// Get PR number from event
let prNumber;
if (context.payload.pull_request) {
prNumber = context.payload.pull_request.number;
} else if (context.payload.workflow_run) {
// Get PR from workflow run
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}`
});
if (prs.length === 0) {
console.log('No open PR found for this branch');
return;
}
prNumber = prs[0].number;
} else {
console.log('Could not determine PR number');
return;
}
console.log(`Checking PR #${prNumber}`);
// Get PR details
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
if (pr.state !== 'open') {
console.log('PR is not open');
return;
}
if (pr.draft) {
console.log('PR is still in draft');
return;
}
// Check if PR is approved
const { data: reviews } = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
const latestReviews = {};
for (const review of reviews) {
latestReviews[review.user.login] = review.state;
}
const hasApproval = Object.values(latestReviews).includes('APPROVED');
const hasRequestChanges = Object.values(latestReviews).includes('CHANGES_REQUESTED');
if (!hasApproval) {
console.log('PR has not been approved yet');
return;
}
if (hasRequestChanges) {
console.log('PR has requested changes');
return;
}
// Check CI status - support both old and new gated workflows
const { data: checks } = await github.rest.checks.listForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: pr.head.sha
});
// Required checks for old CI/CD workflow
const legacyRequiredChecks = ['Lint Code', 'Build Application', 'E2E Tests'];
// Required gate checks for new Enterprise Gated CI/CD Pipeline
const gatedRequiredChecks = [
'Gate 1: Code Quality - Passed ✅',
'Gate 2: Testing - Passed ✅',
'Gate 3: Build & Package - Passed ✅'
];
const checkStatuses = {};
for (const check of checks.check_runs) {
checkStatuses[check.name] = check.conclusion;
}
console.log('Check statuses:', checkStatuses);
// Check if using new gated workflow or old workflow
const hasGatedChecks = gatedRequiredChecks.some(checkName =>
checkStatuses[checkName] !== undefined
);
const requiredChecks = hasGatedChecks ? gatedRequiredChecks : legacyRequiredChecks;
console.log('Using checks:', hasGatedChecks ? 'Enterprise Gated' : 'Legacy');
// Wait for all required checks to pass
const allChecksPassed = requiredChecks.every(checkName =>
checkStatuses[checkName] === 'success' || checkStatuses[checkName] === 'skipped'
);
if (!allChecksPassed) {
console.log('Not all required checks have passed');
// Check if any checks failed
const anyChecksFailed = Object.values(checkStatuses).some(status =>
status === 'failure'
);
if (anyChecksFailed) {
console.log('Some checks failed, not merging');
return;
}
console.log('Checks are still running, will retry later');
return;
}
console.log('All conditions met, merging PR');
// Add comment before merging
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: '✅ All checks passed and PR is approved! Auto-merging and cleaning up branch.'
});
try {
// Merge the PR
await github.rest.pulls.merge({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
merge_method: 'squash',
commit_title: `${pr.title} (#${prNumber})`,
commit_message: pr.body || ''
});
console.log('PR merged successfully');
// Delete the branch
try {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${pr.head.ref}`
});
console.log(`Branch ${pr.head.ref} deleted successfully`);
} catch (deleteError) {
console.log('Could not delete branch:', deleteError.message);
// Don't fail the workflow if branch deletion fails
}
} catch (mergeError) {
console.error('Failed to merge PR:', mergeError.message);
// Post comment about merge failure
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `❌ Auto-merge failed: ${mergeError.message}\n\nPlease merge manually.`
});
}
+277 -277
View File
@@ -1,277 +1,277 @@
name: Automated Code Review
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
checks: read
jobs:
automated-review:
name: AI-Assisted Code Review
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontends/nextjs
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.4'
- name: Cache Bun dependencies
uses: actions/cache@v4
with:
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
path: |
frontends/nextjs/node_modules
~/.bun
restore-keys: bun-deps-${{ runner.os }}-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Generate Prisma Client
run: bun run db:generate
env:
DATABASE_URL: file:./dev.db
- name: Run linter for review
id: lint
run: |
bun run lint > lint-output.txt 2>&1 || echo "LINT_FAILED=true" >> $GITHUB_OUTPUT
cat lint-output.txt
continue-on-error: true
- name: Analyze code changes
id: analyze
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
// Get PR diff
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
let issues = [];
let warnings = [];
let suggestions = [];
// Analyze each file
for (const file of files) {
const patch = file.patch || '';
const filename = file.filename;
// Check for security issues
if (patch.match(/eval\s*\(/)) {
issues.push(`⚠️ **Security**: Use of \`eval()\` found in ${filename}`);
}
if (patch.match(/innerHTML\s*=/)) {
warnings.push(`⚠️ **Security**: Direct \`innerHTML\` usage in ${filename}. Consider using safer alternatives.`);
}
if (patch.match(/dangerouslySetInnerHTML/)) {
warnings.push(`⚠️ **Security**: \`dangerouslySetInnerHTML\` usage in ${filename}. Ensure content is sanitized.`);
}
// Check for code quality
if (patch.match(/console\.(log|debug|info)/)) {
warnings.push(`🔍 **Code Quality**: Console statements found in ${filename}. Remove before merging.`);
}
if (patch.match(/debugger/)) {
issues.push(`🐛 **Debug Code**: Debugger statement found in ${filename}. Remove before merging.`);
}
if (patch.match(/(:\s*any\b|\bany\s*[>;,\)])/)) {
suggestions.push(`💡 **Type Safety**: Consider replacing \`any\` types with specific types in ${filename}`);
}
// Check for best practices
if (filename.endsWith('.tsx') || filename.endsWith('.jsx')) {
if (patch.match(/useEffect.*\[\]/) && !patch.includes('// eslint-disable')) {
suggestions.push(`💡 **React**: Empty dependency array in useEffect in ${filename}. Verify if intentional.`);
}
}
// Check for large files
if (file.additions > 500) {
warnings.push(`📏 **File Size**: ${filename} has ${file.additions} additions. Consider breaking into smaller files.`);
}
}
// Read lint output if exists
let lintIssues = '';
try {
lintIssues = fs.readFileSync('lint-output.txt', 'utf8');
} catch (e) {
// File doesn't exist
}
// Determine if auto-approve is appropriate
const hasBlockingIssues = issues.length > 0 || lintIssues.includes('error');
return {
issues,
warnings,
suggestions,
lintIssues,
hasBlockingIssues,
fileCount: files.length,
totalAdditions: files.reduce((sum, f) => sum + f.additions, 0),
totalDeletions: files.reduce((sum, f) => sum + f.deletions, 0)
};
- name: Post review comment
uses: actions/github-script@v7
with:
script: |
const analysis = JSON.parse('${{ steps.analyze.outputs.result }}');
let comment = '## 🤖 Automated Code Review\n\n';
comment += `**Changes Summary:**\n`;
comment += `- Files changed: ${analysis.fileCount}\n`;
comment += `- Lines added: ${analysis.totalAdditions}\n`;
comment += `- Lines deleted: ${analysis.totalDeletions}\n\n`;
if (analysis.issues.length > 0) {
comment += '### ❌ Blocking Issues\n\n';
analysis.issues.forEach(issue => comment += `- ${issue}\n`);
comment += '\n';
}
if (analysis.warnings.length > 0) {
comment += '### ⚠️ Warnings\n\n';
analysis.warnings.forEach(warning => comment += `- ${warning}\n`);
comment += '\n';
}
if (analysis.suggestions.length > 0) {
comment += '### 💡 Suggestions\n\n';
analysis.suggestions.forEach(suggestion => comment += `- ${suggestion}\n`);
comment += '\n';
}
if (analysis.lintIssues && analysis.lintIssues.includes('error')) {
comment += '### 🔴 Linting Errors\n\n';
comment += '```\n' + analysis.lintIssues + '\n```\n\n';
}
if (analysis.hasBlockingIssues) {
comment += '---\n';
comment += '### ❌ Review Status: **CHANGES REQUESTED**\n\n';
comment += 'Please address the blocking issues above before this PR can be approved.\n';
} else {
comment += '---\n';
comment += '### ✅ Review Status: **APPROVED**\n\n';
comment += 'No blocking issues found! This PR looks good to merge after CI checks pass.\n';
}
// Check if we already commented
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('Automated Code Review')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: comment
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}
- name: Add labels based on review
uses: actions/github-script@v7
with:
script: |
const analysis = JSON.parse('${{ steps.analyze.outputs.result }}');
let labels = [];
if (analysis.hasBlockingIssues) {
labels.push('needs-changes');
} else {
labels.push('ready-for-review');
}
if (analysis.warnings.length > 0) {
labels.push('has-warnings');
}
if (analysis.totalAdditions > 500) {
labels.push('large-pr');
}
// Remove conflicting labels first
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'needs-changes'
});
} catch (e) {
// Label doesn't exist
}
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'ready-for-review'
});
} catch (e) {
// Label doesn't exist
}
// Add new labels
for (const label of labels) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: [label]
});
} catch (e) {
console.log(`Label ${label} might not exist, skipping...`);
}
}
- name: Auto-approve if no issues
if: steps.analyze.outputs.result && !fromJSON(steps.analyze.outputs.result).hasBlockingIssues
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
event: 'APPROVE',
body: '✅ Automated review passed! No blocking issues found. This PR is approved pending successful CI checks.'
});
name: Automated Code Review
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
checks: read
jobs:
automated-review:
name: AI-Assisted Code Review
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontends/nextjs
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.4'
- name: Cache Bun dependencies
uses: actions/cache@v4
with:
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
path: |
frontends/nextjs/node_modules
~/.bun
restore-keys: bun-deps-${{ runner.os }}-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Generate Prisma Client
run: bun run db:generate
env:
DATABASE_URL: file:./dev.db
- name: Run linter for review
id: lint
run: |
bun run lint > lint-output.txt 2>&1 || echo "LINT_FAILED=true" >> $GITHUB_OUTPUT
cat lint-output.txt
continue-on-error: true
- name: Analyze code changes
id: analyze
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
// Get PR diff
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
let issues = [];
let warnings = [];
let suggestions = [];
// Analyze each file
for (const file of files) {
const patch = file.patch || '';
const filename = file.filename;
// Check for security issues
if (patch.match(/eval\s*\(/)) {
issues.push(`⚠️ **Security**: Use of \`eval()\` found in ${filename}`);
}
if (patch.match(/innerHTML\s*=/)) {
warnings.push(`⚠️ **Security**: Direct \`innerHTML\` usage in ${filename}. Consider using safer alternatives.`);
}
if (patch.match(/dangerouslySetInnerHTML/)) {
warnings.push(`⚠️ **Security**: \`dangerouslySetInnerHTML\` usage in ${filename}. Ensure content is sanitized.`);
}
// Check for code quality
if (patch.match(/console\.(log|debug|info)/)) {
warnings.push(`🔍 **Code Quality**: Console statements found in ${filename}. Remove before merging.`);
}
if (patch.match(/debugger/)) {
issues.push(`🐛 **Debug Code**: Debugger statement found in ${filename}. Remove before merging.`);
}
if (patch.match(/(:\s*any\b|\bany\s*[>;,\)])/)) {
suggestions.push(`💡 **Type Safety**: Consider replacing \`any\` types with specific types in ${filename}`);
}
// Check for best practices
if (filename.endsWith('.tsx') || filename.endsWith('.jsx')) {
if (patch.match(/useEffect.*\[\]/) && !patch.includes('// eslint-disable')) {
suggestions.push(`💡 **React**: Empty dependency array in useEffect in ${filename}. Verify if intentional.`);
}
}
// Check for large files
if (file.additions > 500) {
warnings.push(`📏 **File Size**: ${filename} has ${file.additions} additions. Consider breaking into smaller files.`);
}
}
// Read lint output if exists
let lintIssues = '';
try {
lintIssues = fs.readFileSync('lint-output.txt', 'utf8');
} catch (e) {
// File doesn't exist
}
// Determine if auto-approve is appropriate
const hasBlockingIssues = issues.length > 0 || lintIssues.includes('error');
return {
issues,
warnings,
suggestions,
lintIssues,
hasBlockingIssues,
fileCount: files.length,
totalAdditions: files.reduce((sum, f) => sum + f.additions, 0),
totalDeletions: files.reduce((sum, f) => sum + f.deletions, 0)
};
- name: Post review comment
uses: actions/github-script@v7
with:
script: |
const analysis = JSON.parse('${{ steps.analyze.outputs.result }}');
let comment = '## 🤖 Automated Code Review\n\n';
comment += `**Changes Summary:**\n`;
comment += `- Files changed: ${analysis.fileCount}\n`;
comment += `- Lines added: ${analysis.totalAdditions}\n`;
comment += `- Lines deleted: ${analysis.totalDeletions}\n\n`;
if (analysis.issues.length > 0) {
comment += '### ❌ Blocking Issues\n\n';
analysis.issues.forEach(issue => comment += `- ${issue}\n`);
comment += '\n';
}
if (analysis.warnings.length > 0) {
comment += '### ⚠️ Warnings\n\n';
analysis.warnings.forEach(warning => comment += `- ${warning}\n`);
comment += '\n';
}
if (analysis.suggestions.length > 0) {
comment += '### 💡 Suggestions\n\n';
analysis.suggestions.forEach(suggestion => comment += `- ${suggestion}\n`);
comment += '\n';
}
if (analysis.lintIssues && analysis.lintIssues.includes('error')) {
comment += '### 🔴 Linting Errors\n\n';
comment += '```\n' + analysis.lintIssues + '\n```\n\n';
}
if (analysis.hasBlockingIssues) {
comment += '---\n';
comment += '### ❌ Review Status: **CHANGES REQUESTED**\n\n';
comment += 'Please address the blocking issues above before this PR can be approved.\n';
} else {
comment += '---\n';
comment += '### ✅ Review Status: **APPROVED**\n\n';
comment += 'No blocking issues found! This PR looks good to merge after CI checks pass.\n';
}
// Check if we already commented
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('Automated Code Review')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: comment
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}
- name: Add labels based on review
uses: actions/github-script@v7
with:
script: |
const analysis = JSON.parse('${{ steps.analyze.outputs.result }}');
let labels = [];
if (analysis.hasBlockingIssues) {
labels.push('needs-changes');
} else {
labels.push('ready-for-review');
}
if (analysis.warnings.length > 0) {
labels.push('has-warnings');
}
if (analysis.totalAdditions > 500) {
labels.push('large-pr');
}
// Remove conflicting labels first
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'needs-changes'
});
} catch (e) {
// Label doesn't exist
}
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'ready-for-review'
});
} catch (e) {
// Label doesn't exist
}
// Add new labels
for (const label of labels) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: [label]
});
} catch (e) {
console.log(`Label ${label} might not exist, skipping...`);
}
}
- name: Auto-approve if no issues
if: steps.analyze.outputs.result && !fromJSON(steps.analyze.outputs.result).hasBlockingIssues
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
event: 'APPROVE',
body: '✅ Automated review passed! No blocking issues found. This PR is approved pending successful CI checks.'
});
+132 -132
View File
@@ -1,132 +1,132 @@
name: Check for Merge Conflicts
on:
pull_request:
types: [opened, synchronize, reopened]
# Also run when the base branch is updated
push:
branches:
- main
- master
jobs:
check-conflicts:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Fetch base branch
run: |
git fetch origin ${{ github.base_ref || github.event.repository.default_branch }}
- name: Check for merge conflicts
id: conflict-check
run: |
# Determine the base branch
BASE_BRANCH="${{ github.base_ref }}"
if [ -z "$BASE_BRANCH" ]; then
BASE_BRANCH="${{ github.event.repository.default_branch }}"
fi
echo "Checking for conflicts with origin/$BASE_BRANCH"
# Try to merge the base branch to see if there are conflicts
if git merge-tree $(git merge-base HEAD origin/$BASE_BRANCH) origin/$BASE_BRANCH HEAD | grep -q "^<<<<<"; then
echo "has_conflicts=true" >> $GITHUB_OUTPUT
echo "✗ Merge conflicts detected!"
else
echo "has_conflicts=false" >> $GITHUB_OUTPUT
echo "✓ No merge conflicts detected"
fi
- name: Comment on PR if conflicts exist
if: steps.conflict-check.outputs.has_conflicts == 'true' && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const comment = `## ⚠️ Merge Conflicts Detected
@copilot This pull request has merge conflicts that need to be resolved.
**Please resolve the conflicts by:**
1. Merging the latest changes from the base branch
2. Resolving any conflicting files
3. Pushing the updated changes
---
*This is an automated message from the merge conflict checker.*`;
// Check if we already commented
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('Merge Conflicts Detected')
);
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: comment
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}
- name: Add label if conflicts exist
if: steps.conflict-check.outputs.has_conflicts == 'true' && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['merge-conflict']
});
} catch (error) {
console.log('Label might not exist yet, skipping...');
}
- name: Remove label if no conflicts
if: steps.conflict-check.outputs.has_conflicts == 'false' && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'merge-conflict'
});
} catch (error) {
console.log('Label does not exist or is not applied, skipping...');
}
- name: Fail if conflicts exist
if: steps.conflict-check.outputs.has_conflicts == 'true'
run: |
echo "❌ This PR has merge conflicts and cannot be merged."
exit 1
name: Check for Merge Conflicts
on:
pull_request:
types: [opened, synchronize, reopened]
# Also run when the base branch is updated
push:
branches:
- main
- master
jobs:
check-conflicts:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Fetch base branch
run: |
git fetch origin ${{ github.base_ref || github.event.repository.default_branch }}
- name: Check for merge conflicts
id: conflict-check
run: |
# Determine the base branch
BASE_BRANCH="${{ github.base_ref }}"
if [ -z "$BASE_BRANCH" ]; then
BASE_BRANCH="${{ github.event.repository.default_branch }}"
fi
echo "Checking for conflicts with origin/$BASE_BRANCH"
# Try to merge the base branch to see if there are conflicts
if git merge-tree $(git merge-base HEAD origin/$BASE_BRANCH) origin/$BASE_BRANCH HEAD | grep -q "^<<<<<"; then
echo "has_conflicts=true" >> $GITHUB_OUTPUT
echo "✗ Merge conflicts detected!"
else
echo "has_conflicts=false" >> $GITHUB_OUTPUT
echo "✓ No merge conflicts detected"
fi
- name: Comment on PR if conflicts exist
if: steps.conflict-check.outputs.has_conflicts == 'true' && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const comment = `## ⚠️ Merge Conflicts Detected
@copilot This pull request has merge conflicts that need to be resolved.
**Please resolve the conflicts by:**
1. Merging the latest changes from the base branch
2. Resolving any conflicting files
3. Pushing the updated changes
---
*This is an automated message from the merge conflict checker.*`;
// Check if we already commented
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('Merge Conflicts Detected')
);
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: comment
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}
- name: Add label if conflicts exist
if: steps.conflict-check.outputs.has_conflicts == 'true' && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['merge-conflict']
});
} catch (error) {
console.log('Label might not exist yet, skipping...');
}
- name: Remove label if no conflicts
if: steps.conflict-check.outputs.has_conflicts == 'false' && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'merge-conflict'
});
} catch (error) {
console.log('Label does not exist or is not applied, skipping...');
}
- name: Fail if conflicts exist
if: steps.conflict-check.outputs.has_conflicts == 'true'
run: |
echo "❌ This PR has merge conflicts and cannot be merged."
exit 1
+193 -193
View File
@@ -1,193 +1,193 @@
name: PR Labeling and Management
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
label-pr:
name: Auto-Label Pull Request
runs-on: ubuntu-latest
if: github.event.action == 'opened' || github.event.action == 'synchronize'
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Analyze PR and add labels
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
// Get PR files
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
});
let labels = [];
// Analyze file changes
const fileTypes = {
workflows: files.some(f => f.filename.includes('.github/workflows')),
tests: files.some(f => f.filename.includes('test') || f.filename.includes('spec') || f.filename.includes('e2e')),
docs: files.some(f => f.filename.includes('README') || f.filename.includes('.md') || f.filename.includes('docs/')),
components: files.some(f => f.filename.includes('components/') || f.filename.includes('.tsx')),
styles: files.some(f => f.filename.includes('.css') || f.filename.includes('style')),
config: files.some(f => f.filename.match(/\.(json|yml|yaml|config\.(js|ts))$/)),
dependencies: files.some(f => f.filename === 'package.json' || f.filename === 'package-lock.json'),
};
if (fileTypes.workflows) labels.push('workflows');
if (fileTypes.tests) labels.push('tests');
if (fileTypes.docs) labels.push('documentation');
if (fileTypes.components) labels.push('ui');
if (fileTypes.styles) labels.push('styling');
if (fileTypes.config) labels.push('configuration');
if (fileTypes.dependencies) labels.push('dependencies');
// Size labels
const totalChanges = files.reduce((sum, f) => sum + f.additions + f.deletions, 0);
if (totalChanges < 50) {
labels.push('size: small');
} else if (totalChanges < 200) {
labels.push('size: medium');
} else {
labels.push('size: large');
}
// Check PR title for type
const title = pr.title.toLowerCase();
if (title.match(/^fix|bug/)) labels.push('bug');
if (title.match(/^feat|feature|add/)) labels.push('enhancement');
if (title.match(/^refactor/)) labels.push('refactor');
if (title.match(/^docs/)) labels.push('documentation');
if (title.match(/^test/)) labels.push('tests');
if (title.match(/^chore/)) labels.push('chore');
// Add labels
if (labels.length > 0) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: labels
});
} catch (e) {
console.log('Some labels may not exist:', e.message);
}
}
check-pr-description:
name: Check PR Description
runs-on: ubuntu-latest
if: github.event.action == 'opened'
steps:
- name: Validate PR description
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const body = pr.body || '';
let issues = [];
// Check if description is too short
if (body.length < 50) {
issues.push('PR description is too short. Please provide more details about the changes.');
}
// Check if description links to an issue
if (!body.match(/#\d+|https:\/\/github\.com/)) {
issues.push('Consider linking to a related issue using #issue_number');
}
// Check for test information
if (body.toLowerCase().includes('test') === false &&
!pr.labels.some(l => l.name === 'documentation')) {
issues.push('Please mention how these changes were tested.');
}
if (issues.length > 0) {
const issueList = issues.map(i => '- [ ] ' + i).join('\n');
const comment = [
'## \uD83D\uDCCB PR Description Checklist',
'',
'The following items could improve this PR:',
'',
issueList,
'',
'**Good PR descriptions include:**',
'- What changes were made and why',
'- How to test the changes',
'- Any breaking changes or special considerations',
'- Links to related issues',
'- Screenshots (for UI changes)',
'',
'This is a friendly reminder to help maintain code quality! \uD83D\uDE0A'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: comment
});
}
link-related-issues:
name: Link Related Issues
runs-on: ubuntu-latest
if: github.event.action == 'opened'
steps:
- name: Find and link related issues
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const body = pr.body || '';
const title = pr.title;
// Extract issue numbers from PR body
const issueNumbers = [...body.matchAll(/#(\d+)/g)].map(m => m[1]);
if (issueNumbers.length > 0) {
const relatedList = issueNumbers.map(n => '#' + n).join(', ');
const comment = [
'\uD83D\uDD17 **Related Issues**',
'',
'This PR is related to: ' + relatedList,
'',
'These issues will be automatically closed when this PR is merged.'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: comment
});
// Add comment to related issues
for (const issueNum of issueNumbers) {
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(issueNum),
body: '\uD83D\uDD17 Pull request #' + pr.number + ' has been created to address this issue.'
});
} catch (e) {
console.log('Could not comment on issue #' + issueNum);
}
}
}
name: PR Labeling and Management
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
label-pr:
name: Auto-Label Pull Request
runs-on: ubuntu-latest
if: github.event.action == 'opened' || github.event.action == 'synchronize'
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Analyze PR and add labels
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
// Get PR files
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
});
let labels = [];
// Analyze file changes
const fileTypes = {
workflows: files.some(f => f.filename.includes('.github/workflows')),
tests: files.some(f => f.filename.includes('test') || f.filename.includes('spec') || f.filename.includes('e2e')),
docs: files.some(f => f.filename.includes('README') || f.filename.includes('.md') || f.filename.includes('docs/')),
components: files.some(f => f.filename.includes('components/') || f.filename.includes('.tsx')),
styles: files.some(f => f.filename.includes('.css') || f.filename.includes('style')),
config: files.some(f => f.filename.match(/\.(json|yml|yaml|config\.(js|ts))$/)),
dependencies: files.some(f => f.filename === 'package.json' || f.filename === 'package-lock.json'),
};
if (fileTypes.workflows) labels.push('workflows');
if (fileTypes.tests) labels.push('tests');
if (fileTypes.docs) labels.push('documentation');
if (fileTypes.components) labels.push('ui');
if (fileTypes.styles) labels.push('styling');
if (fileTypes.config) labels.push('configuration');
if (fileTypes.dependencies) labels.push('dependencies');
// Size labels
const totalChanges = files.reduce((sum, f) => sum + f.additions + f.deletions, 0);
if (totalChanges < 50) {
labels.push('size: small');
} else if (totalChanges < 200) {
labels.push('size: medium');
} else {
labels.push('size: large');
}
// Check PR title for type
const title = pr.title.toLowerCase();
if (title.match(/^fix|bug/)) labels.push('bug');
if (title.match(/^feat|feature|add/)) labels.push('enhancement');
if (title.match(/^refactor/)) labels.push('refactor');
if (title.match(/^docs/)) labels.push('documentation');
if (title.match(/^test/)) labels.push('tests');
if (title.match(/^chore/)) labels.push('chore');
// Add labels
if (labels.length > 0) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: labels
});
} catch (e) {
console.log('Some labels may not exist:', e.message);
}
}
check-pr-description:
name: Check PR Description
runs-on: ubuntu-latest
if: github.event.action == 'opened'
steps:
- name: Validate PR description
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const body = pr.body || '';
let issues = [];
// Check if description is too short
if (body.length < 50) {
issues.push('PR description is too short. Please provide more details about the changes.');
}
// Check if description links to an issue
if (!body.match(/#\d+|https:\/\/github\.com/)) {
issues.push('Consider linking to a related issue using #issue_number');
}
// Check for test information
if (body.toLowerCase().includes('test') === false &&
!pr.labels.some(l => l.name === 'documentation')) {
issues.push('Please mention how these changes were tested.');
}
if (issues.length > 0) {
const issueList = issues.map(i => '- [ ] ' + i).join('\n');
const comment = [
'## \uD83D\uDCCB PR Description Checklist',
'',
'The following items could improve this PR:',
'',
issueList,
'',
'**Good PR descriptions include:**',
'- What changes were made and why',
'- How to test the changes',
'- Any breaking changes or special considerations',
'- Links to related issues',
'- Screenshots (for UI changes)',
'',
'This is a friendly reminder to help maintain code quality! \uD83D\uDE0A'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: comment
});
}
link-related-issues:
name: Link Related Issues
runs-on: ubuntu-latest
if: github.event.action == 'opened'
steps:
- name: Find and link related issues
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const body = pr.body || '';
const title = pr.title;
// Extract issue numbers from PR body
const issueNumbers = [...body.matchAll(/#(\d+)/g)].map(m => m[1]);
if (issueNumbers.length > 0) {
const relatedList = issueNumbers.map(n => '#' + n).join(', ');
const comment = [
'\uD83D\uDD17 **Related Issues**',
'',
'This PR is related to: ' + relatedList,
'',
'These issues will be automatically closed when this PR is merged.'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: comment
});
// Add comment to related issues
for (const issueNum of issueNumbers) {
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(issueNum),
body: '\uD83D\uDD17 Pull request #' + pr.number + ' has been created to address this issue.'
});
} catch (e) {
console.log('Could not comment on issue #' + issueNum);
}
}
}
+218 -218
View File
@@ -1,218 +1,218 @@
name: Planning & Design
on:
issues:
types: [opened, labeled]
permissions:
contents: read
issues: write
jobs:
architecture-review:
name: Architecture & Design Review
runs-on: ubuntu-latest
if: |
github.event.action == 'labeled' &&
(github.event.label.name == 'enhancement' || github.event.label.name == 'feature-request')
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Review against architecture principles
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const title = issue.title;
const body = issue.body || '';
let suggestions = [];
let questions = [];
// Check if feature aligns with declarative approach
if (body.toLowerCase().includes('component') && !body.toLowerCase().includes('json')) {
suggestions.push('💡 Consider implementing this as a **declarative component** using JSON configuration and Lua scripts instead of a TypeScript file.');
}
// Check if database schema is mentioned
if (!body.toLowerCase().includes('database') && !body.toLowerCase().includes('schema')) {
questions.push('🤔 Will this feature require database schema changes? Consider adding Prisma schema details.');
}
// Check if package structure is considered
if (body.toLowerCase().includes('new') && !body.toLowerCase().includes('package')) {
suggestions.push('📦 This might be a good candidate for a **package-based implementation** with isolated seed data.');
}
// Check for multi-tenant considerations
if (!body.toLowerCase().includes('tenant') && !body.toLowerCase().includes('supergod')) {
questions.push('🏢 How should this feature work across different **tenants**? Should it be tenant-specific or global?');
}
// Check for permission levels
if (!body.toLowerCase().match(/level [1-5]|user|admin|god|supergod/)) {
questions.push('🔐 Which **permission levels** should have access to this feature? (user/admin/god/supergod)');
}
// Check for Lua consideration
if (body.toLowerCase().includes('logic') && !body.toLowerCase().includes('lua')) {
suggestions.push('🌙 Consider implementing business logic in **Lua scripts** for better flexibility and sandboxing.');
}
let comment = `## 🏗️ Architecture Review\n\n`;
comment += `Thank you for proposing this enhancement! Here's an architectural review:\n\n`;
if (suggestions.length > 0) {
comment += `### 💡 Architectural Suggestions\n\n`;
suggestions.forEach(s => comment += `${s}\n\n`);
}
if (questions.length > 0) {
comment += `### 🤔 Questions to Consider\n\n`;
questions.forEach(q => comment += `${q}\n\n`);
}
comment += `### ✅ Design Checklist\n\n`;
comment += `- [ ] Database schema changes identified\n`;
comment += `- [ ] Package structure planned (if applicable)\n`;
comment += `- [ ] Multi-tenant implications considered\n`;
comment += `- [ ] Permission levels defined\n`;
comment += `- [ ] Declarative approach preferred over imperative\n`;
comment += `- [ ] Component size kept under 150 LOC\n`;
comment += `- [ ] Security implications reviewed\n`;
comment += `- [ ] Testing strategy outlined\n\n`;
comment += `---\n`;
comment += `**@copilot** can help implement this feature following these architectural principles.\n\n`;
comment += `📖 See [Copilot Instructions](/.github/copilot-instructions.md) for development guidelines.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
prd-check:
name: Check PRD Alignment
runs-on: ubuntu-latest
if: github.event.action == 'labeled' && github.event.label.name == 'enhancement'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Check PRD for similar features
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const issue = context.payload.issue;
try {
const prd = fs.readFileSync('docs/getting-started/PRD.md', 'utf8');
// Extract key terms from issue
const issueText = (issue.title + ' ' + issue.body).toLowerCase();
const keywords = ['level', 'god', 'tenant', 'package', 'component', 'workflow', 'lua', 'declarative'];
const foundKeywords = keywords.filter(k => issueText.includes(k));
let comment = `## 📋 PRD Alignment Check\n\n`;
if (foundKeywords.length > 0) {
comment += `This feature relates to the following PRD concepts: **${foundKeywords.join(', ')}**\n\n`;
comment += `Please review [docs/getting-started/PRD.md](/docs/getting-started/PRD.md) to ensure alignment with the project mission and existing features.\n\n`;
}
comment += `### 🎯 Mission Statement\n\n`;
comment += `MetaBuilder aims to be a "fully declarative, procedurally-generated multi-tenant application platform where 95% of functionality is defined through JSON and Lua."\n\n`;
comment += `Does this feature support that mission? If so, how?\n\n`;
comment += `---\n`;
comment += `**@copilot** Review the PRD and suggest implementation approach.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
} catch (e) {
console.log('Could not read PRD.md:', e.message);
}
suggest-implementation:
name: Suggest Implementation Approach
runs-on: ubuntu-latest
if: |
github.event.action == 'labeled' &&
github.event.label.name == 'ready-to-implement'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Generate implementation suggestion
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
let comment = `## 🛠️ Implementation Guidance\n\n`;
comment += `This issue is ready for implementation! Here's a suggested approach:\n\n`;
comment += `### 📝 Step-by-Step Plan\n\n`;
comment += `1. **Planning Phase**\n`;
comment += ` - [ ] Review PRD.md and update if needed\n`;
comment += ` - [ ] Check existing package structure\n`;
comment += ` - [ ] Design database schema changes (if any)\n`;
comment += ` - [ ] Sketch component hierarchy\n\n`;
comment += `2. **Database Phase**\n`;
comment += ` - [ ] Update \`prisma/schema.prisma\`\n`;
comment += ` - [ ] Run \`bun run db:generate\`\n`;
comment += ` - [ ] Create or update seed data\n`;
comment += ` - [ ] Test database operations\n\n`;
comment += `3. **Implementation Phase**\n`;
comment += ` - [ ] Create package structure (if new package)\n`;
comment += ` - [ ] Build generic renderers (prefer over specific components)\n`;
comment += ` - [ ] Add Lua scripts for business logic\n`;
comment += ` - [ ] Wire up UI components\n`;
comment += ` - [ ] Ensure components are <150 LOC\n\n`;
comment += `4. **Testing Phase**\n`;
comment += ` - [ ] Run \`bun run lint\` and fix issues\n`;
comment += ` - [ ] Add E2E tests in \`e2e/\` directory\n`;
comment += ` - [ ] Test at all permission levels\n`;
comment += ` - [ ] Verify multi-tenant isolation\n`;
comment += ` - [ ] Test package import/export\n\n`;
comment += `5. **Documentation Phase**\n`;
comment += ` - [ ] Update PRD.md with feature details\n`;
comment += ` - [ ] Document Lua APIs if new\n`;
comment += ` - [ ] Add usage examples\n`;
comment += ` - [ ] Update workflow docs if needed\n\n`;
comment += `### 🤖 Copilot Assistance\n\n`;
comment += `**@copilot** can help with:\n`;
comment += `- Generating Prisma schema definitions\n`;
comment += `- Creating seed data JSON structures\n`;
comment += `- Writing Lua script templates\n`;
comment += `- Building generic component renderers\n`;
comment += `- Writing E2E tests\n\n`;
comment += `### 🔗 Useful Resources\n\n`;
comment += `- [Copilot Instructions](/.github/copilot-instructions.md)\n`;
comment += `- [PRD](/.github/../PRD.md)\n`;
comment += `- [Workflow Testing](/.github/workflows/README.md)\n`;
comment += `- [Package Structure](/packages/)\n\n`;
comment += `Ready to start? Create a branch: \`feature/issue-${issue.number}\``;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
name: Planning & Design
on:
issues:
types: [opened, labeled]
permissions:
contents: read
issues: write
jobs:
architecture-review:
name: Architecture & Design Review
runs-on: ubuntu-latest
if: |
github.event.action == 'labeled' &&
(github.event.label.name == 'enhancement' || github.event.label.name == 'feature-request')
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Review against architecture principles
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const title = issue.title;
const body = issue.body || '';
let suggestions = [];
let questions = [];
// Check if feature aligns with declarative approach
if (body.toLowerCase().includes('component') && !body.toLowerCase().includes('json')) {
suggestions.push('💡 Consider implementing this as a **declarative component** using JSON configuration and Lua scripts instead of a TypeScript file.');
}
// Check if database schema is mentioned
if (!body.toLowerCase().includes('database') && !body.toLowerCase().includes('schema')) {
questions.push('🤔 Will this feature require database schema changes? Consider adding Prisma schema details.');
}
// Check if package structure is considered
if (body.toLowerCase().includes('new') && !body.toLowerCase().includes('package')) {
suggestions.push('📦 This might be a good candidate for a **package-based implementation** with isolated seed data.');
}
// Check for multi-tenant considerations
if (!body.toLowerCase().includes('tenant') && !body.toLowerCase().includes('supergod')) {
questions.push('🏢 How should this feature work across different **tenants**? Should it be tenant-specific or global?');
}
// Check for permission levels
if (!body.toLowerCase().match(/level [1-5]|user|admin|god|supergod/)) {
questions.push('🔐 Which **permission levels** should have access to this feature? (user/admin/god/supergod)');
}
// Check for Lua consideration
if (body.toLowerCase().includes('logic') && !body.toLowerCase().includes('lua')) {
suggestions.push('🌙 Consider implementing business logic in **Lua scripts** for better flexibility and sandboxing.');
}
let comment = `## 🏗️ Architecture Review\n\n`;
comment += `Thank you for proposing this enhancement! Here's an architectural review:\n\n`;
if (suggestions.length > 0) {
comment += `### 💡 Architectural Suggestions\n\n`;
suggestions.forEach(s => comment += `${s}\n\n`);
}
if (questions.length > 0) {
comment += `### 🤔 Questions to Consider\n\n`;
questions.forEach(q => comment += `${q}\n\n`);
}
comment += `### ✅ Design Checklist\n\n`;
comment += `- [ ] Database schema changes identified\n`;
comment += `- [ ] Package structure planned (if applicable)\n`;
comment += `- [ ] Multi-tenant implications considered\n`;
comment += `- [ ] Permission levels defined\n`;
comment += `- [ ] Declarative approach preferred over imperative\n`;
comment += `- [ ] Component size kept under 150 LOC\n`;
comment += `- [ ] Security implications reviewed\n`;
comment += `- [ ] Testing strategy outlined\n\n`;
comment += `---\n`;
comment += `**@copilot** can help implement this feature following these architectural principles.\n\n`;
comment += `📖 See [Copilot Instructions](/.github/copilot-instructions.md) for development guidelines.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
prd-check:
name: Check PRD Alignment
runs-on: ubuntu-latest
if: github.event.action == 'labeled' && github.event.label.name == 'enhancement'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Check PRD for similar features
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const issue = context.payload.issue;
try {
const prd = fs.readFileSync('docs/getting-started/PRD.md', 'utf8');
// Extract key terms from issue
const issueText = (issue.title + ' ' + issue.body).toLowerCase();
const keywords = ['level', 'god', 'tenant', 'package', 'component', 'workflow', 'lua', 'declarative'];
const foundKeywords = keywords.filter(k => issueText.includes(k));
let comment = `## 📋 PRD Alignment Check\n\n`;
if (foundKeywords.length > 0) {
comment += `This feature relates to the following PRD concepts: **${foundKeywords.join(', ')}**\n\n`;
comment += `Please review [docs/getting-started/PRD.md](/docs/getting-started/PRD.md) to ensure alignment with the project mission and existing features.\n\n`;
}
comment += `### 🎯 Mission Statement\n\n`;
comment += `MetaBuilder aims to be a "fully declarative, procedurally-generated multi-tenant application platform where 95% of functionality is defined through JSON and Lua."\n\n`;
comment += `Does this feature support that mission? If so, how?\n\n`;
comment += `---\n`;
comment += `**@copilot** Review the PRD and suggest implementation approach.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
} catch (e) {
console.log('Could not read PRD.md:', e.message);
}
suggest-implementation:
name: Suggest Implementation Approach
runs-on: ubuntu-latest
if: |
github.event.action == 'labeled' &&
github.event.label.name == 'ready-to-implement'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Generate implementation suggestion
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
let comment = `## 🛠️ Implementation Guidance\n\n`;
comment += `This issue is ready for implementation! Here's a suggested approach:\n\n`;
comment += `### 📝 Step-by-Step Plan\n\n`;
comment += `1. **Planning Phase**\n`;
comment += ` - [ ] Review PRD.md and update if needed\n`;
comment += ` - [ ] Check existing package structure\n`;
comment += ` - [ ] Design database schema changes (if any)\n`;
comment += ` - [ ] Sketch component hierarchy\n\n`;
comment += `2. **Database Phase**\n`;
comment += ` - [ ] Update \`prisma/schema.prisma\`\n`;
comment += ` - [ ] Run \`bun run db:generate\`\n`;
comment += ` - [ ] Create or update seed data\n`;
comment += ` - [ ] Test database operations\n\n`;
comment += `3. **Implementation Phase**\n`;
comment += ` - [ ] Create package structure (if new package)\n`;
comment += ` - [ ] Build generic renderers (prefer over specific components)\n`;
comment += ` - [ ] Add Lua scripts for business logic\n`;
comment += ` - [ ] Wire up UI components\n`;
comment += ` - [ ] Ensure components are <150 LOC\n\n`;
comment += `4. **Testing Phase**\n`;
comment += ` - [ ] Run \`bun run lint\` and fix issues\n`;
comment += ` - [ ] Add E2E tests in \`e2e/\` directory\n`;
comment += ` - [ ] Test at all permission levels\n`;
comment += ` - [ ] Verify multi-tenant isolation\n`;
comment += ` - [ ] Test package import/export\n\n`;
comment += `5. **Documentation Phase**\n`;
comment += ` - [ ] Update PRD.md with feature details\n`;
comment += ` - [ ] Document Lua APIs if new\n`;
comment += ` - [ ] Add usage examples\n`;
comment += ` - [ ] Update workflow docs if needed\n\n`;
comment += `### 🤖 Copilot Assistance\n\n`;
comment += `**@copilot** can help with:\n`;
comment += `- Generating Prisma schema definitions\n`;
comment += `- Creating seed data JSON structures\n`;
comment += `- Writing Lua script templates\n`;
comment += `- Building generic component renderers\n`;
comment += `- Writing E2E tests\n\n`;
comment += `### 🔗 Useful Resources\n\n`;
comment += `- [Copilot Instructions](/.github/copilot-instructions.md)\n`;
comment += `- [PRD](/.github/../PRD.md)\n`;
comment += `- [Workflow Testing](/.github/workflows/README.md)\n`;
comment += `- [Package Structure](/packages/)\n\n`;
comment += `Ready to start? Create a branch: \`feature/issue-${issue.number}\``;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
File diff suppressed because it is too large Load Diff
+92 -92
View File
@@ -1,92 +1,92 @@
name: Code Size Limits
on:
pull_request:
paths:
- 'frontends/nextjs/src/**/*.{ts,tsx,js,jsx}'
- 'tools/enforce-size-limits.ts'
- '.github/workflows/size-limits.yml'
push:
branches:
- main
paths:
- 'frontends/nextjs/src/**/*.{ts,tsx,js,jsx}'
jobs:
size-limits:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontends/nextjs
steps:
- uses: actions/checkout@v6
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.4'
- name: Cache Bun dependencies
uses: actions/cache@v4
with:
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
path: |
frontends/nextjs/node_modules
~/.bun
restore-keys: bun-deps-${{ runner.os }}-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Check code size limits
run: bunx tsx ../../tools/enforce-size-limits.ts
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: size-limits-report
path: frontends/nextjs/size-limits-report.json
retention-days: 7
- name: Comment on PR
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('frontends/nextjs/size-limits-report.json', 'utf8'));
let comment = '## 📏 Code Size Limits\n\n';
if (report.errors === 0 && report.warnings === 0) {
comment += '✅ All files pass size limits!';
} else {
if (report.errors > 0) {
comment += `### ❌ Errors (${report.errors})\n`;
report.violations
.filter(v => v.severity === 'error')
.forEach(v => {
comment += `- **${v.file}**: ${v.metric} (${v.current} / ${v.limit})\n`;
});
}
if (report.warnings > 0) {
comment += `\n### ⚠️ Warnings (${report.warnings})\n`;
report.violations
.filter(v => v.severity === 'warning')
.forEach(v => {
comment += `- **${v.file}**: ${v.metric} (${v.current} / ${v.limit})\n`;
});
}
comment += '\n[See refactoring guide →](../blob/main/docs/REFACTORING_ENFORCEMENT_GUIDE.md)';
}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
name: Code Size Limits
on:
pull_request:
paths:
- 'frontends/nextjs/src/**/*.{ts,tsx,js,jsx}'
- 'tools/enforce-size-limits.ts'
- '.github/workflows/size-limits.yml'
push:
branches:
- main
paths:
- 'frontends/nextjs/src/**/*.{ts,tsx,js,jsx}'
jobs:
size-limits:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontends/nextjs
steps:
- uses: actions/checkout@v6
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.4'
- name: Cache Bun dependencies
uses: actions/cache@v4
with:
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
path: |
frontends/nextjs/node_modules
~/.bun
restore-keys: bun-deps-${{ runner.os }}-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Check code size limits
run: bunx tsx ../../tools/enforce-size-limits.ts
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: size-limits-report
path: frontends/nextjs/size-limits-report.json
retention-days: 7
- name: Comment on PR
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('frontends/nextjs/size-limits-report.json', 'utf8'));
let comment = '## 📏 Code Size Limits\n\n';
if (report.errors === 0 && report.warnings === 0) {
comment += '✅ All files pass size limits!';
} else {
if (report.errors > 0) {
comment += `### ❌ Errors (${report.errors})\n`;
report.violations
.filter(v => v.severity === 'error')
.forEach(v => {
comment += `- **${v.file}**: ${v.metric} (${v.current} / ${v.limit})\n`;
});
}
if (report.warnings > 0) {
comment += `\n### ⚠️ Warnings (${report.warnings})\n`;
report.violations
.filter(v => v.severity === 'warning')
.forEach(v => {
comment += `- **${v.file}**: ${v.metric} (${v.current} / ${v.limit})\n`;
});
}
comment += '\n[See refactoring guide →](../blob/main/docs/REFACTORING_ENFORCEMENT_GUIDE.md)';
}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
+162 -162
View File
@@ -1,162 +1,162 @@
name: TODO to Issues Sync
# This workflow can be triggered manually to convert TODO items to GitHub issues
# or can be run on a schedule to keep issues in sync with TODO files
on:
workflow_dispatch:
inputs:
mode:
description: 'Execution mode'
required: true
type: choice
options:
- dry-run
- export-json
- create-issues
default: 'dry-run'
filter_priority:
description: 'Filter by priority (leave empty for all)'
required: false
type: choice
options:
- ''
- critical
- high
- medium
- low
filter_label:
description: 'Filter by label (e.g., security, frontend)'
required: false
type: string
exclude_checklist:
description: 'Exclude checklist items'
required: false
type: boolean
default: true
limit:
description: 'Limit number of issues (0 for no limit)'
required: false
type: number
default: 0
# Uncomment to run on a schedule (e.g., weekly)
# schedule:
# - cron: '0 0 * * 0' # Every Sunday at midnight
jobs:
convert-todos:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install GitHub CLI
run: |
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
&& sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
&& sudo apt update \
&& sudo apt install gh -y
- name: Authenticate GitHub CLI
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "$GH_TOKEN" | gh auth login --with-token
gh auth status
- name: Build command arguments
id: args
run: |
ARGS=""
# Add mode
if [ "${{ inputs.mode }}" = "dry-run" ]; then
ARGS="$ARGS --dry-run"
elif [ "${{ inputs.mode }}" = "export-json" ]; then
ARGS="$ARGS --output todos-export.json"
elif [ "${{ inputs.mode }}" = "create-issues" ]; then
ARGS="$ARGS --create"
fi
# Add filters
if [ -n "${{ inputs.filter_priority }}" ]; then
ARGS="$ARGS --filter-priority ${{ inputs.filter_priority }}"
fi
if [ -n "${{ inputs.filter_label }}" ]; then
ARGS="$ARGS --filter-label ${{ inputs.filter_label }}"
fi
if [ "${{ inputs.exclude_checklist }}" = "true" ]; then
ARGS="$ARGS --exclude-checklist"
fi
# Add limit if specified
if [ "${{ inputs.limit }}" != "0" ]; then
ARGS="$ARGS --limit ${{ inputs.limit }}"
fi
echo "args=$ARGS" >> $GITHUB_OUTPUT
echo "Command arguments: $ARGS"
- name: Run populate-kanban script
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python3 tools/project-management/populate-kanban.py ${{ steps.args.outputs.args }}
- name: Upload JSON export (if applicable)
if: inputs.mode == 'export-json'
uses: actions/upload-artifact@v4
with:
name: todos-export
path: todos-export.json
retention-days: 30
- name: Create summary
if: always()
run: |
echo "## TODO to Issues Conversion" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Mode:** ${{ inputs.mode }}" >> $GITHUB_STEP_SUMMARY
if [ -n "${{ inputs.filter_priority }}" ]; then
echo "**Priority Filter:** ${{ inputs.filter_priority }}" >> $GITHUB_STEP_SUMMARY
fi
if [ -n "${{ inputs.filter_label }}" ]; then
echo "**Label Filter:** ${{ inputs.filter_label }}" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ inputs.exclude_checklist }}" = "true" ]; then
echo "**Checklist Items:** Excluded" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ inputs.limit }}" != "0" ]; then
echo "**Limit:** ${{ inputs.limit }} items" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ inputs.mode }}" = "export-json" ]; then
echo "✅ JSON export created successfully" >> $GITHUB_STEP_SUMMARY
echo "Download the artifact from the workflow run page" >> $GITHUB_STEP_SUMMARY
elif [ "${{ inputs.mode }}" = "create-issues" ]; then
echo "✅ GitHub issues created successfully" >> $GITHUB_STEP_SUMMARY
echo "View issues: https://github.com/${{ github.repository }}/issues" >> $GITHUB_STEP_SUMMARY
else
echo "️ Dry run completed - no issues created" >> $GITHUB_STEP_SUMMARY
fi
name: TODO to Issues Sync
# This workflow can be triggered manually to convert TODO items to GitHub issues
# or can be run on a schedule to keep issues in sync with TODO files
on:
workflow_dispatch:
inputs:
mode:
description: 'Execution mode'
required: true
type: choice
options:
- dry-run
- export-json
- create-issues
default: 'dry-run'
filter_priority:
description: 'Filter by priority (leave empty for all)'
required: false
type: choice
options:
- ''
- critical
- high
- medium
- low
filter_label:
description: 'Filter by label (e.g., security, frontend)'
required: false
type: string
exclude_checklist:
description: 'Exclude checklist items'
required: false
type: boolean
default: true
limit:
description: 'Limit number of issues (0 for no limit)'
required: false
type: number
default: 0
# Uncomment to run on a schedule (e.g., weekly)
# schedule:
# - cron: '0 0 * * 0' # Every Sunday at midnight
jobs:
convert-todos:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install GitHub CLI
run: |
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
&& sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
&& sudo apt update \
&& sudo apt install gh -y
- name: Authenticate GitHub CLI
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "$GH_TOKEN" | gh auth login --with-token
gh auth status
- name: Build command arguments
id: args
run: |
ARGS=""
# Add mode
if [ "${{ inputs.mode }}" = "dry-run" ]; then
ARGS="$ARGS --dry-run"
elif [ "${{ inputs.mode }}" = "export-json" ]; then
ARGS="$ARGS --output todos-export.json"
elif [ "${{ inputs.mode }}" = "create-issues" ]; then
ARGS="$ARGS --create"
fi
# Add filters
if [ -n "${{ inputs.filter_priority }}" ]; then
ARGS="$ARGS --filter-priority ${{ inputs.filter_priority }}"
fi
if [ -n "${{ inputs.filter_label }}" ]; then
ARGS="$ARGS --filter-label ${{ inputs.filter_label }}"
fi
if [ "${{ inputs.exclude_checklist }}" = "true" ]; then
ARGS="$ARGS --exclude-checklist"
fi
# Add limit if specified
if [ "${{ inputs.limit }}" != "0" ]; then
ARGS="$ARGS --limit ${{ inputs.limit }}"
fi
echo "args=$ARGS" >> $GITHUB_OUTPUT
echo "Command arguments: $ARGS"
- name: Run populate-kanban script
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python3 tools/project-management/populate-kanban.py ${{ steps.args.outputs.args }}
- name: Upload JSON export (if applicable)
if: inputs.mode == 'export-json'
uses: actions/upload-artifact@v4
with:
name: todos-export
path: todos-export.json
retention-days: 30
- name: Create summary
if: always()
run: |
echo "## TODO to Issues Conversion" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Mode:** ${{ inputs.mode }}" >> $GITHUB_STEP_SUMMARY
if [ -n "${{ inputs.filter_priority }}" ]; then
echo "**Priority Filter:** ${{ inputs.filter_priority }}" >> $GITHUB_STEP_SUMMARY
fi
if [ -n "${{ inputs.filter_label }}" ]; then
echo "**Label Filter:** ${{ inputs.filter_label }}" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ inputs.exclude_checklist }}" = "true" ]; then
echo "**Checklist Items:** Excluded" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ inputs.limit }}" != "0" ]; then
echo "**Limit:** ${{ inputs.limit }} items" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ inputs.mode }}" = "export-json" ]; then
echo "✅ JSON export created successfully" >> $GITHUB_STEP_SUMMARY
echo "Download the artifact from the workflow run page" >> $GITHUB_STEP_SUMMARY
elif [ "${{ inputs.mode }}" = "create-issues" ]; then
echo "✅ GitHub issues created successfully" >> $GITHUB_STEP_SUMMARY
echo "View issues: https://github.com/${{ github.repository }}/issues" >> $GITHUB_STEP_SUMMARY
else
echo "️ Dry run completed - no issues created" >> $GITHUB_STEP_SUMMARY
fi
+198 -198
View File
@@ -1,198 +1,198 @@
name: Issue and PR Triage
on:
issues:
types: [opened, edited, reopened]
pull_request:
types: [opened, reopened, synchronize, edited]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
triage-issue:
name: Triage Issues
if: github.event_name == 'issues'
runs-on: ubuntu-latest
steps:
- name: Categorize and label issue
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const title = (issue.title || '').toLowerCase();
const body = (issue.body || '').toLowerCase();
const text = `${title}\n${body}`;
const labels = new Set();
const missing = [];
const typeMatchers = [
{ regex: /bug|error|crash|broken|fail/, label: 'bug' },
{ regex: /feature|enhancement|add|new|implement/, label: 'enhancement' },
{ regex: /document|readme|docs|guide/, label: 'documentation' },
{ regex: /test|testing|spec|e2e/, label: 'testing' },
{ regex: /security|vulnerability|exploit|xss|sql/, label: 'security' },
{ regex: /performance|slow|optimize|speed/, label: 'performance' },
];
for (const match of typeMatchers) {
if (text.match(match.regex)) {
labels.add(match.label);
}
}
const areaMatchers = [
{ regex: /frontend|react|next|ui|component|browser/, label: 'area: frontend' },
{ regex: /api|backend|service|server/, label: 'area: backend' },
{ regex: /database|prisma|schema|sql/, label: 'area: database' },
{ regex: /workflow|github actions|ci|pipeline/, label: 'area: workflows' },
{ regex: /docs|readme|guide/, label: 'area: documentation' },
];
for (const match of areaMatchers) {
if (text.match(match.regex)) {
labels.add(match.label);
}
}
if (text.match(/critical|urgent|asap|blocker/)) {
labels.add('priority: high');
} else if (text.match(/minor|low|nice to have/)) {
labels.add('priority: low');
} else {
labels.add('priority: medium');
}
if (text.match(/beginner|easy|simple|starter/) || labels.size <= 2) {
labels.add('good first issue');
}
const reproductionHints = ['steps to reproduce', 'expected', 'actual'];
for (const hint of reproductionHints) {
if (!body.includes(hint)) {
missing.push(hint);
}
}
const supportInfo = body.includes('version') || body.match(/v\d+\.\d+/);
if (!supportInfo) {
missing.push('version information');
}
if (labels.size > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: Array.from(labels),
}).catch(e => console.log('Some labels may not exist:', e.message));
}
const checklist = missing.map(item => `- [ ] Add ${item}`).join('\n') || '- [x] Description includes key details.';
const summary = Array.from(labels).map(l => `- ${l}`).join('\n') || '- No labels inferred yet.';
const comment = [
'👋 Thanks for reporting an issue! I ran a quick triage:',
'',
'**Proposed labels:**',
summary,
'',
'**Missing details:**',
checklist,
'',
'Adding the missing details will help reviewers respond faster. If the proposed labels look wrong, feel free to update them.',
'',
'@copilot Please review this triage and refine labels or request any additional context needed—no Codex webhooks involved.'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment,
});
triage-pr:
name: Triage Pull Requests
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Analyze PR files and label
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
});
const labels = new Set();
const fileFlags = {
workflows: files.some(f => f.filename.includes('.github/workflows')),
docs: files.some(f => f.filename.match(/\.(md|mdx)$/) || f.filename.startsWith('docs/')),
frontend: files.some(f => f.filename.includes('frontends/nextjs')),
db: files.some(f => f.filename.includes('prisma/') || f.filename.includes('dbal/')),
tests: files.some(f => f.filename.match(/(test|spec)\.[jt]sx?/)),
};
if (fileFlags.workflows) labels.add('area: workflows');
if (fileFlags.docs) labels.add('area: documentation');
if (fileFlags.frontend) labels.add('area: frontend');
if (fileFlags.db) labels.add('area: database');
if (fileFlags.tests) labels.add('tests');
const totalChanges = files.reduce((sum, f) => sum + f.additions + f.deletions, 0);
const highRiskPaths = files.filter(f => f.filename.includes('.github/workflows') || f.filename.includes('prisma/'));
let riskLabel = 'risk: low';
if (highRiskPaths.length > 0 || totalChanges >= 400) {
riskLabel = 'risk: high';
} else if (totalChanges >= 150) {
riskLabel = 'risk: medium';
}
labels.add(riskLabel);
const missing = [];
const body = (pr.body || '').toLowerCase();
if (!body.includes('test')) missing.push('Test plan');
if (fileFlags.frontend && !body.includes('screenshot')) missing.push('Screenshots for UI changes');
if (!body.match(/#\d+|https:\/\/github\.com/)) missing.push('Linked issue reference');
if (labels.size > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: Array.from(labels),
}).catch(e => console.log('Some labels may not exist:', e.message));
}
const labelSummary = Array.from(labels).map(l => `- ${l}`).join('\n');
const missingList = missing.length ? missing.map(item => `- [ ] ${item}`).join('\n') : '- [x] Description includes required context.';
const comment = [
'🤖 **Automated PR triage**',
'',
'**Proposed labels:**',
labelSummary,
'',
'**Description check:**',
missingList,
'',
'If any labels look incorrect, feel free to adjust them. Closing the missing items will help reviewers move faster.',
'',
'@copilot Please double-check this triage (no Codex webhook) and add any extra labels or questions for the author.'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: comment,
});
name: Issue and PR Triage
on:
issues:
types: [opened, edited, reopened]
pull_request:
types: [opened, reopened, synchronize, edited]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
triage-issue:
name: Triage Issues
if: github.event_name == 'issues'
runs-on: ubuntu-latest
steps:
- name: Categorize and label issue
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const title = (issue.title || '').toLowerCase();
const body = (issue.body || '').toLowerCase();
const text = `${title}\n${body}`;
const labels = new Set();
const missing = [];
const typeMatchers = [
{ regex: /bug|error|crash|broken|fail/, label: 'bug' },
{ regex: /feature|enhancement|add|new|implement/, label: 'enhancement' },
{ regex: /document|readme|docs|guide/, label: 'documentation' },
{ regex: /test|testing|spec|e2e/, label: 'testing' },
{ regex: /security|vulnerability|exploit|xss|sql/, label: 'security' },
{ regex: /performance|slow|optimize|speed/, label: 'performance' },
];
for (const match of typeMatchers) {
if (text.match(match.regex)) {
labels.add(match.label);
}
}
const areaMatchers = [
{ regex: /frontend|react|next|ui|component|browser/, label: 'area: frontend' },
{ regex: /api|backend|service|server/, label: 'area: backend' },
{ regex: /database|prisma|schema|sql/, label: 'area: database' },
{ regex: /workflow|github actions|ci|pipeline/, label: 'area: workflows' },
{ regex: /docs|readme|guide/, label: 'area: documentation' },
];
for (const match of areaMatchers) {
if (text.match(match.regex)) {
labels.add(match.label);
}
}
if (text.match(/critical|urgent|asap|blocker/)) {
labels.add('priority: high');
} else if (text.match(/minor|low|nice to have/)) {
labels.add('priority: low');
} else {
labels.add('priority: medium');
}
if (text.match(/beginner|easy|simple|starter/) || labels.size <= 2) {
labels.add('good first issue');
}
const reproductionHints = ['steps to reproduce', 'expected', 'actual'];
for (const hint of reproductionHints) {
if (!body.includes(hint)) {
missing.push(hint);
}
}
const supportInfo = body.includes('version') || body.match(/v\d+\.\d+/);
if (!supportInfo) {
missing.push('version information');
}
if (labels.size > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: Array.from(labels),
}).catch(e => console.log('Some labels may not exist:', e.message));
}
const checklist = missing.map(item => `- [ ] Add ${item}`).join('\n') || '- [x] Description includes key details.';
const summary = Array.from(labels).map(l => `- ${l}`).join('\n') || '- No labels inferred yet.';
const comment = [
'👋 Thanks for reporting an issue! I ran a quick triage:',
'',
'**Proposed labels:**',
summary,
'',
'**Missing details:**',
checklist,
'',
'Adding the missing details will help reviewers respond faster. If the proposed labels look wrong, feel free to update them.',
'',
'@copilot Please review this triage and refine labels or request any additional context needed—no Codex webhooks involved.'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment,
});
triage-pr:
name: Triage Pull Requests
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Analyze PR files and label
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
});
const labels = new Set();
const fileFlags = {
workflows: files.some(f => f.filename.includes('.github/workflows')),
docs: files.some(f => f.filename.match(/\.(md|mdx)$/) || f.filename.startsWith('docs/')),
frontend: files.some(f => f.filename.includes('frontends/nextjs')),
db: files.some(f => f.filename.includes('prisma/') || f.filename.includes('dbal/')),
tests: files.some(f => f.filename.match(/(test|spec)\.[jt]sx?/)),
};
if (fileFlags.workflows) labels.add('area: workflows');
if (fileFlags.docs) labels.add('area: documentation');
if (fileFlags.frontend) labels.add('area: frontend');
if (fileFlags.db) labels.add('area: database');
if (fileFlags.tests) labels.add('tests');
const totalChanges = files.reduce((sum, f) => sum + f.additions + f.deletions, 0);
const highRiskPaths = files.filter(f => f.filename.includes('.github/workflows') || f.filename.includes('prisma/'));
let riskLabel = 'risk: low';
if (highRiskPaths.length > 0 || totalChanges >= 400) {
riskLabel = 'risk: high';
} else if (totalChanges >= 150) {
riskLabel = 'risk: medium';
}
labels.add(riskLabel);
const missing = [];
const body = (pr.body || '').toLowerCase();
if (!body.includes('test')) missing.push('Test plan');
if (fileFlags.frontend && !body.includes('screenshot')) missing.push('Screenshots for UI changes');
if (!body.match(/#\d+|https:\/\/github\.com/)) missing.push('Linked issue reference');
if (labels.size > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: Array.from(labels),
}).catch(e => console.log('Some labels may not exist:', e.message));
}
const labelSummary = Array.from(labels).map(l => `- ${l}`).join('\n');
const missingList = missing.length ? missing.map(item => `- [ ] ${item}`).join('\n') : '- [x] Description includes required context.';
const comment = [
'🤖 **Automated PR triage**',
'',
'**Proposed labels:**',
labelSummary,
'',
'**Description check:**',
missingList,
'',
'If any labels look incorrect, feel free to adjust them. Closing the missing items will help reviewers move faster.',
'',
'@copilot Please double-check this triage (no Codex webhook) and add any extra labels or questions for the author.'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: comment,
});