chore(quality): add CodeFactor no-regression gate and prune complexity debt
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
complexitymax 17 with existing violations baselined ineslint-suppressions.json; new or worse violations failpnpm lint. - Add Greptile review gate (
scripts/release/greptile-review-gate.mjs) wired intoscripts/release/run-gates.mjs(--skip-greptile) andreview: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:pruneandpnpm lint:complexity:baseline; bumptypescript-eslintto^8.62.1.
- Enforce ESLint
-
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.mdwith the quality gate closeout.
- Prune four complexity hotspots: simplify optional text checks, extract
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 tostdoutso stderr warnings don't flip the result; fails (exit 1) when findings appear on stdout or non-zero output; integrated intorun-gates.mjswith--skip-greptileand tested at 18 cases covering every branch. - Complexity pruning: four hotspots reduced — optional-field guards in
repeatable-metadata-parsers.ts, command normalization extracted tonormalizeFieldCommandsinfields-file.ts,stableValueEqualsdecomposed into per-type comparators with a sharedhasUnmatchedEquivalenthelper inserialization.ts, and error-message classification table-driven inobservability.ts. - Tooling:
typescript-eslintbumped to^8.62.1;pnpm lint:complexity:baselineandpnpm lint:eslint:prunescripts 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
| Filename | Overview |
|---|---|
| scripts/release/greptile-review-gate.mjs | New 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.mjs | Adds --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.ts | Decomposes 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.ts | Converts 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.ts | Extracts normalizeFieldCommands from normalizeAddFieldInput; the undefined and empty-array edge-cases produce DEFAULT_FIELD_COMMANDS as before. |
| src/cli/commands/repeatable-metadata-parsers.ts | Replaces 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.ts | 18 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.mjs | Tightens 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)
-
scripts/release/greptile-review-gate.mjs, line 996-999 (link)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 asskipped(incomplete) rather than a gate failure. Because the failure branch is reached before thecleancheck, a run where Greptile exits, say,1with 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. Doesgreptile review --agentalways 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