From cd0b0fedda9caccc850d481c5250bb0d12c53cfb Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Sun, 30 Aug 2026 13:44:18 -0400 Subject: [PATCH 1/3] =?UTF-8?q?docs:=20AGENTS.md=20=E2=80=94=20the=20repo?= =?UTF-8?q?=20as=20an=20agent-maintainable=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The auto-generated Next.js block stays; below it, what an agent (or any new contributor) needs to land a correct change unassisted: the four seams and where to extend, the five invariants that fail review, the full verification command list with the two expensive gotchas (stale .next, stale widget shell), the hand-maintained-union and completion-semantics traps, and the conventions that keep diffs reviewable. Gates decide merges; this file makes the gates findable. Co-Authored-By: Claude Fable 5 --- AGENTS.md | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 643577df..3aa3d065 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,3 +7,97 @@ This version has breaking changes — APIs, conventions, and file structure may This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + +# Working in this repo + +This project expects agents as contributors and maintainers, not just +assistants. Everything below is written so a competent agent can land a +correct change without a human walking it through — and so the gates, not +vibes, decide whether a change merges. + +## What this is + +The open activity server for teaching agents: verified interactive practice +out, evidence of what the student did back. One Next.js process serves an +MCP endpoint (`/api/mcp`), REST mirrors, and a reference teacher/student +app. The product is the loop: assign a standards-verified activity → the +student works → structured evidence returns to whatever assigned it. + +## The four seams (extend here, not elsewhere) + +| Seam | Where | Extend by | +|---|---|---| +| Widget registry | `src/lib/widgets/types.ts` | one definition file + one import line — see CONTRIBUTING.md "Adding a widget" | +| Standards source | `src/lib/standards/types.ts` | implement the interface; copy `example.ts`; select with `STANDARDS_SOURCE` | +| Storage adapter | `src/lib/storage/types.ts` | implement the interface; select with `STORAGE_ADAPTER` (default: auto — Supabase if configured, else memory) | +| Protocol facades | `src/app/api/mcp`, `/api/widget` | logic never lives in a facade; add transports, don't fork business logic | + +## Invariants that fail review + +1. **The model never writes code that runs.** Generated output fills Zod + schemas rendered by human-written components. Never eval, never render + model-emitted markup outside the schema path. +2. **Student identity never reaches this server from an integration.** + Anonymous ids only on the wire. Educator identity may someday reach the + edge; student identity never does. +3. **No verdicts for unmeasured work.** `correct: null` for anything + without a real check; a widget kind must declare `assesses` (a "check" + step on a non-assessing kind reports mastery nothing measured). +4. **Honesty over polish.** Degraded runs say so ("exploration pathway", + "not saved — "); errors name the problem; docs never describe + unmerged features as shipped without saying so. +5. **Contract changes need an issue first.** Anything a third party + programs against (widget spec schemas, catalog entry shape, API routes, + persisted formats, the evidence contract) — see GOVERNANCE.md. + +## Verify before you push + +```bash +rm -rf .next && npx tsc --noEmit # stale .next causes phantom type errors after branch switches +pnpm lint # lint src/mcp/scripts explicitly if stale local worktrees pollute the default glob +pnpm test # vitest (once the test-foundation PR lands) +pnpm conformance # A2UI surfaces vs vendored Google schemas (once conformance lands) +pnpm build # next build; also typechecks; needs no secrets +pnpm mcp:build # REQUIRED after any change reachable from widget components: + # the committed public/widget-shell.html goes stale silently, + # and stale = widgets break in every MCP host. CI diffs it. +``` + +## Gotchas that cost real time + +- **`widgetSpec`/`widgetKind` in `src/lib/pathway/schema.ts` are + hand-maintained** alongside the registry (circular-import constraint). A + registry test guards the drift; adding a kind touches both. +- **Completion semantics live in `PathwayWalkthrough.tsx`** + (`HAS_OWN_CTA`/`ALWAYS_ENABLED` sets) until the registry-owned-semantics + refactor lands — a new kind with its own continue button must be added + there or its UX silently breaks. +- **The catalog/generator split is load-bearing**: catalog entries + (client-safe) and generators (server-only, AI SDK) register from separate + files. Importing a generator from client-reachable code drags the AI SDK + into the browser bundle. +- **Local storage default**: with Supabase env vars present the adapter + auto-selects Supabase. On shared dev machines another project's local + Supabase may own the default ports — set `STORAGE_ADAPTER=memory` unless + you know the database is this project's. + +## Conventions + +- Sentence case for all UI copy. Semantic state always ships icon + word, + never color alone. +- Comments explain why, not what. No metadata comments. +- Don't touch `package.json`/`pnpm-lock.yaml` unless the PR is about + dependencies. Toolchain majors (TypeScript, ESLint) are deliberate + standalone PRs, not drive-by bumps. +- Commit messages state what changed and why it's safe; PR bodies state + what a reviewer should scrutinize and what was deliberately deferred. +- DCO: by submitting you certify you have the right to contribute under + Apache-2.0 (see GOVERNANCE.md). + +## Where the docs live + +`docs/` (once the landing-and-docs PR lands): the registry API reference is +the centerpiece; `docs/messaging.md` governs how the project describes +itself — claims there are load-bearing, don't invent new ones. The a2learn +format draft lives under `docs/a2learn/`; A2UI conformance fixtures under +`spec/a2learn/fixtures/` with vendored upstream schemas in `spec/a2ui/`. From e6d66c9bea7d4ac68c54e4487dea03a072b1c2ec Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Sun, 30 Aug 2026 15:13:52 -0400 Subject: [PATCH 2/3] =?UTF-8?q?docs:=20testing=20doctrine=20in=20AGENTS.md?= =?UTF-8?q?=20=E2=80=94=20which=20instrument=20for=20which=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- AGENTS.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 3aa3d065..f95eb87e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -63,6 +63,32 @@ pnpm mcp:build # REQUIRED after any change reachable from wi # and stale = widgets break in every MCP host. CI diffs it. ``` +## What needs a test — and which kind + +The PR template's "a test that fails without this PR" box is load-bearing: +in an agent-maintained repo, gates are the contract, and untested logic is +a gate with a hole in it. Match the instrument to the code: + +- **Pure or deterministic logic** (scoring, collectors, state machines, + strip/aggregate helpers): vitest, colocated as `*.test.ts`. If review + finds a behavior bug in logic like this, the fix ships with the test + that would have caught it. +- **Wire and format surfaces** (protocol envelopes, emitted documents): + pin the contract — real `Request`s against the route for the envelope; + golden files validated against vendored upstream schemas for formats + (`pnpm conformance` is the house pattern), always with negative + controls so a vacuously-green validator can't hide. +- **Model-call quality** (generation, scoring accuracy): never unit + tests — that's the eval harness (planned; Braintrust-local). Don't + fake it with snapshot tests of model output. +- **UI components**: lowest priority today; behavior worth guarding + usually lives in an extractable pure helper — extract and test that + instead. + +`vitest` stubs Next's `server-only` marker (see `vitest.config.mts`); +tests must run offline — use the keyless `example` standards source +(`STANDARDS_SOURCE=example`) instead of mocking verification. + ## Gotchas that cost real time - **`widgetSpec`/`widgetKind` in `src/lib/pathway/schema.ts` are From c2a1eb7dd13a7f2c96d2d763a60c6d33e10985fb Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Sun, 30 Aug 2026 15:51:58 -0400 Subject: [PATCH 3/3] docs: invariant 2 names the boundary it actually protects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Student identity never reaches this server" reads as "this server can never know who a student is", which the reference app already contradicts: its roster maps anonymous ids to names in the deployment's own storage adapter, and that is the intended design. An evaluator whose deployment needs attributable records — a university, an employer — reads the old wording as a blocker and stops. The rule being protected is the boundary, not the deployment: identity must not arrive from an integration, and must not leak to one. Reworded to say that, and to name the in-deployment mapping as supported rather than an exception. No behavior changes and nothing loosens — the wire rule is identical. Stated self-contained rather than linking docs/evidence.md, which does not exist until the landing-and-docs branch further up the stack. Co-Authored-By: Claude Opus 5 (1M context) --- AGENTS.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f95eb87e..2483dbdf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,9 +37,14 @@ student works → structured evidence returns to whatever assigned it. 1. **The model never writes code that runs.** Generated output fills Zod schemas rendered by human-written components. Never eval, never render model-emitted markup outside the schema path. -2. **Student identity never reaches this server from an integration.** - Anonymous ids only on the wire. Educator identity may someday reach the - edge; student identity never does. +2. **Student identity never crosses the integration boundary.** Anonymous + ids only on the wire — an assigning agent never sends one, and this + server never hands one back. A deployment that maps those ids to real + people inside its own storage adapter is the supported pattern, not an + exception: the mapping stays in the deployment and never travels. What + the rule forbids is identity arriving from, or leaking to, an + integration. Educator identity may someday reach the edge; student + identity never does. 3. **No verdicts for unmeasured work.** `correct: null` for anything without a real check; a widget kind must declare `assesses` (a "check" step on a non-assessing kind reports mastery nothing measured).