← All work

Case study · In-house product

M7SEO

Most SEO tools hand you a list of problems and leave you to fix them. M7SEO finds what is holding a site back, works out the fix, writes it as a pull request against the site's own source, and then stops – every change waits for a human to approve it. We built it for ourselves, and method7.co.uk is the site it runs against.

Role
Product owner & sole engineer
Status
Live pilot · in active development
Stack
Next.js 15 · Postgres 17 · Drizzle · Claude
Pilot site
method7.co.uk
An M7SEO recommendation: what was found, on which competitors' sites, what to do about it, and accept or reject buttons
A recommendation is not a score out of a hundred. It is what was found, on whose sites, what to do about it, how confident it is, and one click to accept or reject.
M7SEO competitors view, stating plainly that the list is not a ranking
Competitors are found by searching the web, then each site is checked automatically: does it load, is it a real business, does it do this work in this area. It says plainly that it is not a ranking, because it cannot see Google's results and will not pretend to.
An M7SEO plan marked out of date, naming the commit it was written against
A plan written against an older version of the site refuses to be applied, and names both commits rather than editing lines that may no longer exist.

Design

Two rules the whole thing is built on

Everything is scoped by an explicit context

The platform runs one site today and many later. That is only safe because the data is already tenant-shaped, and because nothing reads the current website from a global or an environment variable. Ambient tenancy is how multi-tenant systems leak data between customers, and it is far harder to remove later – while there is one site and the bug is invisible – than to avoid now. The sole-website resolver throws the moment a second site exists, so every place that assumed one fails loudly rather than quietly mixing two.

Claude reasons, code calculates

Priority scoring, cannibalisation detection and click-through curve fitting are deterministic SQL, not prompts. The agents supply judgement – is this query commercially relevant, is this the right page to own it, how much work is this – and their answers are schema-validated before anything is persisted. An agent never supplies a priority number, because a number nobody can reproduce is not a number.

Architecture

A pnpm monorepo, two apps, two packages

Roughly 18,000 lines of TypeScript. The queue lives in Postgres, the domain logic lives in one package both apps import, and the scheduler builds its crontab per active website at boot – because every job has to name the site it acts on.

  • apps/web

    Next.js 15 console. Server components and server actions, no internal HTTP API – adding one before an external consumer exists buys serialisation and versioning cost for nothing.

  • apps/worker

    Graphile Worker. The queue lives in Postgres, so a job and the rows it writes commit together and there is no second system to keep in sync.

  • packages/core

    The domain layer: crawling, Search Console import, strategy, scoring, competitor research, planning, policy and audit. Both apps call it directly.

  • packages/db

    Drizzle schema, migrations and seed. Every domain table carries a website id and every unique constraint is composite.

What it demonstrates

Agents you can audit

Crawling that stays cheap as it speeds up

  • robots.txt with longest-match Allow/Disallow and crawl-delay, sitemap discovery, byte-capped streaming reads and per-page failure isolation.
  • The internal link graph is resolved after the crawl rather than during it, so it does not depend on the order pages happened to be fetched.
  • Content is hashed over title, description, canonical, headings and body text – deliberately not raw HTML, so a rotating build hash in an asset filename cannot defeat it. Unchanged pages are the reason agent spend stays flat as crawl frequency rises.

Search data imported honestly

  • Search Console over plain fetch, no SDK in the domain layer, with an incremental import that resumes from the newest stored date.
  • Only finalised data is imported, so numbers Google later revises never enter the store, and sampled or capped responses are tracked as partial rather than treated as complete.
  • Search Console serves roughly sixteen months. Data that ages out before the first import is unrecoverable, which makes connecting the one task with a real deadline attached.

Scoring that can be argued with

  • Every score records the version of the function that produced it, so scores are only ever compared within a version and the function can be backtested against outcomes later.
  • Two corrections the current version makes over the first, both found by running it on real data: brand searches are discounted, because someone searching your company name finds you at position seven as readily as position one; and a title rewrite is scored as a click-rate gain at the current position rather than as a ranking it cannot win.
  • Each opportunity records whether its click rate was measured or assumed, whether a human confirmed its theme weight, and where value-per-visit came from. Only when none of it rests on an assumption is it marked fully grounded.

Changes that arrive as pull requests

  • A content adapter reads the site's own source, drafts the change in the conventions already there, and opens a pull request through a GitHub App – proposal rights only, never publish rights.
  • A plan is pinned to the commit it was written against. If the site has moved on, it refuses to be applied and says which commit it was written for and which one the site is on now.
  • Accepting or rejecting writes a status and a reason, and rejected opportunities are never re-proposed – matched by a fingerprint that survives regeneration.

Governance built in from the first commit

  • An execution policy engine that fails closed on any action type it has not been taught to classify.
  • An audit trail carrying the evidence behind each decision, and per-website, per-agent token accounting logged from the very first Claude call, because it cannot be backfilled.
  • AES-256-GCM credential encryption: integration secrets live in the database, never in an environment file.

Role & context

M7SEO is our own product, not client work. We designed and built it single-handed, and method7.co.uk is the pilot site it runs against – including several of the changes on this site's own sitemap, structured data and machine-readable output. Running it against ourselves first is the point: the corrections in the scoring function were found by watching it mis-rank our own pages, which is not a thing a demo site would have told us.

It is in active development rather than finished. Observation, strategy, competitor research, opportunity scoring and change planning are built and running; multi-site management, roles and billing are deliberately last.

Want this pointed at your site?