Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
24fb436
feat: upgrade to streaming uploads, implement CSS rewriting, and impr…
lemon-mint May 29, 2026
dfcd056
feat: implement rewriting and proxying for srcset attributes in HTML …
lemon-mint May 29, 2026
9e31692
feat: add ZeroProxy Phase 4 development plan for API fidelity, XHR co…
lemon-mint May 29, 2026
3fd6dda
feat: implement granular CSP-based script injection controls, enhance…
lemon-mint May 29, 2026
92d6e86
feat: implement robust document.write interception and script re-exec…
lemon-mint May 29, 2026
399261b
feat: enhance attribute proxying by intercepting additional Node and …
lemon-mint May 30, 2026
c4326b9
feat: improve runtime navigation handling and block meta CSP policy t…
lemon-mint May 30, 2026
dcbb8c0
feat: improve CSP with blob support, dynamic navigator identity, and …
lemon-mint May 30, 2026
67b328f
refactor: preserve URL fragments across proxy fetches and update CSP …
lemon-mint May 30, 2026
add4184
fix: map proxied iframe targetOrigin to proxy origin in postMessage a…
lemon-mint May 30, 2026
311571a
refactor: improve browser fingerprint masking and worker security thr…
lemon-mint May 30, 2026
1c4fad3
feat: implement virtualized sandbox attribute handling for iframe and…
lemon-mint May 30, 2026
02c90f6
build(lint): add golangci-lint v2 config (non-complexity gates)
metaphorics May 30, 2026
67016fd
build(lint): add rust clippy + rustfmt gates
metaphorics May 30, 2026
a939e94
build(lint): add Biome JS lint config + devDep
metaphorics May 30, 2026
96deb2e
build(ci): add parallel lint job + npm lint/fmt scripts
metaphorics May 30, 2026
623ab3f
test(membrane): freeze security-boundary invariants before refactor
metaphorics May 30, 2026
1142523
feat(kernel): add challenge classifier + per-tab compat arm opt-in
metaphorics May 30, 2026
b347062
feat(headers): skip no-store overwrite for armed challenge subresources
metaphorics May 30, 2026
6081c90
feat(zp-core): project challenge CSP honoring target eval grant
metaphorics May 30, 2026
c8d9bd5
feat(sw): thread challenge-compat marker into CSP + arm plumbing
metaphorics May 30, 2026
edae226
feat(web): activate challenge-compat via trusted opt-in arm sender
metaphorics May 30, 2026
e317379
test(e2e): add armed-path turnstile challenge-compat trace harness
metaphorics May 30, 2026
a1858d7
docs(turnstile): record Increment-1 challenge-compat scope + honest n…
metaphorics May 30, 2026
1585ce8
refactor(server): decompose readSOCKS5Connect/handle/legacyZP under c…
metaphorics May 30, 2026
eef3e27
refactor(zphttp): decompose BuildHTTP1Request/refererHeader/dialTarge…
metaphorics May 30, 2026
db839d5
refactor(cookiejar): decompose cookies and VisibleRecords under compl…
metaphorics May 30, 2026
5a1dfa1
refactor(rewriter): decompose walk_statement/walk_expression/collect_…
metaphorics May 30, 2026
5f7fa6e
chore: ignore .claude/ worktree-isolation scratch dir
metaphorics May 30, 2026
a3102ce
refactor(runtime-prelude): decompose DOM/CSS/cookie/HTML/script/worke…
metaphorics May 30, 2026
b1abf08
refactor(sw): decompose handleMessage/transportFetch/runtimeAPI/class…
metaphorics May 30, 2026
2eb0c05
refactor(worker-prelude): decompose openRelayedUploadStream/openUploa…
metaphorics May 30, 2026
cc9998d
refactor(htmltx): decompose rewriteToken/TransformTo/parseSrcset/rewr…
metaphorics May 30, 2026
d2422c3
build(lint): enforce complexity gates (hard) with documented residual…
metaphorics May 31, 2026
9d04852
fix(web): add native URL validation to the target input
metaphorics May 31, 2026
56f20de
build(ci): pin npm cache to the root lockfile
metaphorics May 31, 2026
24b7d06
docs(turnstile): correct SW script-path classification comment, recor…
metaphorics May 31, 2026
18cc032
feat(turnstile): add human-run live verification harness
metaphorics May 31, 2026
f393051
fix(turnstile): print verdict on Ctrl-C and exit promptly
metaphorics May 31, 2026
c53c493
fix(turnstile): clean up on SIGTERM/SIGHUP, not just SIGINT
metaphorics May 31, 2026
3de79c5
refactor(build): drop dead resolveNodeModule helper
metaphorics May 31, 2026
8bad36a
refactor(swhttp): remove dead runtimeapi + ResponseRecorder pair
metaphorics May 31, 2026
e8f4ebc
fix(swhttp): responseMayHaveBody excludes 1xx informational bodies
metaphorics May 31, 2026
a58d502
refactor(utlskernel): remove dead Wrap + http1OnlyChromeSpec forwarders
metaphorics May 31, 2026
8692aba
test(socks5): fail-closed adversarial coverage for hostile relay replies
metaphorics May 31, 2026
e960bac
refactor(shareurl): remove dead New convenience wrapper
metaphorics May 31, 2026
be858f9
refactor(wsconn): remove never-wired Relay byte-pump
metaphorics May 31, 2026
7921146
test(wsproto): fail-closed adversarial coverage for frame parser
metaphorics May 31, 2026
a378c3c
refactor(headers): decompose ConstructorPolicy under the hard complex…
metaphorics May 31, 2026
0c7bfae
refactor(shareurl): decompose share-token + relay normalizer under th…
metaphorics May 31, 2026
c1abad1
refactor(zphttp): decompose redirect engine Do under the gate
metaphorics May 31, 2026
c2ff2a2
refactor(socks5): decompose ConnectDomain into handshake-phase helpers
metaphorics May 31, 2026
8e79755
refactor(wsproto): extract upgrade-request/response + frame-length he…
metaphorics May 31, 2026
95352ae
docs(lint): correct stale .golangci.yml header — complexity gates are…
metaphorics May 31, 2026
28baa44
docs: populate AGENTS.md with agent-facing conventions and traps
metaphorics May 31, 2026
e0d9ddc
chore: Symlink AGENTS.md to CLAUDE.md
metaphorics May 31, 2026
ddd0a7f
docs: correct AGENTS.md Biome suppression inventory
metaphorics May 31, 2026
a607bd5
docs(agents): record challenge compatibility guardrails
metaphorics May 31, 2026
44b5958
fix(wsproto): decode RFC6455 frame length by indicator, not decoded v…
metaphorics May 31, 2026
130ee84
refactor(membrane): remove dead unsupportedDynamicCompile stub
metaphorics May 31, 2026
18a8ff9
refactor(rewriter): drop unused _span param from render_chain_element
metaphorics May 31, 2026
492903c
refactor(e2e): remove dead external SOCKS5 test harness
metaphorics May 31, 2026
bf534c2
refactor(e2e): extract shared helpers into test/e2e/helpers.js
metaphorics May 31, 2026
252a6b1
refactor(swhttp): decompose RequestFromJS under the complexity gate
metaphorics May 31, 2026
12242e5
refactor(swhttp): decompose readableStreamFrom under the complexity gate
metaphorics May 31, 2026
a870405
refactor(zp-core): decompose normalizeRelayServers under the cognitiv…
metaphorics May 31, 2026
3dfe4de
chore: cargo fmt
metaphorics May 31, 2026
56fcc92
refactor(wasm-kernel): decompose newJSWebSocketStream read loop under…
metaphorics May 31, 2026
60ba382
test(wasm): run wasm-tagged Go tests in CI and via npm run test:wasm
metaphorics May 31, 2026
5dbfe24
refactor(wasm-kernel): decompose jsHTTP post-fetch path into deliverR…
metaphorics May 31, 2026
54732b5
refactor(web): decompose fixedCSP connect-src builder; drop biome-ignore
metaphorics May 31, 2026
79cea15
docs: correct the suppression-inventory tail to match live code
metaphorics May 31, 2026
3ac8467
chore: delete orphan swhttp.JSValue stub type
metaphorics May 31, 2026
65b6b7a
docs: drop stranded references to a replaced switch in route comments
metaphorics May 31, 2026
3d668af
test(shareurl): pin that the IV is under the MAC (adversarial)
metaphorics May 31, 2026
bb4728f
test(server): pin cspWithScriptSrc + cross-language CSP skeleton (spi…
metaphorics May 31, 2026
3e94565
fix(cookiejar): block document.cookie from clobbering an HttpOnly cookie
metaphorics May 31, 2026
673b3bc
test(htmltx): pin streaming transform stays fail-degraded on truncati…
metaphorics May 31, 2026
d194d2f
refactor(shareurl): unexport normalizeRelayServers (single in-package…
metaphorics May 31, 2026
0104c80
refactor(server)!: remove legacy URL redirect subsystem (Phase-3 cuto…
metaphorics May 31, 2026
365fc26
refactor: remove legacy Turnstile compatibility test suite and redund…
lemon-mint Jun 1, 2026
69f9508
feat: pass request object to transportFetch for API scripts and updat…
lemon-mint Jun 1, 2026
4ebfaf3
Remove obsolete documentation files and update references in project …
lemon-mint Jun 2, 2026
1aa9b7a
docs: simplify ZeroProxy project description in README
lemon-mint Jun 2, 2026
a74ac25
ci: remove main branch restriction from push trigger
lemon-mint Jun 2, 2026
2da4fc8
refactor: modularize web runtime and service worker sources and imple…
lemon-mint Jun 2, 2026
9c74aa7
add transform latency benchmarks, document injection rationales, and …
lemon-mint Jun 2, 2026
dcb87da
feat: implement service worker readiness state tracking and expand if…
lemon-mint Jun 2, 2026
afd040c
feat: add support for module worker bootstrapping and expand script/U…
lemon-mint Jun 2, 2026
0db422f
test: reformat assertions for improved readability and linting compli…
lemon-mint Jun 2, 2026
6b2ae7e
feat: upgrade browser target to Chrome 148 and implement extensive ru…
lemon-mint Jun 2, 2026
1b6692a
feat: implement facade layers for window and document objects to virt…
lemon-mint Jun 2, 2026
79616c2
test: implement fingerprinting and property collection differential a…
lemon-mint Jun 2, 2026
7b5c89f
feat: support initial about:blank frames by introducing defineReplaci…
lemon-mint Jun 2, 2026
0a4c454
docs: rewrite README to improve project overview, setup instructions,…
lemon-mint Jun 2, 2026
0db9096
chore: configure local puppeteer caching and update Node.js version t…
lemon-mint Jun 2, 2026
545dc2e
fix: update optional call rewriter for complex chain support and add …
lemon-mint Jun 2, 2026
6a9a84d
chore: format optional call rewriter test
lemon-mint Jun 2, 2026
6586cbd
feat: enhance network runtime with internal asset request support and…
lemon-mint Jun 2, 2026
bdbcc86
refactor: implement idempotent API initialization and improve locatio…
lemon-mint Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 70 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ name: CI

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

Expand Down Expand Up @@ -37,8 +36,9 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
node-version: 24.16.0
cache: npm
cache-dependency-path: package-lock.json
- name: Print toolchain versions
run: |
go version
Expand All @@ -50,6 +50,14 @@ jobs:
- name: Install wasm-bindgen CLI
run: cargo install wasm-bindgen-cli --version 0.2.122 --locked

- name: Cache Puppeteer browser
uses: actions/cache@v4
with:
path: .puppeteer-cache
key: puppeteer-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
puppeteer-${{ runner.os }}-

- name: Install Node dependencies
run: npm ci

Expand All @@ -59,8 +67,68 @@ jobs:
- name: Run Go tests
run: go test ./...

- name: Run Go js/wasm tests
run: npm run test:wasm

- name: Run JavaScript and Puppeteer tests
run: npm test

- name: Build deployable artifacts
run: npm run build

lint:
name: Lint (Go, Rust, JavaScript)
runs-on: ubuntu-24.04
timeout-minutes: 20

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
components: clippy, rustfmt

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 24.16.0
cache: npm
cache-dependency-path: package-lock.json

- name: Install golangci-lint v2.12.2
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/v2.12.2/install.sh \
| sh -s -- -b "$(go env GOPATH)/bin" v2.12.2
golangci-lint version

- name: Verify golangci-lint config
run: golangci-lint config verify

- name: Run golangci-lint (native)
run: golangci-lint run --timeout=5m

- name: Run golangci-lint (js/wasm)
run: GOOS=js GOARCH=wasm golangci-lint run --timeout=5m

- name: Check Rust formatting
run: cargo fmt --manifest-path rewriter-rs/Cargo.toml --all --check

- name: Run clippy
run: cargo clippy --manifest-path rewriter-rs/Cargo.toml --all-targets -- -D warnings

- name: Install Node dependencies
run: npm ci
env:
PUPPETEER_SKIP_DOWNLOAD: "true"

- name: Run Biome
run: npx biome ci web scripts test
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@ web/wasm_exec.js
web/oxc_parser_wasm_bg.wasm
node_modules/
coverage/
.cache/
artifacts/
rewriter-rs/target/
wasm-kernel
/wasm-kernel
GOAL.md
.claude/
.npm-cache
.puppeteer-cache
195 changes: 195 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# golangci-lint v2 configuration (schema version "2").
#
# All enabled linters land HARD (CI must be green). This INCLUDES the
# complexity gates (cyclop / gocognit / nestif): they are ACTIVE and enforcing
# — new code over budget fails CI. See linters.settings for the thresholds and
# linters.exclusions for the test-file carve-out.
#
# A few pre-existing, *intrinsic* findings are narrowly excluded with a
# "# TODO(ratchet):" comment (e.g. SHA-1 mandated by the RFC6455 WebSocket
# handshake, protocol byte encodings, err-shadowing). The only remaining
# complexity suppressions are narrow inline //nolint:<linter> // TODO(complexity)
# at the few wasm-tagged (js && wasm) protocol/membrane sites; every other
# function is decomposed under budget.
version: "2"

run:
timeout: 5m
# Lint test files too. The js/wasm pass (GOOS=js GOARCH=wasm) is invoked
# separately and is the only pass that covers the //go:build js && wasm
# files: bridge_js.go / conn_js.go / cmd/wasm-kernel/main.go.
tests: true

linters:
# Start from an empty set so the enabled linters are exactly the spec:
# this guarantees no complexity linter is silently active.
default: none
enable:
- govet
- staticcheck
- errcheck
- ineffassign
- unused
- unparam
- unconvert
- misspell
- gosec
# A.2: complexity gates flipped to HARD error (see settings + exclusions).
- cyclop
- gocognit
- nestif

settings:
govet:
# Enable every vet analyzer except fieldalignment (too noisy / churny).
enable-all: true
disable:
- fieldalignment
# TODO(ratchet): `shadow` flags idiomatic `if _, err := ...` blocks
# across app and test code. Satisfying it requires renaming variables
# in application logic, which A.1 must not touch. Re-enable after a
# dedicated shadow-cleanup pass. Reported as a concern.
- shadow
misspell:
locale: US
# TODO(ratchet): "cancelled" (British spelling) appears as a local
# variable in security-membrane code (internal/swhttp/bridge_js.go).
# A.1 must not reformat/edit the membrane. Ignore the word here rather
# than rename the variable. Reported as a concern.
# (v2 schema: misspell uses `ignore-rules`, not the v1 `ignore-words`.)
ignore-rules:
- cancelled
# NOTE: staticcheck is intentionally left at its golangci-lint default
# check set (which already excludes the ST10xx stylecheck rules). Do NOT
# add `checks: [all, ...]` here: that switches the entire ST family on and
# surfaces unrelated structural findings (e.g. ST1000 package comments).
# QF1003 is deferred via linters.exclusions.rules below instead.
errcheck:
# TODO(ratchet): unchecked errors on best-effort write paths.
# `out` is a *bufio.Writer; WriteString on it can only fail if the
# underlying writer errors, and an immediate Flush already surfaces that.
# Fixing the remaining sites edits application logic (forbidden in A.1).
# Reported as a concern. (Deferred Close/SetDeadline are covered by the
# std-error-handling preset and the SetDeadline rule below.)
exclude-functions:
- (*bufio.Writer).WriteString
gosec:
# TODO(ratchet): the findings below are INTRINSIC to a TLS-intercepting
# proxy / SOCKS5 / WebSocket protocol implementation and cannot be
# "fixed" without changing security-membrane behavior (forbidden in A.1).
# Each excluded sub-rule is reported as a concern for the A.2 ratchet:
# G101 - false positive: "zp-streamiso-v1\x00" is a protocol prefix,
# not a credential.
# G114 - http.ListenAndServe in the dev server (server hardening is a
# separate task).
# G115 - int->byte/uint16 conversions are deliberate protocol-frame
# length/port encodings (SOCKS5 / WS).
# G124 - cookie jar mirrors upstream Set-Cookie attributes verbatim by
# design; it must not inject Secure/HttpOnly/SameSite.
# G304/G703 - os.Open of an operator-supplied path (config/asset
# loading); both fire on the same call site.
# G401/G505 - SHA-1 is MANDATED by the RFC6455 WebSocket handshake.
# G710 - http.Redirect target is policy-validated upstream.
#
# G104 is a different class: it is gosec's GENERIC unchecked-error rule,
# fully redundant with errcheck (which stays enabled globally as the
# authoritative, more configurable unchecked-error linter). Excluding
# G104 removes double-reporting on lines where errcheck is deliberately
# excluded; it does NOT reduce unchecked-error coverage.
excludes:
- G101
- G104
- G114
- G115
- G124
- G304
- G401
- G505
- G703
- G710

# ----------------------------------------------------------------------
# A.2: complexity gates are now HARD errors. New code over budget fails CI.
# Pre-existing residuals are handled HONESTLY: _test.go is excluded below
# (test-function complexity is out of scope), and the remaining wasm-tagged
# (js && wasm) protocol/membrane functions still over budget carry a narrow
# inline `//nolint:<linter> // TODO(complexity): ...` at each site. Native
# offenders have been decomposed under budget. cyclop.package-average is
# intentionally omitted (fragile).
cyclop:
max-complexity: 10
gocognit:
min-complexity: 15
nestif:
min-complexity: 4
# ----------------------------------------------------------------------

exclusions:
# Be lax on generated files (e.g. files with a generated-code header).
generated: lax
# Opt into golangci-lint's built-in "std-error-handling" preset (the old
# EXC0001): excludes unchecked errors from best-effort cleanup calls such
# as deferred Close/Flush. v2 ships NO default exclusions, so this is an
# explicit, narrow opt-in rather than a blanket relaxation.
presets:
- std-error-handling
# Skip vendored / build-output / non-Go trees entirely.
paths:
- dist
- bin
- rewriter-rs/target
- node_modules
rules:
# Test files: relax rules that are noisy or low-value in tests.
# A.2: cyclop/gocognit/nestif are excluded here too — test-function
# complexity is OUT OF SCOPE (e.g. table-driven / scenario bodies like
# relay_test.go TestBridgeInternalSOCKS, jar_test, transform_*_test).
# Production complexity stays HARD.
- path: _test.go
linters:
- gosec
- errcheck
- unparam
- cyclop
- gocognit
- nestif
# TODO(ratchet): QF1003 ("use tagged switch") is a stylistic suggestion;
# acting on it edits application logic. Deferred. Reported as a concern.
- linters:
- staticcheck
text: "QF1003"
# TODO(ratchet): deferred SetDeadline reset on a connection (best-effort
# cleanup) in the SOCKS5 client. The receiver is an anonymous interface,
# so it is excluded by path+text here rather than via
# errcheck.exclude-functions. Scoped to this one call site. Reported as
# a concern.
- path: internal/socks5/client\.go
linters:
- errcheck
text: "c\\.SetDeadline"
# TODO(ratchet): in-flight scaffolding on the feature branch. These
# specific symbols/params are wired for the nextgen rewriter and are not
# safe to delete in a config-only task. Scoped by name so genuinely dead
# code added later is still caught. Reported as a concern; revisit in A.2.
- path: internal/htmltx/transform\.go
linters:
- unused
text: "rewriteEventHandler|pathEscape"
- path: internal/htmltx/transform\.go
linters:
- unparam
text: "wrapAttrURL - nav is unused"
- path: cmd/zeroproxy-server/main\.go
text: "workerBootstrap - r is unused"
linters:
- unparam

formatters:
enable:
- gofumpt
exclusions:
paths:
- dist
- bin
- rewriter-rs/target
- node_modules
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cache=.npm-cache
5 changes: 5 additions & 0 deletions .puppeteerrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const path = require('node:path');

module.exports = {
cacheDirectory: path.join(__dirname, '.puppeteer-cache'),
};
27 changes: 27 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# AGENTS.md — ZeroProxy

ZeroProxy is a **human-in-the-loop** virtual-browsing privacy membrane: a real person drives a real browser, and target traffic egresses only through `Service Worker → Go WASM kernel → WebSocket/yamux → SOCKS5 → uTLS`. `ARCHITECTURE.md` holds the data-flow diagram and the full **Core invariants** list — read it before touching membrane/transport code; this file only adds what that doesn't, the conventions and traps that are expensive to rediscover.

## Membrane/protocol refactor discipline (load-bearing)

- A behavior-preserving change to membrane or protocol code must be proven by a **transient differential harness**, not a green suite: freeze the pre-change function verbatim under a new name, drive both old and new over a generated + edge corpus through the package's existing test seam (`scriptedRW` in socks5, `net.Pipe`/`pipeMux` in wsproto/zphttp), assert **0 mismatches** (return value, error string, bytes on the wire), then **delete the harness — never commit it** (`zz_*` scaffolding is correctly rejected in review). Keep a *permanent* characterization/adversarial oracle. *Why:* the `transform.go` decomposition passed the full suite but silently changed a marker; only a differential caught it. Suite-green ≠ behavior-preserved.

- Removing a complexity `//nolint` is only real if the gate actually fires on that file. Prove it **red-before**, not just green-after: drop the pre-decomposition original (nolint stripped) at the path, confirm golangci **fails** on the complexity linter, then restore the decomposed file byte-identical (md5). *Why:* a stale `.golangci.yml` header once claimed the gates were "disabled" while they were live — green-after alone would have been hollow.

## Lint / complexity gates

- Complexity gates are **live and hard**: golangci `cyclop` ≤10 / `gocognit` ≤15 / `nestif` ≤4 (`_test.go` excluded), clippy `cognitive_complexity = "deny"` @15, Biome `noExcessiveCognitiveComplexity` @15. **Decompose to satisfy them — do not add new suppressions.** *Why:* the campaign is burning these down, not accumulating them.
- Known, deliberate remaining suppressions (a burn-down tail, not free license) take three forms — keep them straight, they are easy to conflate, and verify against `git grep` not this list, which drifts as the burn-down lands: **(1)** inline `//nolint:cyclop // TODO(complexity)` on exactly one wasm-tagged kernel function — `cmd/wasm-kernel/main.go` `relayEnsure` (cyclop 11; its only safe decomposition splits the engine mutex region and is not differentially verifiable without live `wsconn.Dial`); **(2)** a Biome glob override turning `noExcessiveCognitiveComplexity` **off** for exactly two files — `web/runtime-prelude.js` (the ~4.4k-line membrane, ~72 fns over budget — the one genuinely large remaining decomposition workstream) and `web/index.html` (its inline bootstrap) — plus `test/**` (test bodies out of scope); **(3)** a single inline `biome-ignore lint/complexity/noExcessiveCognitiveComplexity` at `web/worker-prelude.js` (module IIFE — no inner fn exceeds 15; the count is the wrapper-guard aggregate, so splitting relocates the global-exposure boundary rather than cutting complexity). `web/zp-core.js` carries **no** inline complexity ignores (both decomposed). The separate `web/**` Biome override disables only the **formatter** (so `biome ci` never reformats the membrane), **not** the complexity linter — the cognitive-complexity gate stays **hard on every other `web/` file, including `sw.js`**. All of these need a differential-harness decomposition, not a quick edit.

## Build / verify traps

- **wasm-tagged files** (`//go:build js && wasm`: `cmd/wasm-kernel/main.go`, `internal/swhttp/bridge_js.go`, `internal/wsconn/conn_js.go`) are **skipped by `go test ./...` and the native golangci pass.** Lint/build coverage is `GOOS=js GOARCH=wasm golangci-lint run` and `GOOS=js GOARCH=wasm go build ./cmd/wasm-kernel` (`npm run lint:go` runs both golangci passes). Wasm-tagged **tests** (e.g. `internal/swhttp/bridge_js_test.go`, `cmd/wasm-kernel/wsstream_test.go`) are likewise skipped by `go test ./...`; they run only under `npm run test:wasm`, which executes them via the Go `go_js_wasm_exec` runner (CI runs this as its own step). The script wraps the run in `env -i` preserving only `PATH`/`HOME`/`GOCACHE`/`GOMODCACHE` — the wasm runtime copies the whole env into a bounded argv+env buffer, so an unstripped (large) env overflows it (`total length of command line and environment variables exceeds limit`). Run `npm run test:wasm` after any transport/bridge change or you have verified nothing for that code.
- Run `golangci-lint cache clean` before trusting lint results — the results cache serves stale issues from deleted worktrees (paths like `../../../../tmp/…`, "can't read file").
- Use the npm test scripts (`npm test` / `test:js` / `test:e2e` → `node scripts/test.mjs [js|e2e]`). **Do not** run `node --test test/js` — Node 24 treats the directory as a module and reports a spurious failure.
- Rust rewriter behavior is not covered by `npm test`: run `cargo test --manifest-path rewriter-rs/Cargo.toml` after rewriter changes. `npm run lint:rust` is clippy + fmt only, and CI runs the Rust tests as a separate gate. *Why:* the Rust WASM rewriter is the static compiler pipeline for target scripts/CSS, so a green JS/Puppeteer suite alone can miss parser/rewriter regressions.
- Do not blanket-format `web/**`. Biome formatting is intentionally disabled there, and `npm run fmt:fix` formats Go/Rust plus `scripts`/`test`, not the membrane web assets. *Why:* large membrane files keep reviewable hand-shaped layout until a differential-harness decomposition proves the behavior-preserving change.
- **E2E flake:** the two heavy Puppeteer tests can mutually starve under load — one times out at the ~31.5s page deadline while the other passes, and *which* one fails migrates between runs. A migrating failure is environmental, not a regression (a real regression fails the same test deterministically); re-run, or run the e2e tests individually, before blaming a code change.

## Commits

- Conventional Commits, one concern per commit; substantive commits carry an `Op: compress|extend|correct` trailer (plus `Restores: …` for `correct`). Use the configured git identity — **no** `--author`, `Co-Authored-By`, `Signed-off-by`, or any agent trailer; do not mutate git config.
Loading
Loading