# Project export: SpecSentry

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: Turn acceptance criteria into evidence backed browser tests and GitHub-ready bug reports.
- Devpost: https://devpost.com/software/specsentry
- GitHub: https://github.com/Cliffinkent/specsentry
- Demo: https://specsentry-production.up.railway.app/
- Video: https://www.youtube.com/embed/Ofmzp1dp7eY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Cliffinkent (7 commits)

## Devpost submission (written by the team)

### Inspiration

Small dev teams may write / know what their features acceptance criteria is, and may even write them down, but small teams don't always have time to run the comprehensive tests needed to validate those ac's Manual QA takes time and is often an after thought at the end of a sprint. Traditional browser automation kind of works today, but it needs engineering effort and can become brittle when interfaces change. General browser agents can navigate websites, but they do not always produce the evidence a developer needs to reproduce a problem. SpecSentry started with one question: Could AI turn the requirements teams already write into a useful first test pass?

### What it does

SpecSentry turns a staging URL, user story and acceptance criteria into an evidence backed browser test. It: creates a structured test plan lets a human review and approve the plan runs the journey in an isolated Chromium browser records actions, screenshots and browser state returns pass, fail, blocked or inconclusive creates a draft finding with severity, confidence and reproduction steps lets a human edit, approve or reject the finding previews the exact GitHub issue before any external write The hosted demo is restricted to the bundled Sentry Shop example. Self-hosted deployments can test explicitly approved staging domains. How I built it ChatGPT 5.6 Sol in chat and Codex SpecSentry is a Next.js and TypeScript application deployed on Railway. The AI workflow has three separate phases: Planner GPT-5.6 Terra converts the specification into structured test steps, expected results, evidence checkpoints, retry limits and stop rules. Planner GPT-5.6 Terra converts the specification into structured test steps, expected results, evidence checkpoints, retry limits and stop rules. Executor OpenAI computer use and Playwright run the approved journey in an isolated Chromium session. The executor records what happened but cannot decide whether the test passed or failed. Executor OpenAI computer use and Playwright run the approved journey in an isolated Chromium session. The executor records what happened but cannot decide whether the test passed or failed. Evaluator A separate model call receives the original criterion, approved plan and persisted evidence. It returns a structured result and creates a finding only when the evidence supports a failure. Evaluator A separate model call receives the original criterion, approved plan and persisted evidence. It returns a structured result and creates a finding only when the evidence supports a failure. Zod validates model and API outputs. SQLite stores runs, findings and review state. Screenshots and action history provide the evidence trail. GitHub export remains behind human approval, preview and explicit confirmation. Codex was used throughout the build to design the architecture, implement features, write tests, debug the live browser loop, harden security controls and prepare the Railway deployment. Challenges I ran into Browser agents are probabilistic. They can click the wrong control, wait too long or misunderstand the visible state. I reduced this risk with: fixed browser dimensions approved test plans action and runtime limits retry ceilings exact hostname restrictions screenshots after checkpoints partial reports when execution fails Evidence integrity was another challenge. Every cited screenshot must map to a recorded browser action and a real persisted file. The interface therefore keeps captured evidence separate from AI assessment. The public deployment also needed strict controls. It blocks arbitrary external sites, private network targets, local files, downloads, pop-ups and unattended GitHub writes. Accomplishments that I'm proud of The full workflow works end to end: acceptance criterion to structured plan approved plan to live browser run browser evidence to structured finding human review to GitHub issue preview A controlled ten-case live evaluation produced: 5/5 expected passes 3/3 seeded failures 1/1 blocked result 1/1 inconclusive result 0 false failures 0 retries 0 missing screenshots 0 off-domain navigation All three failures produced findings backed by persisted screenshots and recorded actions. The final application has 71 passing unit and service tests, 16 passing Playwright tests and no production dependency vulnerabilities. I got the whole thing done while on holiday in Crete with my family (evenings while they slept and cheeky check ins via the mobile app were a lifesaver!) What I learned The model is only part of the product. The useful behaviour came from the controls around it: separate responsibilities for planning, execution and evaluation strict schemas persisted evidence clear stop conditions human approval safe domain restrictions honest handling of blocked and ambiguous criteria The strongest QA output is not a confident answer. It is a result that shows exactly what happened and gives a developer enough evidence to reproduce it.

### What's next

The next steps are: support authenticated staging accounts generate reusable Playwright tests from approved findings run checks against pull requests compare results between application builds add durable hosted storage for team use support a wider range of staging-site patterns For Build Week, the scope stayed deliberately narrow: one reliable journey from acceptance criterion to evidence-backed finding.

## README (from the GitHub repository)

# SpecSentry

SpecSentry turns a written acceptance criterion into an approved browser journey and an evidence-backed result. This repository contains the first complete vertical slice for OpenAI Build Week: the same workflow fails a deliberately defective Sentry Shop checkout and passes the corrected build.

> Acceptance criteria in. Evidence-backed bugs out.

The canonical Build Week scope and decision history is in the [product requirements document](docs/PRODUCT_REQUIREMENTS.md). This README and [STATUS.md](STATUS.md) describe the final implementation and deliberate limitations.

## What works

1. Enter a staging URL, exact allowed hostname, user story, acceptance criterion and optional starting instructions, or use **Load demo**.
2. GPT-5.6 creates a strict structured plan in a separate planner request. Invalid output receives one controlled retry.
3. Review and edit every plan field, then explicitly approve it.
4. SpecSentry opens an isolated 1440 × 900 Chromium context and executes one approved step at a time through the OpenAI computer screenshot/action loop.
5. Every normalized action, observation, timestamp and screenshot reference is persisted in SQLite and streamed to the UI over server-sent events.
6. A separate evaluator request judges only the recorded evidence and returns pass, fail, blocked or inconclusive.
7. The report separates captured browser evidence from AI assessment and traces criterion → checkpoint → action → screenshot → judgement.
8. A failed finding starts as a persisted draft with an immutable AI original, editable human copy, read-only evidence, and explicit approve/reject/reopen transitions.
9. An approved finding can render the exact GitHub title and Markdown body; a separate confirmation creates at most one issue and stores its URL.
10. Planner, computer executor and evaluator token usage is persisted and shown by phase without a cost estimate.
11. A fixed ten-case evaluation catalog runs selected cases or the complete set through a real-by-default managed workflow and emits evidence-integrity checks, controlled-set metrics, and a submission report.

The controlled fixture is hosted inside the same application at `/demo/shop`. `?mode=passing` provides the corrected journey; `?mode=defective` withholds delivery charge and final total; `?mode=validation-missing` accepts empty required delivery data; `?mode=basket-lost` drops the selected product at review; and `?mode=dependency-unavailable` exposes a defined unavailable prerequisite.

## Requirements

- Node.js 24 or newer (the repository uses the stable `node:sqlite` API)
- npm
- Chromium installed through Playwright
- An OpenAI API key for live GPT-5.6 and computer-tool runs; no key is needed for automated checks or the deterministic mocked demo

## Railway deployment

Production is one Docker-based Railway service built from `main`. The image pins Node 24 and the official Playwright `v1.61.1-noble` runtime to the exact installed Playwright version, starts the standalone Next.js server through `tini`, and defaults to the image's non-root `pwuser` account.

Stable demo URL: **[https://specsentry-production.up.railway.app](https://specsentry-production.up.railway.app)**

Create the Railway service from `Cliffinkent/specsentry`, select `main`, keep GitHub autodeploy enabled, and attach one Railway volume at `/app/data`. Configure these service variables in Railway only; never commit their values:

```dotenv
OPENAI_API_KEY=
OPENAI_MODEL=gpt-5.6-terra
SPECSENTRY_PUBLIC_DEMO=true
SPECSENTRY_DATA_DIR=/app/data
PUBLIC_APP_URL=https://specsentry-production.up.railway.app
GITHUB_TOKEN=
GITHUB_OWNER=
GITHUB_REPO=
GITHUB_REPOSITORY_ALLOWLIST=
```

Railway volumes are mounted as root. Set `RAILWAY_RUN_UID=0` so the mounted `/app/data` directory remains writable, and set `RAILWAY_SHM_SIZE_BYTES=536870912` to give Chromium a 512 MiB shared-memory segment. The image still runs as `pwuser` anywhere the volume runtime supports non-root ownership. Do not set `ALLOW_LOCALHOST`, `OPENAI_MOCK`, or `GITHUB_MOCK` in production.

Generate the stable Railway HTTPS domain first, then set `PUBLIC_APP_URL` to that exact origin and redeploy. `railway.json` configures `/api/health`, a 180-second startup health window, bounded crash restarts and a 30-second SIGTERM drain. The health route only checks that the SQLite parent and screenshot directories are writable; it does not call OpenAI, open SQLite, or launch Chromium.

Production verification on 20 July 2026 completed a real defective Build Week run at the stable origin. Run `0fc601eb-d273-4c70-af47-b77fac2ba99e` produced the expected high-severity finding with 24 recorded actions and 24 screenshots, retained its approved review and 1440 x 900 evidence after a controlled Railway redeploy, and generated the exact GitHub preview without creating an issue.

With `SPECSENTRY_PUBLIC_DEMO=true`, both plan and run APIs accept only the exact deployed `/demo/shop?mode=...` fixture on `PUBLIC_APP_URL`. The server applies stricter per-client and global request budgets, permits one active browser run, blocks other origins and fixture paths, and rejects GitHub issue creation at the service boundary. Exact GitHub preview remains available after all GitHub variables are deliberately configured, but the public demo cannot create an issue even with a valid token.

Local Docker proof uses a disposable mounted directory:

```bash
docker build -t specsentry:railway .
SPECSENTRY_SMOKE_DATA=$(mktemp -d)
chmod 0777 "$SPECSENTRY_SMOKE_DATA"
docker run --rm --init --name specsentry-smoke -p 3000:3000 \
  -e SPECSENTRY_PUBLIC_DEMO=true \
  -e SPECSENTRY_DATA_DIR=/app/data \
  -e PUBLIC_APP_URL=https://specsentry.example \
  -v "$SPECSENTRY_SMOKE_DATA:/app/data" \
  specsentry:railway
curl --fail http://127.0.0.1:3000/
curl --fail http://127.0.0.1:3000/api/health
```

The root Dockerfile, `.dockerignore`, `railway.json`, `/api/health`, volume-backed SQLite/WAL files and screenshot tree are the complete deployment surface. There is no pre-deploy database command and no destructive startup migration.

## Setup

```bash
npm install
npx playwright install chromium
cp .env.example .env.local
```

For the deterministic local demo, edit `.env.local`:

```dotenv
OPENAI_MOCK=true
ALLOW_LOCALHOST=true
SPECSENTRY_DATA_DIR=./data
```

Then start the one Node application:

```bash
npm run dev
```

Open [http://localhost:3000](http://localhost:3000), choose a fixture build, click **Load demo**, generate the plan, review it, and click **Approve plan & start run**. For the submission journey, **Load Build Week demo** fills the defective delivery-charge story in one click. Screenshots and `specsentry.sqlite` are created below the gitignored `data` directory. Recent reports can be reopened from the home screen without rerunning the test.

## Human review and GitHub export

Only failed findings enter review. Edit title, severity, summary, expected result, actual result, reproduction steps and suggested next test, then save the draft. Evidence identifiers are captured data and cannot be edited. Drafts may be approved or rejected; rejected findings can be reopened. Approval itself never calls GitHub.

Configure the server-only export boundary in `.env.local`:

```dotenv
GITHUB_TOKEN=your-fine-grained-token
GITHUB_OWNER=your-owner
GITHUB_REPO=your-repository
GITHUB_REPOSITORY_ALLOWLIST=your-owner/your-repository
PUBLIC_APP_URL=https://your-specsentry-origin.example
```

Use a fine-grained token scoped to the selected repository with **Issues: write** permission. `PUBLIC_APP_URL` must use HTTPS except for localhost development. None of these variables may use a `NEXT_PUBLIC_` prefix. Restart the server after changing them.

From an approved failed report, click **Preview exact GitHub issue**. The server generates the exact title and escaped Markdown body, including absolute report/evidence links and source attribution. Review it, check the separate confirmation, then click **Confirm and creat

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 86 recognized source files, 402 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Docker (technology) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (96 of 96)

```
.dockerignore
.env.example
.gitignore
AGENTS.md
app/api/evidence/[runId]/[file]/route.ts
app/api/health/route.ts
app/api/plans/route.ts
app/api/runs/[id]/cancel/route.ts
app/api/runs/[id]/events/route.ts
app/api/runs/[id]/github/export/route.ts
app/api/runs/[id]/github/preview/route.ts
app/api/runs/[id]/review/[transition]/route.ts
app/api/runs/[id]/review/route.ts
app/api/runs/[id]/route.ts
app/api/runs/route.ts
app/demo/shop/page.tsx
app/demo/shop/shop-journey.tsx
app/globals.css
app/layout.tsx
app/page.tsx
components/specsentry-app.tsx
data/evaluation-results-2026-07-20T16-39-34-803Z.json
Dockerfile
docs/BUILD_PLAN.md
docs/DEMO_SCRIPT.md
docs/EVALUATION_REPORT.md
docs/PRODUCT_REQUIREMENTS.md
eslint.config.mjs
lib/ai/mock-provider.ts
lib/ai/openai-provider.ts
lib/ai/services.ts
lib/ai/types.ts
lib/evaluation/artifacts.ts
lib/evaluation/cases.ts
lib/evaluation/evidence.ts
lib/evaluation/managed-config.ts
lib/evaluation/managed-file.ts
lib/evaluation/results.ts
lib/evaluation/runner-config.ts
lib/executor/keyboard-policy.ts
lib/executor/limits.ts
lib/executor/run-executor.ts
lib/executor/runtime-lifecycle.ts
lib/github/client.ts
lib/github/config.ts
lib/github/export-service.ts
lib/github/markdown.ts
lib/health.ts
lib/report.ts
lib/repository.ts
lib/schemas.ts
lib/security/public-demo.ts
lib/security/rate-limit.ts
lib/security/redaction.ts
lib/security/request-policy.ts
lib/security/url-policy.ts
lib/storage.ts
next-env.d.ts
next.config.ts
package.json
playwright.config.ts
postcss.config.mjs
railway.json
README.md
scripts/evaluate.ts
scripts/live-smoke.ts
scripts/live-vertical.ts
STATUS.md
tests/e2e/shop-evaluation.spec.ts
tests/e2e/shop.spec.ts
tests/e2e/vertical-slice.spec.ts
tests/unit/ai-services.test.ts
tests/unit/evaluation-artifacts.test.ts
tests/unit/evaluation-cases.test.ts
tests/unit/evaluation-evidence.test.ts
tests/unit/evaluation-managed-config.test.ts
tests/unit/evaluation-managed-file.test.ts
tests/unit/evaluation-metrics.test.ts
tests/unit/evaluation-provider.test.ts
tests/unit/evaluation-runner-config.test.ts
tests/unit/github-export.test.ts
tests/unit/health.test.ts
tests/unit/keyboard-policy.test.ts
tests/unit/limits.test.ts
tests/unit/openai-provider.test.ts
tests/unit/public-demo-notice.test.ts
tests/unit/public-demo.test.ts
tests/unit/rate-limit.test.ts
tests/unit/redaction.test.ts
tests/unit/report.test.ts
tests/unit/repository-review.test.ts
tests/unit/request-policy.test.ts
tests/unit/schemas.test.ts
tests/unit/url-policy.test.ts
tsconfig.json
vitest.config.ts
```

### Dependencies

- package.json: @playwright/test@1.61.1, @tailwindcss/postcss@^4.0.0, @types/node@^24.0.0, @types/react@^19.0.0, @types/react-dom@^19.0.0, eslint@^9.0.0, eslint-config-next@^16.0.0, next@^16.0.0, openai@^6.0.0, playwright@1.61.1, postcss@8.5.19, react@^19.0.0, react-dom@^19.0.0, tailwindcss@^4.0.0, tsx@^4.0.0, typescript@^5.9.0, vitest@^4.0.0, zod@^4.0.0

### Recent commits (newest first)

- docs: publish final Build Week product requirements
- feat: clarify public demo domain restriction
- Document live Railway demo
- Fix Railway drain config type
- Deploy safe Railway public demo
- feat: add controlled evaluation suite and Build Week demo
- Initial SpecSentry vertical slice with verified GitHub export

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

### AGENTS.md

```markdown
# SpecSentry Agent Guide

## Architecture

- One Next.js TypeScript App Router application owns the UI, API routes, Sentry Shop fixture, run orchestrator and report.
- OpenAI integration uses the Node SDK and Responses API. Planner, computer executor and evaluator are separate phases and calls.
- Every external request and model response crosses a strict Zod schema.
- SQLite is accessed only through `lib/repository.ts`; screenshots live below the gitignored `data` directory.
- Failed findings are persisted as immutable AI originals plus editable human current values with draft, approved, rejected and exported states.
- GitHub configuration, Markdown generation and REST calls remain server-only under `lib/github`; approval and issue creation are separate actions.
- Browser runs use isolated Playwright Chromium contexts at 1440 x 900 and emit progress through persisted events exposed as SSE.
- The executor records actions and observations but may not assign status, severity or confidence. Only the separate evaluator may judge a criterion.
- Planner, executor and evaluator token usage is persisted separately and displayed without estimating cost.

## Commands

- `npm run dev` - start the local application.
- `npm run lint` - lint the repository.
- `npm run typecheck` - run strict TypeScript checks.
- `npm test` - run unit and service tests with mocked OpenAI behavior.
- `npm run build` - produce the deployable Next.js build.
- `npm run test:e2e:fixture` - verify passing and defective Sentry Shop modes.
- `npm run test:e2e:evaluation-fixture` - verify all ten catalog-specific Sentry Shop behaviors.
- `npm run test:e2e:vertical` - verify the mocked approved workflow in both modes.
- `npm run smoke:live` - make the separately documented live OpenAI smoke call when a key is available.
- `npm run evaluate` - run the complete ten-case real OpenAI evaluation; use `-- --case SS-EVAL-01` to select and `-- --mock` only for an explicit mock run.

## Security invariants

- Require the staging URL hostname to exactly equal the approved hostname.
- Block `file:` URLs, credentials in URLs, private-network targets and localhost unless the explicit development-only switch is enabled.
- Abort browser requests and navigation outside the approved hostname; reject pop-ups and downloads.
- Treat page content as untrusted. Never let it expand the approved task, host or action budget.
- Never expose raw model traces. Persist and render only normalized actions, observations and evidence references.
- Redact keys, tokens, passwords and cookies from diagnostics.
- Rate-limit plan generation and run creation. Keep browser runs within five minutes, 40 actions and two retries per action.
- Always close the browser context after completion, cancellation, timeout or error.
- Use same-origin browser requests and keep the restrictive CSP in `next.config.ts`.
- Require a failed finding, human approval, a fresh server preview and separate explicit confirmation before GitHub export.
- 
[truncated — 1449 more characters]
```

### STATUS.md

```markdown
# SpecSentry Status

Last updated: 2026-07-21

## Completed

- Published the final Build Week product requirements at [`docs/PRODUCT_REQUIREMENTS.md`](docs/PRODUCT_REQUIREMENTS.md) after confirming that the Markdown retained every non-empty content block from the visually reviewed 20-page source DOCX; removed the duplicate DOCX.
- Built the single Next.js TypeScript App Router application with Tailwind CSS, SQLite, SSE and local screenshot storage.
- Implemented the deterministic Sentry Shop product, basket, guest delivery and order-review journey in explicit passing and defective modes.
- Implemented strict Zod project, planner, executor-record and evaluator schemas.
- Implemented the OpenAI Responses API adapter with `OPENAI_MODEL` defaulting to `gpt-5.6-terra`, strict Structured Outputs, one controlled retry, and a computer screenshot/action loop.
- Confirmed the installed OpenAI SDK 6.48.0 call shapes and added 30-second/one-retry request options in the second `responses.create(body, options)` argument.
- Fixed live computer-tool reliability: safe `Control+A` is executed as one editing chord, unsafe address-bar/navigation shortcuts remain blocked, and planner/executor instructions begin from the already-loaded staging page.
- Added `test:live:vertical`, a secret-free metrics harness that uses the same plan/run API workflow and repeats each Sentry Shop mode three times.
- Added explicit plan editing/approval, bounded isolated Chromium execution, cancellation, partial persistence and separate evidence-constrained evaluation.
- Added a report with duration, result, expected/actual result, reproduction steps, screenshot gallery, ordered action history and criterion-to-judgement evidence trace.
- Added in-place SQLite migration for original/current finding review data, draft/approved/rejected/exported states, timestamps, idempotency and GitHub issue URL.
- Added editable failed-finding review with save, approve, reject and reopen; evidence remains read-only and run-owned.
- Added server-only, repository-allow-listed GitHub configuration; exact escaped Markdown preview; separate explicit confirmation; atomic export claim; marker recovery; stored-URL idempotency; and safe retry behavior.
- Added planner receipts plus planner/executor/evaluator token-usage capture and report display without cost estimation.
- Added an exactly ten-case controlled evaluation catalog: five positive passes, three distinct seeded failures, one unavailable-prerequisite block, and one deliberately ambiguous criterion.
- Added a real-by-default `npm run evaluate` harness with selected-case support, an explicit `--mock` flag, managed server shutdown, case/run persistence, action/file evidence integrity checks, controlled metrics, and clear missing-key failure.
- Added deterministic missing-validation, basket-loss, and dependency-unavailable fixture modes while preserving the original passing/defective journeys.
- Added **Load Build Week demo** plus a maximum-2:50 recording sc
[truncated — 11300 more characters]
```

### Dockerfile

```
ARG PLAYWRIGHT_VERSION=1.61.1

FROM node:24-bookworm-slim AS dependencies
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM dependencies AS builder
ENV NEXT_TELEMETRY_DISABLED=1
COPY . .
RUN npm run build

FROM node:24-bookworm-slim AS node-runtime

FROM mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-noble AS runner
USER root
RUN apt-get update \
  && apt-get install -y --no-install-recommends tini \
  && rm -rf /var/lib/apt/lists/*
COPY --from=node-runtime /usr/local/bin/node /usr/local/bin/node

WORKDIR /app
ENV NODE_ENV=production \
  NEXT_TELEMETRY_DISABLED=1 \
  HOSTNAME=0.0.0.0 \
  PORT=3000 \
  PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \
  SPECSENTRY_DATA_DIR=/app/data

RUN install -d -o pwuser -g pwuser -m 0700 /app/data /app/data/screenshots
COPY --from=builder --chown=pwuser:pwuser /app/.next/standalone ./
COPY --from=builder --chown=pwuser:pwuser /app/.next/static ./.next/static

USER pwuser
EXPOSE 3000
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["node", "server.js"]

```

### package.json

```
{
  "name": "specsentry",
  "version": "0.1.0",
  "private": true,
  "engines": {
    "node": ">=24.0.0"
  },
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint .",
    "typecheck": "tsc --noEmit",
    "test": "vitest run",
    "test:watch": "vitest",
    "test:e2e": "playwright test",
    "test:e2e:fixture": "playwright test tests/e2e/shop.spec.ts",
    "test:e2e:evaluation-fixture": "playwright test tests/e2e/shop-evaluation.spec.ts",
    "test:e2e:vertical": "playwright test tests/e2e/vertical-slice.spec.ts",
    "test:live:vertical": "node --env-file-if-exists=.env.local --import tsx scripts/live-vertical.ts",
    "evaluate": "node --env-file-if-exists=.env.local --import tsx scripts/evaluate.ts",
    "smoke:live": "node --env-file-if-exists=.env.local --import tsx scripts/live-smoke.ts"
  },
  "dependencies": {
    "next": "^16.0.0",
    "openai": "^6.0.0",
    "playwright": "1.61.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "zod": "^4.0.0"
  },
  "devDependencies": {
    "@playwright/test": "1.61.1",
    "@tailwindcss/postcss": "^4.0.0",
    "@types/node": "^24.0.0",
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0",
    "eslint": "^9.0.0",
    "eslint-config-next": "^16.0.0",
    "postcss": "8.5.19",
    "tailwindcss": "^4.0.0",
    "tsx": "^4.0.0",
    "typescript": "^5.9.0",
    "vitest": "^4.0.0"
  },
  "overrides": {
    "postcss": "8.5.19"
  }
}

```

### app/page.tsx

```typescript
import { SpecSentryApp } from "@/components/specsentry-app";
import { isPublicDemoMode } from "@/lib/security/public-demo";

export const dynamic = "force-dynamic";

export default function HomePage() {
  return <SpecSentryApp publicDemo={isPublicDemoMode()} />;
}

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
  title: "SpecSentry",
  description: "Acceptance criteria in. Evidence-backed bugs out.",
};

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

```

### app/api/health/route.ts

```typescript
import { NextResponse } from "next/server";
import { checkStorageHealth } from "@/lib/health";
import { isPublicDemoMode } from "@/lib/security/public-demo";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

export async function GET() {
  try {
    await checkStorageHealth();
    return NextResponse.json(
      { status: "ok", storage: "writable", mode: isPublicDemoMode() ? "public-demo" : "standard" },
      { headers: { "Cache-Control": "no-store" } },
    );
  } catch {
    console.error("[SpecSentry:health] Persistent storage is not writable.");
    return NextResponse.json(
      { status: "error", storage: "unavailable" },
      { status: 503, headers: { "Cache-Control": "no-store" } },
    );
  }
}

```

### app/demo/shop/page.tsx

```typescript
import Link from "next/link";
import { demoModeSchema } from "@/lib/schemas";
import { ShopJourney } from "./shop-journey";

type ShopPageProps = {
  searchParams: Promise<Record<string, string | string[] | undefined>>;
};

export default async function ShopPage({ searchParams }: ShopPageProps) {
  const rawMode = (await searchParams).mode;
  const mode = demoModeSchema.safeParse(Array.isArray(rawMode) ? rawMode[0] : rawMode);

  if (!mode.success) {
    return (
      <main className="mx-auto min-h-screen max-w-4xl px-6 py-14">
        <p className="text-xs font-bold uppercase tracking-[0.2em] text-[var(--teal)]">Sentry Shop fixture</p>
        <h1 className="mt-4 text-5xl font-black tracking-[-0.04em]">Choose an explicit demo build.</h1>
        <p className="mt-5 max-w-2xl text-lg leading-8 text-[var(--muted)]">Fixture behavior is never selected implicitly. Use one of the validated modes below.</p>
        <div className="mt-10 flex flex-wrap gap-4">
          <Link className="bg-[var(--ink)] px-5 py-3 font-bold text-white" href="/demo/shop?mode=defective">Open defective build</Link>
          <Link className="border border-[var(--ink)] bg-[var(--surface)] px-5 py-3 font-bold" href="/demo/shop?mode=passing">Open passing build</Link>
          <Link className="border border-[var(--ink)] bg-[var(--surface)] px-5 py-3 font-bold" href="/demo/shop?mode=validation-missing">Open missing-validation build</Link>
          <Link className="border border-[var(--ink)] bg-[var(--surface)] px-5 py-3 font-bold" href="/demo/shop?mode=basket-lost">Open basket-loss build</Link>
          <Link className="border border-[var(--ink)] bg-[var(--surface)] px-5 py-3 font-bold" href="/demo/shop?mode=dependency-unavailable">Open unavailable-dependency build</Link>
        </div>
      </main>
    );
  }

  return <ShopJourney mode={mode.data} />;
}

```

### app/api/plans/route.ts

```typescript
import { NextResponse } from "next/server";
import { generatePlanWithUsage } from "@/lib/ai/services";
import { getRepository } from "@/lib/repository";
import { newTestInputSchema } from "@/lib/schemas";
import { PublicDemoConfigurationError, PublicDemoRequestError, assertPublicDemoInput } from "@/lib/security/public-demo";
import { takePlanningRateLimit } from "@/lib/security/rate-limit";
import { logServerError, safeUserError } from "@/lib/security/redaction";
import { assertSameOriginRequest, readJsonBody } from "@/lib/security/request-policy";
import { assertApprovedUrl, developmentLocalhostAllowed } from "@/lib/security/url-policy";

export const runtime = "nodejs";

export async function POST(request: Request) {
  const limit = takePlanningRateLimit(request);
  if (!limit.allowed) {
    return NextResponse.json(
      { error: "Too many plan requests. Try again shortly." },
      { status: 429, headers: { "Retry-After": String(limit.retryAfterSeconds) } },
    );
  }

  try {
    assertSameOriginRequest(request);
    const input = newTestInputSchema.parse(await readJsonBody(request));
    assertPublicDemoInput(input);
    assertApprovedUrl(input.stagingUrl, input.allowedHostname, {
      allowDevelopmentLocalhost: developmentLocalhostAllowed(),
    });
    const { plan, usage } = await generatePlanWithUsage(input);
    const planId = getRepository().createPlanReceipt(input, usage);
    return NextResponse.json({ plan, planId });
  } catch (error) {
    logServerError("plan", error);
    const validationError = error instanceof PublicDemoRequestError || error instanceof Error && (error.name === "ZodError" || /JSON request body|Cross-origin/.test(error.message));
    if (error instanceof PublicDemoConfigurationError) {
      return NextResponse.json({ error: "The public demo is temporarily unavailable." }, { status: 503 });
    }
    return NextResponse.json(
      { error: validationError ? "Check the test details and try again." : safeUserError(error, "The plan could not be generated. Try again shortly.") },
      { status: validationError ? 400 : 502 },
    );
  }
}

```

### app/api/runs/route.ts

```typescript
import { NextResponse } from "next/server";
import { executeRun } from "@/lib/executor/run-executor";
import { hasActiveRuns } from "@/lib/executor/runtime-lifecycle";
import { buildRunReport } from "@/lib/report";
import { getRepository } from "@/lib/repository";
import { runCreationRequestSchema } from "@/lib/schemas";
import { PublicDemoConfigurationError, PublicDemoRequestError, assertPublicDemoInput, isPublicDemoMode } from "@/lib/security/public-demo";
import { takeRunCreationRateLimit } from "@/lib/security/rate-limit";
import { logServerError, safeUserError } from "@/lib/security/redaction";
import { assertSameOriginRequest, readJsonBody } from "@/lib/security/request-policy";
import { assertSafeNetworkTarget, developmentLocalhostAllowed } from "@/lib/security/url-policy";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

export async function GET() {
  const runs = getRepository().listRecentRuns(12).map(buildRunReport);
  return NextResponse.json({ runs }, { headers: { "Cache-Control": "no-store" } });
}

export async function POST(request: Request) {
  const limit = takeRunCreationRateLimit(request);
  if (!limit.allowed) {
    return NextResponse.json(
      { error: "Too many run requests. Try again shortly." },
      { status: 429, headers: { "Retry-After": String(limit.retryAfterSeconds) } },
    );
  }

  try {
    assertSameOriginRequest(request);
    const runRequest = runCreationRequestSchema.parse(await readJsonBody(request));
    assertPublicDemoInput(runRequest.input);
    await assertSafeNetworkTarget(runRequest.input.stagingUrl, runRequest.input.allowedHostname, {
      allowDevelopmentLocalhost: developmentLocalhostAllowed(),
    });
    if (isPublicDemoMode() && hasActiveRuns()) {
      return NextResponse.json({ error: "A public demo run is already in progress. Try again shortly." }, { status: 429 });
    }
    const repository = getRepository();
    const id = crypto.randomUUID();
    repository.createRun(id, runRequest);
    void executeRun(id, { repository }).catch((error) => {
      logServerError(`run:${id}`, error);
      repository.appendError(id, "The run stopped because of an internal error.");
      repository.setStatus(id, "error", true);
    });
    return NextResponse.json({ id }, { status: 202 });
  } catch (error) {
    logServerError("run-create", error);
    const validationError = error instanceof PublicDemoRequestError || error instanceof Error && (error.name === "ZodError" || /JSON request body|Cross-origin/.test(error.message));
    if (error instanceof PublicDemoConfigurationError) {
      return NextResponse.json({ error: "The public demo is temporarily unavailable." }, { status: 503 });
    }
    return NextResponse.json(
      { error: validationError ? "The approved plan or project details are invalid." : safeUserError(error, "The run could not be started.") },
      { status: validationError ? 400 : 422 },
    );
  }
}

```

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