pmat 3.30.1

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP)
Documentation
# Every feature this crate advertises must compile.
#
# WHY THIS EXISTS
# ci.yml compiles `cargo check --bin pmat --locked` — default features only. In
# the 3.30.0 cycle that blind spot let SIX advertised features ship broken:
#
#   java-ast, csharp-ast, kotlin-ast, ruby-ast, scala-ast
#       one bug, five instances. Sprint 46 emptied the `*-ast` features but left
#       the mapper imports gated on them, while their use sites in `create()`
#       were gated on `polyglot-*`. Under `#![deny(unused_imports)]` each became
#       a hard error, so `cargo check --lib --features java-ast` had not
#       compiled for months.
#   mcp-integration
#       `register_jvm_tools` binds a write lock used only inside JVM-gated
#       blocks, so a build without them tripped `#![deny(unused_variables)]`.
#       Consequence: 440 lines of java_tools_tests.rs never ran under any
#       invocation.
#
# The same gap broke three FULL STRUCT LITERALS when a field was added to the
# public `TdgScore`: benches/storage_backend_bench.rs, examples/
# quality_gate_violations.rs, and src/tdg/olap_analytics_trueno.rs. The last
# sits behind analytics-gpu / advanced-analysis / full, so
# `cargo install pmat --features full` did not compile for an entire release.
#
# A feature a user can type is a feature that must build. This job is the only
# thing that checks that.
name: Feature Matrix

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]
  workflow_dispatch:

concurrency:
  group: feature-matrix-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

env:
  CARGO_TERM_COLOR: always
  # Every leg checks the same dependency graph, so the expensive part compiles
  # once per shard and each feature after that recompiles only the lib.
  CARGO_INCREMENTAL: 0

jobs:
  # The user-facing bundles: what someone actually types after
  # `cargo install pmat --features …`. Fast, and blocking.
  bundles:
    name: bundle / ${{ matrix.feature }}
    runs-on: ubuntu-latest
    timeout-minutes: 45
    strategy:
      fail-fast: false
      matrix:
        feature:
          - default
          - full
          - all-languages
          - most-languages
          - core-languages
          - extended-languages
          - rust-only
          - advanced-analysis
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
        with:
          key: featmatrix-${{ matrix.feature }}
      - name: cargo check --lib --features ${{ matrix.feature }}
        run: cargo check --lib --locked --features '${{ matrix.feature }}'
      # The bin is what `cargo install` builds; a lib that compiles and a binary
      # that does not is still a broken install.
      - name: cargo check --bin pmat --features ${{ matrix.feature }}
        run: cargo check --bin pmat --locked --features '${{ matrix.feature }}'

  # Every individual feature, sharded so the wall clock stays reasonable.
  # Deps compile once per shard; each feature after that is a lib recompile.
  #
  # The list is derived from Cargo.toml at run time rather than duplicated here,
  # because a hand-copied list silently stops covering features added later —
  # which is the same "the check does not know what it is not checking" defect
  # this job exists to catch.
  individual:
    name: individual / shard ${{ matrix.shard }}
    runs-on: ubuntu-latest
    timeout-minutes: 60
    strategy:
      fail-fast: false
      matrix:
        shard: [0, 1, 2, 3, 4, 5]
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
        with:
          key: featmatrix-shard-${{ matrix.shard }}
      - name: cargo check every feature in this shard
        shell: bash
        run: |
          set -uo pipefail
          # `broken-tests` is a deliberate non-compiling quarantine — the only
          # feature exempt, and named here so the exemption is visible.
          mapfile -t FEATURES < <(
            python3 - <<'PY'
          import re
          body = re.search(r'^\[features\]\n(.*?)(?=^\[)', open('Cargo.toml').read(), re.S | re.M).group(1)
          skip = {'broken-tests'}
          for m in re.finditer(r'^([a-zA-Z0-9_-]+)\s*=', body, re.M):
              if m.group(1) not in skip:
                  print(m.group(1))
          PY
          )
          total=${#FEATURES[@]}
          echo "::notice::${total} features discovered; this is shard ${{ matrix.shard }} of 6"
          if [ "$total" -lt 40 ]; then
            echo "::error::only ${total} features parsed from Cargo.toml — the parser is broken, not the crate"
            exit 1
          fi
          failed=()
          for i in "${!FEATURES[@]}"; do
            if [ $(( i % 6 )) -ne ${{ matrix.shard }} ]; then continue; fi
            f="${FEATURES[$i]}"
            echo "::group::cargo check --lib --features $f"
            if cargo check --lib --locked --features "$f"; then
              echo "ok $f"
            else
              echo "::error::feature '$f' does not compile"
              failed+=("$f")
            fi
            echo "::endgroup::"
          done
          if [ ${#failed[@]} -ne 0 ]; then
            echo "features that do not compile: ${failed[*]}"
            exit 1
          fi
          echo "all features in shard ${{ matrix.shard }} compile"

  # benches/ and examples/ are the two target classes `cargo check --bin pmat`
  # never touches, and both were broken this release by a field added to a
  # public struct. `--all-targets` is the only invocation that sees them.
  all-targets:
    name: clippy --all-targets
    runs-on: ubuntu-latest
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy
      - uses: Swatinem/rust-cache@v2
      - name: clippy --all-targets (compiles benches and examples)
        run: cargo clippy --all-targets --locked -- -D warnings

  # crates.io rejects an upload over 10 MiB compressed, and it does NOT say so:
  # `cargo publish` returns a bare 503 from the gateway, and `--dry-run` passes
  # because it never uploads. 3.30.0 went out at 9.4 MiB — 94% of the ceiling —
  # and only because a 4.4 MB unstripped debug binary (`test_exclude`, committed
  # 2026-07-03, referenced by nothing) was found and removed mid-publish. It had
  # been inside every published crate for a month.
  #
  # Fail well before the wall, so the failure lands on the PR that added the
  # weight rather than on whoever next tries to release.
  package-size:
    name: package size
    runs-on: ubuntu-latest
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: the packaged tarball builds, and fits
        shell: bash
        run: |
          set -uo pipefail
          # NOT --no-verify. `cargo package --no-verify` skips building the
          # packaged tarball, so it cannot see that an exclude pattern removed a
          # source file. That happened here: an unanchored "coverage/" pattern
          # matched src/services/agent_context/query/coverage/ as well as the
          # top-level artifacts dir, and --no-verify reported a healthy 7.4 MiB
          # for a tarball that did not compile. Measuring a package without
          # building it is the same defect this repo keeps finding.
          out=$(cargo package --locked --allow-dirty 2>&1) || {
            echo "$out"; echo "::error::cargo package failed to build the packaged tarball"; exit 1; }
          echo "$out" | tail -3
          line=$(echo "$out" | grep -oE '\([0-9.]+MiB compressed\)' | tail -1)
          mib=$(echo "$line" | grep -oE '[0-9.]+')
          if [ -z "$mib" ]; then
            echo "::error::could not read the packaged size from cargo output — the check cannot pass without a measurement"
            exit 1
          fi
          echo "packaged: ${mib} MiB compressed (crates.io hard limit: 10 MiB)"
          # 9.0 leaves room for one ordinary addition; the hard limit leaves none.
          if awk "BEGIN{exit !($mib >= 9.0)}"; then
            echo "::error::package is ${mib} MiB compressed, at or past the 9.0 MiB budget (crates.io rejects >10 MiB with an opaque 503). Add large paths to Cargo.toml's exclude list, or drop the asset."
            exit 1
          fi
          if awk "BEGIN{exit !($mib >= 8.0)}"; then
            echo "::warning::package is ${mib} MiB compressed; the budget is 9.0 and the hard limit is 10."
          fi

  feature-gate:
    name: feature-gate
    runs-on: ubuntu-latest
    needs: [bundles, individual, all-targets, package-size]
    if: always()
    steps:
      - name: Require every leg
        run: |
          for r in "${{ needs.bundles.result }}" "${{ needs.individual.result }}" "${{ needs.all-targets.result }}" "${{ needs.package-size.result }}"; do
            if [ "$r" != "success" ]; then
              echo "a leg failed: bundles=${{ needs.bundles.result }} individual=${{ needs.individual.result }} all-targets=${{ needs.all-targets.result }} package-size=${{ needs.package-size.result }}"
              exit 1
            fi
          done
          echo "every advertised feature compiles and the package fits"