chore(quality): add CodeFactor no-regression gate and prune complexity debt

Merged
UN
4mo ago
+1242-218

Summary

  • Add the CodeFactor-aligned no-regression quality lane from pm-bkcv: ESLint complexity ceiling at CodeFactor's Complex Method threshold, suppression baseline, Greptile local review gate wiring, and run-gates integration from the branch baseline.
  • Prune four stale complexity suppressions by simplifying repeatable metadata parsing, schema field command normalization, stable serialization equality, and telemetry error-code classification.
  • Refresh typescript-eslint to 8.62.1, keep @types/node on the Node 22 support floor, and harden the Greptile gate after local Greptile findings.

pm tracking

  • Active item: pm-bkcv under CodeFactor no-regression / A+ quality epic pm-92if.
  • Linked files, tests, GitHub/security sweep, telemetry/Sentry status, temp-directory smoke, and Greptile follow-up evidence are recorded on pm-bkcv.

Verification

  • npm install -g .; pm --version 2026.6.24; node v26.4.0; pnpm 10.33.4; pnpm build.
  • PM linked test run: pm test pm-bkcv --run --progress, 6/6 passed.
  • Full coverage: node scripts/run-tests.mjs coverage, 262 files, 5245 tests, 100% statements/branches/functions/lines.
  • Focused tests: schema fields/create/update, serialization-utils, telemetry-observability, greptile-review-gate.
  • pnpm lint; pnpm typecheck; pnpm contracts:check; pnpm security:scan; pnpm audit --audit-level moderate.
  • pnpm changelog:pm:check with pm-changelog 2026.6.27 and unchanged CHANGELOG.md.
  • pm health --check-only --summary --json clean; pm validate --check-resolution --check-history-drift has no history drift and only the existing resolution-backfill warning backlog.
  • node dist/cli.js package doctor --project --detail deep --json clean.
  • node scripts/release/sentry-telemetry-gate.mjs --json --telemetry-mode best-effort clean: zero critical/high Sentry issues; telemetry finish error rate 0.79%; no missing error-code rows.
  • Isolated temp smoke passed init/create/update/files/docs/test run/close/release/health/validate plus declarative package scaffold npm test and package doctor.

GitHub / dependency sweep

  • No open PRs at start of branch work.
  • GitHub issue #374 remains blocked by missing RELEASE_PAT in scheduled release environment; SENTRY_PERSONAL_ADMIN_TOKEN is configured.
  • Dependabot alerts, code scanning alerts, secret scanning alerts: none reported by gh API; pnpm audit found no known vulnerabilities.
  • pm-changelog latest available version is active: 2026.6.27.

Greptile

  • Ran greptile review --help and local review before push.
  • Initial Greptile review reported two findings: stale baseline script name and non-zero review false-clean handling. Both were fixed and covered by tests.
  • A rerun returned cached stale text; a fresh layout-diff review was dispatched but did not finish in the local polling window. The current diff contains both fixes and PR-side review agents are requested below.

Summary by cubic

Adds a CodeFactor‑aligned no‑regression gate for complex methods (CC ≥ 18) and a local Greptile review gate; prunes four complexity hotspots, stabilizes a flaky test, keeps coverage at 100%, and updates the CHANGELOG to record the gate closeout. Latest: the Greptile gate anchors “clean” detection to stdout, tolerates stderr warnings, and fails on findings from stdout or stderr, including non‑zero exits.

  • New Features

    • Enforce ESLint complexity max 17 with existing violations baselined in eslint-suppressions.json; new or worse violations fail pnpm lint.
    • Add Greptile review gate (scripts/release/greptile-review-gate.mjs) wired into scripts/release/run-gates.mjs (--skip-greptile) and review:greptile:gate; skips when CLI is missing/unauthenticated, supports --base/--branch, --timeout-ms, and report‑only; matches the clean sentinel on stdout; prints combined stdout/stderr on failures and catches stderr‑only findings; tests at 100%.
    • Add pnpm lint:eslint:prune and pnpm lint:complexity:baseline; bump typescript-eslint to ^8.62.1.
  • Refactors and Fixes

    • Prune four complexity hotspots: simplify optional text checks, extract normalizeFieldCommands, hoist stable‑equality comparators, and table‑drive telemetry error‑message classification.
    • Stabilize the context “no‑agenda” coverage test and update CHANGELOG.md with the quality gate closeout.

Greptile Summary

This PR adds a CodeFactor no-regression quality lane: a new Greptile review gate script wired into the release pipeline, an ESLint complexity ceiling at CC ≥ 18 with a suppression baseline, and four refactors that eliminate the pre-existing violations.

  • Greptile gate (scripts/release/greptile-review-gate.mjs): skips gracefully when the CLI is absent or unauthenticated; on a clean run anchors detection to stdout so stderr warnings don't flip the result; fails (exit 1) when findings appear on stdout or non-zero output; integrated into run-gates.mjs with --skip-greptile and tested at 18 cases covering every branch.
  • Complexity pruning: four hotspots reduced — optional-field guards in repeatable-metadata-parsers.ts, command normalization extracted to normalizeFieldCommands in fields-file.ts, stableValueEquals decomposed into per-type comparators with a shared hasUnmatchedEquivalent helper in serialization.ts, and error-message classification table-driven in observability.ts.
  • Tooling: typescript-eslint bumped to ^8.62.1; pnpm lint:complexity:baseline and pnpm lint:eslint:prune scripts added; context-command test stabilized with an explicit deadline fixture.

Confidence Score: 5/5

Safe to merge — the gate logic is well-tested and all four refactors preserve their original semantics exactly.

The Greptile gate correctly isolates the clean-sentinel check to stdout, handles all non-zero exit combinations, and skips without blocking in environments where the CLI is unavailable. The four complexity refactors were verified to be semantically equivalent: classifier order in observability.ts is preserved, stableValueEquals specialized-type dispatch follows the same precedence as the original if-chain, normalizeFieldCommands handles the undefined and empty-array cases identically to the inline logic it replaced, and the event-field guard loop in repeatable-metadata-parsers.ts is a straight extraction. No changed code path was left without test coverage.

No files require special attention.

Important Files Changed

FilenameOverview
scripts/release/greptile-review-gate.mjsNew gate script that wraps greptile review --agent, detects findings via a stdout-anchored clean sentinel, handles non-zero exits with vs without output, and skips gracefully when the CLI is absent or unauthenticated.
scripts/release/run-gates.mjsAdds --skip-greptile flag and wires greptile-review-gate.mjs into the gate pipeline; follows the same runCheckedStep + ok: true pattern used by all other hard gates.
src/core/shared/serialization.tsDecomposes the monolithic stableValueEquals into focused per-type comparators dispatched through SPECIALIZED_STABLE_VALUE_COMPARISONS; extracts the Set-matching helper hasUnmatchedEquivalent; semantics are preserved exactly.
src/core/telemetry/observability.tsConverts the flat if-chain error-message classifier into table-driven TELEMETRY_ERROR_MESSAGE_CLASSIFIERS and TELEMETRY_EXIT_CODE_FALLBACKS; classifier order and exit-code fallback semantics are preserved.
src/core/schema/fields-file.tsExtracts normalizeFieldCommands from normalizeAddFieldInput; the undefined and empty-array edge-cases produce DEFAULT_FIELD_COMMANDS as before.
src/cli/commands/repeatable-metadata-parsers.tsReplaces four identical if (rawValue !== undefined && !trimmedValue) throw guards with a single loop over a tuple array; behaviour unchanged.
tests/unit/scripts/release/greptile-review-gate.spec.ts18 focused unit tests covering every gate branch: skip paths (not installed, not authenticated, timeout, non-zero clean), pass paths (clean stdout, stderr warnings, aliased --branch), and fail paths (stdout findings, stderr findings, non-zero with findings, --report-only).
eslint.config.mjsTightens the cyclomatic-complexity ceiling from 260 to 17 to match CodeFactor's Complex Method threshold; pre-existing violations are baselined in eslint-suppressions.json.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[greptile-review-gate.mjs] --> B{greptile whoami}
    B -- spawnFailed --> C[skip: not installed, exit 0]
    B -- status ≠ 0 --> D[skip: not authenticated, exit 0]
    B -- status 0 --> E[greptile review --agent]
    E --> F{timedOut?}
    F -- yes --> G[skip: timed out, exit 0]
    F -- no --> H{status ≠ 0?}
    H -- yes, output empty OR stdout ends clean --> I[skip: did not complete, exit 0]
    H -- yes, output has findings --> J{--report-only?}
    H -- no, status 0 --> K{stdout ends with clean sentinel?}
    J -- yes --> L[ok:false skipped:false, exit 0]
    J -- no --> M[ok:false skipped:false, exit 1]
    K -- yes --> N[ok:true findings:0, exit 0]
    K -- no --> O{--report-only?}
    O -- yes --> P[ok:false, exit 0]
    O -- no --> Q[ok:false, exit 1]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[greptile-review-gate.mjs] --> B{greptile whoami}
    B -- spawnFailed --> C[skip: not installed, exit 0]
    B -- status ≠ 0 --> D[skip: not authenticated, exit 0]
    B -- status 0 --> E[greptile review --agent]
    E --> F{timedOut?}
    F -- yes --> G[skip: timed out, exit 0]
    F -- no --> H{status ≠ 0?}
    H -- yes, output empty OR stdout ends clean --> I[skip: did not complete, exit 0]
    H -- yes, output has findings --> J{--report-only?}
    H -- no, status 0 --> K{stdout ends with clean sentinel?}
    J -- yes --> L[ok:false skipped:false, exit 0]
    J -- no --> M[ok:false skipped:false, exit 1]
    K -- yes --> N[ok:true findings:0, exit 0]
    K -- no --> O{--report-only?}
    O -- yes --> P[ok:false, exit 0]
    O -- no --> Q[ok:false, exit 1]

Comments Outside Diff (1)

  1. scripts/release/greptile-review-gate.mjs, line 996-999 (link)

    P1 Non-zero exit silently swallows findings

    Any non-zero exit from greptile review — including an exit code used to signal "I found issues" — is treated as skipped (incomplete) rather than a gate failure. Because the failure branch is reached before the clean check, a run where Greptile exits, say, 1 with full finding text in stdout would be reported as { ok: true, skipped: true } and the gate would pass silently.

    Many review/lint-style CLI tools use a non-zero exit to signal findings. If that's the case here, real findings would never block the local pipeline. The PR description states the Greptile GitHub App still reviews every PR independently, which partially mitigates this, but the local gate's "fail on findings" contract only holds when status === 0. Does greptile review --agent always exit 0 when it produces findings and only exit non-zero on transport/infrastructure errors? If non-zero exits can also carry findings, the current logic needs to inspect the output even when status ≠ 0 before deciding to skip.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: scripts/release/greptile-review-gate.mjs
    Line: 996-999
    
    Comment:
    **Non-zero exit silently swallows findings**
    
    Any non-zero exit from `greptile review` — including an exit code used to signal "I found issues" — is treated as `skipped` (incomplete) rather than a gate failure. Because the failure branch is reached before the `clean` check, a run where Greptile exits, say, `1` with full finding text in stdout would be reported as `{ ok: true, skipped: true }` and the gate would pass silently.
    
    Many review/lint-style CLI tools use a non-zero exit to signal findings. If that's the case here, real findings would never block the local pipeline. The PR description states the Greptile GitHub App still reviews every PR independently, which partially mitigates this, but the local gate's "fail on findings" contract only holds when `status === 0`. Does `greptile review --agent` always exit 0 when it produces findings and only exit non-zero on transport/infrastructure errors? If non-zero exits can also carry findings, the current logic needs to inspect the output even when status ≠ 0 before deciding to skip.
    
    How can I resolve this? If you propose a fix, please make it concise.
    

Reviews (17): Last reviewed commit: "docs(changelog): include quality gate cl..." | Re-trigger Greptile