Skip to content

feat(v0.53): close out v0.51/v0.52 follow-ups — lint debt, crawler flake, missing_state_change FP#268

Merged
cunninghambe merged 1 commit into
mainfrom
feat/v0.53-finish-lint-debt-crawler-state-mutation
May 16, 2026
Merged

feat(v0.53): close out v0.51/v0.52 follow-ups — lint debt, crawler flake, missing_state_change FP#268
cunninghambe merged 1 commit into
mainfrom
feat/v0.53-finish-lint-debt-crawler-state-mutation

Conversation

@cunninghambe

Copy link
Copy Markdown
Owner

Summary

Closes the four outstanding follow-ups from the v0.51/v0.52 session:

  1. Date.now lint debt cleared (58 → 0 errors). New lib/perf.ts helper for harness-side duration measurements; sed-replaced `Date.now()` with `perfMs()` in 6 phase files. Template-literal browser scripts kept their inline `Date.now()` (out of rule scope).
  2. Lint added to verify CI. New `lint:ci` npm script (errors only, warnings advisory) wired into `.github/workflows/verify.yml` between typecheck and build.
  3. Crawler case 11 unskipped. URL-keyed mock replaces the brittle tick counter. Robust to future evaluate-call growth.
  4. missing_state_change FP fixed (spoonworks Remove-row case). MutationObserver's payload was being discarded; classifier now sees `postState.domMutationCount` and returns null when > 0.

22 unrelated pre-existing lint errors also fixed (unused imports, nullable booleans, promise-executor returns, useless escape, script-url whitelist).

Verification

  • ✅ `npm run typecheck`
  • ✅ `npm run lint:ci` (0 errors, 167 warnings advisory)
  • ✅ `npm run lint` shows 167 warnings, 0 errors (strict mode for local dev)
  • ✅ Targeted tests: crawler (36/36), state-change (10/10, new), execute-api-surface (3/3)
  • ⏳ Re-validate spoonworks; expect 5 → 4 clusters / 100 % precision

Test plan

  • All targeted tests pass locally
  • Verify CI run green
  • Spoonworks re-validation drops the Remove-row cluster

🤖 Generated with Claude Code

…nflaked + missing_state_change FP fixed

Sweeps the four outstanding follow-ups from the v0.51/v0.52 session.

## 1. Date.now lint debt cleared (was 58 errors)

The `no-restricted-syntax` rule (scoped to `packages/cli/src/phases/*.ts`)
banned `Date.now()` to preserve V32 frozen-clock determinism. 58 sites in
6 files had been deferred since the rule landed. Per
`docs/follow-ups/date-now-lint-debt.md`, this was Option A: scope the
rule out of duration measurements, because elapsed-time / deadline
checks belong to the harness, not the system-under-test.

New helper `packages/cli/src/lib/perf.ts` exports `perfMs()` (returns
`Date.now()` from outside the rule's scope). Every `Date.now()` call site
in the 6 phase files now uses `perfMs()`. Template-literal browser
scripts (form-submit-runner) keep their inline `Date.now()` — those run
in the browser realm where `perfMs` doesn't exist; they were never in
scope of the rule.

22 unrelated pre-existing errors also fixed: unused imports, nullable
boolean conditional, promise-executor return values, useless escape,
script-url whitelist. Plus dropped `await` from a returned promise.

Lint state now: 0 errors / 167 warnings (advisory).

## 2. Lint added to verify CI (was missing)

New `lint:ci` npm script runs eslint without `--max-warnings 0`. Verify
workflow gains a `Lint (errors only — warnings advisory)` step between
typecheck and build. `npm run lint` keeps the strict 0-warnings local
contract; `lint:ci` gates errors only.

## 3. Crawler case 11 unskipped

Test was skipped in PR #264 because the evaluate() mock counted callCount
globally; post-V56 crawler does more evaluate() calls per page so
callCount=2 landed on the seed instead of /fail-next. URL-keyed mock
replaces the tick counter — throws when the visited URL is /fail-next,
returns links normally otherwise. Robust to future evaluate-tick growth.

## 4. missing_state_change FP fixed (spoonworks Remove-row case)

The single remaining cluster from the v0.52 spoonworks validation was a
`missing_state_change` on RecipeEditor's "Remove row" button — manual code
review confirmed the handler (`setRows(p.filter(...))`) is correct. Root
cause: the MutationObserver was capturing DOM mutations but
`execute.ts` discarded everything except `durationMs`. The classifier
saw no URL change / network / aria / portal and emitted.

Fix: count meaningful (childList) mutations from the observer's payload,
expose as `postState.domMutationCount`, and have
`classifyMissingStateChange` return null when > 0. Conservative for
backward compat: `undefined` falls through to legacy behavior (won't
silently change pre-v0.53 PostStates).

10 new unit tests in `classify/state-change.test.ts` cover both the
v0.53 mutation-signal path and the legacy 5 fall-through conditions.

## Predicted spoonworks impact

Run after v0.53 should drop the last false positive: 5 → 4 clusters,
**4/4 = 100 % precision**. (vs. v0.52 measured 4/5 = 80 %.)
@github-actions

Copy link
Copy Markdown

BugHunter Calibration | | 2026-05-16

Overall: tp=0 fp=0 fn=0 precision=1 recall=1 f1=0

BugKind Precision Recall F1 Status

@cunninghambe cunninghambe merged commit 3aa31da into main May 16, 2026
2 checks passed
cunninghambe added a commit that referenced this pull request May 16, 2026
PR #268's squash merge dropped the execute.ts changes that wire
domMutationCount from MUTATION_OBSERVER_STOP_SCRIPT through to PostState.
types.ts (field declaration) and state-change.ts (classifier check) both
landed correctly, but without execute.ts setting the field the classifier
always sees undefined and falls through to legacy behavior.

Spoonworks v0.53 validation (run xkyzfn64ao7w5yia4891my4p) confirmed:
postState had mutationObserverWindowMs=137 (observer ran) but no
domMutationCount field → missing_state_change FP persisted at 1/5
clusters.

This commit re-applies the mutPayload extraction + childList mutation
count + PostState assignment. No new tests needed (state-change.test.ts
already covers the classifier path).
cunninghambe added a commit that referenced this pull request May 16, 2026
Spoonworks v0.53.1 validation (run si4uxdehzj5v6od6027pkt3o) emitted 4
clusters, all real `vulnerable_dependency_high`. The missing_state_change
FP that lingered in v0.52 cleared after PR #269 restored the execute.ts
mutation-count threading dropped by PR #268's squash merge.

Honest framing: includes the camofox cache failure caveat (3047/3338
tests completed). The 4 real-bug clusters were captured before the env
issue triggered `max_infra_failures`.
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