From 1cf202d96ece7e65a4e6c3a327a83e220a14ab69 Mon Sep 17 00:00:00 2001 From: tomas Date: Wed, 26 Aug 2026 10:25:46 +0000 Subject: [PATCH 1/5] fix(ci): make qlty check actually analyze files The Qlty Check job has never analyzed a single file. Both `qlty check` and `qlty smells` default to changed-files-only and resolve the comparison from local branch refs; actions/checkout leaves PR runs on a detached merge ref with no local branch, so qlty fell back to HEAD, found nothing, and reported `No issues`. actionlint, trufflehog and osv-scanner have never run. The failure is invisible from a laptop, where a local branch always exists. - Pass an explicit `--upstream` to both commands and restrict the job to pull_request, the only event with a real base ref. Under the previous snippet a push to main resolves to `origin/main` == HEAD and analyzes nothing, recreating the same vacuous green. - osv-scanner's plugin declares `skip_upstream`, so changed-file runs drop it entirely. Give it its own `--all` step, reporting-only for now: it finds 12 medium CVEs in src/test/vscode-notebook-perf/package-lock.json today. - Raise the timeout from 3 minutes, which was only ever plausible for a job that did no work. Also fix seven qlty.toml entries that were silently discarded on every run: - `exclude_patterns` sat under `[[source]]`, so TOML bound it to that table. It has to precede every table header, `[[plugin]]` included. Dropped `build/**` (48 tracked source files live there, so activating the pattern would newly hide real code from the scanners) and `.git/**` (never a target). - Six smell names are Code Climate's, not qlty's. Renamed to file_complexity, function_complexity, identical_code and similar_code; dropped function_length, large_class and long_parameter_list, which have no equivalent. Thresholds are qlty's defaults rather than the old numbers, whose units do not carry over. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011Kgq63XyXuKc6QM4WK4gi2 --- .github/workflows/ci.yml | 19 ++++++++++++++++--- .qlty/qlty.toml | 41 ++++++++++++++++------------------------ 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee0292d990..d1316f7d0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,8 +64,15 @@ jobs: qlty: name: Qlty Check + # qlty check and qlty smells analyze changed files only, against an upstream ref. + # A pull request is the only event that supplies one: actions/checkout leaves the + # runner on a detached merge ref, so without --upstream qlty compares HEAD against + # itself, finds nothing to analyze, and passes vacuously. + if: github.event_name == 'pull_request' runs-on: ubuntu-latest - timeout-minutes: 3 + timeout-minutes: 10 + env: + UPSTREAM_REF: origin/${{ github.base_ref }} steps: - name: Checkout code @@ -78,10 +85,16 @@ jobs: uses: qltysh/qlty-action/install@08a0a862c159eae9b9003081da6663d96efef637 # v2.3.0 - name: Run qlty check - run: qlty check + run: qlty check --upstream "$UPSTREAM_REF" - name: Run qlty code smells analysis - run: qlty smells + run: qlty smells --upstream "$UPSTREAM_REF" + + # The osv-scanner plugin declares skip_upstream, so changed-file runs drop it + # entirely; --all is the only mode in which it ever sees a lockfile. Reporting + # only until the vulnerabilities it finds today are triaged. + - name: Run qlty dependency scan + run: qlty check --all --filter osv-scanner --no-fail build: name: Build & Test diff --git a/.qlty/qlty.toml b/.qlty/qlty.toml index d2382eb4b7..eb7af1fe7c 100644 --- a/.qlty/qlty.toml +++ b/.qlty/qlty.toml @@ -2,6 +2,16 @@ # Learn more at https://docs.qlty.sh config_version = "0" +# Exclusion patterns. Must stay above every table header: a bare key after +# `[[plugin]]`/`[[source]]` binds to that table and qlty silently drops it. +exclude_patterns = [ + "node_modules/**", + "dist/**", + "coverage/**", + "**/*.min.js", + "**/*.min.css", +] + # Plugins configuration [[plugin]] name = "actionlint" @@ -17,17 +27,6 @@ name = "osv-scanner" name = "default" default = true -# Exclusion patterns -exclude_patterns = [ - "node_modules/**", - "dist/**", - "build/**", - "coverage/**", - "**/*.min.js", - "**/*.min.css", - ".git/**", -] - # Code Smells Configuration [smells] mode = "block" @@ -44,26 +43,18 @@ threshold = 4 enabled = true threshold = 5 -[smells.function_length] +[smells.file_complexity] enabled = true threshold = 50 -[smells.file_length] +[smells.function_complexity] enabled = true -threshold = 500 +threshold = 18 -[smells.cognitive_complexity] +[smells.identical_code] enabled = true threshold = 15 -[smells.duplicate_code] +[smells.similar_code] enabled = true -threshold = 6 - -[smells.large_class] -enabled = true -threshold = 500 - -[smells.long_parameter_list] -enabled = true -threshold = 2 +threshold = 15 From 87bc9fe2c73712201fd31f1598f4e97de0e11406 Mon Sep 17 00:00:00 2001 From: tomas Date: Wed, 26 Aug 2026 12:48:54 +0000 Subject: [PATCH 2/5] fix(ci): keep the qlty dependency scan running after a failed check A step with no `if:` carries an implicit success(), so a failing `qlty check` or `qlty smells` skipped the OSV report on exactly the runs someone is already looking at. Guarded the same way as the Codecov upload at ci.yml:142. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011Kgq63XyXuKc6QM4WK4gi2 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1316f7d0d..9832ab8894 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,6 +94,7 @@ jobs: # entirely; --all is the only mode in which it ever sees a lockfile. Reporting # only until the vulnerabilities it finds today are triaged. - name: Run qlty dependency scan + if: '!cancelled()' run: qlty check --all --filter osv-scanner --no-fail build: From 66b2c19b8496711c5ffccba77e9d694e23448501 Mon Sep 17 00:00:00 2001 From: tomas Date: Wed, 26 Aug 2026 16:26:38 +0000 Subject: [PATCH 3/5] ci(qlty): scan the whole tree off a pull request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopts the ref-conditional invocation from deepnote-internal's Code Quality job (deepnote-internal 1f754c86cc), which the previous commit's `if: pull_request` job guard worked around instead: a pull request compares against its base, and anywhere without a base scans the whole tree rather than not running at all. osv-scanner stays out of the full-tree filter — the dedicated step below still reports its findings without gating on them, and gating would turn main red on 12 untriaged CVEs. Guarding `qlty smells` with !cancelled() comes from watching the opposite play out in that repo: an unrelated image-size CVE failed `qlty check` on develop, and the implicit success() on the following steps silently skipped both the smells pass and madge circular-dependency analysis for nine days. Verified locally against every command the expression can produce: qlty check --upstream origin/main -> exit 0 qlty smells --upstream origin/main -> exit 0 qlty check --all --filter=actionlint,trufflehog -> exit 0, 16.5s qlty check --all --filter=osv-scanner --no-fail -> exit 0, 12 reported Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011Kgq63XyXuKc6QM4WK4gi2 --- .github/workflows/ci.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9832ab8894..2561e712ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,14 +64,10 @@ jobs: qlty: name: Qlty Check - # qlty check and qlty smells analyze changed files only, against an upstream ref. - # A pull request is the only event that supplies one: actions/checkout leaves the - # runner on a detached merge ref, so without --upstream qlty compares HEAD against - # itself, finds nothing to analyze, and passes vacuously. - if: github.event_name == 'pull_request' runs-on: ubuntu-latest timeout-minutes: 10 env: + # Empty off a pull request, where the full-tree mode below runs instead. UPSTREAM_REF: origin/${{ github.base_ref }} steps: @@ -85,9 +81,18 @@ jobs: uses: qltysh/qlty-action/install@08a0a862c159eae9b9003081da6663d96efef637 # v2.3.0 - name: Run qlty check - run: qlty check --upstream "$UPSTREAM_REF" + # qlty analyzes changed files only and needs an explicit base: actions/checkout + # leaves pull requests on a detached merge ref with no local branch, so qlty + # compares HEAD against itself and analyzes nothing. Off a pull request there is + # no base to compare against at all, so the whole tree is scanned instead — + # osv-scanner stays out of that filter because the step below reports its + # findings rather than gating on them. + run: qlty check ${{ github.event_name == 'pull_request' && '--upstream "$UPSTREAM_REF"' || '--all --filter=actionlint,trufflehog' }} - name: Run qlty code smells analysis + # A custom `if:` still carries an implicit success(), so !cancelled() is what + # keeps this running when the check above fails. + if: "!cancelled() && github.event_name == 'pull_request'" run: qlty smells --upstream "$UPSTREAM_REF" # The osv-scanner plugin declares skip_upstream, so changed-file runs drop it @@ -95,7 +100,7 @@ jobs: # only until the vulnerabilities it finds today are triaged. - name: Run qlty dependency scan if: '!cancelled()' - run: qlty check --all --filter osv-scanner --no-fail + run: qlty check --all --filter=osv-scanner --no-fail build: name: Build & Test From 08dd70debd7ad35de90035a213fcaf3620ee63eb Mon Sep 17 00:00:00 2001 From: tomas Date: Fri, 28 Aug 2026 11:40:05 +0000 Subject: [PATCH 4/5] ci(qlty): split the qlty steps by event and scan smells on the full tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The single `qlty check` step chose its arguments through an inline expression, which hid two different jobs behind one name. Split it into explicit per-event steps: `--upstream` on pull requests, `--all` off them, grouped by event so the log reads in the order it runs. `qlty smells` was pull-request-only because `--upstream` needs a base ref that a push does not have. `qlty smells --all` is the non-PR analogue — 8s over 1437 files here, and exit 0 by construction, so it reports without gating. Trim the file's comments to the three that carry what the code cannot: the implicit success() inside a custom `if:`, why osv-scanner needs its own --all pass, and the npm optional-deps workaround link. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011Kgq63XyXuKc6QM4WK4gi2 --- .github/workflows/ci.yml | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2561e712ab..b30c9ceb8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,6 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 env: - # Empty off a pull request, where the full-tree mode below runs instead. UPSTREAM_REF: origin/${{ github.base_ref }} steps: @@ -80,24 +79,24 @@ jobs: - name: Install qlty uses: qltysh/qlty-action/install@08a0a862c159eae9b9003081da6663d96efef637 # v2.3.0 - - name: Run qlty check - # qlty analyzes changed files only and needs an explicit base: actions/checkout - # leaves pull requests on a detached merge ref with no local branch, so qlty - # compares HEAD against itself and analyzes nothing. Off a pull request there is - # no base to compare against at all, so the whole tree is scanned instead — - # osv-scanner stays out of that filter because the step below reports its - # findings rather than gating on them. - run: qlty check ${{ github.event_name == 'pull_request' && '--upstream "$UPSTREAM_REF"' || '--all --filter=actionlint,trufflehog' }} - - - name: Run qlty code smells analysis - # A custom `if:` still carries an implicit success(), so !cancelled() is what - # keeps this running when the check above fails. + - name: Run qlty check (pull request) + if: github.event_name == 'pull_request' + run: qlty check --upstream "$UPSTREAM_REF" + + - name: Run qlty code smells analysis (pull request) + # A custom `if:` still implies success(), so !cancelled() is what survives a failed check above. if: "!cancelled() && github.event_name == 'pull_request'" run: qlty smells --upstream "$UPSTREAM_REF" - # The osv-scanner plugin declares skip_upstream, so changed-file runs drop it - # entirely; --all is the only mode in which it ever sees a lockfile. Reporting - # only until the vulnerabilities it finds today are triaged. + - name: Run qlty check (full tree) + if: github.event_name != 'pull_request' + run: qlty check --all --filter=actionlint,trufflehog + + - name: Run qlty code smells analysis (full tree) + if: "!cancelled() && github.event_name != 'pull_request'" + run: qlty smells --all + + # osv-scanner declares skip_upstream, so only --all ever sees a lockfile; reporting-only until triaged. - name: Run qlty dependency scan if: '!cancelled()' run: qlty check --all --filter=osv-scanner --no-fail @@ -124,8 +123,7 @@ jobs: - name: Install dependencies run: | npm ci --prefer-offline --no-audit - # Verify Tailwind CSS native modules are installed for Linux (npm optional deps bug) - # See: https://github.com/npm/cli/issues/4828 + # Workaround for the npm optional-deps bug: https://github.com/npm/cli/issues/4828 node -e "try { require('lightningcss'); } catch { process.exit(1); }" 2>/dev/null || npm install lightningcss-linux-x64-gnu node -e "try { require('@tailwindcss/oxide'); } catch { process.exit(1); }" 2>/dev/null || npm install @tailwindcss/oxide-linux-x64-gnu @@ -246,7 +244,6 @@ jobs: run: npm ci --prefer-offline --no-audit - name: Run audit for production dependencies - # Uses better-npm-audit with .nsprc exceptions file run: npx better-npm-audit audit --production audit-all: @@ -269,5 +266,4 @@ jobs: run: npm ci --prefer-offline --no-audit - name: Run audit for all dependencies - # Uses better-npm-audit with .nsprc exceptions file run: npx better-npm-audit audit From f8c3a7a782c8fabd8aa39aabb19c79af6fae741f Mon Sep 17 00:00:00 2001 From: tomas Date: Fri, 28 Aug 2026 13:37:02 +0000 Subject: [PATCH 5/5] ci(qlty): exclude type declarations and un-anchor directory excludes Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011Kgq63XyXuKc6QM4WK4gi2 --- .qlty/qlty.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.qlty/qlty.toml b/.qlty/qlty.toml index eb7af1fe7c..23358e1e56 100644 --- a/.qlty/qlty.toml +++ b/.qlty/qlty.toml @@ -5,11 +5,12 @@ config_version = "0" # Exclusion patterns. Must stay above every table header: a bare key after # `[[plugin]]`/`[[source]]` binds to that table and qlty silently drops it. exclude_patterns = [ - "node_modules/**", - "dist/**", - "coverage/**", + "**/node_modules/**", + "**/dist/**", + "**/coverage/**", "**/*.min.js", "**/*.min.css", + "**/*.d.ts", ] # Plugins configuration