diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 00000000..79a76b1a --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,141 @@ +name: Code Quality and Coverage + +on: + push: + branches: [main, develop] + paths: + - "packages/**" + - "lib/**" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - "package.json" + - "tsconfig.base.json" + - "eslint.shared.mjs" + - ".github/workflows/code-quality.yml" + pull_request: + branches: [main, develop] + paths: + - "packages/**" + - "lib/**" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - "package.json" + - "tsconfig.base.json" + - "eslint.shared.mjs" + - ".github/workflows/code-quality.yml" + workflow_dispatch: + +permissions: read-all + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +env: + NODE_VERSION: "24" + PNPM_VERSION: "11.20.0" + +jobs: + package-coverage: + name: Shared Packages Coverage Gate + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Harden runner + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup pnpm + uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 + with: + version: ${{ env.PNPM_VERSION }} + + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: ${{ env.NODE_VERSION }} + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --frozen-lockfile --prefer-offline + + # CLI compatibility tests execute lib/bin/generate-manifest.js, so the + # full dependency chain must be built before coverage is collected. + - name: Build shared packages and legacy collection tooling + run: pnpm --filter "@ai-primitives-hub/*" --filter "@prompt-registry/collection-scripts" build + + - name: Run package tests with coverage thresholds + run: pnpm --filter "@ai-primitives-hub/*" run test:coverage + + - name: Add coverage summary + if: always() + shell: bash + run: | + node <<'NODE' + const fs = require('node:fs'); + const packages = ['core', 'infra', 'app', 'cli']; + const rows = []; + for (const name of packages) { + const file = `packages/${name}/coverage/coverage-summary.json`; + if (!fs.existsSync(file)) continue; + const total = JSON.parse(fs.readFileSync(file, 'utf8')).total; + rows.push(`| ${name} | ${total.statements.pct}% | ${total.branches.pct}% | ${total.functions.pct}% | ${total.lines.pct}% |`); + } + const summary = [ + '## Shared package coverage', + '', + '| Package | Statements | Branches | Functions | Lines |', + '|---|---:|---:|---:|---:|', + ...rows, + '', + 'Coverage floors are enforced by each package\'s `vitest.config.mjs`.' + ].join('\n'); + fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${summary}\n`); + NODE + + - name: Upload coverage reports + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: shared-package-coverage + path: packages/*/coverage + if-no-files-found: warn + retention-days: 14 + + codeql: + name: CodeQL Analysis + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + security-events: write + + steps: + - name: Harden runner + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@24c7eb380a2dc368f2d129e4c65e51d172983a1e # v4 + with: + languages: javascript-typescript + queries: security-extended + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@24c7eb380a2dc368f2d129e4c65e51d172983a1e # v4 + with: + category: "/language:javascript-typescript" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index c0bd2c8f..1b9e39b9 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -11,6 +11,7 @@ on: paths: - '**/package.json' - '**/package-lock.json' + - 'pnpm-lock.yaml' jobs: dependency-review: @@ -21,7 +22,7 @@ jobs: pull-requests: write steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6106ceac..cce5848d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -41,7 +41,7 @@ jobs: pull-requests: read steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit @@ -101,7 +101,7 @@ jobs: url: ${{ steps.deployment.outputs.page_url }} steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit diff --git a/.github/workflows/lib-collection-scripts-ci.yml b/.github/workflows/lib-collection-scripts-ci.yml index 0e9a8ee7..aa654b5e 100644 --- a/.github/workflows/lib-collection-scripts-ci.yml +++ b/.github/workflows/lib-collection-scripts-ci.yml @@ -41,7 +41,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 with: scan-type: "fs" scan-ref: lib diff --git a/.github/workflows/post-pr-comment.yml b/.github/workflows/post-pr-comment.yml index 00ba2be8..55c665cb 100644 --- a/.github/workflows/post-pr-comment.yml +++ b/.github/workflows/post-pr-comment.yml @@ -19,7 +19,7 @@ jobs: if: github.event.workflow_run.event == 'pull_request' steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit diff --git a/.github/workflows/vscode-extension-secure-ci.yml b/.github/workflows/vscode-extension-secure-ci.yml index f144037b..dbe92846 100644 --- a/.github/workflows/vscode-extension-secure-ci.yml +++ b/.github/workflows/vscode-extension-secure-ci.yml @@ -56,7 +56,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit @@ -94,7 +94,7 @@ jobs: - name: Run Trivy vulnerability scanner id: trivy_scan - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 with: scan-type: "fs" scan-ref: ${{ env.EXTENSION_DIR }} @@ -115,7 +115,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit @@ -177,7 +177,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit @@ -327,7 +327,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit @@ -395,7 +395,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit diff --git a/.github/workflows/vscode-extension-secure-publish.yml b/.github/workflows/vscode-extension-secure-publish.yml index 0027bed0..c1cdca56 100644 --- a/.github/workflows/vscode-extension-secure-publish.yml +++ b/.github/workflows/vscode-extension-secure-publish.yml @@ -50,7 +50,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit @@ -106,7 +106,7 @@ jobs: npm version ${{ steps.version.outputs.version }} --no-git-tag-version - name: Run final security scan - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 with: scan-type: "fs" scan-ref: ${{ env.EXTENSION_DIR }} @@ -185,7 +185,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit @@ -247,7 +247,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit @@ -301,7 +301,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@v2 + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit diff --git a/packages/app/vitest.config.mjs b/packages/app/vitest.config.mjs new file mode 100644 index 00000000..3b330f7a --- /dev/null +++ b/packages/app/vitest.config.mjs @@ -0,0 +1,19 @@ +import { + defineConfig, +} from 'vitest/config'; + +export default defineConfig({ + test: { + coverage: { + provider: 'v8', + include: ['src/**/*.ts'], + reporter: ['text', 'html', 'json-summary'], + thresholds: { + statements: 85, + branches: 75, + functions: 85, + lines: 85 + } + } + } +}); diff --git a/packages/cli/vitest.config.mjs b/packages/cli/vitest.config.mjs new file mode 100644 index 00000000..ded5ef04 --- /dev/null +++ b/packages/cli/vitest.config.mjs @@ -0,0 +1,19 @@ +import { + defineConfig, +} from 'vitest/config'; + +export default defineConfig({ + test: { + coverage: { + provider: 'v8', + include: ['src/**/*.ts'], + reporter: ['text', 'html', 'json-summary'], + thresholds: { + statements: 65, + branches: 45, + functions: 55, + lines: 65 + } + } + } +}); diff --git a/packages/core/vitest.config.mjs b/packages/core/vitest.config.mjs new file mode 100644 index 00000000..8939bd81 --- /dev/null +++ b/packages/core/vitest.config.mjs @@ -0,0 +1,19 @@ +import { + defineConfig, +} from 'vitest/config'; + +export default defineConfig({ + test: { + coverage: { + provider: 'v8', + include: ['src/**/*.ts'], + reporter: ['text', 'html', 'json-summary'], + thresholds: { + statements: 80, + branches: 75, + functions: 80, + lines: 80 + } + } + } +}); diff --git a/packages/infra/vitest.config.mjs b/packages/infra/vitest.config.mjs new file mode 100644 index 00000000..abc9bf1e --- /dev/null +++ b/packages/infra/vitest.config.mjs @@ -0,0 +1,19 @@ +import { + defineConfig, +} from 'vitest/config'; + +export default defineConfig({ + test: { + coverage: { + provider: 'v8', + include: ['src/**/*.ts'], + reporter: ['text', 'html', 'json-summary'], + thresholds: { + statements: 80, + branches: 70, + functions: 80, + lines: 80 + } + } + } +});