name: CI # Tests, typechecks and drift-checks every push to main and every pull request: # the .NET suites, the web and MCP suites, a guard that the generated rule # files still match rules/guardrails.json, and the duplicate-code check. # # The deploy workflows build but never test, so this is the only place the suites # actually run. It does not gate anything — main is deliberately unprotected, so a # red run reports rather than blocks. Read the result and follow up. # # Path filters: commits that only bump a private submodule gitlink (internal/, infra/, # .claude/commands), touch prose or edit another workflow cannot change anything the # suites cover, so they are skipped instead of burning ~15 runner-minutes each. A gitlink bump reports the bare # path ('internal', no trailing slash), which 'internal/**' does NOT match — both forms # are listed. GitHub skips only when EVERY changed path matches; a commit that touches # docs AND code still runs. on: push: branches: [main] paths-ignore: - 'internal' - 'internal/**' - 'infra' - 'infra/**' - '.claude/**' - '.gitmodules' - 'zizmor.yml' - '**/*.md' - 'LICENSE' - '.github/ISSUE_TEMPLATE/**' - '.github/FUNDING.yml' - '.github/dependabot.yml' - '.github/assets/**' - '.github/codeql/**' - '.github/scripts/**' # Every other workflow: nothing here builds or tests anything CI covers. paths-ignore # cannot negate, so they are listed one by one; ci.yml itself stays out of the list # so a change to CI runs CI. A new workflow file is opt-in — add it here too. - '.github/workflows/action-pins.yml' - '.github/workflows/bootstrap-script-gates.yml' - '.github/workflows/build-agent.yml' - '.github/workflows/build-msi.yml' - '.github/workflows/codeql.yml' - '.github/workflows/combine-rules.yml' - '.github/workflows/deploy-backend.yml' - '.github/workflows/deploy-mcp.yml' - '.github/workflows/deploy-web.yml' - '.github/workflows/indexnow-notify.yml' - '.github/workflows/publish-scripts.yml' pull_request: branches: [main] paths-ignore: - 'internal' - 'internal/**' - 'infra' - 'infra/**' - '.claude/**' - '.gitmodules' - 'zizmor.yml' - '**/*.md' - 'LICENSE' - '.github/ISSUE_TEMPLATE/**' - '.github/FUNDING.yml' - '.github/dependabot.yml' - '.github/assets/**' - '.github/codeql/**' - '.github/scripts/**' # Every other workflow: nothing here builds or tests anything CI covers. paths-ignore # cannot negate, so they are listed one by one; ci.yml itself stays out of the list # so a change to CI runs CI. A new workflow file is opt-in — add it here too. - '.github/workflows/action-pins.yml' - '.github/workflows/bootstrap-script-gates.yml' - '.github/workflows/build-agent.yml' - '.github/workflows/build-msi.yml' - '.github/workflows/codeql.yml' - '.github/workflows/combine-rules.yml' - '.github/workflows/deploy-backend.yml' - '.github/workflows/deploy-mcp.yml' - '.github/workflows/deploy-web.yml' - '.github/workflows/indexnow-notify.yml' - '.github/workflows/publish-scripts.yml' workflow_dispatch: permissions: contents: read concurrency: group: ci-${{ github.ref }} cancel-in-progress: true jobs: dotnet: name: .NET tests (backend + agent) # windows-latest is required: the agent test projects target net48. runs-on: windows-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: '10.0.x' - name: Restore run: dotnet restore AutopilotMonitor.sln - name: Build run: dotnet build AutopilotMonitor.sln --configuration Release --no-restore - name: Backend tests run: dotnet test src/Backend/AutopilotMonitor.Functions.Tests --configuration Release --no-build --verbosity quiet --logger "console;verbosity=minimal" - name: Agent V2 Core tests run: dotnet test src/Agent/AutopilotMonitor.Agent.V2.Core.Tests --configuration Release --no-build --verbosity quiet --logger "console;verbosity=minimal" - name: Decision Core tests run: dotnet test src/Agent/AutopilotMonitor.DecisionCore.Tests --configuration Release --no-build --verbosity quiet --logger "console;verbosity=minimal" - name: Summary Dialog tests run: dotnet test src/Agent/AutopilotMonitor.SummaryDialog.Tests --configuration Release --no-build --verbosity quiet --logger "console;verbosity=minimal" - name: Push tests run: dotnet test src/Push/AutopilotMonitor.Push.Tests --configuration Release --no-build --verbosity quiet --logger "console;verbosity=minimal" web: name: Web (typecheck + tests) runs-on: ubuntu-latest defaults: run: working-directory: src/Web/autopilot-monitor-web steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '22' cache: npm cache-dependency-path: src/Web/autopilot-monitor-web/package-lock.json - run: npm ci # next-env.d.ts + .next type stubs are gitignored (Next-managed since # v16); typegen generates them so tsc sees the same types as a build. - name: Generate Next.js types run: npx next typegen - name: Typecheck run: npx tsc --noEmit # Errors fail outright, and so does ANY warning: the codebase is # warning-clean and must stay that way — fix new findings, never # raise the cap. - name: Lint run: npm run lint -- --max-warnings 0 # Unused files, exports and dependencies (knip.json). Its own script on # purpose: chained into `lint`, the `-- --max-warnings 0` above would be # handed to knip and ESLint would lose its cap. - name: Unused code run: npm run knip - name: Tests run: npm test mcp: name: MCP server tests runs-on: ubuntu-latest defaults: run: working-directory: src/McpServer/autopilot-monitor-mcp steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '22' cache: npm cache-dependency-path: src/McpServer/autopilot-monitor-mcp/package-lock.json - run: npm ci # tsconfig.json covers sources, tests and scripts; the build emits via # tsconfig.build.json. - name: Typecheck run: npx tsc --noEmit # Same rule as the web job: any warning fails — fix new findings, never # raise the cap. - name: Lint run: npm run lint -- --max-warnings 0 - name: Unused code run: npm run knip # The vector-search tests tokenize with the real all-MiniLM-L6-v2 model # (~23 MB tokenizer + ONNX weights). transformers.js caches under # node_modules by default, which npm ci recreates — so every CI run # re-downloaded from huggingface.co and rate-limits (429) failed the job # (run 31252869254). Persist the model across runs instead; HF_CACHE_DIR # is honored by vector-search-provider.ts. - name: Cache embedding model uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ github.workspace }}/.hf-cache key: hf-model-Xenova-all-MiniLM-L6-v2 - name: Tests run: npm test env: HF_CACHE_DIR: ${{ github.workspace }}/.hf-cache guardrails-in-sync: name: Generated guardrails match rules/guardrails.json runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '22' - name: Regenerate run: node rules/scripts/combine.js - name: Fail if the generated files drifted run: | if ! git diff --quiet; then echo "::error::Generated files are out of date. Run 'node rules/scripts/combine.js' and commit the result." git diff --stat exit 1 fi shared-manifests-in-sync: name: Generated wire types match shared-manifests.json runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '22' # JSON ↔ C# parity is the dotnet job's SharedManifestParityTests; this job pins # JSON ↔ generated TS (shared-manifests.generated.ts, wire-types.generated.ts and the # MCP's wire-types + wire-vocabularies copies - the vocabularies are what the MCP # tool input schemas derive their z.enum() from). - name: Regenerate run: node scripts/generate-shared-manifest-types.js working-directory: src/Web/autopilot-monitor-web - name: Fail if the generated files drifted run: | if ! git diff --quiet -- src/Web/autopilot-monitor-web/lib/generated/shared-manifests.generated.ts src/Web/autopilot-monitor-web/lib/generated/wire-types.generated.ts src/McpServer/autopilot-monitor-mcp/src/generated/wire-types.generated.ts src/McpServer/autopilot-monitor-mcp/src/generated/wire-vocabularies.generated.ts; then echo "::error::Generated wire types are out of date. Run 'npm run generate:manifests' in src/Web/autopilot-monitor-web and commit the result." git diff --stat exit 1 fi duplicates: name: Duplicate code runs-on: ubuntu-latest defaults: run: working-directory: tools/quality steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '22' cache: npm cache-dependency-path: tools/quality/package-lock.json - run: npm ci --ignore-scripts # jscpd over the C# and TypeScript sources (.jscpd.json). A clone missing from # jscpd-baseline.json fails, and so does a baseline entry whose clone is gone, # so the baseline only shrinks. Locally: npm run dup in tools/quality. - name: Duplicate check run: npm run dup