diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 21904649..05c98a35 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -34,6 +34,8 @@ concurrency: env: ORG_NAME: ${{ github.repository_owner }} REPO_NAME: ${{ github.event.repository.name }} + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} jobs: # ========================================================================== @@ -345,3 +347,47 @@ jobs: else echo "â„šī¸ No documentation changes" fi + + # ========================================================================== + # Docker: Build and Push + # ========================================================================== + docker: + name: Docker Release + needs: [nodejs, python] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=raw,value=latest,enable={{github.ref == 'refs/heads/main'}} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1626c724..f867802e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI (Python) +name: CI on: push: @@ -6,10 +6,12 @@ on: pull_request: workflow_dispatch: +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + env: FORCE_COLOR: "1" - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_NO_PYTHON_VERSION_WARNING: "1" permissions: contents: read @@ -17,8 +19,98 @@ permissions: id-token: write jobs: + # ========================================================================== + # Node.js / TypeScript + # ========================================================================== lint: - name: Lint & Type Check + name: Lint (Node) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'pnpm' + - run: pnpm install --no-frozen-lockfile + - run: pnpm lint + + typecheck: + name: Typecheck (Node) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'pnpm' + - run: pnpm install --no-frozen-lockfile + - run: pnpm typecheck + + build: + name: Build (Node) + needs: [lint, typecheck] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'pnpm' + - run: pnpm install --no-frozen-lockfile + - run: pnpm build + - name: Verify builds + run: | + ERROR=0 + for pkg in packages/*/; do + if [ -d "$pkg/dist" ]; then + echo "✅ $pkg built successfully" + else + echo "❌ $pkg has no dist" + ERROR=1 + fi + done + exit $ERROR + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-artifacts + path: | + packages/*/dist + dist + retention-days: 1 + + test: + name: Test (Node) + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'pnpm' + - run: pnpm install --no-frozen-lockfile + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-artifacts + - name: Run tests with coverage + run: pnpm test:coverage + - name: Upload coverage to Coveralls + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + file: coverage/lcov.info + + # ========================================================================== + # Python + # ========================================================================== + lint-python: + name: Lint (Python) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -35,10 +127,10 @@ jobs: uv sync --all-extras uv run mypy . - test: + test-python: name: Test (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest - needs: lint + needs: lint-python strategy: matrix: python-version: ["3.11", "3.12", "3.13"] @@ -53,15 +145,18 @@ jobs: run: uv sync --all-extras - name: Run tests run: uv run pytest --cov --cov-report=xml - - name: Upload coverage + - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} + # ========================================================================== + # Release + # ========================================================================== release: name: Release if: github.ref == 'refs/heads/main' && github.event_name == 'push' - needs: test + needs: [test, test-python] runs-on: ubuntu-latest permissions: contents: write @@ -71,6 +166,18 @@ jobs: with: fetch-depth: 0 token: ${{ secrets.CI_GITHUB_TOKEN }} + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'pnpm' + - run: pnpm install + - name: Release (Node) + env: + GITHUB_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npx semantic-release + - uses: astral-sh/setup-uv@v5 - name: Python Semantic Release env: diff --git a/.github/workflows/docs-push.yml b/.github/workflows/docs-push.yml index 3a6df6b8..ab5057ac 100644 --- a/.github/workflows/docs-push.yml +++ b/.github/workflows/docs-push.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Detect Project Type id: detect diff --git a/.github/workflows/ecosystem-agents.yml b/.github/workflows/ecosystem-agents.yml index 37504ee5..2b3793ea 100644 --- a/.github/workflows/ecosystem-agents.yml +++ b/.github/workflows/ecosystem-agents.yml @@ -64,7 +64,7 @@ jobs: repo: ${{ steps.select.outputs.repo }} prompt: ${{ steps.select.outputs.prompt }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Select Agent id: select @@ -169,7 +169,7 @@ jobs: if: needs.dispatch.outputs.agent != 'triage' && needs.dispatch.outputs.agent != 'assessment' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.CI_GITHUB_TOKEN }} @@ -209,7 +209,7 @@ jobs: if: needs.dispatch.outputs.agent == 'triage' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Triage Issues and PRs env: @@ -279,7 +279,7 @@ jobs: if: needs.dispatch.outputs.agent == 'assessment' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/ecosystem-assessment.yml b/.github/workflows/ecosystem-assessment.yml index 92ab32ea..493e7ab3 100644 --- a/.github/workflows/ecosystem-assessment.yml +++ b/.github/workflows/ecosystem-assessment.yml @@ -49,7 +49,7 @@ jobs: branch: ${{ steps.branch.outputs.name }} improvements: ${{ steps.parse.outputs.matrix }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.CI_GITHUB_TOKEN }} diff --git a/.github/workflows/ecosystem-connector.yml b/.github/workflows/ecosystem-connector.yml index 55f7968a..44e5a2fc 100644 --- a/.github/workflows/ecosystem-connector.yml +++ b/.github/workflows/ecosystem-connector.yml @@ -22,7 +22,7 @@ jobs: if: github.event_name == 'pull_request' && !github.event.pull_request.draft runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-python@v5 with: python-version: "3.12" diff --git a/.github/workflows/ecosystem-control.yml b/.github/workflows/ecosystem-control.yml index 36fec373..734c0aca 100644 --- a/.github/workflows/ecosystem-control.yml +++ b/.github/workflows/ecosystem-control.yml @@ -116,7 +116,7 @@ jobs: if: needs.route.outputs.agent == 'claude' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.CI_GITHUB_TOKEN }} diff --git a/.github/workflows/ecosystem-surveyor.yml b/.github/workflows/ecosystem-surveyor.yml index 3db4fbef..4a2ec132 100644 --- a/.github/workflows/ecosystem-surveyor.yml +++ b/.github/workflows/ecosystem-surveyor.yml @@ -52,7 +52,7 @@ jobs: outputs: orgs: ${{ steps.discover.outputs.orgs }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Discover Managed Organizations id: discover @@ -104,12 +104,12 @@ jobs: org: ${{ fromJson(needs.survey.outputs.orgs) }} fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: path: source - name: Checkout Target - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{ matrix.org }}/control-center token: ${{ secrets.CI_GITHUB_TOKEN }} diff --git a/.github/workflows/jules-supervisor.yml b/.github/workflows/jules-supervisor.yml index ca4fce2b..d00ab0b2 100644 --- a/.github/workflows/jules-supervisor.yml +++ b/.github/workflows/jules-supervisor.yml @@ -9,7 +9,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v3 diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml new file mode 100644 index 00000000..8f39a1e6 --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,73 @@ +name: SonarCloud + +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize, reopened] + +concurrency: + group: sonar-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: read + +jobs: + sonarcloud: + name: SonarCloud Analysis + runs-on: ubuntu-latest + if: | + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository + steps: + - name: Check SonarCloud Token + id: check_token + shell: bash + run: | + if [ -z "${{ secrets.SONAR_TOKEN }}" ]; then + echo "::warning ::SONAR_TOKEN is missing. Skipping analysis." + echo "has_token=false" >> $GITHUB_OUTPUT + else + echo "has_token=true" >> $GITHUB_OUTPUT + fi + + # IMPORTANT: fetch-depth: 0 is REQUIRED for SonarCloud blame info + - uses: actions/checkout@v4 + if: steps.check_token.outputs.has_token == 'true' + with: + fetch-depth: 0 + + # pnpm version is read from packageManager in package.json + - uses: pnpm/action-setup@v4 + if: steps.check_token.outputs.has_token == 'true' + + - uses: actions/setup-node@v4 + if: steps.check_token.outputs.has_token == 'true' + with: + node-version: '22' + cache: 'pnpm' + + - name: Install dependencies + if: steps.check_token.outputs.has_token == 'true' + run: pnpm install --frozen-lockfile + + - name: Build + if: steps.check_token.outputs.has_token == 'true' + run: pnpm build + + # IMPORTANT: Coverage must run in SAME JOB as SonarCloud + - name: Run tests with coverage + if: steps.check_token.outputs.has_token == 'true' + run: pnpm test:coverage + + - name: SonarCloud Scan + if: steps.check_token.outputs.has_token == 'true' + uses: SonarSource/sonarcloud-github-action@v3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + args: > + -Dsonar.projectVersion=${{ github.event.pull_request.head.sha || github.sha }} diff --git a/package.json b/package.json index 4ec13ca4..23914fa6 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "node": ">=22.0.0", "pnpm": ">=9.0.0" }, + "packageManager": "pnpm@9.15.0", "keywords": [ "ai", "agent", diff --git a/packages/agentic-control/vitest.config.ts b/packages/agentic-control/vitest.config.ts index 2c68648e..5ee8b4df 100644 --- a/packages/agentic-control/vitest.config.ts +++ b/packages/agentic-control/vitest.config.ts @@ -7,9 +7,13 @@ export default defineConfig({ provider: 'v8', reporter: ['text', 'lcov', 'html'], reportsDirectory: './coverage', + thresholds: { + lines: 10, + functions: 10, + branches: 10, + statements: 10, + }, }, - // Setup files for test fixtures from vitest-agentic-control - // NOTE: Path is resolved relative to this vitest.config.ts file location setupFiles: ['./tests/setup.ts'], }, }); diff --git a/packages/vitest-agentic-control/vitest.config.ts b/packages/vitest-agentic-control/vitest.config.ts index 1b88e70a..aefe9b41 100644 --- a/packages/vitest-agentic-control/vitest.config.ts +++ b/packages/vitest-agentic-control/vitest.config.ts @@ -7,6 +7,12 @@ export default defineConfig({ provider: 'v8', reporter: ['text', 'lcov', 'html'], reportsDirectory: './coverage', + thresholds: { + lines: 10, + functions: 10, + branches: 10, + statements: 10, + }, }, }, }); diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..ca948341 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,28 @@ +# SonarCloud Configuration +# https://sonarcloud.io/project/configuration?id=agentic-dev-library_control + +sonar.projectKey=agentic-dev-library_control +sonar.organization=agentic-dev-library + +# Project info +sonar.projectName=control + +# Source paths (where your code lives) +sonar.sources=packages/agentic-control/src,packages/vitest-agentic-control/src,packages/providers/src + +# Test paths +sonar.tests=packages/agentic-control/tests,packages/vitest-agentic-control/tests +sonar.test.inclusions=**/*.test.ts,**/*.spec.ts + +# Coverage - REQUIRED for JS/TS (not auto-detected) +# https://docs.sonarsource.com/sonarqube-cloud/enriching/test-coverage/javascript-typescript-test-coverage/ +sonar.javascript.lcov.reportPaths=coverage/lcov.info + +# Exclusions +sonar.exclusions=**/node_modules/**,**/dist/**,**/coverage/**,**/*.d.ts,**/test-results/**,**/*.config.ts,**/*.config.js + +# Test file exclusions from coverage +sonar.coverage.exclusions=**/*.test.ts,**/*.spec.ts,**/tests/**,**/test/** + +# Encoding +sonar.sourceEncoding=UTF-8