From 1e8a924c4c7741e0043a22328c0ee5cf0f2eeed4 Mon Sep 17 00:00:00 2001 From: Ben Senescu <44480372+bensenescu@users.noreply.github.com> Date: Thu, 30 Jul 2026 00:51:50 -0400 Subject: [PATCH] Site Audit - Improve reliability and performance with Durable Objects --- .agents/PAPERCUTS.md | 12 + alchemy.run.ts | 6 +- badseo/public/styles.css | 2 +- badseo/src/routeTree.gen.ts | 7 +- badseo/src/routes/index.tsx | 2 +- drizzle-pg/0014_solid_centennial.sql | 3 + drizzle-pg/meta/0014_snapshot.json | 3732 +++++++++++++++++ drizzle-pg/meta/_journal.json | 7 + drizzle/0037_small_caretaker.sql | 3 + drizzle/meta/0037_snapshot.json | 3401 +++++++++++++++ drizzle/meta/_journal.json | 7 + package.json | 3 +- pnpm-lock.yaml | 9 +- specs/0009-site-audit-crawl-architecture.md | 125 + src/client/features/audit/shared.tsx | 2 +- src/db/audit.schema.ts | 7 + src/db/pg/audit.schema.ts | 7 + src/db/schema-parity.test.ts | 4 + src/env.d.ts | 4 + .../_project/p/$projectId/audit/index.tsx | 50 +- src/server.ts | 6 + src/server/features/audit/AuditScratchpad.ts | 372 ++ .../audit/repositories/AuditRepository.ts | 90 +- .../features/audit/services/AuditService.ts | 43 +- .../audit/services/auditReconciler.ts | 176 + src/server/lib/audit/audit-errors.ts | 79 + src/server/lib/audit/crawl-window.test.ts | 100 + src/server/lib/audit/crawl-window.ts | 50 + src/server/lib/audit/discovery.ts | 48 +- src/server/lib/audit/issues/multipage.ts | 130 +- .../lib/audit/issues/page-reporters.test.ts | 1 + src/server/lib/audit/issues/page-reporters.ts | 2 +- src/server/lib/audit/page-analyzer.test.ts | 208 + src/server/lib/audit/page-analyzer.ts | 273 +- src/server/lib/audit/types.ts | 28 +- src/server/lib/audit/url-policy.test.ts | 88 +- src/server/lib/audit/url-policy.ts | 45 + src/server/mcp/tools/site-audit-tools.ts | 12 +- src/server/workflows/SiteAuditWorkflow.ts | 22 +- src/server/workflows/auditStepConfigs.ts | 40 + .../workflows/site-audit-workflow-helpers.ts | 15 +- .../workflows/siteAuditWorkflowCrawl.ts | 517 ++- .../workflows/siteAuditWorkflowPhases.ts | 204 +- wrangler.jsonc | 15 +- 44 files changed, 9282 insertions(+), 675 deletions(-) create mode 100644 drizzle-pg/0014_solid_centennial.sql create mode 100644 drizzle-pg/meta/0014_snapshot.json create mode 100644 drizzle/0037_small_caretaker.sql create mode 100644 drizzle/meta/0037_snapshot.json create mode 100644 specs/0009-site-audit-crawl-architecture.md create mode 100644 src/server/features/audit/AuditScratchpad.ts create mode 100644 src/server/features/audit/services/auditReconciler.ts create mode 100644 src/server/lib/audit/audit-errors.ts create mode 100644 src/server/lib/audit/crawl-window.test.ts create mode 100644 src/server/lib/audit/crawl-window.ts create mode 100644 src/server/lib/audit/page-analyzer.test.ts create mode 100644 src/server/workflows/auditStepConfigs.ts diff --git a/.agents/PAPERCUTS.md b/.agents/PAPERCUTS.md index 75df0c3..a3ac2b7 100644 --- a/.agents/PAPERCUTS.md +++ b/.agents/PAPERCUTS.md @@ -20,3 +20,15 @@ data, or sensitive paths. ## Resolved Move fixed entries here, mark them checked, and append the resolving date or commit. + +## badseo harness vs `wrangler dev`: sitemap emits badseo.dev locs locally + +`badseo/scripts/run-audit.ts` against a local `wrangler dev --port 8787` fails 4 +sitemap-dependent checks (orphan page, 500, 403, duplicate-content) with +NOT CRAWLED: wrangler dev adopts the `badseo.dev` custom-domain route as the +host the worker sees, so `/sitemap.xml` emits `http://badseo.dev/...` locs that +the crawler's same-origin filter drops. Run it as +`wrangler dev --port 8787 --local-upstream "localhost:8787"` (after +`vite build`). Also: `pnpm --filter badseo audit` fails with +"Unknown option: 'recursive'" from the repo root — badseo is its own pnpm +workspace, not a root workspace member; use `npx tsx badseo/scripts/run-audit.ts`. diff --git a/alchemy.run.ts b/alchemy.run.ts index 8a6f722..559ea63 100644 --- a/alchemy.run.ts +++ b/alchemy.run.ts @@ -400,8 +400,10 @@ export default Alchemy.Stack( // Prod-only: pooled Postgres via the existing Hyperdrive config. ...(prod ? { HYPERDRIVE: makeHyperdrive() } : {}), - // Durable Objects (Agents SDK chat agents). Alchemy backs new DO - // classes with SQLite storage, which both require. + // Durable Objects (chat agents + the per-audit crawl scratchpad). + // Alchemy backs new DO classes with SQLite storage, which all of + // them require; the `migrations` array in wrangler.jsonc only + // applies to the wrangler/workerd surfaces (local dev, Docker). ...Object.fromEntries( wrangler.durable_objects.bindings.map((binding) => [ binding.name, diff --git a/badseo/public/styles.css b/badseo/public/styles.css index 3c33de0..48a3425 100644 --- a/badseo/public/styles.css +++ b/badseo/public/styles.css @@ -46,7 +46,7 @@ a:focus-visible, button:focus-visible { outline: 3px solid #ff6a57; outline-offs .home-copy > p { max-width: 860px; margin: 42px 0 0; font-family: var(--serif); font-size: clamp(28px, 2.2vw, 32px); line-height: 1.08; } .home-issues { scroll-margin-top: 24px; border-top: 3px solid var(--navy); } .home-issue-group { margin: 22px 0 54px; } -.home-issue-group h3 { margin: 0; padding: 18px 0 14px; border-bottom: 1px solid var(--line); font-family: var(--condensed); font-size: 30px; line-height: 1; } +.home-issue-group h2 { margin: 0; padding: 18px 0 14px; border-bottom: 1px solid var(--line); font-family: var(--condensed); font-size: 30px; line-height: 1; } .home-case-head, .home-case-row { display: grid; grid-template-columns: minmax(220px, .72fr) minmax(320px, 1.28fr); gap: clamp(30px, 6vw, 90px); } .home-case-head { padding: 11px 0; border-bottom: 1px solid var(--line-soft); color: var(--muted); font-family: var(--mono); font-size: 8px; } .home-case-row { min-height: 72px; align-items: center; padding: 16px 0; border-bottom: 1px solid var(--line-soft); text-decoration: none; } diff --git a/badseo/src/routeTree.gen.ts b/badseo/src/routeTree.gen.ts index cf879fc..4db5807 100644 --- a/badseo/src/routeTree.gen.ts +++ b/badseo/src/routeTree.gen.ts @@ -75,7 +75,12 @@ export interface FileRoutesById { export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: - '/' | '/$' | '/catalog' | '/privacy' | '/robots.txt' | '/sitemap.xml' + | '/' + | '/$' + | '/catalog' + | '/privacy' + | '/robots.txt' + | '/sitemap.xml' fileRoutesByTo: FileRoutesByTo to: '/' | '/$' | '/catalog' | '/privacy' | '/robots.txt' | '/sitemap.xml' id: diff --git a/badseo/src/routes/index.tsx b/badseo/src/routes/index.tsx index 46c8bbb..05c7a71 100644 --- a/badseo/src/routes/index.tsx +++ b/badseo/src/routes/index.tsx @@ -41,7 +41,7 @@ function HomePage() {
{homepageCategories.map((category) => (
-

{category}

+

{category}