ci: bump actions/cache from 4 to 5 #563
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [trunk] | |
| pull_request: | |
| branches: [trunk] | |
| env: | |
| DOTNET_VERSION: '10.0.x' | |
| DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true | |
| DOTNET_NOLOGO: true | |
| PYTHON_VERSION: '3.11' | |
| jobs: | |
| changes: | |
| name: Detect Non-Test Changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| non_test_changes: ${{ steps.filter.outputs.non_test_changes }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Determine diff range | |
| id: diff | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| git fetch origin ${{ github.base_ref }} --depth=1 | |
| echo "range=origin/${{ github.base_ref }}...${{ github.sha }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "range=${{ github.event.before }}...${{ github.sha }}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Check for non-test changes | |
| id: filter | |
| run: | | |
| CHANGED_FILES=$(git diff --name-only "${{ steps.diff.outputs.range }}" || true) | |
| NON_TEST_FILES=$(echo "$CHANGED_FILES" | grep -Ev '^(tests/|test/)' | sed '/^$/d' || true) | |
| if [ -z "$NON_TEST_FILES" ]; then | |
| echo "non_test_changes=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "non_test_changes=true" >> $GITHUB_OUTPUT | |
| fi | |
| echo "Changed files:" | |
| echo "$CHANGED_FILES" | |
| echo "" | |
| echo "Non-test files:" | |
| echo "$NON_TEST_FILES" | |
| build: | |
| name: Build & Format Check | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.user.login != 'dependabot[bot]' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Instruction Sync (Claude/Codex) | |
| run: bash scripts/check-instructions-sync.sh | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: Restore | |
| run: dotnet restore Honua.sln | |
| - name: Build (warnings as errors) | |
| run: dotnet build Honua.sln --no-restore --configuration Release /p:TreatWarningsAsErrors=true | |
| - name: Format Check | |
| run: dotnet format Honua.sln --verify-no-changes --verbosity diagnostic | |
| test-all: | |
| name: Test Suite (.NET + Python + JS) | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.user.login != 'dependabot[bot]' }} | |
| runs-on: ubuntu-latest | |
| needs: build | |
| timeout-minutes: 35 | |
| env: | |
| TESTCONTAINERS_RYUK_DISABLED: false | |
| TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX: "" | |
| HONUA_TEST_CONFIGURATION: Release | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: Setup Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: 'pip' | |
| cache-dependency-path: tests/python/requirements.txt | |
| - name: Install Localstack CLI (awslocal) | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install --user awscli-local | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: tests/js/package-lock.json | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/packages.lock.json') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nuget- | |
| - name: Restore | |
| run: dotnet restore Honua.sln | |
| - name: Create Test Results Directory | |
| run: mkdir -p ./tests/TestResults | |
| - name: Pre-pull Docker images for faster tests | |
| run: | | |
| docker pull postgis/postgis:16-3.4-alpine || true | |
| docker pull postgis/postgis:18-3.6 || true | |
| - name: Start emulator containers (Azurite + Localstack) | |
| run: | | |
| docker compose -f .devcontainer/docker-compose.emulators.yml up -d | |
| for i in {1..30}; do | |
| if (echo > /dev/tcp/localhost/4566) >/dev/null 2>&1 \ | |
| && (echo > /dev/tcp/localhost/10000) >/dev/null 2>&1; then | |
| exit 0 | |
| fi | |
| sleep 2 | |
| done | |
| echo "Emulators failed to start." | |
| docker compose -f .devcontainer/docker-compose.emulators.yml ps | |
| exit 1 | |
| - name: Run .NET Tests | |
| timeout-minutes: 25 | |
| run: | | |
| dotnet build tests/Honua.Admin.Playwright/Honua.Admin.Playwright.csproj \ | |
| --configuration Release | |
| pwsh tests/Honua.Admin.Playwright/bin/Release/net10.0/playwright.ps1 install | |
| dotnet test Honua.sln \ | |
| --no-restore \ | |
| --configuration Release \ | |
| --logger "trx;LogFileName=all-test-results.trx" \ | |
| --logger "console;verbosity=minimal" \ | |
| --results-directory ./tests/TestResults \ | |
| --blame-hang-timeout 8m \ | |
| --collect:"XPlat Code Coverage" \ | |
| --settings tests/coverlet.runsettings \ | |
| -- RunConfiguration.MaxCpuCount=0 | |
| - name: Install Python test dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r tests/python/requirements.txt | |
| - name: Run Python Integration Tests (OGC + FeatureServer) | |
| run: pytest --tb=short | |
| working-directory: tests/python | |
| - name: Install JavaScript test dependencies | |
| run: npm ci | |
| working-directory: tests/js | |
| - name: TypeScript type check (JavaScript tests) | |
| run: npm run typecheck | |
| working-directory: tests/js | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| files: ./tests/TestResults/**/coverage.cobertura.xml | |
| flags: combined | |
| name: honua-server-coverage | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| - name: Upload Test Results | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| timeout-minutes: 3 | |
| with: | |
| name: all-test-results | |
| path: tests/TestResults/ | |
| retention-days: 7 | |
| js-integration-tests: | |
| name: JavaScript Integration Tests | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.user.login != 'dependabot[bot]' }} | |
| runs-on: ubuntu-latest | |
| needs: build | |
| timeout-minutes: 15 | |
| services: | |
| postgres: | |
| image: postgis/postgis:16-3.4-alpine | |
| env: | |
| POSTGRES_USER: honua | |
| POSTGRES_PASSWORD: honua | |
| POSTGRES_DB: honua_test | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: tests/js/package-lock.json | |
| - name: Restore .NET packages | |
| run: dotnet restore Honua.sln | |
| - name: Build Honua Server | |
| run: dotnet build src/Honua.Server --configuration Release --no-restore | |
| - name: Initialize test database | |
| run: | | |
| PGPASSWORD=honua psql -h localhost -U honua -d honua_test -c "CREATE EXTENSION IF NOT EXISTS postgis;" | |
| env: | |
| PGPASSWORD: honua | |
| - name: Seed FeatureServer metadata | |
| run: | | |
| cat <<'SQL' > /tmp/honua-js-seed.sql | |
| CREATE SCHEMA IF NOT EXISTS honua; | |
| CREATE TABLE IF NOT EXISTS honua.services ( | |
| service_name VARCHAR(64) PRIMARY KEY, | |
| description TEXT NOT NULL DEFAULT '', | |
| srid INT NOT NULL DEFAULT 4326, | |
| max_record_count INT NOT NULL DEFAULT 1000, | |
| supported_formats TEXT[] NOT NULL DEFAULT '{JSON,GeoJSON}', | |
| capabilities TEXT[] NOT NULL DEFAULT '{Query,Extract}', | |
| service_extent GEOMETRY, | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| CREATE TABLE IF NOT EXISTS honua.layers ( | |
| layer_id SERIAL PRIMARY KEY, | |
| layer_name TEXT NOT NULL, | |
| description TEXT, | |
| table_name TEXT NOT NULL, | |
| geometry_type TEXT NOT NULL, | |
| srid INT NOT NULL DEFAULT 4326, | |
| extent GEOMETRY(POLYGON, 4326), | |
| min_scale DOUBLE PRECISION, | |
| max_scale DOUBLE PRECISION, | |
| default_visibility BOOLEAN NOT NULL DEFAULT TRUE, | |
| created_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| CREATE TABLE IF NOT EXISTS honua.service_layers ( | |
| service_name VARCHAR(64) NOT NULL REFERENCES honua.services(service_name) ON DELETE CASCADE, | |
| layer_id INT NOT NULL REFERENCES honua.layers(layer_id) ON DELETE CASCADE, | |
| layer_order INT NOT NULL, | |
| PRIMARY KEY (service_name, layer_id), | |
| UNIQUE (service_name, layer_order) | |
| ); | |
| CREATE TABLE IF NOT EXISTS honua.layer_fields ( | |
| layer_id INT NOT NULL REFERENCES honua.layers(layer_id) ON DELETE CASCADE, | |
| field_name VARCHAR(64) NOT NULL, | |
| field_type VARCHAR(32) NOT NULL, | |
| field_order INT NOT NULL, | |
| max_length INT, | |
| nullable BOOLEAN NOT NULL DEFAULT TRUE, | |
| default_value TEXT, | |
| description TEXT, | |
| PRIMARY KEY (layer_id, field_name) | |
| ); | |
| CREATE TABLE IF NOT EXISTS honua.attachments ( | |
| id BIGSERIAL PRIMARY KEY, | |
| feature_id BIGINT NOT NULL, | |
| layer_id INT NOT NULL, | |
| filename TEXT NOT NULL, | |
| content_type TEXT NOT NULL, | |
| size BIGINT NOT NULL CHECK (size >= 0), | |
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| storage_path TEXT NOT NULL, | |
| keywords TEXT | |
| ); | |
| CREATE TABLE IF NOT EXISTS honua.relationships ( | |
| id SERIAL PRIMARY KEY, | |
| layer_id INT NOT NULL REFERENCES honua.layers(layer_id) ON DELETE CASCADE, | |
| relationship_id INT NOT NULL, | |
| name TEXT NOT NULL, | |
| related_layer_id INT NOT NULL REFERENCES honua.layers(layer_id) ON DELETE CASCADE, | |
| relationship_type TEXT NOT NULL, | |
| origin_foreign_key TEXT NOT NULL, | |
| destination_foreign_key TEXT NOT NULL, | |
| description TEXT, | |
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| CONSTRAINT relationships_layer_relationship_unique UNIQUE(layer_id, relationship_id), | |
| CONSTRAINT relationships_valid_ids CHECK(layer_id >= 0 AND related_layer_id >= 0 AND relationship_id > 0), | |
| CONSTRAINT relationships_valid_fields CHECK( | |
| LENGTH(name) > 0 AND LENGTH(name) <= 128 AND | |
| LENGTH(relationship_type) > 0 AND LENGTH(relationship_type) <= 64 AND | |
| LENGTH(origin_foreign_key) > 0 AND LENGTH(origin_foreign_key) <= 128 AND | |
| LENGTH(destination_foreign_key) > 0 AND LENGTH(destination_foreign_key) <= 128 | |
| ), | |
| CONSTRAINT relationships_valid_type CHECK( | |
| relationship_type IN ('esriRelRoleOrigin', 'esriRelRoleDestination', 'esriRelRoleAny') | |
| ) | |
| ); | |
| CREATE TABLE IF NOT EXISTS features ( | |
| objectid BIGSERIAL PRIMARY KEY, | |
| layer_id INT NOT NULL, | |
| geometry GEOMETRY, | |
| attributes JSONB, | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_service_layers_service_name ON honua.service_layers(service_name); | |
| CREATE INDEX IF NOT EXISTS idx_service_layers_layer_id ON honua.service_layers(layer_id); | |
| CREATE INDEX IF NOT EXISTS idx_layer_fields_layer_id ON honua.layer_fields(layer_id); | |
| CREATE INDEX IF NOT EXISTS idx_relationships_layer_id ON honua.relationships(layer_id); | |
| CREATE INDEX IF NOT EXISTS idx_relationships_related_layer_id ON honua.relationships(related_layer_id); | |
| CREATE INDEX IF NOT EXISTS idx_features_layer_id ON features(layer_id); | |
| CREATE INDEX IF NOT EXISTS idx_features_geometry ON features USING GIST(geometry); | |
| CREATE INDEX IF NOT EXISTS idx_features_attributes ON features USING GIN(attributes); | |
| INSERT INTO honua.services ( | |
| service_name, | |
| description, | |
| srid, | |
| max_record_count, | |
| supported_formats, | |
| capabilities, | |
| service_extent | |
| ) | |
| VALUES ( | |
| 'test_service', | |
| 'Test Feature Service', | |
| 4326, | |
| 1000, | |
| ARRAY['JSON', 'GeoJSON'], | |
| ARRAY['Query', 'Extract', 'Create', 'Update', 'Delete'], | |
| ST_MakeEnvelope(-122.5, 37.7, -122.35, 37.84, 4326) | |
| ) | |
| ON CONFLICT (service_name) DO UPDATE SET | |
| description = EXCLUDED.description, | |
| srid = EXCLUDED.srid, | |
| max_record_count = EXCLUDED.max_record_count, | |
| supported_formats = EXCLUDED.supported_formats, | |
| capabilities = EXCLUDED.capabilities, | |
| service_extent = EXCLUDED.service_extent, | |
| updated_at = NOW(); | |
| INSERT INTO honua.layers ( | |
| layer_id, | |
| layer_name, | |
| description, | |
| table_name, | |
| geometry_type, | |
| srid, | |
| extent, | |
| default_visibility | |
| ) | |
| VALUES ( | |
| 0, | |
| 'Test Layer', | |
| 'Default layer for integration tests', | |
| 'features', | |
| 'Point', | |
| 4326, | |
| ST_MakeEnvelope(-122.5, 37.7, -122.35, 37.84, 4326), | |
| true | |
| ) | |
| ON CONFLICT (layer_id) DO UPDATE SET | |
| layer_name = EXCLUDED.layer_name, | |
| description = EXCLUDED.description, | |
| table_name = EXCLUDED.table_name, | |
| geometry_type = EXCLUDED.geometry_type, | |
| srid = EXCLUDED.srid, | |
| extent = EXCLUDED.extent, | |
| default_visibility = EXCLUDED.default_visibility; | |
| INSERT INTO honua.layer_fields ( | |
| layer_id, | |
| field_name, | |
| field_type, | |
| field_order, | |
| max_length, | |
| nullable, | |
| default_value, | |
| description | |
| ) | |
| VALUES | |
| (0, 'objectid', 'Integer', 0, NULL, false, NULL, 'Object ID'), | |
| (0, 'name', 'String', 1, 255, true, NULL, 'Name'), | |
| (0, 'description', 'String', 2, 1024, true, NULL, 'Description'), | |
| (0, 'shape', 'Geometry', 3, NULL, true, NULL, 'Geometry') | |
| ON CONFLICT (layer_id, field_name) DO NOTHING; | |
| INSERT INTO honua.layer_fields ( | |
| layer_id, | |
| field_name, | |
| field_type, | |
| field_order, | |
| max_length, | |
| nullable, | |
| default_value, | |
| description | |
| ) | |
| VALUES | |
| (0, 'status', 'String', 4, 64, true, NULL, 'Status'), | |
| (0, 'count', 'Integer', 5, NULL, true, NULL, 'Count'), | |
| (0, 'ratio', 'Double', 6, NULL, true, NULL, 'Ratio'), | |
| (0, 'active', 'Boolean', 7, NULL, true, NULL, 'Active flag'), | |
| (0, 'created_at', 'DateTime', 8, NULL, true, NULL, 'Created timestamp'), | |
| (0, 'event_date', 'Date', 9, NULL, true, NULL, 'Event date'), | |
| (0, 'event_time', 'Time', 10, NULL, true, NULL, 'Event time'), | |
| (0, 'uid', 'Uuid', 11, NULL, true, NULL, 'Unique identifier'), | |
| (0, 'tags', 'Json', 12, NULL, true, NULL, 'Tag array'), | |
| (0, 'numbers', 'Json', 13, NULL, true, NULL, 'Number array') | |
| ON CONFLICT (layer_id, field_name) DO NOTHING; | |
| INSERT INTO honua.service_layers ( | |
| service_name, | |
| layer_id, | |
| layer_order | |
| ) | |
| VALUES ('test_service', 0, 0) | |
| ON CONFLICT (service_name, layer_id) DO NOTHING; | |
| SQL | |
| PGPASSWORD=honua psql -v ON_ERROR_STOP=1 -h localhost -U honua -d honua_test -f /tmp/honua-js-seed.sql | |
| - name: Start Honua Server | |
| run: | | |
| dotnet run --project src/Honua.Server --configuration Release --no-build & | |
| sleep 10 | |
| curl --retry 10 --retry-delay 2 --retry-connrefused http://localhost:5000/health || exit 1 | |
| env: | |
| ConnectionStrings__DefaultConnection: "Host=localhost;Database=honua_test;Username=honua;Password=honua" | |
| ASPNETCORE_URLS: "http://localhost:5000" | |
| Security__ConnectionEncryption__MasterKey: "test-master-key-that-is-at-least-32-characters-long-for-security" | |
| Security__ConnectionEncryption__Salt: "dGVzdC1zYWx0LWZvci1lbmNyeXB0aW9uLXRlc3RpbmctcHVycG9zZXM=" | |
| - name: Install JavaScript test dependencies | |
| run: npm ci | |
| working-directory: tests/js | |
| - name: Run JavaScript Integration Tests | |
| run: npm test | |
| working-directory: tests/js | |
| env: | |
| HONUA_BASE_URL: http://localhost:5000 | |
| HONUA_SERVICE_ID: test_service | |
| HONUA_LAYER_ID: '0' | |
| llm-architecture-review: | |
| name: LLM Architecture Review | |
| runs-on: ubuntu-latest | |
| needs: [build, test-all, aot-build] # Run after core tests complete | |
| if: github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.event.pull_request.user.login != 'dependabot[bot]' | |
| outputs: | |
| assessment: ${{ steps.llm-analysis.outputs.assessment }} | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Checkout PR | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Get Changed Files | |
| id: changed-files | |
| run: | | |
| # Get ALL changed files for comprehensive review (acceptance criteria, issue linking) | |
| ALL_CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...${{ github.sha }}) | |
| # Get C# files separately for architecture-specific analysis | |
| CS_CHANGED_FILES=$(echo "$ALL_CHANGED_FILES" | grep -E '\.(cs|csproj)$' || echo "") | |
| if [ -z "$ALL_CHANGED_FILES" ]; then | |
| echo "No files changed, skipping review" | |
| echo "skip_review=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "changed_files<<EOF" >> $GITHUB_OUTPUT | |
| echo "$ALL_CHANGED_FILES" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| echo "cs_files<<EOF" >> $GITHUB_OUTPUT | |
| echo "$CS_CHANGED_FILES" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| echo "skip_review=false" >> $GITHUB_OUTPUT | |
| # Show what we found | |
| echo "All changed files:" | |
| echo "$ALL_CHANGED_FILES" | |
| echo "" | |
| echo "C# files (for architecture analysis):" | |
| echo "$CS_CHANGED_FILES" | |
| - name: Setup Python | |
| if: steps.changed-files.outputs.skip_review == 'false' | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.11' | |
| - name: Install Dependencies | |
| if: steps.changed-files.outputs.skip_review == 'false' | |
| run: | | |
| python -m pip install --upgrade pip | |
| # Only install OpenAI if API key is available | |
| if [ -n "${{ secrets.OPENAI_API_KEY }}" ]; then | |
| pip install openai | |
| fi | |
| - name: LLM Architecture Analysis | |
| if: steps.changed-files.outputs.skip_review == 'false' | |
| id: llm-analysis | |
| env: | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| GITHUB_BASE_REF: ${{ github.base_ref }} | |
| GITHUB_SHA: ${{ github.sha }} | |
| GITHUB_PR_NUMBER: ${{ github.event.number }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| REVIEW_CHUNK_LINES: 300 | |
| REVIEW_MAX_FILES: 50 | |
| run: | | |
| python scripts/architecture-review.py | |
| - name: Post Review Comment | |
| if: steps.changed-files.outputs.skip_review == 'false' | |
| uses: actions/github-script@v8 | |
| env: | |
| ANALYSIS_OUTPUT: ${{ steps.llm-analysis.outputs.analysis }} | |
| ASSESSMENT_OUTPUT: ${{ steps.llm-analysis.outputs.assessment }} | |
| with: | |
| script: | | |
| const analysis = process.env.ANALYSIS_OUTPUT; | |
| const assessment = process.env.ASSESSMENT_OUTPUT; | |
| let assessmentIcon = '✅'; | |
| let assessmentColor = 'green'; | |
| if (assessment === 'NEEDS_ATTENTION') { | |
| assessmentIcon = '⚠️'; | |
| assessmentColor = 'orange'; | |
| } else if (assessment === 'BLOCKING_ISSUES') { | |
| assessmentIcon = '🚫'; | |
| assessmentColor = 'red'; | |
| } | |
| const body = `## 🤖 LLM Architecture Review | |
| ${assessmentIcon} **Assessment: ${assessment}** | |
| ${analysis} | |
| --- | |
| *Automated architectural analysis powered by OpenAI GPT-4* | |
| *This review focuses on architectural patterns and design decisions* | |
| *Human review still recommended for complex changes*`; | |
| // Check if we already have a review comment | |
| const comments = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number | |
| }); | |
| const existingComment = comments.data.find(comment => | |
| comment.body.includes('🤖 LLM Architecture Review') | |
| ); | |
| if (existingComment) { | |
| // Update existing comment | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existingComment.id, | |
| body: body | |
| }); | |
| } else { | |
| // Create new comment | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: body | |
| }); | |
| } | |
| aot-build: | |
| name: AOT Build Verification | |
| if: ${{ (github.event_name != 'pull_request' || github.event.pull_request.user.login != 'dependabot[bot]') && needs.changes.outputs.non_test_changes == 'true' }} | |
| runs-on: ubuntu-latest | |
| needs: [build, changes] # Run in parallel with tests | |
| timeout-minutes: 8 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: Restore | |
| run: dotnet restore Honua.sln | |
| - name: Publish AOT | |
| run: | | |
| dotnet publish \ | |
| --configuration Release \ | |
| --runtime linux-x64 \ | |
| --self-contained \ | |
| -p:PublishAot=true \ | |
| -p:StripSymbols=true \ | |
| -o ./publish | |
| working-directory: src/Honua.Server | |
| - name: Verify Binary Size | |
| run: | | |
| SIZE=$(stat --printf="%s" ./publish/Honua.Server 2>/dev/null || echo "0") | |
| SIZE_MB=$((SIZE / 1024 / 1024)) | |
| echo "Binary size: ${SIZE_MB}MB" | |
| # Warn if over 100MB (target from MVP_PLAN) | |
| if [ "$SIZE_MB" -gt 100 ]; then | |
| echo "::warning::AOT binary ${SIZE_MB}MB exceeds 100MB target" | |
| fi | |
| working-directory: src/Honua.Server | |
| # Architecture gate job that blocks merge if LLM review finds blocking issues | |
| architecture-gate: | |
| name: Architecture Gate | |
| needs: llm-architecture-review | |
| runs-on: ubuntu-latest | |
| if: needs.llm-architecture-review.outputs.assessment == 'BLOCKING_ISSUES' | |
| steps: | |
| - name: Block PR on Architectural Violations | |
| run: | | |
| echo "::error::Architecture review found BLOCKING_ISSUES that must be resolved before merge." | |
| echo "::error::Please review the LLM Architecture Review comment and address all critical violations." | |
| echo "::error::Common blocking issues: dependency violations, controller usage, AOT incompatibility, missing docs." | |
| exit 1 |