Skip to content

fix(javascript): catch the first call of a first-declared function breakpoint and explain a late entry stop (#858) #1792

fix(javascript): catch the first call of a first-declared function breakpoint and explain a late entry stop (#858)

fix(javascript): catch the first call of a first-declared function breakpoint and explain a late entry stop (#858) #1792

Workflow file for this run

name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
# Default types only. Adding 'labeled'/'unlabeled' would re-run the WHOLE
# matrix per label event - Dependabot applies each of its default labels
# twice at creation, so 3-5 simultaneous full runs per bot PR - to fix a
# race the gate itself now avoids by reading labels live (issue #580).
workflow_dispatch: # on-demand run when main lands commits without a push event
# (auto-merge pushes with GITHUB_TOKEN, which suppresses triggers)
permissions:
contents: read
jobs:
build-and-test:
name: Build and Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [22.x]
python-version: ['3.11']
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
# version comes from package.json "packageManager" (issue #478)
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Install Python dependencies
run: |
python -m pip install --require-hashes -r requirements/pip.txt
python -m pip install --require-hashes -r requirements/debugpy.txt
- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: '1.21'
- name: Install Delve debugger
run: go install github.com/go-delve/delve/cmd/dlv@v1.24.2
- name: Setup Java 21
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Ruby
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
with:
ruby-version: '3.3'
- name: Verify rdbg (bundled debug gem)
run: rdbg --version
- name: Install Node dependencies
run: pnpm install --frozen-lockfile
env:
SKIP_ADAPTER_VENDOR: 'true'
- name: Audit dependencies
run: pnpm audit --prod --audit-level=high
- name: Vendor adapter binaries
run: pnpm run vendor:adapters
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build project
run: pnpm run build:ci
- name: Run linting
run: pnpm run lint
# The PyPI launcher publishes on every release but previously had zero
# test execution anywhere (issue #641).
- name: Run PyPI launcher unit tests
run: python -m unittest tests.test_commands -v
working-directory: mcp_debugger_launcher
# Use test:ci-coverage which includes Python tests but excludes e2e tests
- name: Run tests with coverage
run: pnpm run test:ci-coverage
env:
CI: true
# Hard-fail on process-listener leaks (issues #159/#183)
LEAK_GUARD_STRICT: '1'
- name: Upload Windows test diagnostics
if: failure() && matrix.os == 'windows-latest'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: windows-test-diagnostics
path: |
tests/**/*.log
dist/**/*.log
logs/tests/**/*.log
logs/tests/adapters/failures/**/*.json
test-results.json
if-no-files-found: warn
- name: Upload coverage reports
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: matrix.os == 'ubuntu-latest'
with:
name: coverage-report
path: coverage/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
if: matrix.os == 'ubuntu-latest'
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/coverage-final.json
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
windows-python-integration:
name: Windows Python Integration Focus
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
# version comes from package.json "packageManager" (issue #478)
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22.x
cache: 'pnpm'
- name: Setup Python 3.11
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
- name: Install Python dependencies
run: |
python -m pip install --require-hashes -r requirements/pip.txt
python -m pip install --require-hashes -r requirements/debugpy.txt
- name: Setup Java 21
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: 'temurin'
java-version: '21'
- name: Install Node dependencies
run: pnpm install --frozen-lockfile
env:
SKIP_ADAPTER_VENDOR: 'true'
- name: Build project (CI)
run: pnpm run build:ci
- name: Run targeted Python integration tests
env:
CI: true
run: pnpm vitest run tests/adapters/python/integration/python-discovery.test.ts tests/adapters/python/integration/python_debug_workflow.test.ts
- name: Upload targeted test diagnostics
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: windows-python-integration-logs
path: |
logs/tests/**/*.log
logs/tests/adapters/failures/**/*.json
tests/**/*.log
if-no-files-found: warn
lint:
name: Lint Code
runs-on: ubuntu-latest
# The changelog gate reads the PR's labels live (issue #580); the
# workflow-level token grants contents:read only, which does not cover the
# pull-requests API `gh pr view` calls.
permissions:
contents: read
pull-requests: read
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Full history so the changelog gate can diff against the PR base.
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
# version comes from package.json "packageManager" (issue #478)
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.x'
cache: 'pnpm'
- name: Setup Java 21
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: 'temurin'
java-version: '21'
- name: Install dependencies
run: pnpm install --frozen-lockfile
env:
SKIP_ADAPTER_VENDOR: 'true'
- name: Run ESLint
run: pnpm run lint
# Type-only checks, no build needed: ~3 s for the sources and ~11 s for the ratchet
# (Lint Code goes from ~25 s to ~40 s). `tsc -b -f .` in `build` stays the
# authoritative compile; this catches type errors before the build job does.
#
# Deliberately the same command a developer runs locally and pre-push runs, so a
# green working tree cannot mean a red required check (issue #562).
# Keyed on the PR's AUTHOR, not github.actor: the actor is whoever triggered
# this particular run, so a human reopening a Dependabot PR, running
# `gh pr update-branch` on it, or pushing to the bot branch would take the
# strict path and reproduce the failure the split exists to avoid (#581).
- name: Type-check sources and tests
if: github.event_name != 'pull_request' || github.event.pull_request.user.login != 'dependabot[bot]'
run: pnpm run typecheck:all
# Dependabot cannot re-record the ratchet baseline, so a bump that makes the
# suite type-CLEANER (a better @types package) would otherwise fail its own
# PR on a stale baseline it has no way to refresh. Increases and newly
# erroring files still fail; a decrease warns (issue #581). The ratchet
# raises a ::warning annotation and a job-summary line when that happens,
# because this PR can auto-merge with GITHUB_TOKEN — which suppresses push
# CI on main — leaving the baseline stale until a human PR refreshes it.
- name: Type-check sources and tests (dependabot)
if: github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]'
run: |
pnpm run typecheck
pnpm run typecheck:tests -- --allow-improvement
- name: Check for personal information
run: pnpm run check:all-personal-paths
# Docs here are hand-maintained -- nothing generates them -- so they drift
# silently. This catches the two classes a machine can catch: relative links
# that no longer resolve, and counts that fell behind the code ("28 tools",
# "eight languages"). Everything else still needs a human reviewer.
- name: Check documentation consistency
run: pnpm run check:docs
- name: Validate changelog fragments
run: pnpm run changelog:check
# A user-visible change must carry a changelog fragment, or its entry never
# reaches the release notes (issues #546, #462). Test-only changes are exempt
# automatically; apply the 'no-changelog' label for genuine no-op PRs.
#
# Labels are read LIVE rather than from the event payload: `gh pr create
# --label` applies labels in a follow-up API call, so the payload of the
# 'opened' event this job runs from can still show none, failing a PR that
# was correctly labelled a second later (issue #580). By the time this step
# runs the label is there. A label added LATER still needs a re-run, but
# "Re-run failed jobs" now picks it up instead of needing an empty push.
# `gh pr view` needs only read access, and works for fork PRs.
- name: Require a changelog fragment
if: github.event_name == 'pull_request'
run: |
# Without pipefail the pipeline below reports `paste`'s status, so an
# auth or API failure from `gh` would read as "this PR has no labels"
# and fail the PR for the wrong reason. An unlabelled PR still yields
# an empty string, which is a legitimate answer.
set -o pipefail
CHANGED_FILES="$(git diff --name-only "$BASE_REF...$HEAD_SHA")"
export CHANGED_FILES
PR_LABELS="$(gh pr view "$PR_NUMBER" \
--repo "$GH_REPO" --json labels -q '.labels[].name' | paste -sd, -)" \
|| { echo "::error::Could not read the PR's labels; refusing to guess."; exit 1; }
export PR_LABELS
echo "Labels read live: ${PR_LABELS:-<none>}"
node scripts/changelog-fragments.mjs --ci
env:
GH_TOKEN: ${{ github.token }}
# Passed as env rather than spliced into the script body: a branch name is
# attacker-controlled on a fork PR, and `${{ }}` interpolates straight into the
# shell. The gate also reads BASE_REF/HEAD_SHA itself, to diff each changed
# package.json and let a devDependencies-only bump through (issue #629).
BASE_REF: origin/${{ github.base_ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_NUMBER: ${{ github.event.pull_request.number }}
GH_REPO: ${{ github.repository }}
# Separate job for container tests on Linux only
container-tests:
name: Container Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
# version comes from package.json "packageManager" (issue #478)
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.x'
cache: 'pnpm'
- name: Setup Java 21
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: 'temurin'
java-version: '21'
- name: Install dependencies
run: pnpm install --frozen-lockfile
env:
SKIP_ADAPTER_VENDOR: 'true'
- name: Vendor adapter binaries
run: pnpm run vendor:adapters
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run linting
run: pnpm run lint
- name: Build project
run: pnpm run build
env:
SKIP_ADAPTER_VENDOR: 'true'
- name: Build Docker image
run: docker build -t mcp-debugger:local .
- name: Run container tests
run: |
mkdir -p artifacts/cobol
pnpm run test:e2e:container --reporter=default --reporter=json --outputFile=artifacts/cobol/container-tests.json
- name: Require both COBOL attach cases to pass
run: |
node --input-type=module <<'NODE'
import { readFileSync } from 'node:fs';
const report = JSON.parse(readFileSync('artifacts/cobol/container-tests.json', 'utf8'));
const suite = report.testResults?.find(result => result.name.endsWith('/docker-smoke-cobol-attach.test.ts'));
const cases = suite?.assertionResults ?? [];
if (cases.length !== 2 || cases.some(test => test.status !== 'passed')) {
throw new Error('Both COBOL attach cases must pass; missing or skipped cases fail this lane');
}
console.log('COBOL Linux attach: 2 passed, 0 skipped');
NODE
- name: Upload container validation evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cobol-linux-attach
path: artifacts/cobol/
if-no-files-found: warn
cobol-host:
name: COBOL Host (GnuCOBOL 3.1.2)
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.x'
cache: 'pnpm'
- name: Install and verify GnuCOBOL 3.1.2
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gnucobol3
cobc --version | tee /tmp/cobc-version.txt
head -1 /tmp/cobc-version.txt | grep -E '^cobc \(GnuCOBOL\) 3\.1\.2([. ]|$)'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build project
run: pnpm build
- name: Run COBOL host smoke and logpoints
run: |
mkdir -p artifacts/cobol
cp /tmp/cobc-version.txt artifacts/cobol/
pnpm exec vitest run --project e2e tests/e2e/mcp-server-smoke-cobol.test.ts tests/e2e/mcp-server-logpoints.test.ts -t 'COBOL|\(cobol\)' --reporter=default --reporter=json --outputFile=artifacts/cobol/host-tests.json
node scripts/check-cobol-e2e-report.mjs artifacts/cobol/host-tests.json
- name: Upload COBOL validation evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cobol-host-312
path: artifacts/cobol/
if-no-files-found: warn
test-summary:
name: Test Summary
needs: [build-and-test, windows-python-integration, lint, container-tests, cobol-host]
runs-on: ubuntu-latest
if: always()
steps:
- name: Check test results
run: |
echo "build-and-test: ${{ needs.build-and-test.result }}"
echo "windows-python-integration: ${{ needs.windows-python-integration.result }}"
echo "lint: ${{ needs.lint.result }}"
echo "container-tests: ${{ needs.container-tests.result }}"
echo "cobol-host: ${{ needs.cobol-host.result }}"
if [ "${{ needs.build-and-test.result }}" != "success" ] \
|| [ "${{ needs.windows-python-integration.result }}" != "success" ] \
|| [ "${{ needs.lint.result }}" != "success" ] \
|| [ "${{ needs.container-tests.result }}" != "success" ] \
|| [ "${{ needs.cobol-host.result }}" != "success" ]; then
echo "❌ One or more required jobs did not succeed"
exit 1
fi
echo "✅ All required jobs succeeded"