# Project export: Wayfinder

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: Your friendly guide to any GitHub repository: learn the project, find the right installer, or plan a contribution without leaving the page.
- Devpost: https://devpost.com/software/wayfinder-ydzc24
- GitHub: https://github.com/Robertg761/Wayfinder
- Video: https://www.youtube.com/embed/QChwx5A8jdM?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Robertg761 (51 commits)

## Devpost submission (written by the team)

### Inspiration

Opening an unfamiliar repository feels less like reading a map and more like being dropped into a city with a list of street names. The files are all visible, but the practical questions remain: What does this project do? What should I read first? How do I run it? Where does a feature actually live? General chat tools can help, but they make users move context out of GitHub and can answer with a plausible path that does not exist. We wanted the experience of an experienced contributor sitting beside you, pointing at the real repository and saying, "Start here, run this, and this is the evidence."

### What it does

Wayfinder appears as a small helper directly on GitHub. Guided mode turns the page into a friendly, project-specific tour. Quick mode gives the highlights without the homework. Ask how to install a finished app and Wayfinder can open GitHub Releases, detect the user's platform, and point to the packaged download instead of a source archive. Ask how to contribute and the same helper builds a clear route through setup, implementation, and verification. It can: summarize a repository and build a clickable reading route show a compact snapshot with stack, package manager, ref, commit, key directories, entry point, and local commands recognize a finished application, open GitHub Releases, detect the user's platform and architecture, and highlight the matching packaged download avoid steering regular users toward source archives, checksums, update metadata, or developer-only setup separate published-project installation from local repository development extract installation, development, test, and build commands from repository evidence label commands as documented, inferred, or conflicting find likely source files from a natural-language question turn a goal such as "I want to change speech generation" into a setup, implementation, and verification route use the active GitHub directory as ranking context classify the active file, route five distinct file actions, resolve explicit local dependencies, and show only target-specific caller and paired-test evidence open every recommended file at the mapped commit and known line range preserve the current trail while the user follows evidence through GitHub keep recent evidence available through temporary network or GitHub failures use GPT-5.6 to synthesize an ordered field brief from several typed tool results The free deterministic route remains fully functional. If the OpenAI key is missing, the model API is unavailable, structured output is invalid, or GPT-5.6 introduces a path or command outside the evidence set, Wayfinder returns the deterministic answer automatically. Paid synthesis is protected by a Cloudflare rate-limit binding and a persistent global budget matching the $100 event credit balance. A SQLite-backed Durable Object serializes spend reservations across all users, reconciles successful calls to actual Luna token usage, and fails closed to the deterministic answer. Cost protection therefore does not turn into a user-facing outage.

### How we built it

The Chrome extension uses WXT, TypeScript, Shadow DOM, and Manifest V3. A content script tracks GitHub single-page navigation, renders the isolated helper, maps visible landmarks, calls the Worker, caches results locally, and keeps the complete experience on the active page. A TypeScript Cloudflare Worker provides explicit repository tools: repository mapper guided tour builder installation evidence extractor contextual file finder deterministic intent router multi-tool contribution orchestrator The mapper reads GitHub metadata, the exact viewed branch, tag, or commit, README content, setup landmarks, and a compact source tree. The file finder ranks the full filtered tree, then fetches only the five strongest small text candidates for content and symbol evidence. Current-file questions first distinguish source, test, documentation, configuration, data, and other files, then route summary, dependency, caller, test, and impact actions separately. Relationship results require target-specific evidence; documentation is summarized from its own headings instead of being treated as executable source. The install tool extracts documented commands with line references, limits repository-wide setup evidence to authoritative root and dedicated setup documents, verifies that consumer commands name the actual project, and uses manifests only for clearly labeled inference. GPT-5.6 Luna is connected through the OpenAI Responses API for contribution Trail Plans. The model receives the user's question and the completed typed evidence, uses the lowest reasoning level that passes our evaluation, and must return strict structured output containing a direct answer, explanation, citations, and up to four ordered actions. Responses are not stored. Before the answer reaches the extension, the Worker verifies that every model evidence path and action coordinate occurs in the deterministic result. Focused questions stay on the deterministic route, and successful model calls report token usage, latency, and estimated cost.

### Challenges we ran into

The hardest problem was not generating an answer. It was deciding which parts of an answer deserved trust. Large repositories need aggressive filtering, but alphabetical truncation can hide the files that explain the architecture. Installation documentation mixes user setup, contributor setup, several package managers, and commands that look executable but are only examples. File names can also be deceptive. During the final dry run, src/pagination.ts looked like the perfect match, but source inspection revealed that it was a deprecated forwarding file. We changed the ranking to reorder candidates after content inspection and prefer the core implementation. We also needed a useful path before model credits arrived. That constraint produced a stronger architecture: deterministic tools are independently valuable, and GPT-5.6 improves interpretation without owning the facts. Accomplishments that we are proud of Every concrete command and file recommendation carries repository evidence. The extension works on public repositories without OpenAI credits. GPT-5.6 output is constrained by a strict schema and an exact-path allow-list. Paid model traffic has both per-client rate limiting and a persistent global budget cap. The same typed contracts drive free mode, model mode, caching, and the interface. Trail Plan combines orientation, sourced setup, implementation discovery, and related tests into one contributor workflow. Consumer install guidance can move from a repository to its Releases page and highlight the correct OS-specific installer in place. The production Worker is live and the Chrome package uses it automatically. The automated suite covers 178 unit and integration cases plus 49 complete browser workflows across URL context, late-rendered and off-screen landmarks, leave-and-return recovery on modern GitHub subpages, compact control clearance, editor focus and host-page shortcut containment, mode persistence, public request validation, ref correctness, latest-release and architecture selection, delayed release assets, per-tab navigation isolation, setup intent and evidence scoping, file-type-aware current-file context, model allowance fallback, global budget accounting, local and edge caching, repository mapping, tours, contribution routing, file ranking, model fallback, responsive layout, and keyboard focus visibility. A repeatable public smoke test passes across TypeScript, Python, Rust, Go, and a truncated JavaScript monorepo. The live public dry run correctly found src/core/pagination.ts in openai/openai-node after excluding its deprecated wrapper.

### What we learned

An agent becomes more useful when its tools expose uncertainty instead of hiding it. Confidence labels, ranked alternatives, source lines, and explicit warnings are not secondary interface details. They are part of the reasoning system. We also learned that a model does not need to own retrieval to provide meaningful intelligence. GPT-5.6 can focus on intent and explanation while deterministic code maintains the boundary around repository facts. That division produces a better fallback and a more trustworthy primary experience.

### What's next

expand the bounded likely-caller search into a complete symbol-aware call graph add private-repository authentication with an explicit consent flow add symbol-aware impact analysis beyond direct imports and paired tests support saved onboarding routes for teams and contributors add a VS Code surface that consumes the same Worker contracts

## README (from the GitHub repository)

<p align="center">
  <img src="docs/assets/wayfinder-mark.svg" alt="Wayfinder compass mascot" width="104" height="104">
</p>

<h1 align="center">Wayfinder</h1>

<p align="center"><strong>Evidence-first repository guidance, directly on GitHub.</strong></p>

<p align="center">
  <a href="https://github.com/Robertg761/Wayfinder/actions/workflows/ci.yml"><img alt="CI status" src="https://github.com/Robertg761/Wayfinder/actions/workflows/ci.yml/badge.svg"></a>
  <a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/license-MIT-42694f"></a>
  <img alt="Chrome Manifest V3" src="https://img.shields.io/badge/Chrome-MV3-e8a72f?logo=googlechrome&amp;logoColor=25231f">
  <img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-5.9-a94425?logo=typescript&amp;logoColor=white">
</p>

Wayfinder is a floating repository guide for public GitHub projects. Its compass
helper points to real page landmarks, explains what they reveal, and expands
into a complete repository agent without making the user leave GitHub.

Ask how to install a project, where a feature lives, what a file depends on, or
how to plan a contribution. Wayfinder answers with commit-pinned paths,
source-backed commands, confidence labels, and links that open the exact
evidence.

| Guided tour | Repository landmark |
|---|---|
| ![Wayfinder welcome panel on the openai-node repository](docs/assets/wayfinder-page-helper-welcome.jpg) | ![Wayfinder highlighting the repository name on GitHub](docs/assets/wayfinder-page-helper-landmark.jpg) |

## Why Wayfinder

GitHub exposes every file, but it rarely tells a newcomer what to read first,
which setup path applies to them, or where a contribution should begin.
Wayfinder turns those scattered clues into a navigable trail.

| Experience | Best for | What it does |
|---|---|---|
| **Guided** | New contributors | Moves through visible GitHub landmarks and teaches the repository one step at a time. |
| **Quick** | Experienced developers | Opens a compact repository snapshot and focused task shortcuts without moving around the page. |
| **Trail Plan** | A concrete change | Combines sourced setup, likely implementation files, related verification, and an ordered reading route. |

The deterministic tools remain fully useful without an OpenAI key. When the
Worker is configured for GPT-5.6 Luna, contribution plans can receive an
additional structured synthesis. Structured model output (evidence paths and
brief steps) is strictly validated against the deterministic evidence and the
whole synthesis is rejected on any mismatch; free-form prose is additionally
screened by heuristics for unsupported paths and command shapes, which blocks
common cases but is not a guarantee.

## Try it locally

Wayfinder is not yet distributed through the Chrome Web Store. Build and load
the production extension from source:

```bash
git clone https://github.com/Robertg761/Wayfinder.git
cd Wayfinder
corepack enable
pnpm install
pnpm --filter @wayfinder/extension build
```

Then:

1. Open `chrome://extensions`.
2. Enable **Developer mode**.
3. Select **Load unpacked**.
4. Choose `apps/extension/.output/chrome-mv3`.
5. Open a public GitHub repository.

Choose **Guide me** for the landmark tour or **Quick map** for the compact
developer surface. `Alt + Shift + W` opens and closes Wayfinder from the
keyboard.

Production builds call the public Worker at
[wayfinder-api.hopit-robert.workers.dev](https://wayfinder-api.hopit-robert.workers.dev/health).
No local Worker or API key is required for the deterministic public-repository
experience.

## What it can do

- map repository, tree, branch, tag, commit, directory, and file context
- preserve the requested ref and pin every answer to the resolved commit SHA
- summarize the project, stack, package manager, entry point, and key directories
- separate end-user installation from local contributor setup
- extract commands from authoritative repository-level setup evidence, verify consumer commands name the project, and label documented or inferred steps
- find likely implementation files with reasons, signals, confidence, and direct links
- classify the active file and answer summary, dependency, caller, test, and impact questions separately
- build a Trail Plan from a concrete contribution goal
- keep saved trails and recent evidence available across GitHub navigation or a temporary network failure
- guide users to the newest compatible GitHub Release without guessing their operating system or processor

## Evidence before prose

Wayfinder treats trust as a product feature:

1. Repository identities, refs, paths, sizes, and timestamps are validated at
   the Worker boundary.
2. Commands carry a repository source and inferred commands are labeled.
3. File relationships require target-specific evidence; possible matches do
   not become headline claims.
4. Model responses use strict structured output and an exact evidence
   allow-list.
5. Missing credentials, exhausted allowance, invalid model output, or upstream
   failure returns the deterministic answer instead of breaking the task.

See [Architecture](docs/ARCHITECTURE.md) for the complete trust boundary and
[Privacy](PRIVACY.md) for data flow and retention.

## Architecture

```text
GitHub page
  └─ Chrome extension (WXT + Shadow DOM)
       ├─ visible landmark guide
       ├─ local commit-aware cache
       └─ public Worker request
            ├─ GitHub repository mapper
            ├─ deterministic tour, install, find, and file-context tools
            ├─ contribution orchestrator
            └─ optional GPT-5.6 Luna synthesis
```

| Workspace | Responsibility |
|---|---|
| `apps/extension` | Manifest V3 extension, page helper, navigation, caching, and evidence UI |
| `apps/api` | Cloudflare Worker, GitHub retrieval, deterministic tools, model guardrails, and budget controls |
| `packages/contracts` | Shared request, response, repository-map, and answer contracts |
| `tests/browser` | Full extension workflows against deterministic GitHub fixtures |
| `scripts/verify-public.mjs` | Repeatable semantic matrix against real public repositories |

## Local development

Requirements: Node.js 22 or newer and pnpm 10.

```bash
pnpm install
cp apps/api/.dev.vars.example apps/api/.dev.vars
```

Both credentials are optional for public-repository development:

```text
GITHUB_TOKEN=
OPENAI_API_KEY=
OPENAI_REASONING_EFFORT=low
```

Start the Worker and extension in separate terminals:

```bash
pnpm dev:api
pnpm dev:extension
```

Development extension builds use `http://localhost:8787`. Set
`WXT_WAYFINDER_API_URL` to test another Worker origin.

### Worker routes

| Route | Purpose |
|---|---|
| `GET /health` | Deployment, model, limiter, and budget status |
| `POST /map` | Repository metadata, tree, README, setup, and commit mapping |
| `POST /tour` | Deterministic page-landmark tour |
| `POST /guide/install` | Consumer or contributor setup evidence |
| `POST /find` | Context-aware repository file discovery |
| `POST /agent` | Intent routing, file context, Trail Plan, and optional Luna synthesis |

## Verification

```bash
pnpm typecheck
pnpm test
pnpm test:browser
pnpm build
pnpm --filter @wayfinder/extension zip
```

The browser suite launches Chromium with the unpacked extension and covers
reloads, GitHub SPA navigation, narrow and dark layouts, keyboard behavior,
reduced motion, setup intent, release selection, evidence navigation, cache
isolation, and failure recovery.

After deploying the Worker, run the real-repository matrix:

```bash
pnpm smoke:public node python rust go monorepo
```

The matrix currently covers `openai/openai-node`, `pallets/flask`,
`BurntSushi/ripgrep`, `cli/cli`, and `vercel/next.js`. Results and pinned
coordinates are recorded in [Verification matrix](docs/VERIFICATION_MATRIX.md).

The optional live Luna evaluation requires `OPENAI_API_KEY`:

```bash
pnpm eval:luna
```

## Project documentation

- [Product plan](PRODUCT_PLAN.md)
- [Build Week plan](

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 60 recognized source files, 766 KB.
- TypeScript (language) — detected in the code
- OpenAI (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (79 of 79)

```
.editorconfig
.github/ISSUE_TEMPLATE/bug_report.yml
.github/ISSUE_TEMPLATE/config.yml
.github/ISSUE_TEMPLATE/feature_request.yml
.github/pull_request_template.md
.github/workflows/ci.yml
.gitignore
apps/api/.dev.vars.example
apps/api/package.json
apps/api/src/agent.ts
apps/api/src/budget.ts
apps/api/src/file-context.ts
apps/api/src/find.ts
apps/api/src/github.ts
apps/api/src/index.ts
apps/api/src/install.ts
apps/api/src/model.ts
apps/api/src/path-classify.ts
apps/api/src/tour.ts
apps/api/test/agent.test.ts
apps/api/test/budget.test.ts
apps/api/test/file-context.test.ts
apps/api/test/find.test.ts
apps/api/test/github.test.ts
apps/api/test/index.test.ts
apps/api/test/install.test.ts
apps/api/test/luna.live.test.ts
apps/api/test/model.test.ts
apps/api/test/tour.test.ts
apps/api/tsconfig.json
apps/api/wrangler.jsonc
apps/extension/.claude/skills/verify/live-smoke.mjs
apps/extension/.claude/skills/verify/runtime.mjs
apps/extension/.claude/skills/verify/SKILL.md
apps/extension/.gitignore
apps/extension/entrypoints/content.ts
apps/extension/entrypoints/keyboard-guard.content.ts
apps/extension/lib/api-client.ts
apps/extension/lib/cache.test.ts
apps/extension/lib/cache.ts
apps/extension/lib/copy-text.test.ts
apps/extension/lib/copy-text.ts
apps/extension/lib/github-url.test.ts
apps/extension/lib/github-url.ts
apps/extension/lib/helper-styles.ts
apps/extension/lib/helper-ui.test.ts
apps/extension/lib/helper-ui.ts
apps/extension/package.json
apps/extension/README.md
apps/extension/tsconfig.json
apps/extension/wxt.config.ts
BUILD_WEEK_PLAN.md
CONTRIBUTING.md
docs/ARCHITECTURE.md
docs/CHROME_WEB_STORE.md
docs/DEMO_SCRIPT.md
docs/DEVPOST_SUBMISSION.md
docs/JUDGING_STRATEGY.md
docs/LUNA_EVALUATION.md
docs/SHIP_CHECKLIST.md
docs/VERIFICATION_MATRIX.md
eslint.config.mjs
LICENSE
package.json
packages/contracts/package.json
packages/contracts/src/index.test.ts
packages/contracts/src/index.ts
packages/contracts/tsconfig.json
playwright.config.ts
pnpm-workspace.yaml
PRIVACY.md
PRODUCT_PLAN.md
README.md
scripts/render-extension-icons.mjs
scripts/store-screenshots.mjs
scripts/store-session.mjs
scripts/verify-public.mjs
SECURITY.md
tests/browser/helper.spec.ts
```

### Dependencies

- apps/api/package.json: @cloudflare/workers-types@^5.20260713.1, @wayfinder/contracts@workspace:*, typescript@^5.9.3, vitest@^3.2.4, wrangler@^4.45.3, zod@^3.25.76
- apps/extension/package.json: @wayfinder/contracts@workspace:*, typescript@^5.9.3, vitest@^3.2.4, wxt@^0.20.27
- package.json: @playwright/test@^1.61.1, eslint@^10.7.0, typescript-eslint@^8.65.0
- packages/contracts/package.json: typescript@^5.9.3, vitest@^3.2.4, zod@^3.25.76

### Recent commits (newest first)

- Add reusable CDP session launcher for store submissions
- Remove unused helper from screenshot script
- Prepare unlisted Chrome Web Store submission
- Merge pull request #5 from Robertg761/remediation/phase-5-architecture
- Merge pull request #4 from Robertg761/remediation/phase-4-contracts
- Merge pull request #3 from Robertg761/remediation/phase-3-extension
- Merge pull request #6 from Robertg761/remediation/phase-2-correctness
- Merge pull request #1 from Robertg761/remediation/phase-1-security
- Update browser fixture to the v2 AgentMode values
- Merge Escape-scoping fix through phase 4
- Merge Escape-scoping fix from phase 3
- Let Escape cancel a tour when focus is unclaimed
- Note verified dev rate-limit namespace ids
- Architecture cleanup across Worker and extension
- Move wire contracts to zod, harden CI, add lint
- Extension robustness and UX fixes
- Fix confirmed correctness bugs across Worker and extension
- Security and abuse hardening for Worker and extension
- Record completed Devpost submission
- Add public Wayfinder demo link

## Key source files (fetched from GitHub, selected and truncated for size)

### SECURITY.md

```markdown
# Wayfinder Security Policy

## Supported version

Wayfinder is currently a pre-release project. Security fixes are applied to the
latest code on `main` and to the active public Worker deployment.

## Reporting a vulnerability

Please do not publish an unpatched vulnerability, credential, exploit, or
sensitive repository data in a public issue.

Use GitHub's **Report a vulnerability** flow from the repository's Security
tab when it is available. If private reporting is unavailable, open a minimal
issue asking the maintainer for a private contact path without including the
vulnerability details.

Useful reports include:

- the affected extension or Worker version
- the public GitHub route or API endpoint involved
- the security boundary that was crossed
- minimal reproduction steps with secrets removed
- the expected safe behavior

Non-sensitive reliability and correctness defects can use the regular bug
report form.

## Security boundaries

Wayfinder is designed for public GitHub repositories. Reports are especially
useful when they concern:

- exposure of extension or Worker credentials
- repository path or command injection
- evidence links that escape the mapped repository and commit
- model output that bypasses deterministic path or command validation
- cross-tab or cross-repository cache leakage
- model-budget or rate-limit bypasses

See [PRIVACY.md](PRIVACY.md) for the current data flow and retention policy.

## Operator configuration requirements

- `GITHUB_TOKEN` must be a **fine-grained personal access token limited to
  public read access** (no repository selected, or public repositories only,
  with read-only permissions). Never configure a classic token with the
  `repo` scope: this Worker serves anonymous public traffic, and such a token
  would let anyone read the operator's private repositories through it.
  The Worker checks classic-token scopes at runtime and refuses to attach a
  token that carries `repo`, but fine-grained token permissions cannot be
  introspected from the API — verifying and rotating those is the operator's
  responsibility.
- `HEALTH_DIAGNOSTICS_KEY` (optional) gates the budget figures and deployment
  metadata on `/health` behind `?diagnostics=<key>`. Without the key those
  fields are never returned.

```

### CONTRIBUTING.md

```markdown
# Contributing to Wayfinder

## Prerequisites

- Node.js 22 or newer
- pnpm 10
- Chrome or another Chromium browser for extension testing

## Local setup

```bash
pnpm install
cp apps/api/.dev.vars.example apps/api/.dev.vars
```

Both tokens are optional for local public-repository development:

```text
GITHUB_TOKEN=
OPENAI_API_KEY=
OPENAI_REASONING_EFFORT=low
```

Start the Worker:

```bash
pnpm dev:api
```

Start the extension in another terminal:

```bash
pnpm dev:extension
```

## Required checks

Run these before committing:

```bash
pnpm typecheck
pnpm test
pnpm test:browser
pnpm build
```

For extension interface changes, also run the repository's unpacked-extension
runtime verification described in `apps/extension/.claude/skills/verify` and
inspect the light, dark, narrow, and failure-state screenshots it produces.

When the compass artwork changes, regenerate every Chrome icon size from the
tracked SVG source:

```bash
pnpm icons:extension
```

Create the Chrome archive with:

```bash
pnpm --filter @wayfinder/extension zip
```

## Public smoke tests

The deployed-service matrix is intentionally separate from unit tests because it calls GitHub and the public Worker.

```bash
pnpm smoke:public node python rust go monorepo
```

Individual case names are `node`, `python`, `rust`, `go`, and `monorepo`.

## Evidence rules

Changes must preserve these product guardrails:

1. Every displayed command has a repository source.
2. Repository-wide setup comes from root or explicitly named setup documents; nested subsystem READMEs do not leak into it.
3. Consumer installation commands name the mapped project, and inferred setup steps are labeled as inferred.
4. File answers include reasons, match signals, and confidence.
5. Model prose cannot introduce a repository path or shell command absent from deterministic evidence.
6. Free mode remains useful when no OpenAI key is configured.
7. New ranking or setup behavior includes a focused regression test.

## Pull requests

- Keep changes scoped to a concrete repository task or reliability boundary.
- Include before-and-after screenshots for visible interface changes.
- Link a public repository or deterministic fixture that demonstrates the task.
- Never commit `.dev.vars`, tokens, private repository content, generated build
  output, or browser profiles.
- By contributing, you agree that your contribution is licensed under the
  repository's [MIT License](LICENSE).

## Commit style

Use short conventional commit messages such as:

```text
feat: add repository context control
fix: prefer implementation files over fixtures
docs: record public verification matrix
```

```

### package.json

```
{
  "name": "wayfinder",
  "private": true,
  "version": "0.1.0",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/Robertg761/Wayfinder.git"
  },
  "homepage": "https://github.com/Robertg761/Wayfinder#readme",
  "bugs": {
    "url": "https://github.com/Robertg761/Wayfinder/issues"
  },
  "packageManager": "pnpm@10.30.1",
  "engines": {
    "node": ">=22"
  },
  "scripts": {
    "build": "pnpm -r build",
    "dev:api": "pnpm --filter @wayfinder/api dev",
    "dev:extension": "pnpm --filter @wayfinder/extension dev",
    "deploy:api": "pnpm --filter @wayfinder/api exec wrangler deploy",
    "eval:luna": "pnpm --filter @wayfinder/api test:luna",
    "icons:extension": "node scripts/render-extension-icons.mjs",
    "smoke:public": "node scripts/verify-public.mjs",
    "test": "pnpm -r test",
    "test:browser": "pnpm --filter @wayfinder/extension build && playwright test",
    "typecheck": "pnpm -r typecheck",
    "lint": "eslint ."
  },
  "devDependencies": {
    "@playwright/test": "^1.61.1",
    "eslint": "^10.7.0",
    "typescript-eslint": "^8.65.0"
  },
  "pnpm": {
    "onlyBuiltDependencies": [
      "esbuild",
      "sharp",
      "workerd"
    ]
  }
}

```

### packages/contracts/package.json

```
{
  "name": "@wayfinder/contracts",
  "private": true,
  "version": "0.1.0",
  "license": "MIT",
  "type": "module",
  "exports": {
    ".": "./src/index.ts"
  },
  "scripts": {
    "build": "tsc --noEmit",
    "test": "vitest run",
    "typecheck": "tsc --noEmit"
  },
  "devDependencies": {
    "typescript": "^5.9.3",
    "vitest": "^3.2.4"
  },
  "dependencies": {
    "zod": "^3.25.76"
  }
}

```

### apps/api/package.json

```
{
  "name": "@wayfinder/api",
  "private": true,
  "version": "0.1.0",
  "license": "MIT",
  "type": "module",
  "scripts": {
    "build": "wrangler deploy --dry-run --outdir dist",
    "dev": "wrangler dev",
    "test": "vitest run",
    "test:luna": "RUN_LUNA_EVAL=1 vitest run test/luna.live.test.ts",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@wayfinder/contracts": "workspace:*",
    "zod": "^3.25.76"
  },
  "devDependencies": {
    "@cloudflare/workers-types": "^5.20260713.1",
    "typescript": "^5.9.3",
    "vitest": "^3.2.4",
    "wrangler": "^4.45.3"
  }
}

```

### apps/extension/package.json

```
{
  "name": "@wayfinder/extension",
  "description": "A floating evidence-first guide that maps and explains public GitHub repositories.",
  "private": true,
  "version": "0.2.0",
  "license": "MIT",
  "type": "module",
  "scripts": {
    "dev": "wxt",
    "dev:firefox": "wxt -b firefox",
    "build": "wxt build",
    "build:firefox": "wxt build -b firefox",
    "zip": "wxt zip",
    "zip:firefox": "wxt zip -b firefox",
    "test": "vitest run",
    "typecheck": "tsc --noEmit",
    "postinstall": "wxt prepare"
  },
  "dependencies": {
    "@wayfinder/contracts": "workspace:*"
  },
  "devDependencies": {
    "typescript": "^5.9.3",
    "vitest": "^3.2.4",
    "wxt": "^0.20.27"
  }
}

```

### packages/contracts/src/index.ts

```typescript
import { z } from "zod";

// Bumped whenever a wire shape changes incompatibly. Served by the Worker as
// the X-Wayfinder-Contract-Version response header and a /health field; the
// extension sends its own version as X-Wayfinder-Extension-Version.
export const CONTRACT_VERSION = 2;

// Deployment endpoints shared by the extension build, tests, and scripts.
// (Plain .mjs scripts that cannot import TypeScript keep their own copy —
// update them together with these.)
export const WAYFINDER_PROD_API_URL = "https://wayfinder-api.hopit-robert.workers.dev";
export const WAYFINDER_DEV_API_URL = "http://localhost:8787";

// --- Extension-side navigation types (not wire shapes) ---------------------

export type GitHubView = "repo" | "tree" | "blob" | "other";

export interface RepoLocation {
  owner: string;
  repo: string;
  ref: string | null;
  path: string | null;
  view: GitHubView;
  url: string;
}

// --- Repository map --------------------------------------------------------

export const repositoryPathSchema = z.string()
  .min(1)
  .max(1_000)
  .refine((path) =>
    !path.startsWith("/") &&
    !/[\u0000-\u001f\u007f]/.test(path) &&
    path.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== ".."),
  { message: "Repository paths must be normalized relative paths." });

export const repoTreeEntrySchema = z.object({
  path: repositoryPathSchema,
  type: z.enum(["blob", "tree"]),
  size: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
});

export const repoMapSchema = z.object({
  repo: z.string().min(3).max(201).regex(/^(?!\.{1,2}\/)(?!.*\/\.{1,2}$)[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/),
  sha: z.string().regex(/^[a-f0-9]{7,64}$/i),
  requestedRef: z.string().min(1).max(255).nullable(),
  resolvedRef: z.string().min(1).max(255),
  defaultBranch: z.string().min(1).max(255),
  description: z.string().max(500).nullable(),
  homepage: z.string().max(2_048).nullable(),
  language: z.string().max(100).nullable(),
  stars: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
  readme: z.string().max(16_000).nullable(),
  tree: z.array(repoTreeEntrySchema).max(4_000),
  setupFiles: z.array(repositoryPathSchema).max(200),
  truncated: z.boolean(),
  generatedAt: z.string().datetime(),
});

export type RepoTreeEntry = z.infer<typeof repoTreeEntrySchema>;
export type RepoMap = z.infer<typeof repoMapSchema>;

// --- Tour ------------------------------------------------------------------

export const tourEntryPointSchema = z.object({
  path: z.string(),
  why: z.string(),
});

export const tourStopSchema = z.object({
  order: z.number().int(),
  title: z.string(),
  path: z.string(),
  lines: z.tuple([z.number(), z.number()]),
  explanation: z.string(),
  lookFor: z.string(),
});

export const repoTourSchema = z.object({
  repo: z.string(),
  sha: z.string(),
  summary: z.string(),
  stack: z.array(z.string()),
  runtimeEntryPoint: tourEntryPointSchema.nullable(),
  entryPoints: z.array(tourEntryPointSchema),
  stops: z.array(tourStopSchema),
});

export type TourEntryPoint = z.infer<typeof tourEntryPointSchema>;
export type TourStop = z.infer<typeof tourStopSchema>;
export type RepoTour = z.infer<typeof repoTourSchema>;

// --- Install guide ---------------------------------------------------------

export const installConfidenceSchema = z.enum(["documented", "inferred", "conflicting"]);
export const installCommandCautionSchema = z.enum(["elevated-privileges", "pipe-to-shell", "external-download"]);

export const installEvidenceSchema = z.object({
  path: z.string(),
  lines: z.tuple([z.number(), z.number()]).optional(),
});

export const installPrerequisiteSchema = z.object({
  text: z.string(),
  evidence: installEvidenceSchema,
  confidence: installConfidenceSchema,
});

export const installStepSchema = z.object({
  order: z.number().int(),
  title: z.string(),
  command: z.string(),
  evidence: installEvidenceSchema,
  confidence: installConfidenceSchema,
  caution: installCommandCautionSchema.optional(),
});

export const installGuideSchema = z.object({
  repo: z.string(),
  sha: z.string(),
  audience: z.enum(["use", "develop"]),
  packageManager: z.string().nullable(),
  runtimes: z.array(z.string()),
  prerequisites: z.array(installPrerequisiteSchema),
  steps: z.array(installStepSchema),
  warnings: z.array(z.string()),
  generatedAt: z.string(),
});

export type InstallConfidence = z.infer<typeof installConfidenceSchema>;
export type InstallCommandCaution = z.infer<typeof installCommandCautionSchema>;
export type InstallEvidence = z.infer<typeof installEvidenceSchema>;
export type InstallPrerequisite = z.infer<typeof installPrerequisiteSchema>;
export type InstallStep = z.infer<typeof installStepSchema>;
export type InstallGuide = z.infer<typeof installGuideSchema>;

// --- File find -------------------------------------------------------------

export const fileMatchSignalSchema = z.enum([
  "filename",
  "path",
  "alias",
  "content",
  "symbol",
  "primary-language",
  "current-directory",
  "test-pair",
  "architecture",
  "deprecated",
  "re-export",
]);

export const fileMatchConfidenceSchema = z.enum(["strong", "likely", "possible"]);

export const fileMatchSchema = z.object({
  path: z.string(),
  score: z.number(),
  confidence: fileMatchConfidenceSchema,
  reason: z.string(),
  signals: z.array(fileMatchSignalSchema),
  lines: z.tuple([z.number(), z.number()]).optional(),
  snippet: z.string().optional(),
});

export const fileFindResponseSchema = z.object({
  repo: z.string(),
  sha: z.string(),
  query: z.string(),
  currentPath: z.string().nullable(),
  results: z.array(fileMatchSchema),
  warnings: z.array(z.string()),
  generatedAt: z.string(),
});

export type FileMatchSignal = z.infer<typeof fileMatchSignalSchema>;
export type FileMatchConfidence = z.infer<typeof fileMatchConfidenceSchema>;
export type FileMatch = z.infer<typeof fileMatchSchema>;
export type FileFindResponse = z.infer<typeof fileFindRespons
[truncated — 5200 more characters]
```

### apps/api/src/index.ts

```typescript
import {
  agentRequestSchema,
  CONTRACT_VERSION,
  findRequestSchema,
  installRequestSchema,
  mapRequestSchema,
  tourRequestSchema,
  type WayfinderErrorCode,
} from "@wayfinder/contracts";
import { z } from "zod";
import { classifyAgentIntent, createAgentAnswer, hasSpecificContributionGoal } from "./agent";
import { createRepoMap, GitHubApiError, publicReadOnlyToken, UpstreamFetchBudget } from "./github";
import { createFileFind } from "./find";
import { createInstallGuide } from "./install";
import { generateTour } from "./tour";
import { WAYFINDER_MODEL, type ReasoningEffort } from "./model";
import {
  actualCostMicroUsd,
  budgetLimitMicroUsd,
  ModelBudget,
  reserveCostMicroUsd,
} from "./budget";

export { ModelBudget };

interface Env {
  // Must be a fine-grained personal access token limited to public read
  // access. Classic tokens carrying the "repo" scope are refused at runtime
  // because they would let this public Worker read private repositories.
  GITHUB_TOKEN?: string;
  OPENAI_API_KEY?: string;
  OPENAI_REASONING_EFFORT?: string;
  MODEL_BUDGET_USD?: string;
  // Shared secret; when set, ?diagnostics=<key> on /health reveals budget
  // figures and deployment metadata. Without it they stay operator-only.
  HEALTH_DIAGNOSTICS_KEY?: string;
  MODEL_RATE_LIMITER?: RateLimit;
  API_RATE_LIMITER?: RateLimit;
  MODEL_BUDGET?: DurableObjectNamespace;
  CF_VERSION_METADATA?: {
    id: string;
    tag: string;
    timestamp: string;
  };
}

const MODEL_BUDGET_LEDGER_NAME = "luna-lifetime-v3";

const corsHeaders = {
  "Access-Control-Allow-Headers": "Content-Type,X-Wayfinder-Extension-Version",
  "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
  "Access-Control-Allow-Origin": "*",
  "X-Wayfinder-Contract-Version": String(CONTRACT_VERSION),
};

function json(body: unknown, status = 200): Response {
  return Response.json(body, { status, headers: corsHeaders });
}

const REQUEST_BODY_LIMIT_BYTES = 1_500_000;

class RequestTooLargeError extends Error {
  constructor() {
    super("The repository request is too large to process safely.");
    this.name = "RequestTooLargeError";
  }
}

// Reads and parses the JSON body while enforcing the byte cap on the actual
// stream, so a missing or forged Content-Length header cannot bypass it.
async function readBoundedJson(request: Request): Promise<unknown> {
  const declaredLength = Number(request.headers.get("content-length") ?? "0");
  if (Number.isFinite(declaredLength) && declaredLength > REQUEST_BODY_LIMIT_BYTES) {
    throw new RequestTooLargeError();
  }
  if (!request.body) return JSON.parse("");

  const reader = request.body.getReader();
  const chunks: Uint8Array[] = [];
  let totalBytes = 0;
  for (;;) {
    const { done, value } = await reader.read();
    if (done) break;
    totalBytes += value.byteLength;
    if (totalBytes > REQUEST_BODY_LIMIT_BYTES) {
      await reader.cancel().catch(() => {});
      throw new RequestTooLargeError();
    }
    chunks.push(value);
  }

  const combined = new Uint8Array(totalBytes);
  let offset = 0;
  for (const chunk of chunks) {
    combined.set(chunk, offset);
    offset += chunk.byteLength;
  }
  return JSON.parse(new TextDecoder().decode(combined));
}

function requestFailure(error: unknown, fallbackError: string, fallbackStatus = 500): Response {
  // Every error response carries the same { error, code, message } shape.
  if (error instanceof z.ZodError) {
    return json({
      error: "invalid_request",
      code: "request-failed",
      message: "The request did not match the Wayfinder API contract.",
      issues: error.issues,
    }, 400);
  }
  if (error instanceof RequestTooLargeError) {
    return json({
      error: "request_too_large",
      code: "request-failed",
      message: error.message,
    }, 413);
  }
  if (error instanceof SyntaxError) {
    return json({
      error: "invalid_json",
      code: "request-failed",
      message: "Request body must be valid JSON.",
    }, 400);
  }
  if (error instanceof GitHubApiError) {
    const status = error.code === "github-rate-limited" ? 429 : error.status;
    return json({
      error: "github_request_failed",
      code: error.code,
      message: error.message,
      ...(error.resetAt ? { resetAt: error.resetAt } : {}),
    }, status);
  }
  // Uncaught errors stay in observability logs; their messages are not part
  // of the public contract and may carry internal detail.
  console.error(JSON.stringify({
    event: "unhandled-request-error",
    error: fallbackError,
    detail: error instanceof Error ? `${error.name}: ${error.message}` : String(error),
  }));
  const code: WayfinderErrorCode = "request-failed";
  return json({ error: fallbackError, code, message: "Wayfinder could not complete this request. Try again shortly." }, fallbackStatus);
}

async function publicApiGate(request: Request, env: Env, path: string): Promise<Response | null> {
  if (!env.API_RATE_LIMITER) return null;

  const clientKey = request.headers.get("cf-connecting-ip")?.trim() || "unknown-client";
  try {
    const { success } = await env.API_RATE_LIMITER.limit({ key: path + ":" + clientKey });
    if (success) return null;
    return json({
      error: "service_rate_limited",
      code: "service-rate-limited",
      message: "Wayfinder is receiving too many requests from this connection. Wait a minute, then try again.",
    }, 429);
  } catch {
    return json({
      error: "request_guard_unavailable",
      code: "upstream-unavailable",
      message: "Wayfinder's request guard is temporarily unavailable. Try again shortly.",
    }, 503);
  }
}

interface ModelBudgetStub {
  fetch(input: string, init?: RequestInit): Promise<Response>;
}

export function createBudgetedModelFetcher(
  budget: ModelBudgetStub,
  limitMicroUsd: number,
  upstreamFetcher: typeof fetch = fetch,
  reservationIdFactory: () => string = () => crypto.randomUUID(),
  waitUntil?: (promise: Promise<unknown>) => void,
): typeof fetch {
  return async (inp
[truncated — 9191 more characters]
```

### pnpm-workspace.yaml

```yaml
packages:
  - apps/*
  - packages/*

```

### playwright.config.ts

```typescript
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests/browser',
  timeout: 30_000,
  fullyParallel: false,
  workers: 1,
  // A stray test.only must fail CI instead of silently shrinking coverage.
  forbidOnly: Boolean(process.env.CI),
  retries: process.env.CI ? 2 : 0,
  reporter: 'line',
});

```

[49 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]