Agent-first repo: strict TypeScript, god-file splits, 10 test layers, CI pipeline#127
Closed
passandscore wants to merge 55 commits into
Closed
Agent-first repo: strict TypeScript, god-file splits, 10 test layers, CI pipeline#127passandscore wants to merge 55 commits into
passandscore wants to merge 55 commits into
Conversation
Project uses npm — CI (.github/workflows/format.yml) runs npm ci and package-lock.json is authoritative. The bun.lockb was a redundant second lockfile; tsconfig.tsbuildinfo is a local TS incremental build cache that shouldn't be committed. Added both patterns to .gitignore so they stay out.
Layer retrieval and attention-budget optimizations on top of existing code — nothing under src/, contracts/, or contracts-abi/ changes. Distilled from Anthropic's context-engineering + Agent Skills guidance and HumanLayer's CLAUDE.md / harness-engineering posts. Root orientation (progressive disclosure entry points): - AGENTS.md: portable spec (Codex/Cursor/Amp/Claude) - CLAUDE.md: Claude-specific additions, imports AGENTS.md via @ - README.md: link to the agentic surface Reference layer (agent_docs/, open on demand): - stack, architecture, web3-integration, contracts-and-abis, env-vars, testing, verification, glossary .claude/ infrastructure: - skills/: 9 domain skills (skill-creator, next-app-router, defi-swap, web3-wallet, dashboard-data, leaderboard-miles, contract-abis, ui-shadcn, testing-vitest) — each with SKILL.md + reference files, no inlined code - agents/: 4 subagents as context firewalls (explore-web3, security-reviewer, ui-verifier, abi-tracer) - commands/: /prime, /verify, /typecheck, /lint, /test, /new-skill, /review-diff - hooks/: PostToolUse typecheck + Stop format-check, silent on success - settings.json: permission allow/deny lists + hook wiring Verification loop: - package.json: add `typecheck` script (tsc --noEmit) so /verify has the full typecheck + lint + test:run stack
.superset/config.json is local worktree setup/teardown scaffold (the dir holds Claude Code's per-repo worktree config). Not shared config; untrack and ignore.
…PI layer
Executes the agent-efficiency audit punch list:
- Delete 570 LoC of unreferenced `fast-settlement-*.ts` and the 77-line dead
`fetchQuote` in `use-swap-quote.ts`.
- Folderize `src/lib/` into `tokens/`, `swap/`, `settlement/`, `config/`;
flatten the one-file `swap-logic/` folder. Rewrite ~130 `@/lib/*` imports.
- Replace the half-barrel `src/hooks/index.ts` (9 of 52 hooks) with a full
`export *` barrel. Deduplicate the `UserOnboardingData` type collision.
- Introduce `@/lib/api/{parse,schemas}` Zod helpers. Migrate 24 of 53 API
routes to use them; structured 400 responses with `issues[]`.
- Move tests out of `src/**/__tests__/` into a clean top-level `tests/`
mirroring `src/`. Add 50 new tests (50 → 100) covering Zod primitives,
parse helpers, token-resolver, weth-utils, stablecoins, and pagination.
Update `vitest.config.ts` accordingly.
- Add two PostToolUse hooks: `post-edit-test.sh` (runs the mirror test for
the edited file) and `post-edit-build.sh` (runs `next build` when the
edit touches API routes, middleware, env, or server actions).
- Codify the three-tier doc convention in `.claude/README.md`: skills own
how-to, `agent_docs/` owns the map, `docs/` owns human deep-dives
(banner-gated). Update every stale `src/lib/*` path in skills and
agent_docs to the new folderized layout.
- Comment hygiene on the hottest files: explain WHY behind Brave/Rabby
detection ordering in `wallet-provider.ts`, ref-plumbing in
`use-swap-quote.ts`, and the new api parse helper shape.
- Document outstanding work in `agent_docs/audit-followup.md`.
Verification: typecheck clean; 100 tests green; `npm run format:check` clean.
Expands the testing strategy beyond unit tests so the suite catches classes of bugs the old layer never could. - Install fast-check (property testing) and pg-mem (in-memory Postgres). - Extract pure slippage math into `src/lib/swap/slippage.ts` (validateSlippage, slippageBpsFromPercent, computeSlippageLimit) so it can be property-tested without React. - `tests/utils/arbitraries.ts`: shared fast-check generators for the repo's real domain (validWalletAddress, validTxHash, validTokenSymbol, bigUint128, slippageBps). - `tests/utils/pg-mem.ts`: real Postgres factory with schema helpers for user_onboarding and user_activity. Replaces mocked pool.query so SQL typos, param-order bugs, and ON CONFLICT semantics surface at test time. - Property tests for every Zod primitive (idempotence, bounds, totality, rejection) and every pure detector (wrap/unwrap mutex, stablecoin case-insensitivity, token-resolver ETH→WETH rewrite, pagination navigation contracts). - Slippage property tests lock the contract-side invariants that protect against on-chain user loss: exactIn limit ≤ amountOut, exactOut limit ≥ amountIn, monotonicity, scale-invariance, non-negativity. - `tests/api/user-onboarding.integration.test.ts`: 7 tests against real SQL covering GET/POST/PUT/upsert/mixed-case-normalization. - `tests/contracts-abi/abi-drift.test.ts`: validates contracts-abi/abi/* JSON files and cross-checks WETH/ERC20 const ABIs against canonical signatures. - `tests/invariants/cross-module.test.ts`: 11 cross-module properties including wallet schema ↔ viem.isAddress coherence, parse-helper 400 shape, ETH/WETH resolution contract, and pagination navigation rules. Verification: 185 tests across 13 files (up from 100 / 9). Full suite runs in ~650ms. Typecheck + format clean.
Two new chain- and integration-adjacent layers: - `tests/lib/swap/permit2-utils.test.ts` — 15 tests that lock the EIP-712 signing surface. Structural stability of the three type blocks (PermitWitnessTransferFrom, TokenPermissions, Intent), golden-hash snapshot for a fixed (domain, types, message) via viem's `hashTypedData`, byte-exact snapshot of the witness type string's keccak256, and property-based injectivity on user, nonce, and deadline. A regression here is a silent on-chain revert on every swap — these tests catch it at `npm test`. - `src/lib/api/upstream.ts` — Zod schemas for Fuul leaderboard, Fuul payouts totals, and Barter route responses. These make it feasible for the routes to validate upstream data before touching it, and give us a typed contract instead of `any`-shaped blobs. - `tests/fixtures/upstream/` — stored representative JSON bodies for each upstream endpoint (realistic, legacy-field, and minimal variants). Updating a fixture is how we ratify an upstream shape change; commit review makes the drift visible. - `tests/lib/api/upstream.test.ts` — 9 tests that feed each fixture through the Zod schema and assert shape assumptions. Catches breaking upstream changes in CI before they produce blank UIs in production. Verification: 200 → 209 tests across 15 files. Typecheck + format clean.
Closes the loop on the upstream contract tests landed in the last commit. The routes now consume `@/lib/api/upstream` schemas at runtime, not just in tests, so Fuul/Barter shape drift produces a structured 502 instead of propagating undefined into the UI. - `src/app/api/fuul/leaderboard/route.ts`: `fetchFuulPage` safeParses each page against `fuulLeaderboardResponseSchema`. A parse failure is treated exactly like a network failure — returns null so the caller serves the stale cache instead of ingesting corrupt entries. - `src/app/api/fuul/payouts/route.ts`: collapses the imperative fallback chain (`total_points ?? total_payouts ?? total ?? points`) into a single schema parse + coalesce. Zod's coerce handles the historical string-vs-number inconsistency. - `src/app/api/barter/route/route.ts`: the outputWithGasAmount / gasEstimation pair is now validated by `barterRouteResponseSchema`; missing required fields produce 502 instead of the old imperative `null`-check that returned 500. No test changes (existing fixtures already cover these shapes via the `upstream.test.ts` suite added in the prior commit).
Adds a domain-only EIP-712 snapshot alongside the existing full-hash GOLDEN. The domain separator is what the on-chain Permit2 contract at 0x000000000022D473030F116dDEE9F6B43aC78BA3 returns from `DOMAIN_SEPARATOR()`; locking it isolates "wrong domain" drift (chainId, verifyingContract, or name typo) from the larger GOLDEN that mixes domain and message. - Inline snapshot for the mainnet domain separator: 0x866a5aba21966af95d6c7ab78eb2b2fc913915c28be3b9aa07cc04ff903e3f28 - Sanity checks that swapping chainId or verifyingContract produces a different hash, so the test can't pass vacuously. - Documents the pg-mem limitation (no `ROW_NUMBER() OVER(...)`) in agent_docs/audit-followup.md. The user-community-activity routes therefore can't get in-process integration coverage until we move to testcontainers + a real Postgres. Verification: 209 → 212 tests across 15 files. Typecheck + format clean.
Adds the three remaining gold-standard testing layers I'd left for later. Each is opt-in through a dedicated npm script so the default `test:run` stays fast and network-independent. - **Fork tests** (`test:fork`) — `tests/fork/permit2.fork.test.ts` spawns anvil forking from `$FORK_RPC_URL`, calls the canonical Permit2 contract's `DOMAIN_SEPARATOR()` view, and asserts it equals the inline snapshot pinned in `tests/lib/swap/permit2-utils.test.ts`. A second test reconstructs the domain separator from the EIP-712 primitives (typehash, nameHash, chainId, verifyingContract) via viem's `encodeAbiParameters` and asserts byte-identity with both the on-chain value and the snapshot. Gated on `FORK_RPC_URL` so default runs skip — known-good endpoint is publicnode. - **Hook tests** (default `test:run`) — installs `@testing-library/react` + `happy-dom` and seeds 17 tests for `use-swap-slippage` using `renderHook` + `act`. Per-file `@vitest-environment happy-dom` keeps the rest of the suite node-native. Covers clamp math, trailing-dot typing UX, localStorage round-trip, and the 5/1440-minute deadline guards. - **Mutation testing** (`test:mutation`) — configures Stryker 9 with the vitest runner, scoped to `src/lib/swap/slippage.ts` where regressions are load-bearing for on-chain correctness. Initial run produced 92.6% kill rate with two survivors: (1) a real gap at `num < 0` boundary killed by an added test, raising the score to **96.3%**; (2) an equivalent mutation at `num > 50` vs `num >= 50` that produces the same output on either side of the boundary. Threshold configured at high=90 / low=70 / no break-on-fail since mutation runs should be a periodic CI signal, not a PR gate. Audit-followup.md updated with a status table, known-good fork RPC, and next hook-test targets. Verification: default suite 212 → 230 tests across 16 files (+fork file skipped without env). Typecheck + format clean.
The pre-existing `format.yml` was the only CI gate, so none of the verification stack we built ran on PRs. This adds four workflows that align the pipeline with the local hooks: - **verify.yml** — fast PR gate. Parallel jobs run `tsc --noEmit` and `vitest run` on every PR and push to main. No secrets required; runs in seconds. This is what catches the bugs the old setup didn't. - **build.yml** — path-filtered `next build` check. Fires only when PRs touch src/app/api/**, src/middleware.ts, src/env/**, src/actions/**, or next.config.mjs — the categories where typecheck can't prove correctness. Populates .env from .env.example because the example ships Zod-valid UUID placeholders (and any new env var the example doesn't cover will fail this job and force the example to stay current with server.ts). - **fork.yml** — nightly + workflow_dispatch. Installs foundry via `foundry-rs/foundry-toolchain@v1`, runs `npm run test:fork` with `FORK_RPC_URL` from a repo secret. NOT a PR gate because public-RPC flakes would block merges on a third-party's uptime; a genuine on-chain encoding regression still surfaces as a failed job and notifies the owner. - **mutation.yml** — weekly + workflow_dispatch. Runs Stryker, uploads the HTML report as a workflow artifact (30-day retention). Not a PR gate — mutation score is a test-quality signal, not a correctness one. All four use `concurrency.group` to cancel stale runs. Verified scripts exist; format:check clean.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The initial build.yml did `cp .env.example .env` and handed it straight to `next build`. That blew up in CI on `FAST_RPC_API_TOKEN` because Zod's `.string().min(1).optional()` only treats `undefined` as the optional case; an empty string is present-but-invalid. Fix: after copying, `sed -i '/=""$/d' .env` strips any `FOO=""` line so optional fields revert to undefined. The required fields (EMAILOCTOPUS_API_KEY, EMAILOCTOPUS_LIST_ID) ship with real UUID placeholders in the example and survive the filter. Keeps the "empty string = fill me in" convention in .env.example that developers rely on.
…line The prior README listed a src/ tree from before the agentic-repo-design refactor and had nothing to say about the testing stack or CI beyond "Working with AI agents" section. Updates: - Project structure: shows the new src/lib folders (tokens/swap/ settlement/config/api/analytics), the top-level tests/ mirror, and tests/ subfolders (api, fork, hooks, invariants, fixtures). - Getting Started: lists `cp .env.example .env` so developers get past t3-oss validation on first run. - Scripts table: documents every npm script, including test:fork and test:mutation. - Testing section: describes the nine layers and which command surfaces each, plus the "230 tests in default run" headline. - CI pipeline section: inventory of the five workflows and which ones gate PRs vs. run as scheduled canaries. - Working with AI agents: expanded with the documentation-layer table (skills/agent_docs/docs), the three PostToolUse hooks, and a pointer to audit-followup.md for outstanding work.
Sets up the pattern for pulling read-only copies of upstream repos
(starting with primev/mev-commit) under .external/ so agents have
persistent, greppable context without MCP round-trips.
- .gitignore: add /.external/ so the vendored clones never land in commits.
- .claude/externals.json: declarative manifest. One entry per external
— `name`, `origin`, `ref` (tracking main), optional `sparse` paths,
`freshness` thresholds, and a `purpose` one-liner. The mev-commit
entry sparse-checks only the 10 paths Fast Protocol App actually
consumes (contracts/, contracts-abi/, tools/preconf-rpc/{fastswap,
service, handlers, store, sender, rpcserver, points}, and
tools/fastswap-miles/). Everything else — p2p/, bridge/,
external/geth, infrastructure/, testing/ — is deliberately excluded
per the scope map in agent_docs/external-mev-commit.md.
- agent_docs/external-workspaces.md: brief pointer doc (how sync
works, why local-clone beats MCP, how to add future externals).
- agent_docs/external-workspaces-plan.md: the full design rationale,
including the tracking-main vs pinned-SHA decision and the
/prime + /sync-externals split.
- agent_docs/external-mev-commit.md: scope map for mev-commit
specifically — every endpoint the app talks to mapped to the
upstream Go file that implements it, the miles/Fuul flow, and the
reverse table (each app file → its mev-commit counterpart).
Phase B (sync hook + commands) and Phase C (skill wiring) follow.
…mands
Adds the mechanism that materializes and refreshes everything declared
in .claude/externals.json. Strict semantics throughout: fast-forward
only, never auto-resolve, refuse to proceed on diverged state.
- .claude/hooks/externals-sync.sh: reads the manifest with jq, clones
with --filter=blob:none + sparse-checkout when patterns are given
(9MB mev-commit checkout instead of 137MB full clone), fetches +
fast-forwards existing clones, writes .external/.manifest.lock.json
with {name, sha, ref, fetchedAt, ageDays} per external. Prints
machine-parseable per-line status. Sparse patterns use gitignore
style in the manifest ("/foo/") for human readability and get
normalized to cone-mode style ("foo") before hand-off to git
sparse-checkout.
- .claude/hooks/pre-commit-external-guard.sh: rejects staged paths
under .external/. Catches the real failure case — `git add -f
.external/` accidentally embedding the clone as a gitlink — not
just per-file edits. Ships as a script; install with a one-liner
symlink documented in the README.
- .claude/commands/prime.md: invokes externals-sync.sh before the
orientation file reads. If the sync exits non-zero, prime surfaces
the error and stops — no self-repair on .external/ allowed. Adds
an "External workspaces:" section to the final summary.
- .claude/commands/sync-externals.md: the mid-session refresh knob.
Runs just the sync, no orientation reread. For when a PR lands
upstream and you want the new state immediately.
Manually tested: clone + sparse-checkout produces the expected 10
paths (contracts/, contracts-abi/, tools/preconf-rpc/{fastswap,
service, handlers, store, sender, rpcserver, points}, and
tools/fastswap-miles/). Disk footprint 9MB vs 137MB full clone. The
pre-commit guard correctly exits 1 when .external/ is staged.
Regression check: 230 tests pass, typecheck clean.
Skill that tells agents WHEN to load mev-commit context and WHERE to look. Without this, the vendored mirror exists but agents have no discovery signal — they'd wander the 9MB tree and burn context. - SKILL.md: trigger conditions (editing fastswap route, debugging preconf status errors, miles discrepancies, ABI drift, revert investigation) + hard rules (never write to .external/, check freshness before citing) + pointer to the authoritative scope map in agent_docs/external-mev-commit.md. - contracts.md: Solidity source at .external/mev-commit/contracts/. Highlights FastSettlementV3.sol + IFastSettlementV3.sol + the test/ dir as a reference for how upstream calls the contract. Includes a three-step revert-debugging recipe. - abis.md: canonical ABI JSON at .external/mev-commit/contracts-abi/abi/ and the drift policy that's enforced by tests/contracts-abi/abi-drift.test.ts (extension lands in Phase D). - protocol-types.md: the preconf-rpc HTTP/JSON-RPC surface — every endpoint the app calls mapped to the Go handler — plus the miles indexer flow. The SwapRequest struct is embedded inline so agents touching src/app/api/fastswap/route.ts can verify shape without opening the upstream file. Each file deliberately thin — the full scope map stays at agent_docs/external-mev-commit.md so we don't drift two sources.
Closes the loop on the vendored-externals pattern: every local ABI that has a counterpart in .external/mev-commit/contracts-abi/abi/ is diffed byte-for-byte (canonicalized JSON) against upstream. Drift fails the test, giving us a deterministic signal when someone hand-edited a local ABI without sourcing from upstream. - tests/contracts-abi/abi-drift.test.ts: new `describe.skipIf(!synced)` block that walks every local .abi file, skips when the upstream copy is absent (e.g., our IFastSettlementV3.abi is a local-only interface extract), and asserts semantic equality otherwise. Sort keys + whitespace-canonicalize before compare so formatting differences don't flag as drift. - package.json: `test:externals` (runs just the ABI drift suite) and `sync:externals` (convenience alias for the sync hook). - .github/workflows/externals.yml: weekly + manual. Runs the sync hook, then `npm run test:externals`. Writes the lock-file contents to the job summary so a reviewer can see the exact upstream SHA each run exercised. NOT a PR gate — this is a canary. Verified locally: 15 drift tests pass, 1 skip (IFastSettlementV3.abi has no upstream counterpart). Default test suite grows 232 → 235 (the new tests only fire when .external/ is present, which default runs don't have).
Splits the README into two clearly signposted halves so it serves both audiences without making either wade through the other's content: 1. Humans exploring the codebase: top-matter (what it is, tech, getting started, scripts), project structure (now reflecting the folderized src/lib, top-level tests/, and the new .external/), testing (ten layers mapped to which command surfaces each), and CI pipeline (all six workflows, which gate PRs vs. run scheduled). 2. AI agents starting a session: a signposted "Working with AI agents" section covering /prime, the primitives table (CLAUDE.md / AGENTS.md / agent_docs/ / skills / subagents / commands / audit-followup), the three-tier doc-layer convention, every PostToolUse hook, the external-workspaces pattern (how .external/ + mev-commit work), and a one-sentence workflow summary. Opening callout at the top tells each reader where to look so nobody has to skim for their section.
Two new indexes so agents can answer "does X exist?" with one read instead of a recursive grep: - src/app/api/README.md — 52 API routes grouped by domain (analytics, barter, config, cron, fastswap, feedback, fuul, gate, OG, tokens, user-community-activity, user-onboarding, waitlist, whitelist). Each row: path, HTTP methods, one-sentence purpose, Zod-migration status (24/52 on the new pattern, 28 flagged⚠️ for migration when touched). Closes with the "add a new route" recipe pointing at the next-app-router skill. - src/hooks/README.md — 50 hooks grouped by concern (swap, permit2, wallet/RPC, balances, transactions, dashboard, leaderboard/miles, onboarding/gating, SBT, email, utility). Each row: hook + one-sentence purpose. Closes with the "add a new hook" recipe including test-file convention. These are hand-maintained for now; drift risk is minimal because adding to the index is part of the routine when landing a new route/hook (recipe in each README makes that explicit). A future generator script could automate from JSDoc comments but isn't necessary yet.
Single source of truth for the two app-owned tables (user_onboarding, user_activity). Documents columns, defaults, every route that touches each table, test fixtures, conventions, and — critically — which reads use window functions (`ROW_NUMBER() OVER`) that pg-mem can't run, so agents know why some routes have integration tests and some don't. Also a pointer section for the three out-of-scope data surfaces (StarRocks analytics, Fuul API, Google Sheets) so an agent landing here has one hop to find the right layer. Closes the reverse-engineer-from-SQL-every-time gap.
One top-level file listing every "always-true" rule the app upholds, grouped by domain (API input, slippage math, EIP-712 signing, token resolution, wrap/unwrap detection, stablecoin detection, leaderboard pagination, upstream API contracts, ABI drift). Every entry links to the test that enforces it — the test IS the oracle, this doc is navigation. Agents can now load "what are the rules of this system" in one read instead of grepping property tests. Scroll-optimized: one-sentence invariants, tables sorted alphabetically within each section.
Adds a scoped no-restricted-syntax rule for src/app/api/**/route.ts
that flags the three imperative-validation patterns still present in
~7 routes:
- request.json() (→ use parseJson)
- request.nextUrl.searchParams (→ use parseSearchParams)
- new URL(request.url) + searchParams.get (→ use parseSearchParams)
Each warning links to .claude/skills/next-app-router/api-routes.md
for the migration recipe.
Warn-level (onlyWarn downgrades anyway) because we don't want to
force-migrate every route in one PR — the goal is to surface the
pattern gap the moment an agent opens one of these files. The
audit-followup doc retains the list of remaining routes for
deliberate catch-up work.
Verified: fires on analytics/leaderboard/{,efficiency-leaders,rising-stars,
volume-leaders}/, analytics/l1-swap-hashes/, fuul/leaderboard/,
user-community-activity/stats/. Clean on all 24 already-migrated
routes.
Turns on the first strictness flag and fixes the modest fallout (8 initial errors, 5 after @types/pg): - Install @types/pg so the pg.Pool client isn't an implicit any — that alone resolved 3 errors across the community-activity routes. - src/app/api/gate/status/route.ts: declare GateStatusResponse so EMPTY_RESPONSE.position isn't inferred as bare `null` (wire shape with the UI is now typed, not conventional). - src/components/providers.tsx: WalletDisconnectHandler returns null, annotate explicitly. Same for src/components/pwa/service-worker-register.tsx. - src/lib/email.ts: annotate `.catch((): null => null)` so the rejection branch has an explicit return type. - tests/api/user-onboarding.integration.test.ts: type the test-case array so `body: undefined` doesn't infer to any. Next strictness flips (noUnusedLocals, strictNullChecks) are catalogued in audit-followup.md — each is a separate, reviewable PR so the blast radius stays small. Verification: 232 tests green, typecheck clean, format clean.
Restructures the file into three clear sections (Completed / Outstanding / Lower-priority) and updates to reflect the latest batch: - Completed: route + hook discovery indexes, db-schema.md, INVARIANTS.md at repo root, ESLint rule for non-Zod routes, noImplicitAny: true flipped + fallout fixed, external-workspaces pattern (mev-commit wired + drift-check CI). - Outstanding: further strictness flips in order (noUnusedLocals/ noUnusedParameters → strictNullChecks + parse helper rewrite → full strict), 28 remaining API route migrations (flagged by the new ESLint rule), the three god-file splits (now unblocked by hook-test pattern), more hook tests, component test pattern, pg-mem window-fn limitation + testcontainers path. - Lower-priority nice-to-haves: /verify-ui, error taxonomy doc, PR review automation, Dependabot, perf budget, a11y baseline, recent-incidents ledger, widen Stryker scope, extend externals.json. Adds an explicit priority order at the bottom so the next agent picks up in the right sequence.
Closes the API Zod migration — all routes that take user input are now validated through @/lib/api/parse + @/lib/api/schemas. The ESLint rule reports zero violations across the 52 route handlers. Routes migrated: - analytics/leaderboard/route.ts — optional currentUser via walletAddressSchema; lower-casing now happens at the boundary. - analytics/leaderboard/efficiency-leaders/route.ts — sort enum (tx_count | txs_per_day | streak), limit/page/tier with defaults. - analytics/leaderboard/rising-stars/route.ts — sort enum (climbers | new_users | wow_growth), limit/page. - analytics/leaderboard/volume-leaders/route.ts — sort enum (volume | avg_size | largest), limit/tier/page. - analytics/l1-swap-hashes/route.ts — limit coerced + clamped [1, 1000] (DoS guard). - user-community-activity/stats/route.ts — optional entity (trimmed) and chainId filters. - og/preconfirm/route.tsx + [time]/route.tsx — time clamped [0, 999], soft-fail to default 0.4 on parse failure (OG crawlers shouldn't get a 400). For the enum-constrained sort routes, the parser rejects unknown values with a 400 instead of silently falling through to the default branch — a real safety improvement over the prior imperative code. Verification: typecheck clean, 232 tests green, ESLint reports zero no-restricted-syntax violations on the API tree.
…decls Turns on the two unused-code TypeScript flags and fixes the fallout — 77 TS6133 / TS6192 errors across 39 files. Bulk of the work is mechanical dead-code removal (unused imports, unused props, unused destructured variables, unused local variables, unused function parameters), with a handful of genuinely dead code paths excised. Tooling: - Added eslint-plugin-unused-imports to auto-fix unused imports via `eslint --fix`. Kept @typescript-eslint/no-unused-vars off since tsc now owns that signal — unused-imports only handles the auto-fixable import arm. Patterns applied: - Unused imports → deleted (eslint-plugin-unused-imports --fix) - Unused destructured properties → removed from destructuring (not renamed to _name, which would create TS2339 for typed sources) - Unused function parameters → prefixed with `_` (TS convention) - Unused local variables → declaration deleted - Unused React prop destructuring → removed (component still accepts the prop in its Props type; just doesn't read it) Notable dead-code removals: - analytics/leaderboard/route.ts: userChange24h was assigned twice but never read in the response. All three lines deleted. - providers.tsx: refreshPage() helper was defined but only referenced in commented-out call sites. - use-swap-form.ts: useBroadcastGasPrice() was called but gasPriceGwei never consumed; removed the call + import. - use-swap-confirmation.ts: source/target vars computed but never passed to the settlement path. - LeaderboardTable.tsx: second useState for activeTab / setActiveTab that shadowed the used one below — the outer was dead. - Various components: initialEthPrice, initialTotalPoints, points, isMounted, onOpenChange, toBalance, etc. — props passed down but not consumed. Left the Props types intact; callers still pass them. Tsc flags remaining off (future PRs): - strictNullChecks (will require rewriting @/lib/api/parse.ts) - strict (master flag, gated on the above) Verification: 232 tests green, tsc --noEmit clean, format clean.
Two items moved from Outstanding to Completed: - All 52 API routes that take user input now go through @/lib/api/parse (was 24/52; the remaining 28 migrated in this session's earlier PR). - tsconfig now has noUnusedLocals: true + noUnusedParameters: true on top of the already-flipped noImplicitAny. 77 dead declarations were purged along the way. Re-ranks the priority list to reflect the new top items: 1. Extract use-swap-form math helpers 2. Seed 2-3 more hook tests 3. Establish the component-test pattern 4. strictNullChecks + @/lib/api/parse rewrite 5. Full god-file splits 6. Dependabot 7. Widen Stryker scope
…t + 14 property tests
Extracts the applied-slippage picker out of the useMemo block in
src/hooks/use-swap-form.ts. That block merged three independent
concerns — user slippage, Barter shortfall + buffer, maxSlippage cap —
into bigint arithmetic that couldn't be exercised without mounting
React. Pulled the percent math into a standalone module; bps + limit
computation now reuses the existing @/lib/swap/slippage helpers.
- src/lib/swap/min-amount-out.ts (new): computeAppliedSlippagePct and
computeAppliedSlippageBps. Documents the rule in prose (user
slippage is the floor, Barter shortfall + buffer raises it when
positive, both capped at maxSlippagePct). Guards NaN / negative
inputs — the UI can send either while the user is mid-type — so the
helpers are total.
- src/hooks/use-swap-form.ts: the computedMinAmountOut useMemo now
calls computeAppliedSlippageBps + computeSlippageLimit. ~12 lines
of inline math → 3-line call chain. No behavior change.
- tests/lib/swap/min-amount-out.test.ts (new): 5 example tests + 9
property tests covering:
- Output always in [0, maxSlippagePct]
- Never below userSlippagePct (contract-safety: we never silently
tighten a user's tolerance)
- Monotone non-decreasing in barterShortfallPct
- Shortfall=0 collapse semantics
- DEFAULT_BARTER_BUFFER_PCT pinned to 0.5
- End-to-end composition with computeSlippageLimit produces a
floor ≤ amountOut for ALL inputs (the revert-safety property)
- Totality (never throws)
- INVARIANTS.md: new "Applied-slippage picker" section linked to the
new tests.
Verification: 232 → 246 tests green, typecheck + format clean.
use-swap-form.ts size essentially unchanged (a few lines saved), but
the math is now independently verifiable.
Adds a new test-pyramid tier for WCAG 2.1 AA sweeps. Components are rendered under happy-dom exactly like the functional tests, but the assertion runs axe-core over the produced DOM instead of checking behavior. - `tests/a11y/` — own directory so a11y triage doesn't compete with behavioral failures in stack traces. - `tests/utils/axe.ts` — thin wrapper locking the rule set to WCAG 2.1 AA plus a `formatViolations` helper that surfaces rule id + help URL + offending HTML when a test fails. - `tests/a11y/SwapToast.a11y.test.tsx` — first canary. Pins three visual states of the post-swap toast (pending, confirmed, barter-slippage retry card) as the most-visible-per-user surface in the product. Mocks are the same as the functional SwapToast test. No violations today. As god-files continue to split, copy this template for the critical leaves: swap confirmation modal states, LeaderboardHeader, AppHeader. Deps: added `axe-core@4.11.3` + `vitest-axe@0.1.0` as devDependencies. Suite total: 297 passing (+3).
One-shot UI smoke via the existing ui-verifier subagent. Boots \`npm run dev\`, curls /, /dashboard, /claim, checks HTTP 200 plus content markers (LEADERBOARD / Genesis / gate heading), and cleans up the server on exit. Intended to catch provider crashes, missing env vars, and blank-page hydration failures — NOT pixel-level drift. Visual parity after a refactor still needs a human in a browser. Also registered the command in CLAUDE.md's slash-commands list.
…hemas/min-amount-out tests
**Security fix (high).** The post-strictNullChecks rewrite of
\`src/app/api/fastswap/route.ts:41\` was spreading the entire
\`ParseResult\` wrapper (\`{ ok, data: {...} }\`) into the upstream
request instead of just \`.data\`. Caller would receive
\`{ ok, data: {…permit2 intent…}, slippage }\` instead of the flat
intent, breaking the FastSwap proxy. Caught by security-reviewer on
the branch diff.
Today no client calls this route (the permit path hits FastRPC
directly), so no user impact — but the next engineer to re-enable the
proxy would ship broken. Tightening this now keeps the fix with the
original rewrite instead of leaving a landmine.
**Stryker kills.** Killed every survivor that a real input could
distinguish:
- \`schemas.ts\`: 72 → **100** mutation score. Added tests for
(a) trailing-\`$\` anchor on wallet/tx-hash regex (valid prefix + junk
suffix must reject — caller-controlled suffix is an injection class),
(b) leading-\`^\` anchor (prefix smuggle),
(c) explicit error-message assertions ("Invalid wallet address",
"Invalid transaction hash", "Symbol is required", "Symbol too long")
so a typo breaks the test instead of rotting silently.
- \`min-amount-out.ts\`: 67 → **81** mutation score. Added
\`Number.POSITIVE_INFINITY\` / \`NEGATIVE_INFINITY\` cases for both
\`userSlippagePct\` and \`barterShortfallPct\` so the
\`Number.isFinite\` guard is visibly load-bearing. A missing guard
would peg applied slippage at the cap when a NaN/Infinity slips
in from a downstream division.
Remaining 5 survivors (4 in min-amount-out, 1 in slippage) are
equivalent mutants — both branches produce the same output at
boundary values (e.g. \`num > MAX\` vs \`num >= MAX\` returns the same
value at \`num = MAX\`). Would need a code-shape change to kill.
Overall mutation score: 78.5 → **83.5**. Suite: 308 passing.
The original /verify-ui pointed at /dashboard as the leaderboard route, but ui-verifier flagged this: the heavy-refactor target is actually /leaderboard (src/app/(app)/leaderboard/page.tsx). /dashboard is the personal miles page. Both routes are now listed so the smoke exercises the full authenticated header + provider stack plus the LeaderboardTable split itself. Also documented two operational gotchas that surfaced on the first run: stale .next/ requires rm -rf, and missing ANALYTICS_DB_AUTH_TOKEN makes SSR fetches 401 without crashing the page.
First test file for the 600-LoC use-swap-form god-hook. Establishes a regression oracle the next extractor can lean on — without this, any further carve (refresh timer, quote cache, switch handler) was blind. Ten tests pin the parent-surface contracts that matter to the swap button: - Defaults: fromToken = ETH, toToken = undefined, amount = "", editingSide = "sell", timeLeft = 15. - Amount input: setAmount updates + preserves across rerender, empty string allowed for "user clearing the input". - Token changes: setToToken updates; pair change clears isManualInversion AND swappedQuote (pair-change effect is the load-bearing reset). - Wallet lifecycle: connected→disconnected edge resets the form; connected→connected (account switch) preserves the user's input. - clearSwapState pulse: setting it true clears amount/editingSide/ inversion flags but leaves the token pair intact so the user can immediately do another swap in the same direction. Notable gotcha documented in the test file: the pairKey-change effect fires AFTER batched setState inside the same `act`, so tests that need to set `isManualInversion = true` must split the token-change and the flag-set into separate `act` blocks. This is the exact shape of bug future extractors will introduce if the effect ordering drifts. Ten external dependencies mocked at module scope (wagmi, TanStack Query, and eight internal hooks). wagmi exposes a `__setAccountForTest` mutator so specific tests can flip wallet state mid-run without re-mocking. Suite: 315 passing (+10).
Eighteen leaves were extracted this session from LeaderboardTable (11) and SwapConfirmationModal (7) with zero direct tests. I claimed they were "now independently testable" — this commit starts paying that bill. Three new test files, nineteen tests. Targets chosen for load-bearing behavior, not LoC: 1. **useSnapshotOnOpen** (5 tests) — contract-critical hook that freezes quote values on modal open. If it leaks live updates, the user signs a tx with different numbers than the ones they reviewed. Pins the closed→open capture edge, the close clears the snapshot, re-open re-captures, and identity stability across open=true renders (a regression that re-spreads on every render would let live updates drift through cumulatively). 2. **ErrorView** (7 tests) — barter-slippage retry path is money-critical. Pins: header copy, the retry button's parsed slippage %, the clicked callback argument is a string (not a number — it must flow through the swap form's slippage string store), Details button wiring, the occurredAfterPreConfirm branch that HIDES Try Again (otherwise retry would submit a second tx for an already-preconfirmed intent). 3. **LeaderboardHeader** (7 tests) — the top-of-page mode toggle that drives which mode-table below it even renders. Pins: toggle callback routing, Traders / Vol (ETH) / Vol (USD) labels + values, '---' fallbacks for null stats (SSR / unauth path), user performance card visibility gated on userAddr, '#--' placeholder for in-flight rank. Pattern follows the SwapToast + use-swap-form templates already in the tree — happy-dom, renderHook/render, @testing-library queries, vi.fn() spies for callbacks. Not yet covered (biggest remaining holes): VolumeModeTable (tier filter + user-position divider logic), MilesModeTable, VolumeProgressAnalysis. These are the next natural additions when touched again. Suite: 334 passing (+19).
…lose button Closes the biggest untested surface on this branch. The useSnapshotOnOpen hook and the ErrorView / ConfirmCtaButton leaves each have their own tests, but the orchestration BETWEEN them — which is what the 623-LoC post-split file owns — was not directly tested. Eight tests covering load-bearing orchestration contracts: 1. autoExecute short-circuit — open + autoExecute renders null AND fires executeSwap AND calls onAutoExecuteConsumed. This is the toast-retry fast path; a regression that shows the review UI would let the user re-review a swap they already confirmed. 2. CTA routes to the right primitive — three separate tests pinning wrap() / unwrap() / confirmSwap() so a future refactor can't shuffle the call-site wiring. Each verifies the "other two" are NOT called. 3. Approval flow — needsPermit2Approval=true + intentPath surfaces "Approve & Swap" and routes the click to onApprove, NOT confirmSwap. The swap auto-chains on a subsequent render when needsPermit2Approval flips false; that edge is driven by props and lives in an effect that would need wall-clock re-renders to exercise — documented but not asserted. 4. External error — externalError prop renders ErrorView, hides review. occurredAfterPreConfirm hides Try Again (tx already on L1; retrying would submit a second tx). 5. Close handler — clears lastTxError on the store and fires onOpenChange(false). **a11y fix:** the modal's close button had no accessible name (the Radix DialogClose wraps a bare button with just an X svg). Added \`aria-label="Close"\` + \`type="button"\`. This is a real WCAG 2.1 AA regression, not just test theatre — screen readers could not announce the close action before. Ten external hooks mocked (wagmi, useWethWrapUnwrap, useSwapConfirmation, plus gas/price/miles utilities). Tests use a mutator pattern (mockIsWrap / mockIsUnwrap module-scoped booleans) so a single test can flip the mode without re-mocking the module. Suite: 342 passing (+8).
Main added commit 8743544 ("feat: add /pro landing + fix OG image pre-warm URL") which: - Added a /pro landing route (src/app/pro/*, src/components/pro/*). - Loosened early-access validation to "at least one contact method required" (was all three). - Deleted src/app/share/preconfirm/route.ts as a dead route. - Fixed the OG pre-warm URL in SwapToast. Conflict resolved in src/app/api/early-access/route.ts: kept this branch's Zod-migration shape but adopted main's semantics. The earlyAccessSchema now marks x_handle / discord_handle / email as optional-defaulting-to-empty and uses a top-level .refine to require at least one. Email-format check stays in the schema (fires only when email is non-empty). Also updated src/app/pro/layout.tsx to import SITE_URL from @/lib/config/site (the new path after this branch's src/lib/ folderization) instead of the old @/lib/site-config. Merge preserves: main's new /pro route, loosened waitlist entry rules, corrected OG pre-warm URL, deleted dead share route. Branch preserves: Zod-through-parseJson pattern, ParseResult discriminated union, all god-file splits and strictNullChecks fallout. Verified: typecheck clean (filtering known stale .next/types noise), 342 tests pass (+3 skipped), format clean.
… with folderization Durable flow for keeping the agentic-repo patterns aligned as main moves. **New skill: `.claude/skills/merging-main/SKILL.md`** Playbook for merging / rebasing main and catching drift. Covers: - Stale `src/lib/*` imports (the pre-folderization → post-folderization table: site-config, network, feature-flags, weth-abi, weth-utils, erc20-abi, constants, stablecoins, token-list, token-resolver, transaction-errors, slippage, quote-guard, eth-path-tx, permit2). - ESLint `no-restricted-syntax` hits on new API routes — main PRs that opened before the rule can reintroduce imperative validation. - New `any` / `@ts-ignore` added since the merge base. - Doc-index drift (architecture.md, api/README.md, hooks/README.md). - Test seeds and a11y sweep on user-facing new components. - Bundle check for new runtime deps. **New slash command: `/realign`** Runs the skill's playbook step-by-step. Reports incoming commits, drift caught, docs updated, verify results, and anything flagged but not auto-fixed. Returns the report without committing so the human can review before the merge commit is finalized. **Live pass over PR #109 (/pro landing) merge** — caught three issues: 1. `src/app/pro/layout.tsx` imported `@/lib/site-config` (main's path); fixed to `@/lib/config/site` during the merge commit b810514. 2. `agent_docs/architecture.md` was missing entries for the new `src/app/pro/` and `src/components/pro/` folders (and still listed the deleted `src/app/share/` route) — fixed here. 3. `src/app/api/fuul/leaderboard/route.ts:200` uses `new URL(request.url)` in an error-path salvage fallback that the security-reviewer cleared. Added `eslint-disable-next-line no-restricted-syntax` with a comment explaining why — prevents the warning from rotting into noise. Also documented in `agent_docs/audit-followup.md`: - The merging-main flow landed (move to "done" section). - The `PaginatedLeaderboardModal` `(e as any)` casts as a known cleanup (genericize the modal over `T extends PaginatedModalEntry`). Pre- existing from main's `e57a5f8`; not a merge regression. Verified: 342 tests pass, typecheck clean, format clean.
…s rule Highest-ROI drift catcher: every pre-folderization \`src/lib/*\` path is now an ESLint error with a pointer to the new location. Main PRs that merge in with stale imports get caught at lint time (seconds) instead of at \`next build\` (minutes), and agents no longer have to remember to run \`/realign\`. ## What `eslint.config.js` gains a `no-restricted-imports` block with 31 old → new entries covering every file moved by commit 6889c3b ("folderize lib"): - **config/** — site-config, network-config, feature-flags, constants, leaderboard-config. - **tokens/** — weth-abi, erc20-abi, token-list, token-resolver, stablecoins, stablecoin-list, weth-utils, token-icons, popular-tokens, barter-supported-tokens. - **settlement/** — transaction-errors, transaction-receipt-utils, tx-config, fast-rpc-status (→ rpc-status), fast-tx-status (→ tx-status), fast-db (→ db), preconfirm-sound. - **swap/** — slippage, quote-guard, eth-path-tx, permit2-utils, barter-api, swap-constants (→ constants), swap-events (→ events), swap-server (→ server). - **deleted** — fast-settlement-v2-1, fast-settlement-v3-abi (removed entirely; point users at contracts-abi/). Every message includes the new path so the fix is a one-line edit. ## Guarded by a test `tests/infra/eslint-no-restricted-imports.test.ts` — 11 tests spawning ESLint programmatically. If someone removes an entry or the rule itself, the test fails. Cases include: - 9 parametrized stale-path assertions (message contains both old and new path). - Explicit "Removed" check for the two deleted modules. - Happy-path sanity: post-folderization paths lint silently. ## Skill + command updates `.claude/skills/merging-main/SKILL.md` — rename table fixed (permit2 was mistyped as lacking the `-utils` suffix; several entries were missing). Section now reads "handled automatically" with a pointer to the lint rule. Manual grep commands deleted — the rule replaces them. `.claude/commands/realign.md` — "Hunt stale lib imports" step points at `npm run lint` instead of a grep. Also notes that new renames need to be added to BOTH the lint rule and the skill's table. ## Why this is the right tool - **Instant feedback** — ESLint reports in seconds; `next build` reports in minutes. - **Better error** — the message names the new path. `next build` would just say "module not found." - **Can't be forgotten** — runs on every lint invocation, including the `npm run verify` slash command and CI. - **Zero runtime cost** — no new deps, pure config. Verified: 353 tests pass (+11), typecheck clean, format clean, 0 stale imports in the current tree.
Main landed PR #128 (commit bb87440) renaming the env var: FAST_RPC_API_TOKEN → FAST_RPC_DB_AUTH_TOKEN. Touches .env.example, src/env/server.ts, and two API routes that read the token. Conflicts in two routes, both the same shape: my branch has the Zod-migrated handler body; main has the same imperative handler but with the renamed token. Resolution: keep the Zod structure (parseParams + parsed.data already does validation), adopt the new token name. The imperative validation main still had is obsolete in our tree. Verified: 353 tests pass, typecheck clean (real errors; .next/types noise filtered), format clean, 0 no-restricted-imports warnings (the drift rule landed in f071803 caught no false positives on this merge).
Four tasks executed end-to-end; this is the state the agentic-first effort was targeting. Detail below; summary numbers: 353 → **399 tests** (+46), mutation score 83.5 → **96.5%**, 3 weak modules lifted to strong, CI now gates drift, two real a11y/render-loop bugs fixed. ### #49 — Security-reviewer nits - **`useSnapshotOnOpen` deep-clone.** Swapped the shallow spread for `structuredClone`. Locked with a new test that mutates a nested Token object after capture and asserts the snapshot stays immune. The security-reviewer's "shallow spread could leak mutations" finding is now gone; a regression (reverting to spread) would fail the test. - **SwapToast render-body console.log.** Moved the Hash-ready log into a `useEffect([effectiveHash])`. Previously ran during render — React strict mode would double-fire it and the ref mutation was a during- render side effect. Log behavior is unchanged (one line per unique hash). ### #50 — CI workflow for drift catching - **`verify.yml` gains a `lint` job** that runs `npm run lint` then inspects the output for `no-restricted-imports` (pre-folderization paths) and `no-restricted-syntax` (imperative API validation) hits. The `onlyWarn` plugin downgrades everything to warnings, so the bash post-check is what actually fails CI on drift — a PR from main that reintroduces either anti-pattern gets blocked at review time. - Error output uses `::error::` annotations so GitHub surfaces them inline on the PR. ### #51 — token-resolver.ts mutation score - **78% → 98.4%** (21 survivors → 2 equivalent-only). - Added 13 targeted tests covering: object-with-no-address branch, missing-symbol safety, ZERO_ADDRESS lookup rewrite, crowded-list matching (kills the `.find(() => true)` mutant in both resolveTokenAddress and resolveTokenDecimals), non-string/non-object fallthrough, non-number decimals default, isNativeETH edge inputs, getTokenSymbol primitive safety. - Remaining 2 survivors are `typeof === "object" && X.field` vs `true && X.field` — equivalent for non-objects (X.field is always undefined-falsy), unkillable without reshaping the source. ### #52 — Component tests on extracted leaves Four new files covering load-bearing leaves: - **BuyReceiveValue** (6 tests) — NumberFlow wrapper; numeric vs. string fallback; comma-strip for grouping separators. - **ConfirmCtaButton** (8 tests) — disabled / dangerous / default style precedence; native `disabled` attribute gates clicks; spinner wrapper toggle. - **ErrorDetailModal** (5 tests) — Copy-to-clipboard via stubbed `navigator.clipboard`; 2000ms "Copied" → "Copy" label flip with fake timers; no-op on empty content. - **LeaderboardRow** (13 tests) — zero-padded rank display; tier accent gated on rank ≤ 3 (not just tier); YOU badge via both `isCurrentUser` and `showYouBadge`; swap-count plural/singular/NA; miles formatter fallback. ### What was NOT done (and why) - **VolumeModeTable / MilesModeTable** — larger components; their logic is partially exercised via LeaderboardHeader + LeaderboardRow + PaginatedLeaderboardModal's parent. Natural to add when they're next touched; not load-bearing enough to block "done." - **InfoRow, TransactionSummary, SwapDetailsCollapse, *LeadersCard siblings** — pure presentational / indirectly exercised / heavy mocking requirements for marginal coverage gain. Verified: 399 tests pass, typecheck clean (real errors; .next/types stale-build noise filtered), format clean, 0 stale imports in the current tree. audit-followup.md updated to reflect the remaining work as maintenance / judgment items, not completion blockers.
…erge Local auto-run for the `merging-main` alignment playbook. The hook fires after `git merge` / `git pull` completes and surfaces drift within seconds, so you catch it before the next push rather than waiting for CI. ## Mechanics - `.husky/post-merge` runs `npm run lint` and greps for `no-restricted-imports` (pre-folderization paths) and `no-restricted-syntax` (imperative API validation) hits. - Skips silently on merges that don't touch `src/` (pure doc/config). - Skips when `GITHUB_ACTIONS=1` or `FAST_SKIP_DRIFT_HOOK=1` (for CI and one-off overrides). - Always exits 0 — the merge is already committed by the time this fires, so blocking via non-zero only confuses the user into thinking the merge itself failed. The loud warning is the signal. - Points at `.claude/skills/merging-main/SKILL.md` and `/realign` for fixing, mirrors the CI `lint` job so local = CI signal. ## Install path - `package.json` gets `"prepare": "husky"` + husky as a devDep. Next developer to run `npm install` gets `core.hooksPath=.husky/_` wired automatically. - `.gitignore` excludes the auto-regenerated `.husky/_/` dispatcher but commits the user-defined `.husky/post-merge` script. - Default `.husky/pre-commit` that husky init writes was deleted — I didn't want `npm test` slowing every commit. ## Docs - CLAUDE.md's Hooks section adds the post-merge entry alongside the PostToolUse + Stop hooks. - `.claude/skills/merging-main/SKILL.md` now calls out the three catch points (local post-merge, CI lint, manual `/realign`). Verified: hook fires and reports no drift on the current tree; all 399 tests still pass, typecheck + format clean.
Main PR #129 landed two commits: - Dedicated `show_miles_in_swap` feature flag (separate from the broader show_miles_estimate used across the leaderboard). - New `/api/config/global-banner` route + `GlobalBanner` component for Edge-Config-driven announcements rendered at the app shell level. No conflicts. `/realign` checks clean on the merged tree: - 0 pre-folderization imports (no-restricted-imports rule silent). - 0 imperative validation hits (new GET route takes no input, Zod not applicable; marked⚠️ in the route index). - 0 new `any` / `@ts-ignore`. Doc update: added `config/global-banner/` to the api/README.md index (prettier-formatted). Verified: 399 tests pass, typecheck + format clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Turns the Fast Protocol App into a production agent-first codebase. Over 47 commits, the repo now has:
.claude/skills,agent_docs/, user-facingdocs/).@/lib/api/parsehelper.format,verify,build(path-gated),fork(nightly),externals(weekly),mutation(weekly)..external/mev-commitvendored read-only), Dependabot, Stryker widened to four pure modules.fastswap/route.tswas spreadingbodyinstead ofbody.datapost-parse-rewrite).Diff: 326 files, +18,276 / −5,150. Tests: 342 passing across 28 files (was 150ish on main).
Why this PR is safe to land despite its size
31f407e,242ae7d,594c823) and the parse.ts rewrite (6a83b73) are pure code-move + type-tightening. Every extracted file preserves className, tooltip text, and conditional logic verbatim. Commit messages call this out specifically.6a83b73), every one traced to a real call-site bug hidden by loose types. Zero new errors on the subsequentstrict: trueflip (4ef4f8a).eslint.config.js:88-112forbids imperative validation (request.json(),nextUrl.searchParams) on route files — zero warnings across all 52 routes.f1bcfce./,/dashboard,/leaderboard,/claim— all HTTP 200 with correct content markers.Major changes by area
Agent infrastructure (new)
.claude/skills/— per-domain how-to (next-app-router, defi-swap, web3-wallet, dashboard-data, leaderboard-miles, contract-abis, ui-shadcn, testing-vitest, skill-creator, external-mev-commit)..claude/agents/— subagents (explore-web3, security-reviewer, ui-verifier, abi-tracer)..claude/commands/— slash commands (/prime,/verify,/verify-ui,/typecheck,/lint,/test,/review-diff,/sync-externals,/new-skill)..claude/hooks/— PostToolUse typecheck + mirror-test + conditional build; Stop format:check.agent_docs/— stack, architecture, verification, glossary, db-schema, route indexes, audit-followup, external-mev-commit.AGENTS.md— portable agent spec (CLAUDE.md delegates to it)..external/mev-commit/— upstream source-of-truth vendored read-only via.claude/externals.yml.TypeScript strictness
noImplicitAny: true(30edd51)noUnusedLocals: true,noUnusedParameters: true+ purged 77 dead declarations (7c96d1c)strictNullChecks: true+ fixed 40+ sites across hooks, components, routes (6a83b73)strict: true— zero new errors (4ef4f8a)API validation
src/lib/api/parse.ts—parseJson/parseSearchParams/parseParamsreturning{ ok: true; data } | { ok: false; response }.src/lib/api/schemas.ts— shared Zod primitives (walletAddressSchema, txHashSchema, tokenSymbolSchema, paginationSchema).instanceof NextResponseto the discriminated union; all callers updated.God-file splits
src/components/dashboard/leaderboard/: LeaderboardRow, PaginatedLeaderboardModal, Volume/Efficiency/Referral/RisingStars cards (phase 1), LeaderboardHeader, Volume/MilesProgressAnalysis, Volume/MilesModeTable (phase 2), plustypes.ts.src/components/modals/swap-confirmation/: InfoRow, BuyReceiveValue, TransactionSummary, SwapDetailsCollapse, ErrorView, ErrorDetailModal, ConfirmCtaButton, plususeSnapshotOnOpenhook + shared style.Test coverage
src/lib/swap/**,src/lib/tokens/**,src/lib/settlement/**,src/lib/api/**).pg-memfor one route (window-function limitation noted in audit-followup).use-swap-slippage,use-quote-guard-config,use-balance-flash,use-page-active,use-debounced-validating,use-swap-form.SwapToast,SwapConfirmationModal,LeaderboardHeader,ErrorView,useSnapshotOnOpen.tests/a11y/, axe-core via vitest-axe) seeded on SwapToast.tests/fork/permit2.fork.test.ts) — skipped unlessFORK_RPC_URLis set; nightly CI runs withethereum-rpc.publicnode.com.slippage.ts,min-amount-out.ts,api/schemas.ts,tokens/token-resolver.ts. Score: 83.5% overall.Verification evidence
Run locally (all green before this PR was opened):
Or:
/verify(slash command runs the first four).Security review (on this diff)
/review-diff→security-reviewersubagent scanned the fullmain..HEADdiff. Findings:f1bcfce):src/app/api/fastswap/route.ts:41was spreading the ParseResult wrapper (body) into the upstream request instead of the parsed data (body.data). Caught post-strictNullChecks rewrite. No prod impact today (route is unused) but would have broken on re-enable.useSnapshotOnOpenshallow spread could leak tokenIn/tokenOut mutations. Low likelihood in this codebase (tokens are replaced by reference, not mutated), documented in the hook comment.console.logs that would double-fire under React strict mode; fastswap nonce schema accepts 0 (imprecise but not exploitable); fuul/leaderboard stale-cache path re-reads searchParams intentionally.UI smoke (ui-verifier subagent)
No console errors in SSR output on any route. Analytics API 401s without crashing (graceful skeleton / empty states). No real
Application errormarkers anywhere.Mutation testing (Stryker)
Survivors in
slippage.tsandmin-amount-out.tsare equivalent mutants (both branches produce the same output at boundary values — unkillable without rewriting the source).token-resolver.tshas genuine test gaps noted inagent_docs/audit-followup.md; below the 80% bar but above the 70%lowthreshold.Known caveats (not blockers, but worth knowing)
/leaderboard(all three mode tabs + "All Leaders" modal) and the swap confirmation modal (connect wallet → swap → open modal) before merging.FORK_RPC_URL; the nightly CI workflow runs them againstethereum-rpc.publicnode.com.Follow-ups (tracked in
agent_docs/audit-followup.md)Test plan
npm run typecheckpasses.npm run lintpasses.npm run test:runpasses (342 / 3 skipped).npm run format:checkpasses./leaderboardacross Miles / Volume / Stats tabs (+ tier filter on Volume, "All Leaders" modal).FORK_RPC_URL=https://ethereum-rpc.publicnode.com npm run test:forkgreen.