Skip to content
MLReps
Back to the three ways in

Setup · step 1, from your pipeline

Your codebase, drawn from your own pipeline

The scan runs in your own build pipeline (CI), on code that is already checked out there. What reaches us is the map it built — file names, the names of functions and other symbols, how they connect, and what the checks found. Never your source, never a diff, never a patch. There is no column in our database that could hold one, and a build gate fails if somebody adds a place for it.

  1. Your project key

    A scan belongs to an organisation, and the key is how your pipeline says which one it is. Signing up creates the organisation — there is nothing else to fill in.

    Make an account
  2. Install it

    One package, one dependency of its own. It reads your repository and writes a report; it never runs your code, never installs anything, and never reaches the network except to send the report here.

    in your repository

    npm i -D mlreps
  3. Publish on every push to main

    This is what keeps your dashboard current. It runs on the default branch rather than on pull requests on purpose — a report published from every branch would make this a picture of whichever job finished last, not of your codebase.

    .github/workflows/mlreps.yml

    name: MLReps
    
    on:
      push:
        branches: [main]
    
    jobs:
      scan:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
            with:
              # Without this the scan reports that every file was never committed.
              fetch-depth: 0
          - uses: actions/setup-node@v4
            with:
              node-version: 24
          - run: npm install
          - run: npx mlreps scan . --publish
            env:
              MLREPS_KEY: ${{ secrets.MLREPS_KEY }}
    

    Add the key as a repository secret named MLREPS_KEY. A key for publishing, which is what the card in step 1 makes — it cannot send telemetry, so a repository secret that leaks cannot start writing traffic onto your map either. The endpoint is /api/scan/v1/report, and the response to every publish is the diagnostic: what we stored, what we could not read, and what was swept out of it.

  4. And on pull requests, if you want it

    A second, separate workflow shows what a change affects on the pull request, as a check. It carries no credential of ours and sends us nothing — the analysis runs on your machine and the result is posted to your repository by your own CI token. It is the stronger arrangement of the two and it costs you the dashboard, which is why both are offered rather than one.

    .github/workflows/codebase.yml

    name: MLReps
    
    on:
      pull_request:
    
    # Without this, GITHUB_TOKEN cannot write a check run and every run ends in a 403.
    permissions:
      contents: read
      checks: write
    
    jobs:
      scan:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: 24
          - run: npm install
          - run: npx mlreps check .
            env:
              GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              # On a pull request, GITHUB_SHA is a merge commit that exists nowhere in
              # the repository — a check run posted against it is invisible.
              PLATFORM_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
    
  5. Then look at it

    Every screen starts with how much of your codebase we could read — because a part we could not read must never look like a part with nothing wrong in it.

Outside GitHub Actions

Nothing above is specific to Actions beyond the check run. On GitLab, Bitbucket, Jenkins or a laptop, set these two and run npx mlreps scan . --publish.

environment

MLREPS_KEY=<your project key>
MLREPS_ENDPOINT=https://www.mlreps.com

Add --dry-run to see exactly what would be sent without sending it. A checkout with no remote is refused rather than guessed at — a scan has to be filed under a repository name, and a folder name is not one.

What the CLI reads

Each language is read by the engine itself or through its own language server, found on PATH on the machine that runs the scan. Without its server a language is shown as unread, never as clean.

Per language: what the CLI needs to read it, whether it shows who calls what, and whether the GitHub App reads it.
LanguageWhat the CLI needsWho calls whatGitHub App
TypeScript and JavaScriptBuilt inYesReads it
PythonFetches pyright itselfYesReads it
PHPFetches intelephense itselfNoReads it
JavaDownload jdtls, set MLREPS_JDTLS_HOME (and a JDK 25 on JAVA_HOME)YesReads it
Rubygem install ruby-lsp (Ruby 3+)NoNot yet
C and C++Install clangd (LLVM, or Xcode command-line tools)YesNot yet
DartInstall the Dart SDK (puts dart on PATH)YesNot yet
SwiftInstall the Swift toolchain / Xcode (sourcekit-lsp); build the project for its indexYesNot yet
KotlinDownload kotlin-language-server; set MLREPS_KOTLIN_JAVA_HOME to a JDK 17-21NoReads it
RustInstall rust-analyzer and the Rust toolchain (rustup component add rust-analyzer)YesReads it
C#dotnet tool install --global csharp-lsSymbols onlyNot yet

Run npx mlreps doctor in your repository (or CI) and it lists, per language, whether its server is found, will be fetched automatically, or is the one line to install by hand. Where a reader is weaker — it cannot tell who calls what, or sees only the names a file defines — every screen says so rather than hiding it.