diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index dde66fcf4..e276a9f0d 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -1,30 +1,14 @@
-name: Documentation site
+name: Documentation build
on:
- push:
- branches: [main]
- paths:
- - "docs/**"
- - "package.json"
- - "package-lock.json"
- - ".nvmrc"
- - ".github/workflows/pages.yml"
- pull_request:
- branches: [main]
- paths:
- - "docs/**"
- - "package.json"
- - "package-lock.json"
- - ".nvmrc"
- - ".github/workflows/pages.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
- group: pages-${{ github.event.pull_request.number || github.ref }}
- cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+ group: documentation-build-${{ github.ref }}
+ cancel-in-progress: true
env:
HUSKY: "0"
@@ -33,6 +17,7 @@ jobs:
build:
name: Build documentation
runs-on: ubuntu-latest
+ timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -41,23 +26,3 @@ jobs:
cache: npm
- run: npm ci
- run: npm run docs:build
- - uses: actions/upload-pages-artifact@v4
- if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
- with:
- path: docs/.vitepress/dist
-
- deploy:
- name: Publish documentation
- if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
- needs: build
- runs-on: ubuntu-latest
- permissions:
- pages: write
- id-token: write
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- steps:
- - uses: actions/configure-pages@v5
- - uses: actions/deploy-pages@v4
- id: deployment
diff --git a/.github/workflows/website-check.yml b/.github/workflows/website-check.yml
new file mode 100644
index 000000000..42257f867
--- /dev/null
+++ b/.github/workflows/website-check.yml
@@ -0,0 +1,28 @@
+name: Website checks
+
+on:
+ pull_request:
+ branches: [main]
+ paths:
+ - "website/**"
+ - "scripts/verify-website.py"
+ - ".github/workflows/**"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: website-check-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ check:
+ name: Website checks
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - name: Check out the repository
+ uses: actions/checkout@v4
+ - name: Check website links and publishing configuration
+ run: python3 scripts/verify-website.py
diff --git a/.github/workflows/website-pages.yml b/.github/workflows/website-pages.yml
index 3967a89e4..a6118bb6a 100644
--- a/.github/workflows/website-pages.yml
+++ b/.github/workflows/website-pages.yml
@@ -21,6 +21,8 @@ jobs:
steps:
- name: Check out the repository
uses: actions/checkout@v4
+ - name: Check website links and publishing configuration
+ run: python3 scripts/verify-website.py
- name: Configure Pages
uses: actions/configure-pages@v5
- name: Upload the website
diff --git a/.oxlintrc.json b/.oxlintrc.json
index 8ca5017b3..2da0945d6 100644
--- a/.oxlintrc.json
+++ b/.oxlintrc.json
@@ -28,7 +28,8 @@
{
"files": [
"scripts/electron-active-hook*.cjs",
- "tests/conformance/readiness/electron/*.js"
+ "tests/conformance/readiness/electron/*.js",
+ "website/public/examples/notes-electron/*.js"
],
"rules": {
"no-var-requires": "off"
diff --git a/scripts/verify-website.py b/scripts/verify-website.py
new file mode 100644
index 000000000..60e8e0b0f
--- /dev/null
+++ b/scripts/verify-website.py
@@ -0,0 +1,106 @@
+"""Check local website references and the sole GitHub Pages publisher."""
+
+from html.parser import HTMLParser
+from pathlib import Path
+import re
+import sys
+from urllib.parse import unquote, urlsplit
+import xml.etree.ElementTree as ET
+
+
+class HtmlReferences(HTMLParser):
+ """Collect document IDs and references without fetching external URLs."""
+
+ def __init__(self):
+ super().__init__()
+ self.ids = set()
+ self.references = []
+ self.duplicate_ids = []
+
+ def handle_starttag(self, tag, attrs):
+ attributes = dict(attrs)
+ identifier = attributes.get("id")
+ if identifier is not None:
+ if identifier in self.ids:
+ self.duplicate_ids.append(identifier)
+ self.ids.add(identifier)
+ for attribute in ("href", "src"):
+ reference = attributes.get(attribute)
+ if reference is not None:
+ self.references.append(reference)
+
+
+def check_site(site):
+ """Check assets, HTML fragment destinations and SVG syntax."""
+ pages = {}
+ errors = []
+ references = 0
+ for path in sorted(site.rglob("*.html")):
+ parser = HtmlReferences()
+ parser.feed(path.read_text(encoding="utf-8"))
+ pages[path] = parser
+ if site / "index.html" not in pages:
+ errors.append("website/public/index.html is missing")
+
+ for path, page in pages.items():
+ label = path.relative_to(site)
+ for identifier in page.duplicate_ids:
+ errors.append(f"{label}: duplicate ID {identifier!r}")
+ for reference in page.references:
+ url = urlsplit(reference)
+ if url.scheme or url.netloc:
+ continue
+ references += 1
+ if url.path.startswith("/"):
+ errors.append(f"{label}: use a relative site reference: {reference}")
+ continue
+ target = (path.parent / unquote(url.path)).resolve() if url.path else path
+ if not target.is_relative_to(site):
+ errors.append(f"{label}: reference leaves the website: {reference}")
+ continue
+ if target.is_dir():
+ target /= "index.html"
+ if not target.is_file():
+ errors.append(f"{label}: missing local target: {reference}")
+ elif target in pages and url.fragment and unquote(url.fragment) not in pages[target].ids:
+ errors.append(f"{label}: missing HTML fragment: {reference}")
+
+ for path in sorted(site.rglob("*.svg")):
+ try:
+ ET.parse(path)
+ except ET.ParseError as error:
+ errors.append(f"{path.relative_to(site)}: invalid SVG XML: {error}")
+ return len(pages), references, errors
+
+
+def check_publisher(root):
+ """Detect a second Pages action that could replace the website."""
+ expected = root / ".github/workflows/website-pages.yml"
+ workflows = root / ".github/workflows"
+ deploy_action = re.compile(r"^\s*-?\s*uses:\s*['\"]?actions/deploy-pages@", re.MULTILINE)
+ publishers = [
+ path
+ for path in sorted(workflows.iterdir())
+ if path.suffix in (".yml", ".yaml") and deploy_action.search(path.read_text(encoding="utf-8"))
+ ]
+ if publishers != [expected]:
+ actual = ", ".join(str(path.relative_to(root)) for path in publishers) or "none"
+ return [f"Pages must have one publisher, website-pages.yml; found: {actual}"]
+ return []
+
+
+def main():
+ """Run the same checks locally, on website PRs and before publishing."""
+ root = Path(__file__).resolve().parent.parent
+ pages, references, errors = check_site(root / "website/public")
+ errors.extend(check_publisher(root))
+ if errors:
+ print("Website checks failed:\n" + "\n".join(errors), file=sys.stderr)
+ return 1
+ print(f"Verified {pages} HTML pages, {references} local references and SVG XML.")
+ print("website-pages.yml is the sole GitHub Pages publisher.")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/website/README.md b/website/README.md
index edb950181..434cc2db2 100644
--- a/website/README.md
+++ b/website/README.md
@@ -1,6 +1,6 @@
# REA website
-An English static website with explanatory figures and a DX-Ball investigation.
+An English static website with explanatory figures, worked guides and a DX-Ball investigation.
The public files are in `website/public/`. The site uses HTML, CSS and a small
script for copying code and following the assembly-to-C comparison; it has no
build step or npm dependencies.
@@ -20,6 +20,8 @@ Open . Refresh the browser after editing a file.
- `public/index.html`: product introduction and DX-Ball overview.
- `public/showcase/dx-ball/index.html`: sound-pan investigation and project status.
- `public/get-started/index.html`: agent setup, first CLI result and provider guides.
+- `public/guides/`: a guide hub and native, JavaScript/Electron and browser examples.
+- `public/examples/`: downloadable Electron source and an interactive Notes browser app.
- `public/assets/`: shared styles, interactions, favicon and explanatory figures.
Navigation and assets use relative paths, so the same files work at the local
@@ -29,7 +31,18 @@ root and a GitHub Pages project path such as `/rea/`.
Keep the copy direct and specific. Explain the task and the result before listing
tool names. Setup commands and runtime requirements should match the released
-package.
+package. Keep the core learning path inside the site. Source, issue and evidence
+reference links may point to the corresponding repositories.
+
+[evidence/guide-examples.md](evidence/guide-examples.md) records the published
+REA package, example digests and observed results behind the guides. The Notes
+Electron fixture is for static analysis; Electron is not a prerequisite for
+following that example. The separate Notes browser app runs in the local preview
+and constructs a CSV download after fetching its JSON data.
+
+Agent terminals show example prompts, not transcripts of previous
+investigations. A short cursor animation starts when the prompt enters view and
+respects reduced-motion preferences. All prompt text is present without JavaScript.
DX-Ball figures and findings refer to the linked 7 October 2026 checkpoint,
commit `a55dca27ec0a07018c1b2c95ae2be027f7d8c3c4`. Update those links and figures
@@ -41,23 +54,47 @@ Evidence. [evidence/dx-ball-sound-pan.md](evidence/dx-ball-sound-pan.md) records
their provenance and the scope of the validation claims. The original executable
and complete private Evidence records are not website assets.
-The diagrams are maintained as SVG source. Initial layout references were
+The homepage and DX-Ball overview diagrams are maintained as SVG source. Initial layout references were
created with the built-in image generation tool; [figures.md](figures.md) retains
their prompts and the current asset notes. Figures provide an overview;
REA requests, assembly and C remain selectable HTML text. On narrow
screens, the diagrams scroll horizontally and can also be opened at full size.
+The worked guides use semantic HTML flows that stack vertically on smaller
+screens. The Electron teaching example uses CommonJS, matching its preload
+code; the scoped lint override admits `require` only in that example directory.
## GitHub Pages
`.github/workflows/website-pages.yml` prepares and deploys only `website/public`.
-It is manually triggered and restricted to `main`; ordinary pushes and pull
-requests do not publish the site.
+It is the sole Pages publisher, manually triggered and restricted to `main`;
+ordinary pushes and pull requests do not publish the site.
+
+Website checks run only for pull requests that change `website/`, the verification
+script or workflow definitions. They check local links, HTML fragments, SVG XML
+and the single Pages publisher without installing npm dependencies. The same
+checks run before each manual deployment. You can also run them locally:
+
+```sh
+python3 scripts/verify-website.py
+```
+
+`.github/workflows/pages.yml` is a separate, manual-only VitePress build. It
+has no Pages artifact upload, deployment job or deployment permissions. This
+prevents documentation updates from replacing the public website. The legacy
+publishing job has been removed, so enabling this build workflow cannot publish
+the old site.
After the site is approved and merged, select **GitHub Actions** under the
repository's **Settings → Pages → Build and deployment**. Then run **Publish REA
website** from the Actions tab on `main`. The workflow uses the `github-pages`
environment and the official Pages actions.
+The equivalent CLI command is:
+
+```sh
+gh workflow run website-pages.yml --repo morluto/rea --ref main
+```
+
Local development does not change Pages settings or run the deployment workflow.
Any environment protection rules are configured separately when publication is
approved.
diff --git a/website/evidence/guide-examples.md b/website/evidence/guide-examples.md
new file mode 100644
index 000000000..cae290a8c
--- /dev/null
+++ b/website/evidence/guide-examples.md
@@ -0,0 +1,78 @@
+# Guide example evidence
+
+The native guide reuses the recorded DX-Ball observations in
+[dx-ball-sound-pan.md](dx-ball-sound-pan.md). No new native-provider claim is
+introduced by this iteration.
+
+## Notes Electron example
+
+This six-file, source-owned teaching fixture is in
+`website/public/examples/notes-electron/`. It is a static analysis target, not
+an installed Electron distribution. Its graph was obtained using the released
+`rea-agents` 4.1.0 package, Node.js 24.18.0 on Linux x64, on 7 October 2026:
+
+```sh
+rea analyze-javascript-application \
+ /absolute/path/to/website/public/examples/notes-electron --json
+```
+
+- Evidence ID: `ev_a7e68cbdbd52ee15a959047774d1164d385e3e5ceb704084395639d97fee093d`.
+- Root artifact SHA-256: `dae5292f341b4f0dfd5be0846d4cfa0d4821f3e062e970cc1cd568c18c3fb37c`.
+- Four JavaScript files parsed, zero parse failures.
+- One exposed API member, one literal IPC channel, one renderer invocation,
+ one main-process handler, one paired invocation, zero ambiguous invocations.
+- The guide's result excerpt comes from `normalized_result.summary.ipc`.
+- The `exposes`, `invokes` and `handles` graph edges retain their static
+ inference authority and source locations. The guide's end-to-end explanation
+ also uses the visible source in `renderer.js`, `main.js` and `csv.js`; it does
+ not claim that the complete button-to-file path was observed executing.
+
+The source was formatted before this result was recorded. Re-run analysis
+when the example files change.
+
+## Notes browser example
+
+The three-file browser app is in `website/public/examples/notes-web/`.
+A dedicated headless Chrome profile opened it at
+`http://127.0.0.1:4173/examples/notes-web/`. REA 4.1.0 passively inspected that
+existing page with an 8-second window, accessibility text and JSON body shapes.
+A separate Playwright controller clicked the button during the capture and
+saved the resulting CSV; REA did not perform the click.
+
+- Evidence ID: `ev_7df243651c257e41f525e4225cbca5427467cf1e7b4b5b624ab41ab5717fe9c6`.
+- Window: `2026-10-07T09:38:43.492Z` to
+ `2026-10-07T09:38:51.531Z`.
+- One observed `GET` to `/examples/notes-web/notes.json`.
+- Status `200`, MIME type `application/json`, script initiator `export.js`.
+- Body shape: `/notes` array, `/notes/*/id` number, `/notes/*/title` string.
+- The independent browser run downloaded `notes.csv` and displayed
+ “Exported 2 notes to notes.csv.” CSV construction is explained from source,
+ not inferred solely from the request capture.
+
+The CLI guide uses a 10-second window to give the reader time to perform the
+action. Capture only covers requests after attachment. The example request
+path differs under a project prefix such as `/rea/`, while the relative fetch
+and export behavior stay the same.
+
+The guide's follow-up CLI commands were also exercised against the real page:
+`list-browser-targets` returned its exact page target, and
+`inspect-web-page --include-script-sources` returned the loaded `export.js`
+source. Discovery Evidence: `ev_31abb33e7707d47625ac909ab9256c705d03a31ee6bcd643fa96730e72469162`. Script capture
+Evidence: `ev_aeb777df405c09dc1bbdd4c185a5af2452abaa3cb493683c296c5afa97efbc11`.
+
+Full local outputs and the browser verification harness are under
+`/tmp/rea-website-guides/` in the development environment, not website assets.
+
+## Example source digests
+
+| File | SHA-256 |
+| -------------------------------------- | ------------------------------------------------------------------ |
+| `examples/notes-electron/csv.js` | `6875aeecc1d3e6c1ff5b888a199c26b377d812d5b10e5fb4025e192a206b0018` |
+| `examples/notes-electron/index.html` | `a31df9b29eb9d479f913c7806364609019eedd1d19cda386cc24706d5068082d` |
+| `examples/notes-electron/main.js` | `cc5b00911bfe428754d94ca40e8c0f354cfaae86f716873423aa5a62b9e0379a` |
+| `examples/notes-electron/package.json` | `bfef2a7cba39a989715f56e304ca2578807159f28bdb040b75893bb341caaee7` |
+| `examples/notes-electron/preload.js` | `3cb814f2a0822c2fef437ba722374a2eabf6c6795905f41656d9c33fe8d5d9c4` |
+| `examples/notes-electron/renderer.js` | `b24aa22ed969c0849dd9da1102935a6b36b96bae46870234bdea10039dd9a5a0` |
+| `examples/notes-web/export.js` | `66f5c2501bfca72eb743a1fffbdce9a58d295e69ca66f19562e9b0fa9fc189a6` |
+| `examples/notes-web/index.html` | `97bb4729dcd838de8adae7a00eb49df0dbe8a7b73becc1c7d2a6662825013064` |
+| `examples/notes-web/notes.json` | `9fc1155e251cfbed09dadca6b195507072fffe3f602564812d644ab27c422282` |
diff --git a/website/public/assets/main.js b/website/public/assets/main.js
index 45e6adc98..00efa23a3 100644
--- a/website/public/assets/main.js
+++ b/website/public/assets/main.js
@@ -55,3 +55,22 @@ function initializeStepComparisons() {
}
initializeStepComparisons();
+
+function initializeAgentTerminals() {
+ if (!("IntersectionObserver" in window)) return;
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (!entry.isIntersecting) return;
+ entry.target.classList.add("is-visible");
+ observer.unobserve(entry.target);
+ });
+ },
+ { threshold: 0.25 },
+ );
+ document.querySelectorAll(".agent-terminal").forEach((terminal) => {
+ observer.observe(terminal);
+ });
+}
+
+initializeAgentTerminals();
diff --git a/website/public/assets/styles.css b/website/public/assets/styles.css
index fde289bf7..f27f68211 100644
--- a/website/public/assets/styles.css
+++ b/website/public/assets/styles.css
@@ -707,6 +707,209 @@ pre code {
.small-heading {
margin-top: 1.8rem;
}
+.agent-terminal {
+ margin-block: 1.5rem;
+ overflow: hidden;
+ border: 1px solid #303d4a;
+ border-radius: 6px;
+ background: #202933;
+ color: #f1f4f8;
+}
+.terminal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ padding: 0.65rem 1rem;
+ border-bottom: 1px solid #384551;
+ color: #bdc8d4;
+ font-size: 0.8rem;
+}
+.terminal-title {
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+}
+.terminal-dots {
+ display: flex;
+ gap: 4px;
+}
+.terminal-dots i {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: #81909e;
+}
+.agent-terminal .copy-button {
+ color: #d6dee7;
+}
+.agent-terminal .copy-button:hover {
+ background: #384551;
+ color: white;
+}
+.agent-terminal .terminal-prompt {
+ margin: 0;
+ padding: 1.4rem;
+ color: #f1f4f8;
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
+ font-size: 0.9rem;
+ line-height: 1.9;
+}
+.prompt-symbol {
+ color: #9bbfe5;
+ margin-right: 0.65rem;
+}
+.terminal-cursor {
+ display: inline-block;
+ width: 0.5em;
+ height: 1em;
+ margin-left: 0.4em;
+ vertical-align: -0.12em;
+ background: #9bbfe5;
+}
+.agent-terminal.is-visible .terminal-cursor {
+ animation: terminal-blink 1s step-end 6;
+}
+@keyframes terminal-blink {
+ 50% {
+ opacity: 0;
+ }
+}
+.workflow-figure {
+ margin: 1.7rem 0;
+ border: 1px solid var(--line);
+ border-radius: 5px;
+ padding: 1.4rem;
+ background: #fafbfc;
+}
+.figure-title {
+ margin: 0 0 1.3rem;
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: var(--ink);
+}
+.flow-steps {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 1.9rem;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+}
+.flow-steps li {
+ position: relative;
+ margin: 0;
+ min-width: 0;
+}
+.flow-steps li:not(:last-child)::after {
+ content: "→";
+ position: absolute;
+ right: -1.4rem;
+ top: 2.1rem;
+ color: #8191a2;
+}
+.flow-label {
+ display: block;
+ margin-bottom: 0.65rem;
+ color: var(--muted);
+ font-size: 0.75rem;
+}
+.flow-node {
+ display: block;
+ padding: 0.8rem;
+ background: white;
+ border: 1px solid #ced9e5;
+ border-radius: 4px;
+ color: var(--accent);
+ font-size: 0.875rem;
+ line-height: 1.5;
+ font-weight: 500;
+}
+.flow-note {
+ display: block;
+ margin-top: 0.65rem;
+ font-size: 0.8rem;
+ line-height: 1.55;
+ color: var(--muted);
+}
+.flow-result {
+ margin: 1.3rem 0 0;
+ padding: 0.9rem 1rem;
+ border-left: 2px solid var(--accent);
+ background: #edf2f8;
+ color: var(--ink);
+ font-size: 0.9rem;
+}
+.workflow-figure figcaption {
+ margin-top: 1rem;
+ color: var(--muted);
+ font-size: 0.8rem;
+}
+.guide-index {
+ padding-bottom: 3rem;
+}
+.guide-entry {
+ display: grid;
+ grid-template-columns: 160px minmax(0, 1fr);
+ gap: 2.5rem;
+ padding-block: 2.5rem;
+ border-top: 1px solid var(--line);
+}
+.guide-entry .eyebrow {
+ margin: 0;
+}
+.guide-entry h2 {
+ font-size: 1.75rem;
+}
+.guide-entry p {
+ max-width: 660px;
+ color: var(--muted);
+}
+.guide-route {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ align-items: center;
+ margin-bottom: 1.25rem;
+ font-size: 0.85rem;
+ color: var(--muted);
+}
+.guide-route span {
+ padding: 0.2rem 0.55rem;
+ background: var(--soft);
+ border-radius: 3px;
+ color: #414b55;
+}
+.example-files {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.65rem 1.25rem;
+ padding: 0;
+ list-style: none;
+ font-size: 0.9rem;
+}
+.example-files li + li {
+ margin: 0;
+}
+.example-app {
+ max-width: 720px;
+ padding-block: 3.5rem;
+}
+.example-app h1 {
+ font-size: 3rem;
+}
+.example-notes {
+ padding: 0;
+ list-style: none;
+ margin-block: 2rem;
+}
+.example-notes li {
+ padding-block: 0.8rem;
+ border-bottom: 1px solid var(--line);
+}
+.example-status {
+ margin-block: 1.5rem 1rem;
+}
.sr-only {
position: absolute;
width: 1px;
@@ -737,6 +940,17 @@ pre code {
.rea-query-pair {
grid-template-columns: 1fr;
}
+ .flow-steps {
+ grid-template-columns: 1fr;
+ gap: 2rem;
+ }
+ .flow-steps li:not(:last-child)::after {
+ content: "↓";
+ top: auto;
+ bottom: -1.7rem;
+ right: auto;
+ left: 0.85rem;
+ }
}
@media (max-width: 640px) {
@@ -827,6 +1041,13 @@ pre code {
grid-template-columns: 1fr;
gap: 0.25rem;
}
+ .guide-entry {
+ grid-template-columns: 1fr;
+ gap: 0.7rem;
+ }
+ .workflow-figure {
+ padding: 1rem;
+ }
.step-selector {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
@@ -839,6 +1060,12 @@ pre code {
}
}
+@media (prefers-reduced-motion: reduce) {
+ .agent-terminal.is-visible .terminal-cursor {
+ animation: none;
+ }
+}
+
@media (max-width: 360px) {
.header-inner {
flex-wrap: wrap;
diff --git a/website/public/examples/notes-electron/csv.js b/website/public/examples/notes-electron/csv.js
new file mode 100644
index 000000000..3ebfd4cc6
--- /dev/null
+++ b/website/public/examples/notes-electron/csv.js
@@ -0,0 +1,11 @@
+function toCsv(notes) {
+ const quote = (value) => `"${String(value).replaceAll('"', '""')}"`;
+ return (
+ [
+ "id,title",
+ ...notes.map((note) => `${note.id},${quote(note.title)}`),
+ ].join("\n") + "\n"
+ );
+}
+
+module.exports = { toCsv };
diff --git a/website/public/examples/notes-electron/index.html b/website/public/examples/notes-electron/index.html
new file mode 100644
index 000000000..fc4e3a62d
--- /dev/null
+++ b/website/public/examples/notes-electron/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ Notes — Electron analysis example
+
+
+
+ Notes
+ Export notes
+
+
+
diff --git a/website/public/examples/notes-electron/main.js b/website/public/examples/notes-electron/main.js
new file mode 100644
index 000000000..6cfa662e7
--- /dev/null
+++ b/website/public/examples/notes-electron/main.js
@@ -0,0 +1,19 @@
+const { app, BrowserWindow, ipcMain } = require("electron");
+const { writeFileSync } = require("node:fs");
+const path = require("node:path");
+const { toCsv } = require("./csv.js");
+
+const notes = [{ id: 1, title: "First note" }];
+
+ipcMain.handle("notes:export", () => {
+ const destination = path.join(app.getPath("downloads"), "notes.csv");
+ writeFileSync(destination, toCsv(notes), "utf8");
+ return destination;
+});
+
+app.whenReady().then(() => {
+ const window = new BrowserWindow({
+ webPreferences: { preload: path.join(__dirname, "preload.js") },
+ });
+ window.loadFile("index.html");
+});
diff --git a/website/public/examples/notes-electron/package.json b/website/public/examples/notes-electron/package.json
new file mode 100644
index 000000000..3840d8f31
--- /dev/null
+++ b/website/public/examples/notes-electron/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "rea-notes-example",
+ "version": "1.0.0",
+ "private": true,
+ "main": "main.js"
+}
diff --git a/website/public/examples/notes-electron/preload.js b/website/public/examples/notes-electron/preload.js
new file mode 100644
index 000000000..9c0aed503
--- /dev/null
+++ b/website/public/examples/notes-electron/preload.js
@@ -0,0 +1,5 @@
+const { contextBridge, ipcRenderer } = require("electron");
+
+contextBridge.exposeInMainWorld("notes", {
+ exportCsv: () => ipcRenderer.invoke("notes:export"),
+});
diff --git a/website/public/examples/notes-electron/renderer.js b/website/public/examples/notes-electron/renderer.js
new file mode 100644
index 000000000..50feeb443
--- /dev/null
+++ b/website/public/examples/notes-electron/renderer.js
@@ -0,0 +1,4 @@
+document.querySelector("#export").addEventListener("click", async () => {
+ const destination = await window.notes.exportCsv();
+ document.querySelector("#status").textContent = `Saved to ${destination}`;
+});
diff --git a/website/public/examples/notes-web/export.js b/website/public/examples/notes-web/export.js
new file mode 100644
index 000000000..8b1d4d0c5
--- /dev/null
+++ b/website/public/examples/notes-web/export.js
@@ -0,0 +1,24 @@
+document.querySelector("#export").addEventListener("click", async () => {
+ const status = document.querySelector("#status");
+ try {
+ const response = await fetch("./notes.json");
+ if (!response.ok)
+ throw new Error(`Notes request failed: ${response.status}`);
+ const { notes } = await response.json();
+ const quote = (value) => `"${String(value).replaceAll('"', '""')}"`;
+ const csv =
+ [
+ "id,title",
+ ...notes.map((note) => `${note.id},${quote(note.title)}`),
+ ].join("\n") + "\n";
+ const url = URL.createObjectURL(new Blob([csv], { type: "text/csv" }));
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = "notes.csv";
+ link.click();
+ window.setTimeout(() => URL.revokeObjectURL(url), 1000);
+ status.textContent = `Exported ${notes.length} notes to notes.csv.`;
+ } catch (error) {
+ status.textContent = error.message;
+ }
+});
diff --git a/website/public/examples/notes-web/index.html b/website/public/examples/notes-web/index.html
new file mode 100644
index 000000000..00d2c80b8
--- /dev/null
+++ b/website/public/examples/notes-web/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+ Notes — REA browser example
+
+
+
+
+
+
+
+ Browser observation guide / Example
+
+ A small app to inspect
+ Notes
+
+ Two notes. One CSV export. Observe what happens when you click.
+
+
+ First note
+ Follow the export
+
+ Export notes
+
+ Ready to export.
+
+
+ This example loads notes.json and creates a CSV in your
+ browser.
+
+
+
+
diff --git a/website/public/examples/notes-web/notes.json b/website/public/examples/notes-web/notes.json
new file mode 100644
index 000000000..bb67dc2d3
--- /dev/null
+++ b/website/public/examples/notes-web/notes.json
@@ -0,0 +1,6 @@
+{
+ "notes": [
+ { "id": 1, "title": "First note" },
+ { "id": 2, "title": "Follow the export" }
+ ]
+}
diff --git a/website/public/get-started/index.html b/website/public/get-started/index.html
index 26ba6a21f..0541e21c4 100644
--- a/website/public/get-started/index.html
+++ b/website/public/get-started/index.html
@@ -24,7 +24,7 @@
DX-Ball Get started Docs Guides GitHub
@@ -39,10 +39,6 @@
You'll need Node.js 22 (22.19+), 24 (24.11+) or 26+, and npm.
- Full installation guide
@@ -59,11 +55,9 @@
With your agent
- Run setup to connect REA to Claude Code, Codex, Cursor or another
- supported client .
+ Run setup to connect REA to your coding agent. Client choices
+ include Claude Code, Codex, Cursor, Gemini CLI, Windsurf and other
+ supported MCP clients.
- Continue with the JavaScript application guide
@@ -164,47 +157,111 @@
Working with native binaries
- Choose a deep-analysis provider for your target and host. If you
- already use one of these tools, start with its connection guide.
+ REA connects your agent to a local analysis provider. Choose the
+ tool you already use, configure its connection, then ask about
+ your executable or library.
Ghidra
- Use an existing Ghidra installation and JDK. Setup checks
- their paths and records them for your selected clients.
+ Use an existing Ghidra 12.1.x installation and a 64-bit JDK
+ 21 or newer on Linux x64 or macOS x64/arm64. Set the paths
+ before running setup:
-
Ghidra setup and supported hosts
+
+ Connect your Ghidra installation
+
+
+
export GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra_12.1.4_PUBLIC
+export JAVA_HOME=/absolute/path/to/jdk-21
+npx -y rea-agents@latest doctor --provider ghidra --json
+npx rea-agents@latest setup
+
+
+ Replace both paths with your existing installations. Setup
+ checks them and records valid settings for your selected
+ client. On macOS, use a Ghidra package with the native
+ decompiler matching your host architecture.
+
+
Hopper
- Connect Hopper on macOS or Linux. Setup can also install it
- after you review and approve the installation plan.
+ Connect Hopper on macOS or Linux. Run the setup command
+ above; it detects an existing installation or offers an
+ installation plan for your approval.
+
+
+ On macOS, complete Hopper's first-run choice of demo mode or
+ license activation before an unattended analysis.
-
Hopper installation
IDA
- Reuse an existing IDA MCP registration for read-only
- analysis through an attached GUI or headless database.
+ The verified IDA setups use Windows, with an existing MCP
+ registration for an attached GUI or a headless database.
+ Linux and macOS headless analysis remains unverified.
-
IDA connection guide
+
+ Connect an existing IDA MCP registration
+
+ Keep the upstream server's working command,
+ args and optional env in a JSON
+ file. An existing mcpServers object with an
+ ida-pro-mcp entry is also accepted. Point REA
+ at that file before setup:
+
+
+
+
$env:REA_IDA_MCP_CONFIG = "C:/analysis/ida-mcp.json"
+$env:REA_ANALYSIS_PROVIDER = "ida"
+npx -y rea-agents@latest doctor --provider ida --json
+npx rea-agents@latest setup
+
+
+ The attached profile uses upstream legacy 1.4.0 tools with
+ the original binary already open in IDA. The headless
+ profile uses the upstream database-supervisor API and a
+ registration with "mode": "headless"; real
+ verification covers an IDA 9.3 Windows x64 supervisor.
+ Doctor checks the registration without starting IDA.
+
+
+ Give REA the original executable path, with
+ --provider ida in a CLI query. For an agent,
+ include REA_IDA_MCP_CONFIG in the REA
+ registration's environment and select IDA when opening the
+ binary.
+
+
@@ -228,8 +285,13 @@
Replace main with the function name or address you
- want to inspect. The first query imports and analyzes the target,
- so it can take longer than later requests.
+ want to inspect. Each CLI invocation imports and analyzes the
+ target in its own session. Your agent's MCP queries reuse the
+ imported binary while that session is open.
+
+
+ Follow a native analysis example
+ to see how the function result leads to a recovered calculation.
@@ -273,12 +335,10 @@
The report gives the failed check and a suggested next step.
- Installation and troubleshooting Choose an investigation guideClient configuration, versions and provider
- requirements. Worked examples for a binary, an Electron app and a
+ browser.
diff --git a/website/public/guides/browser/index.html b/website/public/guides/browser/index.html
new file mode 100644
index 000000000..8a1a6cb7f
--- /dev/null
+++ b/website/public/guides/browser/index.html
@@ -0,0 +1,339 @@
+
+
+
+
+
+ Browser observation — REA
+
+
+
+
+
+
+
+ Skip to content
+
+
+
+
+ Home / Guides / Browser
+ observation
+
+
See what happens when you click.
+
+ Use a small browser app to connect an action, its network request and
+ the JavaScript that builds a download.
+
+
+
+
+ In this guide
+
+
+
+
+ What happens when you click Export?
+
+ The
+ Notes browser example has
+ an Export notes button. Clicking it loads
+ notes.json, formats the two notes as CSV, and starts
+ a download.
+
+
+ We want to see which request the click makes, then use the script
+ to understand how the response becomes a file.
+
+
+ One action, captured while it happens
+
+
+ 01 · You Click Export notes Perform the action in your browser while the capture is
+ running.
+
+
+ 02 · REA Record the request Capture the request URL, response status and JSON structure
+ from the selected page.
+
+
+ 03 · Your agent Read the export code Connect the observed request to
+ fetch("./notes.json") in
+ export.js.
+
+
+
+ GET notes.json → 200 → notes[] → CSV
+
+
+ The request and response metadata come from the capture. CSV
+ construction is explained by reading the script.
+
+
+
+
+ Open an observable browser
+
+ Start a Chrome-family browser with a dedicated profile and a local
+ debugging port. On Linux, for this site's local preview:
+
+
+
+
google-chrome \
+ --remote-debugging-port=9222 \
+ --user-data-dir=/tmp/rea-notes-browser \
+ http://127.0.0.1:4173/examples/notes-web/
+
+
+ Use your installed browser's executable path on macOS or Windows.
+ The page URL should point to your local preview or
+ the hosted Notes example .
+
+
+ REA connects to this existing browser. You keep control of the
+ page, perform the action, and close the browser when finished.
+
+
+
+ Ask your agent to watch one action
+
+
+
+ › Use REA to inspect this page while I click Export notes. What
+ request does it make, and how is the CSV built?
+
+
+
+ Example prompt. Give your agent the debugging endpoint,
+ http://127.0.0.1:9222, and the page URL. Let it start
+ the capture before you click.
+
+
+ Your agent discovers the page target, starts an observation
+ window, and reads the resulting evidence. It can then inspect the
+ script behind the export.
+
+
+
+ Or capture from the terminal
+ Find the page's target ID:
+
+
+
npx -y rea-agents@latest list-browser-targets \
+ http://127.0.0.1:9222 --json
+
+
+ Choose the target with the Notes example URL. Replace
+ TARGET_ID below with its ID, then run:
+
+
+
+
npx -y rea-agents@latest inspect-web-page \
+ http://127.0.0.1:9222 TARGET_ID \
+ --observation-ms 10000 \
+ --include-accessibility-text \
+ --include-json-body-shapes --json
+
+
+ Let the command connect for a few seconds, then click
+ Export notes in that browser. The result arrives
+ after the observation window ends.
+
+
+ The captured result includes the selected page, network activity
+ during that window, and the shape of the JSON response. Earlier
+ requests are outside this capture.
+
+
+
+ Read the result, then follow the code
+
+
+
+ Recorded with REA 4.1.0 on the local Notes example
+
+
+
+ Captured fact
+ Value
+
+
+
+
+ Request
+ GET /examples/notes-web/notes.json
+
+
+ Response
+ 200 · application/json
+
+
+ Request initiator
+ export.js · script
+
+
+ Response structure
+
+ notes[] with id and
+ title fields
+
+
+
+
+
+
+
+
npx -y rea-agents@latest inspect-web-page \
+ http://127.0.0.1:9222 TARGET_ID \
+ --include-script-sources --json
+
+
+ The request identifies the data source, and REA records
+ export.js as its initiator. Read the captured script
+ source to see how the data is used:
+
+
+
+
const response = await fetch("./notes.json");
+const { notes } = await response.json();
+// Format each note as a CSV row.
+const url = URL.createObjectURL(new Blob([csv], { type: "text/csv" }));
+link.href = url;
+link.download = "notes.csv";
+link.click();
+
+
+ The download is constructed in the page using the JSON response.
+ The CSV formatting step comes from the script; the network capture
+ establishes that this click requested notes.json.
+
+
+ Open the example ·
+ Download export.js
+ ·
+ Download notes.json
+
+
+
+ Find the script that requests notes.json. How does
+ it escape titles containing commas or quotation marks?
+
+
+
+ For an installed JavaScript or Electron app, continue with the
+ application workflow to follow files
+ and process boundaries.
+
+
+
+
+
+
+
+
+
diff --git a/website/public/guides/index.html b/website/public/guides/index.html
new file mode 100644
index 000000000..8a81f7ade
--- /dev/null
+++ b/website/public/guides/index.html
@@ -0,0 +1,119 @@
+
+
+
+
+
+ Guides — REA
+
+
+
+
+
+
+
+ Skip to content
+
+
+
+
Home / Guides
+
Start with an example.
+
+ Follow an example from the first prompt to the evidence. Then use the
+ same approach with your own app.
+
+
+
+
+ Native binaries
+
+
Recover a sound-pan calculation
+
+ Start with a Windows game executable. Inspect instructions, follow
+ a caller, read numeric constants and check the recovered C.
+
+
+ Instructions →
+ Caller →
+ Constants →
+ C function
+
+
Native analysis guide
+
+
+
+ JavaScript & Electron
+
+
Follow a CSV export
+
+ A small Notes app spreads one feature across the renderer, preload
+ and main process. Use REA to connect the API, IPC channel and file
+ writer.
+
+
+ Button →
+ Preload API →
+ IPC → CSV file
+
+
Application workflows
+
+
+
+ Browser behavior
+
+
Observe a click
+
+ Open a working Notes example, capture an export request and find
+ the code that turns its response into a download.
+
+
+ Your click →
+ Captured request →
+ Export script
+
+
Browser observation guide
+
+
+
+ First investigation
+
+
Connect your agent, then ask.
+
+ Each guide starts with a plain-language question and follows a
+ concrete example. If you haven't connected REA yet, setup takes
+ you through the client and provider choices.
+
+
Get started with REA
+
+
+
+
+
+
+
+
diff --git a/website/public/guides/javascript/index.html b/website/public/guides/javascript/index.html
new file mode 100644
index 000000000..8cbedb458
--- /dev/null
+++ b/website/public/guides/javascript/index.html
@@ -0,0 +1,315 @@
+
+
+
+
+
+ JavaScript & Electron — REA
+
+
+
+
+
+
+
+ Skip to content
+
+
+
+
+ Home / Guides / JavaScript
+ & Electron
+
+
Follow a feature across the app.
+
+ Use a CSV export to see how REA connects JavaScript files, exposed
+ APIs and Electron process boundaries.
+
+
+
+
+ In this guide
+
+
+
+
+ Where does “Export notes” go?
+
+ This guide uses a small Electron app. Clicking
+ Export notes calls an API in the page, crosses
+ into the main process, and writes a CSV. The behavior is spread
+ across several files.
+
+
+ A feature across two processes
+
+
+ Renderer → preload notes.exportCsv()renderer.js calls the API exposed by
+ preload.js.
+
+
+ Between processes notes:exportThe preload invokes an IPC channel: a named message to the
+ main process.
+
+
+ Main process An export handler main.js handles that channel and calls the CSV
+ writer.
+
+
+
+ csv.js formats the notes →
+ main.js writes downloads/notes.csv.
+
+
+ REA matches the literal IPC call to its handler and identifies
+ the exposed API. The path is inferred from code; a runtime
+ capture can check what executes.
+
+
+
+
+ Give your agent one feature
+
+ Connect your agent to REA
+ and give it the app directory or ASAR file:
+
+
+
+
+ › Use REA to find what happens when I export notes in this app.
+ Where is the CSV created?
+
+
+
+ Example prompt. Include the absolute path to the application
+ folder or app.asar.
+
+
+ REA reads the application files and builds a map of entrypoints,
+ modules and process boundaries. Your agent uses that map to narrow
+ the investigation to the export feature.
+
+
+
+ Connect the pieces
+ The page calls a small API
+
+
+
document.querySelector("#export").addEventListener("click", async () => {
+ const destination = await window.notes.exportCsv();
+ document.querySelector("#status").textContent = `Saved to ${destination}`;
+});
+
+
+ The page knows the method name. The implementation lives in the
+ preload, which exposes it to the renderer.
+
+
+
+
contextBridge.exposeInMainWorld("notes", {
+ exportCsv: () => ipcRenderer.invoke("notes:export"),
+});
+
+
+ REA records notes.exportCsv and the literal channel
+ notes:export. It pairs that invocation with the
+ single matching handler in the main process.
+
+
+
+
ipcMain.handle("notes:export", () => {
+ const destination = path.join(app.getPath("downloads"), "notes.csv");
+ writeFileSync(destination, toCsv(notes), "utf8");
+ return destination;
+});
+
+
+ Reading the handler answers the original question:
+ csv.js formats the notes, and the main process writes
+ notes.csv into the user's Downloads folder.
+
+
+
+
{
+ "literal_channels": 1,
+ "renderer_transmissions": 1,
+ "main_handlers": 1,
+ "paired_renderer_transmissions": 1,
+ "ambiguous_renderer_transmissions": 0
+}
+
+
+ Recorded by REA 4.1.0 on the downloadable example below. Source
+ locations accompany the API, channel and handler findings.
+
+
+
+ Analyze the example yourself
+
+ Save these six files together in a folder named
+ notes-example. Static analysis needs Node.js and npm;
+ you can inspect the code without installing or launching Electron.
+
+
+
+
+
npx -y rea-agents@latest analyze-javascript-application \
+ /absolute/path/to/notes-example --json
+
+
+ Replace the path with the folder you saved. Look for
+ normalized_result.summary.ipc and the source
+ locations in normalized_result.graph. The example has
+ one channel and one paired invocation.
+
+
+ For your own app, use the same command with its directory or ASAR
+ path. An agent can follow a literal string, API name or IPC
+ channel with REA's feature tracing tools.
+
+
+
+ Ask the next question
+
+
+ Follow notes:export to its handler. Which inputs
+ reach the CSV writer, and what happens if writing the file
+ fails?
+
+
+
+ Use the source locations to inspect those details. Computed
+ channel names and ambiguous handlers can remain unresolved;
+ runtime observations give you another way to investigate them.
+
+
+
+
+
+
+
+
+
+
diff --git a/website/public/guides/native/index.html b/website/public/guides/native/index.html
new file mode 100644
index 000000000..09912856b
--- /dev/null
+++ b/website/public/guides/native/index.html
@@ -0,0 +1,276 @@
+
+
+
+
+
+ Native analysis — REA
+
+
+
+
+
+
+
+ Skip to content
+
+
+
+
+ Home / Guides / Native analysis
+
+
Recover a calculation. Follow the evidence.
+
+ Use DX-Ball’s sound panning to see how an agent moves from a binary to
+ an explanation and a checked C function.
+
+
+
+
+ In this guide
+
+
+
+
+ A position becomes a sound
+
+ In DX-Ball, a brick hit supplies a horizontal screen position. The
+ sound code turns it into a left/right panning value. We want to
+ recover that calculation from the executable.
+
+
+ The starting function, 0x00406400, has a short
+ decompiler result: a call to __ftol(). REA also
+ returns its instructions and callers, which give the agent a way
+ to investigate the missing arithmetic.
+
+
+
+ Three pieces of evidence, one calculation
+
+
+
+ 01 · Instructions An integer input A stack read supplies the value used by the floating-point
+ instructions.
+
+
+ 02 · Caller A screen coordinate The brick-hit caller passes
+ 20 + 30 × tile_x.
+
+
+ 03 · Data bytes The numeric constants Referenced bytes decode to 1.5625,
+ 500.0 and a scale value.
+
+
+
+ pan = (x × 1.5625 − 500.0) × scale
+
+
+ REA supplies the instructions, call relationship and bytes. The
+ agent interprets them together.
+
+
+
+
+ Ask about the behavior
+
+ After
+ connecting REA to your agent , provide the local executable and a question:
+
+
+
+
+ › Use REA to find how DX-Ball calculates sound panning. Explain
+ the calculation and show the code.
+
+
+
+ Example prompt. Give your agent the path to your local DX-Ball
+ 1.07 executable.
+
+
+ You can start with a feature name. Your agent uses REA to locate
+ relevant functions, then follows the evidence with more focused
+ queries.
+
+
+
+ Follow what REA returns
+ Read the input
+
+
+
MOV EAX, dword ptr [EBP + 0x8]
+MOV dword ptr [EBP + -0xc], EAX
+FILD dword ptr [EBP + -0xc]
+
+
+ The instructions load an integer argument and convert it for
+ floating-point arithmetic. This is the input absent from the first
+ pseudocode view.
+
+ Follow the caller
+
+
+
ADD EAX, 0x14
+PUSH EAX
+CALL 0x00406400
+
+
+ REA identifies the brick-hit caller. Its earlier instructions
+ multiply the tile coordinate by 30; adding 0x14 (20)
+ gives the brick's screen position.
+
+ Read the referenced constants
+
+
+
0x420068 000000000000f93f → 1.5625
+0x420070 0000000000407f40 → 500.0
+0x4210a0 000000000000f03f → 1.0
+
+
+ The arithmetic references these addresses. Reading the bytes
+ supplies the actual values; the instruction order tells the agent
+ how they are used.
+
+
+
+ Write and check the recovered function
+
+
+
pan = (double)x;
+pan = pan * 1.5625;
+pan = pan - 500.0;
+pan = pan * dxball_pan_scale;
+return (DxBallInt)pan;
+
+
+ With a scale of 1.0, screen positions 0, 320 and 640 map to −500,
+ 0 and 500 before integer conversion. The calculation puts the
+ center of the screen at the center of the stereo field.
+
+
+ The agent used REA's findings to write the C. Separate checks
+ compared 3,205 inputs against the original x86 function and
+ reproduced all 63 compiled bytes of the complete function.
+
+
+ See the full investigation and assembly-to-C comparison
+
+
+
+ Try it with your own binary
+
+ Connect a native analysis provider , then give your agent a local executable or library and a
+ feature to investigate. Useful starting questions include “How is
+ this file parsed?” or “Which function handles this menu action?”
+
+
+ If you already know a function name or address, you can query it
+ from the terminal. For a configured Ghidra installation:
+
+
+
+
npx -y rea-agents@latest function /absolute/path/to/program main \
+ --provider ghidra --json
+
+
+ Replace the target path and main with your own
+ function name or address. Each CLI invocation imports and analyzes
+ the binary, then closes its session. Your agent's MCP queries
+ reuse the imported binary while that session is open.
+
+
+ Read the pseudocode alongside instructions and references. When a
+ result leaves a call or value unresolved, use that location as the
+ next question.
+
+
+
+
+
+
+
+
+
diff --git a/website/public/index.html b/website/public/index.html
index 4acb31035..4415b50f1 100644
--- a/website/public/index.html
+++ b/website/public/index.html
@@ -24,7 +24,7 @@
DX-Ball
Get started
- Docs
+ Guides
GitHub
@@ -150,10 +150,7 @@
Inspect functions, strings, references and call relationships in
executables and libraries.
- Native analysis guide
+ Native analysis guide
diff --git a/website/public/showcase/dx-ball/index.html b/website/public/showcase/dx-ball/index.html
index 6d4bfdf0f..f58308465 100644
--- a/website/public/showcase/dx-ball/index.html
+++ b/website/public/showcase/dx-ball/index.html
@@ -24,7 +24,7 @@
DX-Ball
Get started
- Docs
+ Guides
GitHub
@@ -125,6 +125,34 @@
sound code. The investigation needed to recover how that screen
coordinate becomes a left/right panning value.
+
+
+
+ › Use REA to find how DX-Ball calculates sound panning. Explain
+ the calculation and show the code.
+
+
+
+ An example prompt for your coding agent, with the local executable
+ available. The investigation below shows the REA queries and
+ evidence behind the answer.
+
The agent used REA to inspect the function, follow its brick-hit
caller and read the constants from the executable. Those results
@@ -567,10 +595,11 @@
>Current source, progress and local setup.
- REA analysis workflow Pinned tools, MCP sessions and retained evidence.
+ REA analysis workflow Follow the calculation, then use the same approach with your
+ own binary.