Skip to content

fix(lint): ratchet the CLI's style debt so a release stops dying on it, and fix the rule that was wrong - #600

Merged
beyondnetPeru merged 1 commit into
developfrom
fix/lint-ratchet-baseline
Aug 17, 2026
Merged

fix(lint): ratchet the CLI's style debt so a release stops dying on it, and fix the rule that was wrong#600
beyondnetPeru merged 1 commit into
developfrom
fix/lint-ratchet-baseline

Conversation

@beyondnetPeru

Copy link
Copy Markdown
Contributor

fix(lint): ratchet the CLI's style debt so a release stops dying on it, and fix the rule that was wrong

WHY THE RELEASE DIED, AND THE PART NOBODY HAD SEEN

The v1.3.3 tag failed sdk-cli-release at "Architecture Boundary Lint", so upload-assets was
skipped and no GitHub Release exists. Diagnosing it turned up something bigger than the lint:

push to main  ->  build-and-test: SKIPPED   (5 runs tonight, all "success")
push of a tag ->  build-and-test: FAILED    (v1, v1.3.0, v1.3.3 — every tag ever)

build-and-test is gated on release_created, which release-gate sets only for
refs/tags/v*. So it never runs on a branch push, the workflow reports success anyway, and the
job has failed on 100% of the occasions it actually executed. That is why v1.3.0 never produced
a Release either.

And the SBOM fix (#568) was correct AND unmasked the next failure in the same job: v1/v1.3.0
died at "Generate SBOM", v1.3.3 got past it and died at the lint. The job was red for two
reasons; we had removed one.

WHAT THE LINT ACTUALLY REPORTED

87 errors, and NOT ONE of them from boundaries/* — the rules the step is named after pass
clean. The failures are style and complexity debt:

34 max-lines   25 max-params   20 complexity   5 default-case   3 eqeqeq

THE THREE eqeqeq WERE NOT DEBT. THE RULE WAS WRONG.

output-formatter.service.ts:152   const title = i.title != null ? String(i.title) : '';
output-formatter.service.ts:153   const description = i.description != null ? ...
output-formatter.service.ts:165   if (remediation != null && String(remediation).length > 0)

All three are the deliberate != null idiom, which tests null AND undefined in one comparison.
That is correct code flagged by a config that did not match it. eqeqeq is configured in a
block of CWE-mapped rules (CWE-597), so it is set to ['error', 'always', { null: 'ignore' }]
— ESLint's documented option for exactly this — rather than either editing correct code or
recording it as debt. Baselining it would have put correct code in a file that later readers
will treat as a to-do list.

THE RATCHET

The remaining 84 go into eslint-suppressions.json, ESLint 9's native suppressions (9.24+), so
no new script and no new guard — the freeze holds. 80 violations across 51 files under src,
which is exactly what npm run lint (eslint src --ext .ts) checks.

suppressed: 31 max-lines, 24 max-params, 20 complexity, 5 default-case
boundaries/* suppressed: 0

That last line is the point: the architecture gate is untouched. What is silenced is debt in
rules that were riding along under a step named for boundaries.

EXERCISED IN ALL THREE DIRECTIONS

current tree                                   -> exit 0
NEW file with a max-params violation           -> exit 1, names it
an ALREADY-SUPPRESSED file gaining one more    -> exit 1, names it
probe removed, tree restored                   -> exit 0

A baseline that only made things pass would be worth nothing; the second and third cases are
what make it a ratchet.

NOT FIXED, AND WORTH KNOWING

  • The 84 suppressions are real debt: files of 9052 lines, methods with 8 parameters, complexity
    37. Nothing here refactors them; the baseline makes them visible and stops them growing.
  • build-and-test still only runs on a tag. That is the reason this debt reached a release
    instead of a pull request, and it is not addressed here.
  • src/packages/infra-providers has 7 of its own errors (4 max-params, 3 max-lines), measured
    identical before and after this change, and is linted by NO CI step at all.

🤖 Generated with Claude Code

…t, and fix the rule that was wrong

WHY THE RELEASE DIED, AND THE PART NOBODY HAD SEEN

The v1.3.3 tag failed `sdk-cli-release` at "Architecture Boundary Lint", so `upload-assets` was
skipped and no GitHub Release exists. Diagnosing it turned up something bigger than the lint:

    push to main  ->  build-and-test: SKIPPED   (5 runs tonight, all "success")
    push of a tag ->  build-and-test: FAILED    (v1, v1.3.0, v1.3.3 — every tag ever)

`build-and-test` is gated on `release_created`, which `release-gate` sets only for
`refs/tags/v*`. So it never runs on a branch push, the workflow reports success anyway, and the
job has failed on 100% of the occasions it actually executed. That is why v1.3.0 never produced
a Release either.

And the SBOM fix (#568) was correct AND unmasked the next failure in the same job: v1/v1.3.0
died at "Generate SBOM", v1.3.3 got past it and died at the lint. The job was red for two
reasons; we had removed one.

WHAT THE LINT ACTUALLY REPORTED

87 errors, and NOT ONE of them from `boundaries/*` — the rules the step is named after pass
clean. The failures are style and complexity debt:

    34 max-lines   25 max-params   20 complexity   5 default-case   3 eqeqeq

THE THREE eqeqeq WERE NOT DEBT. THE RULE WAS WRONG.

    output-formatter.service.ts:152   const title = i.title != null ? String(i.title) : '';
    output-formatter.service.ts:153   const description = i.description != null ? ...
    output-formatter.service.ts:165   if (remediation != null && String(remediation).length > 0)

All three are the deliberate `!= null` idiom, which tests null AND undefined in one comparison.
That is correct code flagged by a config that did not match it. `eqeqeq` is configured in a
block of CWE-mapped rules (CWE-597), so it is set to `['error', 'always', { null: 'ignore' }]`
— ESLint's documented option for exactly this — rather than either editing correct code or
recording it as debt. Baselining it would have put correct code in a file that later readers
will treat as a to-do list.

THE RATCHET

The remaining 84 go into `eslint-suppressions.json`, ESLint 9's native suppressions (9.24+), so
no new script and no new guard — the freeze holds. 80 violations across 51 files under `src`,
which is exactly what `npm run lint` (`eslint src --ext .ts`) checks.

    suppressed: 31 max-lines, 24 max-params, 20 complexity, 5 default-case
    boundaries/* suppressed: 0

That last line is the point: the architecture gate is untouched. What is silenced is debt in
rules that were riding along under a step named for boundaries.

EXERCISED IN ALL THREE DIRECTIONS

    current tree                                   -> exit 0
    NEW file with a max-params violation           -> exit 1, names it
    an ALREADY-SUPPRESSED file gaining one more    -> exit 1, names it
    probe removed, tree restored                   -> exit 0

A baseline that only made things pass would be worth nothing; the second and third cases are
what make it a ratchet.

NOT FIXED, AND WORTH KNOWING

- The 84 suppressions are real debt: files of 9052 lines, methods with 8 parameters, complexity
  37. Nothing here refactors them; the baseline makes them visible and stops them growing.
- `build-and-test` still only runs on a tag. That is the reason this debt reached a release
  instead of a pull request, and it is not addressed here.
- `src/packages/infra-providers` has 7 of its own errors (4 max-params, 3 max-lines), measured
  identical before and after this change, and is linted by NO CI step at all.

Signed-off-by: aarroyo <beyondnet.peru@gmail.com>
@beyondnetPeru
beyondnetPeru requested a review from a team as a code owner August 17, 2026 03:20
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions

Copy link
Copy Markdown

📊 Bilingual Coverage Impact

PR Changes

  • Paired EN/ES files modified: 0
  • New EN files needing ES translation: 0

Repository Coverage

Metric Value
Total EN files 532
Total ES files 502
Paired files 0
Coverage 0%

Good: All EN changes have ES counterparts.


Generated by GitHub Actions

@beyondnetPeru
beyondnetPeru merged commit 8eecb4b into develop Aug 17, 2026
34 checks passed
beyondnetPeru added a commit that referenced this pull request Aug 17, 2026
…604)

fix(ci): the CLI release type-checks against siblings it never built

THIRD FAILURE ON THE SAME JOB, THIRD DIFFERENT CAUSE.

`build-and-test` in sdk-cli-release has now failed on every tag it ever ran on:

    v1 / v1.3.0   Generate SBOM (CycloneDX)          — npm ls exit code       (fixed, #568)
    v1.3.3        Architecture Boundary Lint          — pre-existing debt      (fixed, #600)
    v1.3.4        Type Check and Build                — this

Each fix uncovered the next, because the job only ever executes on a tag: it is gated on
`release_created`, which `release-gate` sets only for `refs/tags/v*`. On every branch push it
is SKIPPED and the workflow reports success. Five green runs on `main` tonight told us nothing
about it.

WHAT FAILED

`Type Check and Build` runs `npm run build` inside `src/sdk/cli`, which compiles the CLI and
nothing else. In a workspace, `@beyondnet/*` resolve to the local packages by symlink, but a
SUBPATH import needs that package's `dist/` to exist, and it does not after a bare `npm ci`.
100+ errors, all the same shape:

    src/app.module.ts(12,29): error TS2307: Cannot find module
      '@beyondnet/evolith-core-domain/application/sync/sync.service'
    src/commands/agents/agents.command.ts(5,40): error TS2307: Cannot find module
      '@beyondnet/evolith-infra-providers'

THE FIX ALREADY EXISTS, ONE WORKFLOW OVER

`npm-release.yml` has a step called "Build every workspace" that runs the root `npm run build`
(`tsc -b tsconfig.json` over all projects). sdk-cli-release never had it. Added, immediately
after `npm ci` and before the lint and type-check that depend on it.

VERIFIED, INCLUDING THE PART THAT LOOKED LIKE A PROBLEM

Locally, the root build exits 2 — but for a reason that does not exist on the runner, and the
distinction was measured rather than assumed:

    root `npm run build` here      -> exit 2, 4 × TS2307, all '@nestjs/cache-manager'
    is it declared?                -> yes, in src/packages/mcp-server/package.json
    is it in the lockfile?         -> yes, @3.1.3 under src/packages/mcp-server/node_modules
                                       and src/apps/core-api/node_modules
    so `npm ci` installs it        -> and the SAME step in npm-release.yml ran tonight on main
                                       with conclusion=success and 0 TS2307 errors (run 31991739387)

My local tree has a symlinked node_modules without those nested installs. On the runner the
root build passes, which is the only place this step will run.

And after a root build, the CLI's own type-check is clean:

    cli `npm run build` -> exit 0, TS2307 errors: 0

NOT FIXED

The reason all three of these survived: `build-and-test` never runs on a pull request. Fixing
that is a larger change than a release-night fix and is not attempted here — but it is the
actual defect, and each of these three is a symptom of it.

CI: 30 SUCCESS, 2 SKIPPED, 0 failing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant