This document assumes that you are using a MacOS system. The project has been set up with VSCode as the default editor.
This is a pnpm monorepo written in TypeScript with four packages:
extension/– Chrome extension (React, Vite, MV3)backend/– Express.js API (LLM-based analysis)shared/– Shared TypeScript schemas and typesevals/– LLM evaluation suite (accuracy, prompts)
| Command | Description |
|---|---|
pnpm dev:extension |
Start extension dev server |
pnpm dev:backend |
Start backend with hot reload |
pnpm build |
Build all packages |
pnpm build:extension |
Build extension only |
pnpm test |
Run all unit tests |
pnpm test:backend |
Run backend tests only |
pnpm test:extension |
Run extension tests only |
pnpm test:shared |
Run shared tests only |
pnpm test:watch |
Run tests in watch mode |
pnpm check |
Full pre-commit run (format, lint, typecheck, build) |
pnpm start:backend |
Run built backend (after pnpm build) |
pnpm eval |
Run detailed LLM evals |
pnpm eval:latest |
Run detailed evals using prompts/latest.txt |
pnpm eval:all |
Run all evals (snippets + pages) |
Before you begin, ensure you have the following installed:
- Node.js (v22 or higher)
- pnpm (v9.15.4 or higher)
To install Node.js, you can use a version manager like nvm (recommended) or Homebrew if you don't need to manage multiple versions. For more information, see the Node.js official download page.
If you already have npm installed but not pnpm, you can install it via:
npm install --global corepack@latest
corepack enable pnpmIf you don't have npm installed, you can install pnpm via:
brew install pnpm-
Clone the repository:
git clone <repository-url> patch cd patch
-
Install all dependencies:
pnpm install
This installs dependencies for the root workspace and all packages (
extension,backend,shared,evals).
Extension (Vite dev server):
pnpm dev:extensionBackend (Express API with hot reload):
pnpm dev:backendThe backend runs at http://localhost:3000 by default.
The backend uses an OpenAI‑compatible LLM (e.g., xAI Grok). Copy the example env file and fill in your own secrets:
cp backend/.env.example backend/.envUpdate backend/.env with your credentials:
# backend/.env
# OpenAI-compatible endpoint for Grok
LLM_BASE_URL=https://api.x.ai/v1
LLL_MODEL=grok-4-1-fast-non-reasoning # or grok-4-latest, etc.
LLM_API_KEY=your_xai_api_key_here # do NOT commit real keysNotes:
- Do not commit real secrets. Keep
backend/.envout of version control (see.gitignore). Share secrets via your team’s secret manager, not git. backend/.env.exampledocuments the variables we expect; keep it in sync when adding new env keys.
With the backend running (pnpm dev:backend) and .env configured, you can call the API directly:
curl -s -X POST http://localhost:3000/analyze \
-H 'Content-Type: application/json' \
-d '{"domSnippet":"<html><body>Hello</body></html>"}' | jq .The response is JSON conforming to AnalysisResponseSchema in shared/src/analyze/response.ts (patterns array with type, severity, description, evidence, fixes (array of { selector, strategy, fix }), and confidence).
-
Build the extension:
pnpm dev:extension
-
Open Chrome and navigate to
chrome://extensions/ -
Enable Developer mode (toggle in the top right)
-
Click Load unpacked
-
Select the
extension/distfolder
After making changes, click the refresh icon on the extension card in chrome://extensions/. For hot-reloading to work, you must keep the dev:extension script running.
pnpm build:extensionThe extension will be built in the extension/dist folder. You can then load it in Chrome by following the steps above. Note that this is a one-time build and will not be updated automatically.
The evals/ package measures how accurately the LLM detects dark patterns. There are two eval modes:
| Mode | Command | What it measures |
|---|---|---|
| Detailed | pnpm eval |
Field-level accuracy: severity, selectors, evidence, fix quality |
| Latest | pnpm eval:latest |
Same as detailed but always uses prompts/latest.txt |
| Standard | pnpm eval:pages |
Pattern type and count correctness only |
| Snippets | pnpm eval:snippets |
Single-pattern HTML snippets (fast, no fixture files) |
| All | pnpm eval:all |
Both snippet and page standard evals |
The evals call the LLM API directly — no backend server needs to be running. You only need backend/.env configured with a valid LLM_API_KEY:
cp backend/.env.example backend/.env
# then set LLM_API_KEY in backend/.envThen:
pnpm eval # detailed eval (default, recommended)
pnpm eval -- -v # detailed eval with verbose field-level output- Snippet cases:
evals/dataset/snippets/<pattern>.json— single-pattern inline HTML snippets - Page cases:
evals/dataset/pages/complex-pages.json— full-page fixture tests (pattern type + count) - Detailed page cases:
evals/dataset/pages/marketplace.json— field-level expected patterns (severity, evidence, selectors, fixes)
See evals/README.md for full details on adding test cases, dataset format, and prompt engineering.
When adding or updating expected patterns in the dataset, align severity with the system prompt's definitions:
"high"— direct financial harm: hidden subscriptions, pre-selected paid add-ons, deceptive checkout flows"medium"— pressure without direct financial harm: countdown timers, scarcity badges, confirmshaming"low"— mild/borderline: minor visual tricks, social proof claims, informational friction
Write evidence as the text or HTML element a user would see in the rendered page, not as JavaScript source code. For example:
"evidence": "<span id=\"timer-1\">Deal ends in [MM:SS]</span>"not
"evidence": "productTimers[1] = Math.floor(Math.random() * 3600) + 300"Before committing, run:
pnpm checkThis runs format check, lint, dependency checks (knip), typecheck, build, and tests. Simple fixes:
pnpm lint:fix # lint
pnpm format # format
pnpm check-deps:fix # knip fixesEnsure all checks pass before creating a merge request. For testing details, see TESTING.md.
You should NEVER push directly to the main branch. Create a new branch and follow the steps below:
-
All new branches should be associated with a task. You can create a task from the "Issues" section on GitLab.
-
Create a new branch from the task's page on GitLab. Copy the branch name after creating it.
-
Run
git fetch originandgit checkout <branch-name>to switch to the new branch. -
Make and test your changes locally.
-
Go through the Pre-Commit Checklist to check for errors.
-
Commit your changes to the remote repository.
-
To resolve potential conflicts with the main branch, make sure you rebase your branch onto the latest main branch:
git config pull.rebase true # (you only need to do this the first time) git pull origin main
You will need to resolve any conflicts that arise during the rebase either by editing the files then running
git rebase --continueor by runninggit rebase --abortthen starting over. -
Create a merge request to the
mainbranch on GitLab. -
Request for a review on the MR and await approval before merging.
-
Delete the branch after the MR is merged.
When you open a merge request, GitLab CI runs a pipeline that only triggers on merge_request_event. You can see the pipeline status and logs on the MR page.
The pipeline has three stages. Jobs use the default image cimg/node:24.13 unless noted.
- install – Cleans, installs dependencies with
pnpm install --frozen-lockfile, and pushes the cache (.pnpm-store,node_modules). All other jobs depend on this and pull the cache.
- format –
pnpm format:check - lint –
pnpm lint - dependencies –
pnpm check-deps(knip) - build –
pnpm build; produces artifacts (shared/dist,backend/dist,extension/dist) for downstream jobs - typecheck –
pnpm typecheck(needs install + build)
- test-backend – Backend unit tests with coverage; Cobertura and JUnit artifacts (2 weeks)
- test-extension – Extension unit tests with coverage; Cobertura and JUnit artifacts (2 weeks)
- test-shared – Shared package unit tests with coverage; Cobertura and JUnit artifacts (2 weeks)
- test-e2e – E2E tests with Puppeteer (
pnpm test:e2e). Installs Puppeteer’s official Debian dependencies pluschromium; on Linux arm64 (e.g. Docker on Apple Silicon) uses system Chromium since Chrome doesn’t ship an arm64 Linux binary. Needs install + build.
Coverage is reported in the MR; JUnit and coverage artifacts are kept for 2 weeks (unit tests only).
The pipeline must pass before the merge request is merged. If it fails, fix the issues locally (re-run pnpm check and the relevant test commands as needed), push your changes, and the pipeline will run again.
For this project, we use self-hosted GitLab runners. If there are no runners active, the pipelines will not run when you open a merge request. When this happens, you can either contact someone who has a runner set up or configure your own runner by following the instructions below.
- Install GitLab Runner on your machine using the instructions here.
- (Optional but recommended) Install Docker on your machine if it is not already installed. This makes replicating the runner environment and cleaning up pipeline artifacts easier. For MacOS, you can either use the official Docker Desktop, OrbStack, or any other Docker alternative such as Colima, Rancher Desktop, or Podman.
- On the project's GitLab page, navigate to Settings > CI/CD > Runners.
- Click on Create Project Runner. All the fields can be left empty.
- Click on Create Runner.
- Copy the runner registration command.
- Run the registration command to register the runner. If this doesn't work, you can try to register the runner manually by running
gitlab-runner registerand following the prompts. - Start the runner by running
gitlab-runner runwhenever you need to run a pipeline.