mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-05-05 19:19:35 +00:00
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:
+571
-571
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
+1033
-1033
File diff suppressed because it is too large
Load Diff
+610
-610
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+182
-182
@@ -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
@@ -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.`
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.'
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
});
|
||||
|
||||
@@ -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
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user