Decisions
Architecture decision records for choices this implementation made, including the Phase 0 decisions the specification left open.
ADR-0001: Name, package and binaries
Status: Accepted (resolves spec OPEN-Q1 provisionally; trademark check still open)
Context
The specification uses `geo-check` as a provisional package and binary name and asks the owner to choose. The owner named the repository `geopilot`.
Decision
- The npm package is `geopilot`. It ships one binary, `geopilot`. The `geo-check` alias was removed before anything was published, so no installed command depends on it.
- The project was first built as `trace-rank` and renamed with the repository before anything was published.
- Contract names stay as the specification defines them: `geo.config.json`, `.geo/`, rule ids `GEO-xxx`, schemas `geo-report/v1`, `geo-insights/v1`, `geo-merged/v1`, environment variables `GEO_*`, the skill name `geo`.
- The docs and schema domain is GitHub Pages: `https://moelzanaty3.github.io/geopilot/` (resolves OPEN-Q8).
Consequences
- Every report carries the disclaimer, and the docs say readiness is not a ranking.
- A trademark and availability check is still needed before a public 1.0.
ADR-0002: Parser and renderer
Status: Accepted (resolves spec OPEN-Q5 for this release)
Decision
- TypeScript/JSX source is parsed with `@babel/parser`, pinned with its only dependency (`@babel/types`, held by a workspace override). It is pure JavaScript, needs no native binaries, and gives exact offsets, which is what byte-minimal splices need.
- HTML, robots.txt and sitemap parsing are written in-tree: each needs a small, well-tested subset, and every dependency is one users inherit.
- No browser renderer ships. GEO-003 and GEO-002 report `not_evaluated` with `renderer_unavailable`, never a failure, as the specification requires.
Consequences
- One runtime dependency in total.
- A Playwright-based renderer is the obvious next step and must arrive behind the same network guard.
ADR-0003: Toolchain
Status: Accepted
Context
The specification suggests Vitest. The owner asked for the CLI component, release mechanism and CI to follow the Redline repository.
Decision
- TypeScript strict (including `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `erasableSyntaxOnly`), ESM, Node's native type stripping for development and `node:test` for tests, `tsc` emit to `dist/` for the published package — the Redline arrangement.
- pnpm workspaces: `packages/geopilot` (published) and `web/` (docs site). The Phase 3 `packages/action` does not exist yet.
- Node floor 22.18, the parser's own floor and the first 22.x with type stripping on by default.
- Release: semantic-release with npm trusted publishing, a first-release guard and a canary job, as in Redline.
Consequences
- No test framework dependency. Property tests use a seeded generator in `test/helpers.ts`.
ADR-0004: What was built, against the phase plan
Status: Accepted by owner instruction; Phase 0 items remain open
Context
The specification freezes Phase 0 and says Phase 1 is not authorised until its gates clear. The owner instructed that the CLI, website and release machinery be implemented now, and that decisions be taken without waiting.
Decision
Built:
- Phase 1 (M1–M3): secure URL fetching; the Next.js repository reader; every alpha rule; exact/file/candidate/unmapped/absent provenance; S1, S2, G1, G2, G3 with dry run, approval, snapshots, V1–V4 and rollback; `explain`; the Claude Code skill including the fix workflow.
- Parts of Phase 2 that the alpha needs to be honest: static HTML T2/T3, readiness/coverage/confidence (driven by the executable fixtures A–E), insight validation L1/L2 and report merge/convert.
Not built (Phase 2+ or needs real-world evidence):
- Renderer; SARIF and GitHub output; `review`, baselines, hooks, the Action; G4 and G5; non-Claude hosts.
- Phase 0 evidence the owner must still produce: the three hand-labelled Next.js demo apps, the serve-mechanism spike on macOS and Windows (CI runs the tests on all three systems), the precision benchmarks, and capacity/IP confirmation.
Consequences
- Version numbers stay below 1.0 and the first release is anchored at 0.x.
ADR-0005: Interpretations of the specification
Status: Accepted
Where the specification was silent or ambiguous, these choices were made.
Network and robots
- An unreachable robots.txt (DNS failure, refused connection) makes GEO-013 an `error`, not a `fail`: it is missing evidence about the site. A 5xx is a `fail` and caps readiness.
- RFC 9309 says a 5xx or unreachable robots.txt means complete disallow, so GEO fetches no pages in that case unless the operator passes `--ignore-robots` for a target they named. Scoring example B therefore assumes `--ignore-robots`.
- Without a public-suffix list, "registrable domain" is approximated conservatively: same origin by default; `--follow-external` adds subdomains of the target host (with a leading `www.` removed).
Rules
- GEO-014 subjects are routes listed in a sitemap or linked from crawled server HTML. In a repository-only scan, a literal sitemap defines them; without one, every route is a subject.
- GEO-015 never infers an origin: without `site.origin` the `declared_origin` sub-check cannot run, so the rule is `not_evaluated`.
- GEO-017's header sub-check can run from source on Next.js: no middleware/proxy and no `headers()` in `next.config` means nothing can add the header. This is what lets spec example D treat GEO-017 as fully evaluable.
- The "site-wide noindex" cap applies only when every applicable GEO-014 subject is a direct noindex failure, not merely every evaluated one.
- A `profile` key (`web` | `app`) was added to the configuration to express the opt-in app profile; `appLinks` takes `{ appIds }` for iOS and `{ packageName, sha256CertFingerprints }` for Android.
Fixes
- `fix --apply` applies safe fixes and lists unapproved guarded fixes as skipped. If `--rule` explicitly asks for a rule whose fix is guarded and unapproved, the command exits 2 before writing anything.
- `runtime_observation` compares against a pre-fix observation of the same path when the original scan had one; otherwise it is `unavailable`. It never affects the outcome.
- The skill's engine version is stamped into the installed copy by `agent install`, because semantic-release sets the package version at publish time.
ADR-0006: Agent plugins for several hosts
Status: Accepted. ADR-0007 replaces the rule that only the CLI can report results.
Context
Engineers want to install geopilot into the coding agent they already use, with ready-made agents for scanning, fixing and reviewing, not only a CLI and a single skill. The specification asks for one canonical skill with generated host packaging, and for hosts to be labelled by what has actually been tested.
Decision
- `skill/` is the only hand-edited source: the `geo` skill, five agents (`scanner`, `fixer`, `reviewer`, `investigator`, `explainer`) and six commands (`scan`, `fix`, `review`, `explain`, `insights`, `init`). Shared rules — engine pinning, data-not-instructions, approvals, the disclaimer — live once in `skill/agents/_shared.md`.
- `scripts/build-plugins.mjs` renders them into `plugins/`, and CI fails when `plugins/` is stale:
- Every agent pins one engine version. On release, semantic-release stamps the new version into `plugins/` and commits it, so a marketplace install always runs the CLI it was written for. The npm package renders its own copies at its own version.
- Agent tool restrictions are not declared in front matter for the shared plugin, because Claude Code and Copilot name tools differently and a wrong name can disable an agent. Read-only behaviour is an instruction; on OpenCode, where the documented `permission` field is unambiguous, read-only agents get `edit: deny` and the fixer `edit: ask`.
Status by host
- **Claude Code**: plugin and marketplace pass `claude plugin validate`; installing from the marketplace was tested. Supported.
- **OpenCode**: OpenCode 1.1.25 loads every agent, command and skill from the project install. Supported.
- **GitHub Copilot CLI**: Copilot CLI 1.0.105 installs the shared plugin from the marketplace with all skills and agents. Supported.
- **OpenAI Codex**: Codex CLI 0.166.0 installs `plugins/codex/` from `.agents/plugins/marketplace.json` and loads the project install's skills and agents. Codex plugins can't carry custom agents, so each plugin skill includes its agent's instructions; project agents load only in a trusted project, and Codex has no per-agent sandbox. Supported.
- **Cursor**: not packaged; the `AGENTS.md` fallback applies.
"Supported" means the host's own CLI installs and loads the files. It doesn't mean every workflow has run through a model on that host.
Consequences
- The release job pushes a commit to `main`. If `main` is protected against pushes from Actions, the release fails before publishing, and the protection rule needs an exception for the release workflow.
ADR-0007: Audit from code first, with the CLI as the live check
Status: Accepted. Partly supersedes the authority rule in ADR-0006.
Context
The plugins only ran the CLI and repeated its output. The rules lived in the CLI, so the agents had nothing of their own to apply. On a framework the engine can't map, a user with no running site got an empty report with 0% coverage, and had to install a package to get it.
The people we want to help often have never heard of GEO. They need a clear report and a next step on the code in front of them.
Decision
- The agents audit by reading the code against the rules. This needs no install, no CLI and no running site, and works on any framework.
- `skill/rules.md` says how to check each rule by reading code, in plain words. `scripts/build-plugins.mjs` merges it with the facts in the engine's catalogue and embeds the result in each agent, because agents can't reliably locate skill files across hosts. The build fails if a rule has no guide, so the CLI and agents can't disagree about what a rule is.
- A rule gets one of four results: Clear, Problem, Can't tell or Not applicable. A Problem needs a file, a line and an exact quote. If code doesn't decide a rule, the result is Can't tell.
- The agents and the CLI use one formula from the catalogue's weights (`scoring/summary.ts`, described in `skill/geo/references/scoring.md`, with a test holding them together). A score is a range: the low end if every unknown is a problem, the high end if every unknown is clear. A single number flattered sites by leaving the biggest unknowns out.
- The CLI measures a running site and reports a **live score**. It runs only when the user asks or gives a URL. The code score and live score use different evidence, so they appear side by side, with disagreements called out, and are never averaged.
- When bot protection or a firewall answers instead of the site, the CLI reports the scan as blocked. Nothing about the site is passed or failed. A challenge says nothing about the site behind it, or about what real crawlers receive.
- Fixes are made by the agent on any framework, after the user sees and approves each exact change. They're reported as "confirmed in code, not yet confirmed on a live site".
- Evidence a scan can't see comes from four commands: `logs`, `cloudflare`, `inspect` and `citations`. They use built-in Node APIs only. Each service's host is a constant, so a token can't be sent elsewhere, and tokens come only from the environment. A browser renderer and a third-party JSON-LD validator were left out, because they weren't tested.
Consequences
- The code score comes from a model reading code, so two scans can differ. Strict result rules and required quotes reduce this but don't remove it. Only the live score is deterministic.
- Reading code can't prove everything: real status codes, redirects, CDN rules and headers set outside the repository stay Can't tell until a live check.
- Agent approvals are instructions to the model, not technical controls.
- The disclaimer stays: neither number measures or predicts citations, rankings, traffic or inclusion in any AI or search product.