# AGENTS.md: REX Operating Instructions for AI Assistants

This file is your operating instructions for working on a public REX module. Read it fully before starting.

The contributor working with you may not be an experienced JavaScript or TypeScript developer. Guide them step by step. Explain things clearly. Do not assume familiarity with npm, webpack, browser extensions, Chrome APIs, or GitHub.

A human-facing companion document, [Getting Placement Right](https://docs.bric.digital/rex/contribute/getting-placement-right/), covers the most important conceptual decision (where the feature belongs in the module hierarchy) for contributors who want to read about it themselves. You do not need to read it, since the same material is in this file, expanded with agent-specific behavior. It is the canonical reference if the contributor asks you to point them at one.

---

## The Contributor's Outcome May Not Be a PR

Not every session ends in a pull request, and you should not assume one. A contributor might be:

- **Trying something out on a branch** to see if it works, with no immediate plan to upstream it.
- **Patching their own extension via a private fork** (Path A in [Contributing to REX](https://docs.bric.digital/rex/contribute/contributor-orientation/)): pinning their study extension's `package.json` at their fork and shipping. This is a fully supported way to use REX; BRIC just cannot support the fork itself.
- **Building a feature they intend to PR** (Path B). The contribution discipline matters most here.

Ask the contributor early what their goal is if it is not obvious. Calibrate accordingly: experimentation gets latitude; a planned PR gets the full rigor of the [PR checklist](https://docs.bric.digital/rex/contribute/pr-checklist/). Do not herd a contributor toward a PR if that is not what they want. "Let me try this and see" is a legitimate outcome, and pushing them harder than they asked for is unhelpful.

That said: the placement discipline applies to *all* paths. Even an experimental branch benefits from being placed at the right level of generality, because most experiments become real PRs eventually.

---

## Starting a Session

**Before writing any code, run an exploratory pass across the repositories in this working directory.**

The contributor has cloned a standard set of REX module repos into a single folder. Your first task is to understand where the feature belongs, at the right level of abstraction in the module hierarchy. REX has a generality hierarchy: `rex-core` at the root, shared infrastructure modules like `rex-spider` in the middle, and child modules like `rex-spider-chatgpt` at the most specific level. A change that benefits all spider modules belongs in `rex-spider`, not in a child module. A change that only makes sense for one site stays in the child module.

### The #1 reason contributions get rejected or rewritten: wrong level of generality

**Placing the feature at the correct level of generality is the single most challenging part of contributing to REX, and getting it wrong is by far the most common reason a PR is sent back for rework.** A feature put in the wrong place is rarely "broken": it works, the tests pass, the build is clean. It is wrong in a more subtle way. It ossifies an assumption that does not belong at that layer, or it duplicates logic that should have lived one level up, or it pollutes a generic module with something specific to one study or site. These are exactly the kinds of mistakes that look fine in isolation and only reveal themselves when the next researcher tries to reuse the module.

Because the design target is "what serves REX as a framework," not "what makes this study work," the bar for placement is higher than in a typical single-product codebase. Treat the placement decision as the most important architectural call of the session. Get it right before any code is written. If you are not confident about the right level, say so and ask the contributor. Do not guess.

### Step 1: Get a real description of the feature from the contributor

Before exploring anything, make sure you actually understand what the contributor wants to build. A one-line feature title is not enough. Ask the contributor to describe, in some detail:

- **What the feature does**: the observable behavior, end to end
- **Who it is for**: only their study, or potentially any researcher using REX
- **What triggers it**: a user action, a page event, a config flag, a schedule
- **What data it touches or produces**: what is read, what is written, what is sent where
- **Which sites or contexts it applies to**: one site, a category of sites, all sites
- **Why it cannot be done with existing modules**: what is missing today

If the contributor's description is vague ("add tracking for X", "support Y"), ask follow-up questions until you have a concrete picture. Do not move on to exploration with a fuzzy mental model; you will explore the wrong things.

### Step 2: Explore the repos to find the right level of generality

Once you have a real description, explore the repositories in depth before proposing where the feature belongs. Read the actual source files (`.mts` files in each module's `src/` directory), not just READMEs. READMEs are often minimal. The code is the authoritative record of what each module does.

**Check the [module map](https://docs.bric.digital/rex/module-map/) against what is actually cloned.** The contributor may only have cloned the repos they thought were relevant when they started. Based on the feature description, scan the module map for any modules that look like plausible homes, including ones the contributor did not initially consider, and **ask them to clone those repos** so you can read the source. It is perfectly fine, and often necessary, to say something like: "Based on what you described, `rex-page-events` and `rex-lists` could also be the right home for this. They are not in this directory. Could you clone them from `github.com/bric-digital/rex-page-events` and `github.com/bric-digital/rex-lists` so I can look at the source before deciding?" Do not make a placement call without having read the source of every plausible candidate.

**Suggested opening exploration prompt for the contributor to use, after they have described the feature:**

> Now that I have described the feature, please explore all the repositories in this directory in depth to determine: (1) which module is the right place for this feature, (2) at what level of the hierarchy it should live (aim for the highest level of generality that is still appropriate, without going so high that the module takes on responsibilities that do not belong to it), (3) whether any similar functionality already exists that we should build on or extend rather than duplicate, (4) whether any part of this feature is study-specific and belongs in my private extension repo instead of a public module. Read the actual source files, not just READMEs. Report your findings and propose an approach before we start implementation.

Once the right module and the right level of generality are clear, confirm both with the contributor before writing any code. If the contributor disagrees with your placement, take the disagreement seriously; they often know the domain better than the exploration can reveal.

---

## What REX Is

REX (Research EXtensions) is a modular browser extension framework for behavioral research. It is made up of independent TypeScript modules that are composed together into Chrome/Edge extensions for specific research studies.

You are helping a contributor modify one of these public modules. The modules live at `github.com/bric-digital/<module-name>`.

The full module map is at [docs.bric.digital/rex/module-map](https://docs.bric.digital/rex/module-map/). Read it to understand where functionality belongs before writing any code.

---

## The Public/Private Boundary

**This is the most important rule.** Public modules must be generic and reusable across any researcher's study.

Never put any of the following into a public module:
- Study-specific logic or participant flows
- Participant identifiers or study IDs
- Client names or organization names
- Hardcoded URLs specific to one study
- Any assumption that only makes sense for one research project

If the contributor's feature is study-specific, it belongs in their own extension repository, not in the public module. Ask the contributor before proceeding: "Would a researcher at a completely different university find this feature useful?" If no, redirect the changes to their private repo, which should be cloned into a sub-directory of the one you are working in.

---

## The Cardinal Rule: Never Bypass rex-core

Modules must never read from `chrome.storage.local` directly for keys that rex-core manages. Always use the message-passing API:

| What you need | Correct approach |
|---|---|
| The participant identifier | `chrome.runtime.sendMessage({ messageType: 'getIdentifier' })` |
| The extension configuration | `rexCorePlugin.fetchConfiguration()` |
| Set the identifier | `chrome.runtime.sendMessage({ messageType: 'setIdentifier', identifier: ... })` |
| Refresh config from server | `chrome.runtime.sendMessage({ messageType: 'refreshConfiguration' })` |

If you see code reading `chrome.storage.local.get('rexIdentifier')` or `chrome.storage.local.get('REXConfiguration')` directly, that is a bug. Replace it with the appropriate message call above.

### rex-core is also a shared library, not only a message broker

rex-core exports utility functions that other modules are expected to call directly: `sha256()` and related helpers live in `rex-core/src/common.mts`. Before adding a new npm dependency to any module, check whether rex-core (or another `rex-*` module already in the dependency graph) provides the capability.

Concretely, before running `npm install <something>`:

1. Grep `rex-core/src/` for the capability. Common categories that already live there or in sibling modules include crypto and hashing, registrable-domain extraction (`psl` is used in `rex-core`, `rex-history`, and `rex-lists`), storage access, configuration, and messaging.
2. If rex-core has the capability, use it. Do not vendor a parallel library into a downstream module.
3. If rex-core's version is missing the case you need (for example, a synchronous fallback or a plain-HTTP-safe path), **the correct move is to extend rex-core, not to add a parallel implementation in the calling module.** Surface the gap to the contributor and propose the rex-core change as part of the work.
4. Crypto specifically: do not bring crypto libraries into a module. Crypto belongs in rex-core. If rex-core's current crypto helper does not cover the case, that is a rex-core change. Never roll your own implementation in a downstream module, even for non-security uses like deterministic randomization.

The reason this rule is strict: every parallel implementation is a future maintenance burden, a divergence risk, and one more thing a reviewer has to keep in their head. A change that looks like "use a well-known library instead of hand-rolled code" can still be the wrong answer if the library duplicates a capability rex-core already owns.

---

## Module Conventions

### File structure
Each module has up to three TypeScript entry points, listed in the `exports` section of its `package.json`:
- `src/browser.mts`: injected into web pages as a content script (DOM access, page events)
- `src/extension.mts`: the extension's own pages
- `src/service-worker.mts`: the background service worker (Chrome API access, storage, config)

A module registers itself when an extension imports it. Source files use the `.mts` extension (TypeScript ES modules). All modules use `@bric/` scoped package names.

### Browser-context dependency budget

`browser.mts` can be injected into every page the participant visits. Anything imported there ships into the user's browsing context, runs under the page's CSP, and adds to the size of the injected bundle.

The default budget for new `browser.mts` dependencies is **zero**. Adding one requires:

1. **A specific reason it cannot run in `service-worker.mts` instead.** The service worker has full Chrome API access, no CSP constraints, and a single instance per browser rather than one per page. Most "I need to compute X" tasks belong there, with `chrome.runtime.sendMessage` carrying the request and response across the boundary.
2. **A check against the rule above** ("rex-core is also a shared library"). If the capability already exists somewhere in the `rex-*` graph, use it instead of adding a new dependency.
3. **A note in the PR description naming the dependency, its size, and the reason a service-worker round-trip was not viable.**

This applies to new dependencies, not to ones already present in the module.

### ES2022+ hazards: stay conservative
REX modules publish raw TypeScript source. Consumers compile that source through their own toolchain, which may not support ES2022+ features. **Do not use** these in module source:
- `new Error(msg, { cause: err })`; use the single-argument form instead
- `Array.prototype.at()`
- `Object.hasOwn()`
- `structuredClone()`
- Private class fields (`#foo`)
- Top-level `await`

If a linting rule pushes you toward one of these, use the single-arg form and add a comment:
```ts
// eslint-disable-next-line preserve-caught-error -- Error cause {} ctor arg is ES2022, not available in consumer toolchains
throw new Error(`Failed: ${error.message}`)
```

### Promise-based validation
All validation logic must return a `Promise`, not a synchronous boolean. Validation often grows to include async operations (server lookups, storage reads), and a synchronous API forces breaking changes later.

```ts
// correct
validateIdentifier(id: string): Promise<ValidationResult>

// wrong
validateIdentifier(id: string): boolean
```

### Timestamps
Never call a timestamp generator more than once for a single logical event. Capture it once and reuse it:

```ts
// correct
const now = Date.now()
record.createdAt = now
record.updatedAt = now

// wrong
record.createdAt = Date.now()
record.updatedAt = Date.now()
```

### Async/await
Always handle both success and error cases for async operations. Prefer `.catch()` and `.finally()` where the error handling is straightforward; prefer `try/catch` blocks for complex flows. Never leave a Promise unhandled.

### UI conventions (extension pages)
Extensions use Bootstrap 5. Always prefer Bootstrap utility classes over inline styles. Only use inline styles for values Bootstrap has no utility for (e.g. a specific `max-width` in pixels).

---

## Minimal Changes Policy

Changes must be the absolute minimum necessary. The contributor must be able to justify every changed line in a PR review.

- Do not refactor, reformat, or rename surrounding code
- Do not add comments, types, or documentation to code you did not change
- Do not add error handling for scenarios that cannot happen
- Do not add backwards-compatibility shims when you can simply change the code
- If you see a problem in surrounding code that is not part of the task, flag it to the contributor rather than fixing it silently, but do not ignore significant problems either

---

## Before Writing New Code

If you have not already done the exploratory pass described in "Starting a Session" above, do it now before continuing.

1. Confirm you are in the right module and at the right level of the hierarchy (see "Starting a Session").
2. Search the module's source for existing functionality that might serve the need.
3. Search other `rex-*` modules for utilities that already exist. Do not re-implement what is already there.
4. If you are unsure whether a new function or abstraction is warranted, flag the question to the contributor before writing it.
5. If the feature might belong in a new module rather than an existing one, check the criteria in [Getting Placement Right](https://docs.bric.digital/rex/contribute/getting-placement-right/#4-check-whether-a-new-module-is-warranted) before proceeding.

---

## Testing and Build

The contributor tests changes by loading a built extension in Chrome or Edge. The extension for testing is either `rex-demo` (the public demo extension) or the contributor's own extension repository, whichever they prefer. Ask which one they want to use if it is not obvious from the working directory. Module changes must be pushed to GitHub before they can be tested in an extension; local file paths in `package.json` are not allowed.

**To test a module change:**
1. Commit your changes to a feature branch in the module repo, and ask the contributor to push the branch to GitHub.
2. In the extension's `package.json` (either `rex-demo/package.json` or the contributor's own extension), update the module reference to point at that branch:
   ```json
   "@bric/rex-page-manipulation": "github:bric-digital/rex-page-manipulation#your-branch-name"
   ```
3. In the extension's directory, run:
   ```bash
   npm run clean-build:scrub
   npm cache clean --force
   rm -rf node_modules package-lock.json
   npm install
   npm run build
   ```
4. In Chrome or Edge, go to `chrome://extensions`, enable Developer Mode, click "Load unpacked", and select the extension's `dist/extension/` folder (e.g. `rex-demo/dist/extension/`).
5. Verify the change made it into the built bundle before asking the contributor to test.

**Never use local file paths in `package.json`:**
```json
// wrong
"@bric/rex-page-manipulation": "file:../rex-page-manipulation"

// correct
"@bric/rex-page-manipulation": "github:bric-digital/rex-page-manipulation#feature-branch"
```

---

## PR and Contribution Rules

**Skip this section if the contributor is on Path A or just experimenting.** It applies when they intend to open a public PR (Path B).

The [PR checklist](https://docs.bric.digital/rex/contribute/pr-checklist/) describes what a public PR needs. Your job is to make the contributor's PR satisfy it, not to re-derive it.

**Workflow:**
1. Fork the public module repo on GitHub
2. Create a branch for the change (`fix/...` or `feat/...`)
3. Make changes following all conventions above
4. Open a pull request from the fork back to the original repo
5. CLA Assistant will prompt for signature on the first PR; this is required for external contributors

**Commit messages:** One line, imperative verb, lowercase, no period. Examples:
```
add scroll depth to page events
fix url redirect rule not removed on disable
```

**AI use disclosure (mandatory in the PR body):** If you (the agent) wrote or rewrote any committed code, the contributor must disclose this in the PR description. Tell the contributor what to say: name the AI tool and the rough scope (e.g. "drafted the scroll depth tracking in `src/browser.mts`; contributor reviewed and modified"). By making that disclosure, the contributor is also confirming that they have reviewed every line, can explain the design choices in review, and have the right to contribute the code. Do not let them make it without actually having reviewed the code with you.

**Preserve comments by `audaciouscode`.** These comments often capture domain knowledge, historical decisions, or non-obvious constraints that are not recoverable from the surrounding code. Do not delete or rewrite them as part of unrelated changes. Check `git blame` if you are unsure of authorship. The only exception is when such a comment has become inaccurate because the code it describes has changed; in that case, flag it to the contributor rather than silently editing it.

### Interpreting review feedback

When a PR comes back with review comments, the failure mode to watch for is acting on the **literal** reading of a comment while missing the **structural** reading. Architectural advice ("use X instead of Y", "this belongs in module Z", "don't import A into B") almost always has both.

Before implementing a response to review feedback:

1. **Read each comment twice.** Once for the literal instruction, once for the architectural point underneath it. "Don't roll your own crypto" has a literal reading (swap to a library) and a structural reading (crypto belongs in rex-core as a layer boundary). These are different changes.
2. **If a comment has more than one plausible reading, write your interpretation back to the reviewer as a one-liner before implementing.** A single comment like "To make sure I have this right: you want me to (a) extend rex-core's `sha256()` to handle plain-HTTP pages, and (b) call it from `browser.mts` via the existing import, rather than vendor a new SHA library into this module. Is that what you had in mind?" costs nothing and prevents a rewrite.
3. **Treat reviewer sketches as the target design, not the floor.** If the reviewer drew a schema or named specific functions, that is the architecture they want. If your implementation has the same names but a different structure underneath, you have not addressed the comment.
4. **If the reviewer asked for a public API (registration, extension points, hooks), build the public interface, not the private dispatcher.** Punting the public API back to the reviewer with "I left it for you" is what the previous comment was already asking you not to do.
5. **Watch the size signal.** If your response to a review nudge grows the diff substantially rather than shrinking it, that is usually a sign the rework went the wrong direction. Stop and ask before pushing.

The cost of a clarifying question is one round-trip. The cost of a misread rework is the reviewer giving up and rewriting the PR themselves.

**Never commit:**
- Plan files or scratchpad documents (add them to `.gitignore`)
- `.env` files or files containing secrets
- Changes to `package.json` that point at local file paths

**Never push.** Suggest the commit message to the contributor and stop. The contributor decides when to push and when to submit the PR.

---

## What to Never Do

- `git push` yourself; the contributor pushes
- Read `chrome.storage.local.get('rexIdentifier')` directly; use the message API
- Read `chrome.storage.local.get('REXConfiguration')` directly; use `rexCorePlugin.fetchConfiguration()`
- Hardcode any domain name, study ID, participant ID, client name, or study-specific URL in module code
- Use `"file:../rex-core"` or any local file path in `package.json`
- Use ES2022+ features listed in the hazards section above
- Refactor or reformat code outside the scope of the task
- Commit plan files or `.env` files
- Make any change you cannot explain in one sentence to a PR reviewer
