From 0929bfc71e81e071795c338c40b13f263df5021e Mon Sep 17 00:00:00 2001 From: fretchen Date: Thu, 5 Nov 2026 18:00:20 +0100 Subject: [PATCH 1/3] All the agents.md --- AGENTS.md | 100 ++++++++++++----------------------- analytics/AGENTS.md | 7 +++ bunny_js/AGENTS.md | 19 +++++++ comment_service/AGENTS.md | 7 +++ growth-agent/AGENTS.md | 10 ++++ observability/AGENTS.md | 4 ++ scw_js/AGENTS.md | 24 +++++++++ shared/chain-utils/AGENTS.md | 8 +++ website/AGENTS.md | 32 +++++++++++ x402_facilitator/AGENTS.md | 24 +++++++++ 10 files changed, 170 insertions(+), 65 deletions(-) create mode 100644 analytics/AGENTS.md create mode 100644 bunny_js/AGENTS.md create mode 100644 comment_service/AGENTS.md create mode 100644 growth-agent/AGENTS.md create mode 100644 observability/AGENTS.md create mode 100644 scw_js/AGENTS.md create mode 100644 shared/chain-utils/AGENTS.md create mode 100644 x402_facilitator/AGENTS.md diff --git a/AGENTS.md b/AGENTS.md index 81aaa2332..f33b609f7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,23 +2,31 @@ Repo instructions for coding agents working in this repository. +## Policy: README vs AGENTS.md + +READMEs are for humans — quick start, overview, deployment. Each package's `AGENTS.md` holds +only the rules agents need that the README does not already carry: silent failure modes, +ordering rules, skill routing. Every package has one; link, don't duplicate. The chain applies +from the edited file up to this root — the closest file wins on conflict, chat prompts override +everything. + ## Repository Overview Monorepo for a blockchain-based AI service platform. Each subdirectory is an independent npm package — there is no root `package.json` or workspace manager. -| Directory | Purpose | Further reading | -| --------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------- | -| `website/` | Frontend (Vike SSR + React 19 + Panda CSS) | [`README`](website/README.md) · [`AGENTS.md`](website/AGENTS.md) | -| `eth/` | Solidity smart contracts (Hardhat, Optimism L2) | [`README`](eth/README.md) · [`AGENTS.md`](eth/AGENTS.md) | -| `scw_js/` | Serverless backend (Scaleway Functions: image gen, channel cron, growth API) | [`README`](scw_js/README.md) | -| `bunny_js/` | Bunny Edge Scripts: the x402 llm/search/mcp sellers (plus the scw twins' origin domains) | [`README`](bunny_js/README.md) | -| `x402_facilitator/` | EIP-3009 USDC payment facilitator (Scaleway Functions) | [`README`](x402_facilitator/README.md) | -| `comment_service/` | Blog comment backend (Scaleway Functions) | [`README`](comment_service/README.md) | -| `analytics/` | Serverless pageview counter (Scaleway Functions, S3-backed) | [`README`](analytics/README.md) | -| `growth-agent/` | AI growth agent cron container (Python, LangGraph) | [`README`](growth-agent/README.md) | -| `observability/` | Log CLI + Loki alert rules for all Scaleway functions | [`README`](observability/README.md) | -| `shared/chain-utils/` | Shared blockchain utility library (Viem peer dep) | — | -| `notebooks/` | Python Jupyter notebooks for analysis | [`README`](notebooks/README.md) | +| Directory | Purpose | Further reading | +| --------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `website/` | Frontend (Vike SSR + React 19 + Panda CSS) — also the blog | [`README`](website/README.md) · [`AGENTS.md`](website/AGENTS.md) | +| `eth/` | Solidity smart contracts (Hardhat, Optimism L2) | [`README`](eth/README.md) · [`AGENTS.md`](eth/AGENTS.md) | +| `scw_js/` | Serverless backend (Scaleway Functions: image gen, channel cron, growth API) | [`README`](scw_js/README.md) · [`AGENTS.md`](scw_js/AGENTS.md) | +| `bunny_js/` | Bunny Edge Scripts: the x402 llm/search/mcp sellers (plus the scw twins' origin domains) | [`README`](bunny_js/README.md) · [`AGENTS.md`](bunny_js/AGENTS.md) | +| `x402_facilitator/` | EIP-3009 USDC payment facilitator (Scaleway Functions) | [`README`](x402_facilitator/README.md) · [`AGENTS.md`](x402_facilitator/AGENTS.md) | +| `comment_service/` | Blog comment backend (Scaleway Functions) | [`README`](comment_service/README.md) · [`AGENTS.md`](comment_service/AGENTS.md) | +| `analytics/` | Serverless pageview counter (Scaleway Functions, S3-backed) | [`README`](analytics/README.md) · [`AGENTS.md`](analytics/AGENTS.md) | +| `growth-agent/` | AI growth agent cron container (Python, LangGraph) | [`README`](growth-agent/README.md) · [`AGENTS.md`](growth-agent/AGENTS.md) | +| `observability/` | Log CLI + Loki alert rules for all Scaleway functions | [`README`](observability/README.md) · [`AGENTS.md`](observability/AGENTS.md) | +| `shared/chain-utils/` | Shared blockchain utility library (Viem peer dep) | [`README`](shared/chain-utils/README.md) · [`AGENTS.md`](shared/chain-utils/AGENTS.md) | +| `notebooks/` | Python Jupyter notebooks for analysis | [`README`](notebooks/README.md) | ## Commands @@ -55,43 +63,24 @@ skills) and emits a graded inline report with a machine-readable gate verdict. T vendored in `.agents/skills/code-review/` (MIT; upstream origin and pin are in its SKILL.md) — keep it out of `skills-lock.json`, which is only for CLI-managed skills. -## Architecture Patterns - -### Smart Contracts (`eth/`) - -All NFT contracts use the **OpenZeppelin UUPS upgradeable proxy pattern** — never deploy implementation contracts directly. Deploy/upgrade scripts use JSON config files with Zod validation and three execution modes: `validateOnly`, `dryRun`, and execute. - -Hardhat here uses **Viem, not Ethers** — contract calls return `bigint`. +## x402 payments -Repo-specific conventions (the two test categories, the deploy-script export guard, the storage-layout rule) are in [`eth/AGENTS.md`](eth/AGENTS.md). +Applies to `scw_js/`, `x402_facilitator/`, and `bunny_js/`. Use the **`x402`** skill before +writing or reviewing anything that names an x402 role — it carries the official vocabulary, +this repo's verified role map, and links to the spec. The role map is counterintuitive and has +been published wrong: `/imagegen` and `/assistent` (scw_js) are **buyers** (they spend USDC); +the `*-agent.fretchen.eu` endpoints they call are the **sellers** (image gen on Scaleway, +llm/web/MCP on the bunny_js Edge Scripts). -### Serverless (`scw_js/`, `x402_facilitator/`, `comment_service/`, `analytics/`) - -Single Scaleway Function per package with **path-based routing** (`/verify`, `/settle`, `/supported`). Environment variables are split: `env:` in `serverless.yml` for public values, `secret:` configured via the Scaleway Console only. - -Use the **`x402`** skill before writing or reviewing anything that names an x402 role — it carries -the official vocabulary, this repo's verified role map, and links to the spec. The role map is -counterintuitive and has been published wrong: `/imagegen` and `/assistent` are **buyers** -(they spend USDC), the `*-agent.fretchen.eu` endpoints they call are the **sellers** (image gen on Scaleway, llm/web/MCP on the bunny_js Edge Scripts). - -Two things that silently break payments if you get them wrong, both documented in detail where they belong: +Three traps, each documented in detail where it belongs: - **EIP-712 domain names differ by network** (mainnet USDC is `"USD Coin"`, testnet is `"USDC"`). Follow the checklist in [`scw_js/README.md`](scw_js/README.md) → _Adding New Networks_, which carries the verified per-network table. - **The x402 recipient whitelist uses OR logic** across a manual list, testnet-only test wallets, and NFT-holder status. See [`x402_facilitator/README.md`](x402_facilitator/README.md) → _Whitelist Architecture_. - -**Generated OpenAPI specs must publish the strictness they actually enforce.** `scw_js` and `x402_facilitator` generate their `openapi.*.json` from Zod, and `z.object` renders as `additionalProperties: false` — so a schema that merely _strips_ unknown keys publishes a strictness its handler does not enforce. Use `z.looseObject` for a request the handler forwards or ignores extras on, `z.object` where the body is built field by field (every response, and `genimg`'s request, which really does reject unknown fields). Each package's `test/openapi_*_generation.test.ts` asserts this, because nothing else catches it — and it has shipped wrong: the facilitator's published `PaymentRequest` described a request that could not be paid with, since real payloads carry the very fields it declared forbidden. - -### Frontend (`website/`) - -**Vike SSR** with file-based routing: pages in `pages/`, renderer in `renderer/`. Client-only components need `{ ssr: false }` in imports. - -**Panda CSS** compiles `css({})` at build time, so several ways of writing a style fail _silently_ — the component renders, the tests pass, only the CSS is missing. The rules are in [`website/AGENTS.md`](website/AGENTS.md), enforced by `website/test/styleConventions.test.ts`. The design system itself — colours and their jobs, the button recipe, the scales — is in [`website/README.md`](website/README.md); read it before adding any style. - -**Wagmi v2 + TanStack Query** for blockchain state; hooks are auto-generated from `wagmi.config.ts`, not hand-written. +- **Generated OpenAPI specs must publish the strictness they actually enforce.** `scw_js` and `x402_facilitator` generate their `openapi.*.json` from Zod, and `z.object` renders as `additionalProperties: false` — so a schema that merely _strips_ unknown keys publishes a strictness its handler does not enforce. Use `z.looseObject` for a request the handler forwards or ignores extras on, `z.object` where the body is built field by field (every response, and `genimg`'s request, which really does reject unknown fields). Each package's `test/openapi_*_generation.test.ts` asserts this, because nothing else catches it — and it has shipped wrong: the facilitator's published `PaymentRequest` described a request that could not be paid with, since real payloads carry the very fields it declared forbidden. ## Security -See [`.github/THREAT_MODEL.md`](.github/THREAT_MODEL.md) for the full asset inventory, blast radius, and trust boundaries, and [`eth/SECURITY.md`](eth/SECURITY.md) for contract-level findings. Use the **`cve-triage`** skill to evaluate open Dependabot alerts against the threat model; the criteria are in [`.github/CVE_TRIAGE.md`](.github/CVE_TRIAGE.md). Use the **`security-review`** skill for periodic deep audits of the agent and API layers — it operationalizes the threat model's review cadence with OWASP secure-agent-playbook procedures, vendored under `.agents/skills/security-review/` (CC-BY-4.0; keep it out of `skills-lock.json`). +See [`.github/THREAT_MODEL.md`](.github/THREAT_MODEL.md) for the full asset inventory, blast radius, and trust boundaries, and [`eth/SECURITY.md`](eth/SECURITY.md) for contract-level findings. Use the **`cve-triage`** skill to evaluate open Dependabot alerts against the threat model; the criteria are in [`.github/CVE_TRIAGE.md`](.github/CVE_TRIAGE.md). Use the **`security-review`** skill for periodic deep audits of the agent and API layers — it operationalizes the threat model's review cadence with OWASP secure-agent-playbook procedures, vendored under `.agents/skills/security-review/` (CC-BY-4.0; keep it out of `skills-lock.json`). Contract-level rules like the GenImNFTv4 agent whitelist are in [`eth/AGENTS.md`](eth/AGENTS.md). **Key hierarchy** (highest-value first): @@ -104,30 +93,11 @@ See [`.github/THREAT_MODEL.md`](.github/THREAT_MODEL.md) for the full asset inve Rules that apply everywhere: - Private keys go in the Hardhat keystore (`npx hardhat keystore set SEPOLIA_PRIVATE_KEY`) or a Scaleway secret — never in code, never committed. -- **CVE-2025-11-26 (GenImNFTv3):** fixed in v4 with an agent whitelist. Any code calling `requestImageUpdate()` must verify `isAuthorizedAgent()`. - All serverless responses must include CORS headers (`Access-Control-Allow-Origin: *`). ## Blog Posts -Posts are `.md`/`.mdx` in `website/blog/`, with frontmatter: `title`, `publishing_date`, `category`, `description`, `tokenID`, and `network` (the CAIP-2 chain the token lives on — omit for the legacy default `eip155:10`; a Base token must declare `eip155:8453`, because both GenImNFT mainnets have independent id sequences and an invalid value fails the build). Interactive posts import their widgets as regular React components from `website/components/blog/` — there is no `.tsx` post format. - -Use the **`blog-planner`** skill to plan or write one — it enforces the plan-first rule (no MDX before an approved `website/blog/.plan.md`) and carries the audience and style conventions. Use **`blog-critic`** to review a draft; it is read-only and outputs a `.todos.md`. - -### Prose: no manufactured suspense - -State a claim where it arises. Never order sentences so they build to a reveal — the suspense is -fake, because the sentence already knows what it is withholding. Four constructions are banned: - -- **Concede-then-swing** — "X is the benchmark, and it works. But…", "So a good outcome exists. - But…". Put the limitation in the same sentence as the claim: "The planner reaches the right - fleet, but has to know how fast the fish breed." -- **Forward promise** — "the two ends of that bracket everything below", "this point is worth - detailing further", "more on that later". Say it here or cut it. -- **Meta-announcement** — "That is enough to state the tragedy", "Now for the interesting part". - Delete the sentence; the next one is the content. -- **Withheld reveal** — "a third possibility, from an odd direction", "That is the point", or a - heading with an appended promise ("The circuit, and what it implements"). Name the thing in the - sentence that raises it; a heading names its subject and stops. - -Test: read each paragraph's first sentence on its own. If it announces that something is coming -rather than saying something, rewrite it. This applies to figure captions too. +Posts live in `website/blog/` — the frontmatter rules and the prose conventions are in +[`website/AGENTS.md`](website/AGENTS.md). Plan with the **`blog-planner`** skill (it enforces +the plan-first rule: no MDX before an approved `website/blog/.plan.md`); review a draft +with the read-only **`blog-critic`**. diff --git a/analytics/AGENTS.md b/analytics/AGENTS.md new file mode 100644 index 000000000..a82a2161d --- /dev/null +++ b/analytics/AGENTS.md @@ -0,0 +1,7 @@ +# analytics — agent rules + +Serverless pageview counter (Scaleway Function, S3-backed). See [`README.md`](./README.md) +for the API (`POST /hit`, `GET /stats`), the data model, local servers and deployment. + +Public env values go in `env:` in `serverless.yml`; secrets are configured in the Scaleway +Console only. All responses include CORS headers. diff --git a/bunny_js/AGENTS.md b/bunny_js/AGENTS.md new file mode 100644 index 000000000..4c87270dc --- /dev/null +++ b/bunny_js/AGENTS.md @@ -0,0 +1,19 @@ +# bunny_js — agent rules + +Bunny Edge Scripts: the x402 llm/search/mcp **sellers** (plus the scw twins' origin domains). +See [`README.md`](./README.md) for the architecture, commands, environment/secrets, known +limits and deploy paths, and [`SECURITY.md`](./SECURITY.md) for the security posture. + +## Runtime + +The scripts deploy to Bunny's Edge Scripts **Deno 1.x** runtime, not Node — local dev serves +the twins via Deno, and import resolution there differs from the bundler's. Any Node +convention from the sibling npm packages applies here only if [`README.md`](./README.md) → +_Known limits / decisions_ says so. + +## x402 role + +These endpoints are the sellers that the scw*js buyers (`/imagegen`, `/assistent`) pay. Use +the **`x402`** skill before anything that names a role; the shared payment traps (EIP-712 +domain names, the OpenAPI strictness rule) are in the root [`AGENTS.md`](../AGENTS.md) → +\_x402 payments*. diff --git a/comment_service/AGENTS.md b/comment_service/AGENTS.md new file mode 100644 index 000000000..2607ff285 --- /dev/null +++ b/comment_service/AGENTS.md @@ -0,0 +1,7 @@ +# comment_service — agent rules + +Blog comment backend (Scaleway Function). See [`README.md`](./README.md) for the API +(`GET`, `POST`, `OPTIONS /`), development and deployment. + +Public env values go in `env:` in `serverless.yml`; secrets are configured in the Scaleway +Console only. All responses include CORS headers. diff --git a/growth-agent/AGENTS.md b/growth-agent/AGENTS.md new file mode 100644 index 000000000..31a35fe00 --- /dev/null +++ b/growth-agent/AGENTS.md @@ -0,0 +1,10 @@ +# growth-agent — agent rules + +AI growth agent cron container (Python, LangGraph). See [`README.md`](./README.md) for the +architecture, the LLM provider, the project structure, and running individual tasks or the +full handler locally, and [`SECURITY.md`](./SECURITY.md) for the agent-layer security +findings. + +Python package, not npm: `uv` is the package manager (`uv sync --all-extras`), tests run +with `uv run pytest`, lint/format with `uv run ruff` (line-length 100, not the repo's +Prettier 120). diff --git a/observability/AGENTS.md b/observability/AGENTS.md new file mode 100644 index 000000000..2d05e1d74 --- /dev/null +++ b/observability/AGENTS.md @@ -0,0 +1,4 @@ +# observability — agent rules + +Log CLI + Loki alert rules for all Scaleway functions. See [`README.md`](./README.md) for +reading the logs, alerting on log content, and where the credentials live. diff --git a/scw_js/AGENTS.md b/scw_js/AGENTS.md new file mode 100644 index 000000000..a6240a4ea --- /dev/null +++ b/scw_js/AGENTS.md @@ -0,0 +1,24 @@ +# scw_js — agent rules + +Scaleway Functions backend: image generation, channel cron, growth API. See +[`README.md`](./README.md) for the full function list, on-chain integration (RPC, ERC-8004 +registration, bazaar discovery), the S3 storage layout, local testing and deployment. + +## Serverless conventions + +One Scaleway Function with path-based routing. Public env values go in `env:` in +`serverless.yml`; secrets are configured in the Scaleway Console only. All responses include +CORS headers. + +## x402 role + +`/imagegen` and `/assistent` are **buyers** — they spend USDC on the `*-agent.fretchen.eu` +sellers (image gen on Scaleway, llm/web/MCP on bunny*js Edge Scripts). Use the **`x402`** +skill before anything that names a role; the shared payment traps (EIP-712 domain names, the +OpenAPI strictness rule) are in the root [`AGENTS.md`](../AGENTS.md) → \_x402 payments*. + +## Adding a network + +Follow the checklist in [`README.md`](./README.md) → _Adding New Networks_ — it carries the +verified per-network EIP-712 domain name table (mainnet USDC is `"USD Coin"`, testnet is +`"USDC"`). Getting this wrong silently breaks payments. diff --git a/shared/chain-utils/AGENTS.md b/shared/chain-utils/AGENTS.md new file mode 100644 index 000000000..e8b68805c --- /dev/null +++ b/shared/chain-utils/AGENTS.md @@ -0,0 +1,8 @@ +# shared/chain-utils — agent rules + +Shared blockchain utility library (Viem peer dep). See [`README.md`](./README.md) for the +supported networks, the contract overview and the API reference. + +**Build before dependents:** this is a local file dependency +(`"file:../shared/chain-utils"`). Run `npm run build` here before rebuilding any dependent +package. diff --git a/website/AGENTS.md b/website/AGENTS.md index 3c7f79ac7..74e7a30a5 100644 --- a/website/AGENTS.md +++ b/website/AGENTS.md @@ -119,3 +119,35 @@ false misses. backend caps that turn an over-budget `tools` array into a 400. - **ABIs** come from `eth/abi/contracts/*.ts` (TypeScript `as const` exports). After contract changes, regenerate them in `eth/` first, then update the imports here. + +## Blog posts + +Posts are `.md`/`.mdx` in `blog/`, with frontmatter: `title`, `publishing_date`, `category`, +`description`, `tokenID`, and `network` (the CAIP-2 chain the token lives on — omit for the +legacy default `eip155:10`; a Base token must declare `eip155:8453`, because both GenImNFT +mainnets have independent id sequences and an invalid value fails the build). Interactive posts +import their widgets as regular React components from `components/blog/` — there is no `.tsx` +post format. + +Use the **`blog-planner`** skill to plan or write one — it enforces the plan-first rule (no MDX +before an approved `blog/.plan.md`) and carries the audience and style conventions. Use +**`blog-critic`** to review a draft; it is read-only and outputs a `.todos.md`. + +### Prose: no manufactured suspense + +State a claim where it arises. Never order sentences so they build to a reveal — the suspense is +fake, because the sentence already knows what it is withholding. Four constructions are banned: + +- **Concede-then-swing** — "X is the benchmark, and it works. But…", "So a good outcome exists. + But…". Put the limitation in the same sentence as the claim: "The planner reaches the right + fleet, but has to know how fast the fish breed." +- **Forward promise** — "the two ends of that bracket everything below", "this point is worth + detailing further", "more on that later". Say it here or cut it. +- **Meta-announcement** — "That is enough to state the tragedy", "Now for the interesting part". + Delete the sentence; the next one is the content. +- **Withheld reveal** — "a third possibility, from an odd direction", "That is the point", or a + heading with an appended promise ("The circuit, and what it implements"). Name the thing in + the sentence that raises it; a heading names its subject and stops. + +Test: read each paragraph's first sentence on its own. If it announces that something is coming +rather than saying something, rewrite it. This applies to figure captions too. diff --git a/x402_facilitator/AGENTS.md b/x402_facilitator/AGENTS.md new file mode 100644 index 000000000..cf14b36d6 --- /dev/null +++ b/x402_facilitator/AGENTS.md @@ -0,0 +1,24 @@ +# x402_facilitator — agent rules + +EIP-3009 USDC payment facilitator (Scaleway Functions, path-based routing: `/verify`, +`/settle`, `/supported`). See [`README.md`](./README.md) for the payment schemes, recipient +gating, the fee pull, verification checks, error codes and local test endpoints. + +## Serverless conventions + +Public env values go in `env:` in `serverless.yml`; secrets are configured in the Scaleway +Console only. All responses include CORS headers. + +## x402 role + +This package sits between the scw*js **buyers** and the sellers — aside from the fee pull +right after settlement it never moves funds. Use the **`x402`** skill before anything that +names a role; the shared payment traps (EIP-712 domain names, the OpenAPI strictness rule) +are in the root [`AGENTS.md`](../AGENTS.md) → \_x402 payments*. + +## Recipient whitelist (OR logic) + +`/verify` accepts a recipient if **any** of: the manual list, a testnet-only test wallet, or +NFT-holder status. The composition and the reasons are in [`README.md`](./README.md) → +_Whitelist Architecture_ — read it before touching recipient gating, and keep the +batch-settlement test-wallet bypass honored on testnets only. From 5554c57272cbbb7df4130648875e915a41f08525 Mon Sep 17 00:00:00 2001 From: fretchen Date: Thu, 5 Nov 2026 18:05:25 +0100 Subject: [PATCH 2/3] Update README.md --- README.md | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ea1fe8ccc..4cd4f84cd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,27 @@ -Personal website. Not more. Not less. +# fretchen.github.io -Blog posts, including the text and all images made by myself, are released freely to the world under the [WTFPL](http://www.wtfpl.net/). +Personal site and the services behind it — a monorepo of independent packages: a Vike/React +website, Solidity contracts on Optimism and Base, and a handful of serverless services +wired together by x402 USDC payments. + +| Package | What it is | +| --------------------- | ------------------------------------------------------- | +| `website/` | The site + blog (Vike SSR, React 19, Panda CSS) | +| `eth/` | NFT and payment contracts (Hardhat, UUPS proxies) | +| `scw_js/` | Scaleway Functions: image gen, channel cron, growth API | +| `bunny_js/` | Bunny Edge Scripts: the x402 llm/search/mcp sellers | +| `x402_facilitator/` | EIP-3009 USDC payment facilitator | +| `comment_service/` | Blog comments | +| `analytics/` | Pageview counter (S3-backed) | +| `growth-agent/` | AI growth agent (Python, LangGraph) | +| `observability/` | Logs and alert rules for the Scaleway functions | +| `shared/chain-utils/` | Shared Viem utilities | +| `notebooks/` | Analysis notebooks | + +Each package is self-contained — see its `README.md` for setup and its `AGENTS.md` for +coding-agent rules (the root [`AGENTS.md`](AGENTS.md) explains the split). + +## License + +Blog posts, including the text and all images made by myself, are released freely to the +world under the [WTFPL](http://www.wtfpl.net/). From 54f2f6c1e3b1f9358830ea796d523971c8df3db9 Mon Sep 17 00:00:00 2001 From: fretchen Date: Thu, 5 Nov 2026 21:22:22 +0100 Subject: [PATCH 3/3] Cleaning --- .agents/skills/code-review/repo-checklist.md | 204 +++++++++---------- .agents/skills/panda/SKILL.md | 123 +++++++++++ AGENTS.md | 2 +- scw_js/AGENTS.md | 3 +- website/AGENTS.md | 109 +--------- x402_facilitator/AGENTS.md | 8 +- 6 files changed, 239 insertions(+), 210 deletions(-) create mode 100644 .agents/skills/panda/SKILL.md diff --git a/.agents/skills/code-review/repo-checklist.md b/.agents/skills/code-review/repo-checklist.md index 9e66aae70..8af5ceede 100644 --- a/.agents/skills/code-review/repo-checklist.md +++ b/.agents/skills/code-review/repo-checklist.md @@ -10,9 +10,9 @@ Item format (upstream checklist-schema v2.3): ```markdown - [ ] Short description (source: path) - severity: P0|P1|P2|P3 - scope: universal|project|module - frequency: 1x|2x+|chronic + severity: P0|P1|P2|P3 + scope: universal|project|module + frequency: 1x|2x+|chronic ``` Sort on load: chronic > 2x+ > 1x, then P0 > P1 > P2 > P3, then universal > project > module. @@ -20,160 +20,160 @@ Sort on load: chronic > 2x+ > 1x, then P0 > P1 > P2 > P3, then universal > proje ## Security & keys - [ ] Private keys only in the Hardhat keystore or Scaleway secrets — never in code, tests, fixtures, logs, or commits (source: AGENTS.md → Security) - severity: P0 - scope: universal - frequency: chronic + severity: P0 + scope: universal + frequency: chronic - [ ] Any code calling GenImNFTv4's requestImageUpdate() must verify isAuthorizedAgent() first — the CVE-2025-11-26 pattern (source: eth/AGENTS.md → Security) - severity: P0 - scope: project - frequency: 1x + severity: P0 + scope: project + frequency: 1x -- [ ] EIP-712 domain names differ by network: mainnet USDC is "USD Coin", testnet is "USDC" — check the verified per-network table before touching payment signing (source: AGENTS.md → scw_js/README.md, Adding New Networks) - severity: P0 - scope: module - frequency: 1x +- [ ] EIP-712 domain names differ by network: mainnet USDC is "USD Coin", testnet is "USDC" — check the verified per-network table before touching payment signing (source: AGENTS.md → x402 payments; scw_js/README.md → Adding New Networks) + severity: P0 + scope: module + frequency: 1x -- [ ] Serverless secrets are configured via Scaleway Console only; `env:` in serverless.yml is public by definition (source: AGENTS.md → Serverless) - severity: P1 - scope: universal - frequency: 2x+ +- [ ] Serverless secrets are configured via Scaleway Console only; `env:` in serverless.yml is public by definition (source: scw_js/AGENTS.md → Serverless conventions; same rule in the other serverless packages' AGENTS.md) + severity: P1 + scope: universal + frequency: 2x+ -- [ ] All serverless responses include CORS headers (Access-Control-Allow-Origin: *) (source: AGENTS.md → Security) - severity: P1 - scope: universal - frequency: chronic +- [ ] All serverless responses include CORS headers — `Access-Control-Allow-Origin: *` for public endpoints; analytics uses a deliberate origin whitelist, never normalize it to `*` (source: AGENTS.md → Security; analytics/README.md → API) + severity: P1 + scope: universal + frequency: chronic - [ ] Changes touching auth, payments, or keys are checked against .github/THREAT_MODEL.md for blast radius before approval (source: AGENTS.md → Security) - severity: P1 - scope: project - frequency: 2x+ + severity: P1 + scope: project + frequency: 2x+ ## Payments & x402 -- [ ] The x402 role map: /imagegen and /assistent are buyers; the *-agent.fretchen.eu endpoints are sellers — any PR or doc that names a role gets this wrong half the time (source: .agents/skills/x402/SKILL.md) - severity: P1 - scope: project - frequency: chronic +- [ ] The x402 role map: /imagegen and /assistent are buyers; the \*-agent.fretchen.eu endpoints are sellers — any PR or doc that names a role gets this wrong half the time (source: .agents/skills/x402/SKILL.md) + severity: P1 + scope: project + frequency: chronic - [ ] Channel identity: wallet + voucher signer + receiver + token + network hash into channelId — code that recomputes any of these mid-turn silently opens a second channel the user must fund again (source: website/utils/x402PaidFetch.ts) - severity: P1 - scope: module - frequency: 1x + severity: P1 + scope: module + frequency: 1x ## OpenAPI / serverless handlers -- [ ] Published OpenAPI strictness must match handler behavior: z.object only for bodies built field by field, z.looseObject when unknown keys are forwarded or ignored (source: AGENTS.md → Serverless; enforced by test/openapi_*_generation.test.ts) - severity: P1 - scope: universal - frequency: 2x+ +- [ ] Published OpenAPI strictness must match handler behavior: z.object only for bodies built field by field, z.looseObject when unknown keys are forwarded or ignored (source: AGENTS.md → x402 payments; enforced by test/openapi\_\*\_generation.test.ts) + severity: P1 + scope: universal + frequency: 2x+ ## Contracts (eth/) - [ ] UUPS upgradeable proxy pattern — never deploy an implementation contract directly (source: eth/AGENTS.md → Upgrades) - severity: P0 - scope: module - frequency: 1x + severity: P0 + scope: module + frequency: 1x - [ ] Upgrades append new state variables after existing ones only — reordering or inserting corrupts proxy storage (source: eth/AGENTS.md → Upgrades) - severity: P0 - scope: module - frequency: 1x + severity: P0 + scope: module + frequency: 1x - [ ] Hardhat here uses Viem, not Ethers: contract calls return bigint, format with viem helpers (source: eth/AGENTS.md) - severity: P2 - scope: module - frequency: chronic + severity: P2 + scope: module + frequency: chronic -- [ ] Tests go in the right category: *_Functional.ts (Viem, contract logic) vs *_Deployment.ts (ethers + upgrades plugin, script coverage) — never both libraries in one file (source: eth/AGENTS.md → Tests) - severity: P2 - scope: module - frequency: 2x+ +- [ ] Tests go in the right category: _\_Functional.ts (Viem, contract logic) vs _\_Deployment.ts (ethers + upgrades plugin, script coverage) — never both libraries in one file (source: eth/AGENTS.md → Tests) + severity: P2 + scope: module + frequency: 2x+ -- [ ] Deploy/upgrade scripts export their deploy function and guard direct execution, so the *_Deployment.ts import does not run a deployment (source: eth/AGENTS.md → Deployment scripts) - severity: P2 - scope: module - frequency: 2x+ +- [ ] Deploy/upgrade scripts export their deploy function and guard direct execution, so the \*\_Deployment.ts import does not run a deployment (source: eth/AGENTS.md → Deployment scripts) + severity: P2 + scope: module + frequency: 2x+ -- [ ] After changing a contract: run npx hardhat run scripts/export-abi.ts, then update the website's ABI imports from eth/abi/contracts/*.ts (source: AGENTS.md → Commands) - severity: P2 - scope: project - frequency: 2x+ +- [ ] After changing a contract: run npx hardhat run scripts/export-abi.ts, then update the website's ABI imports from eth/abi/contracts/\*.ts (source: AGENTS.md → Commands) + severity: P2 + scope: project + frequency: 2x+ ## Website (Vike + React 19 + Panda CSS) -- [ ] Panda compiles css({}) statically — the six silent-failure rules (variable values, spacing shorthands, unknown tokens, token() outside css({}), fontFamily faces, recipe variants as variables) pass tsc AND tests while the CSS is simply missing; bulk style changes are verified by diffing emitted declarations (source: website/AGENTS.md; enforced by test/styleConventions.test.ts) - severity: P1 - scope: module - frequency: chronic +- [ ] Panda compiles css({}) statically — the six silent-failure rules (variable values, spacing shorthands, unknown tokens, token() outside css({}), fontFamily faces, recipe variants as variables) pass tsc AND tests while the CSS is simply missing; bulk style changes are verified by diffing emitted declarations (source: .agents/skills/panda/SKILL.md; enforced by test/styleConventions.test.ts) + severity: P1 + scope: module + frequency: chronic - [ ] Client-only components are imported with { ssr: false } (source: website/AGENTS.md) - severity: P2 - scope: module - frequency: chronic + severity: P2 + scope: module + frequency: chronic - [ ] npm run prepare (Panda codegen) after config changes; styled-system/ is generated — never edited directly (source: website/AGENTS.md) - severity: P2 - scope: module - frequency: 2x+ + severity: P2 + scope: module + frequency: 2x+ - [ ] Wagmi hooks are auto-generated from wagmi.config.ts, not hand-written (source: website/AGENTS.md) - severity: P2 - scope: module - frequency: 1x + severity: P2 + scope: module + frequency: 1x - [ ] Never read useAccount().isConnected directly — go through useIsWalletConnected (source: website/hooks/useIsWalletConnected.ts; enforced by test/walletConnectionConvention.test.ts) - severity: P2 - scope: module - frequency: chronic + severity: P2 + scope: module + frequency: chronic - [ ] The design system in website/README.md is read before adding any style — colors and their jobs, the button recipe, the scales (source: website/AGENTS.md) - severity: P2 - scope: module - frequency: 2x+ + severity: P2 + scope: module + frequency: 2x+ ## Assistant chat tools (website/tools/, AssistantChat.tsx) - [ ] Every TOOL_REGISTRY entry needs a toolRunners entry — the test suite iterates the registry, so a tool without a runner fails the suite (source: .agents/skills/chat-tools/SKILL.md) - severity: P1 - scope: module - frequency: 2x+ + severity: P1 + scope: module + frequency: 2x+ -- [ ] website/tools/*.ts stay React-free — no hooks, no React imports; that is what keeps them importable from non-browser callers (source: .agents/skills/chat-tools/SKILL.md) - severity: P1 - scope: module - frequency: 2x+ +- [ ] website/tools/\*.ts stay React-free — no hooks, no React imports; that is what keeps them importable from non-browser callers (source: .agents/skills/chat-tools/SKILL.md) + severity: P1 + scope: module + frequency: 2x+ - [ ] Tool definitions and results are input tokens on every later hop: serialized tools stay under MAX_TOOLS_BYTES (8192), results projected hard, and `tools: []` is never sent — pass undefined so the key is absent (source: .agents/skills/chat-tools/SKILL.md) - severity: P1 - scope: module - frequency: chronic + severity: P1 + scope: module + frequency: chronic - [ ] A non-ok tool status withdraws the tool for the rest of the turn unless it is an answer-like status marked recoverable: true (source: .agents/skills/chat-tools/SKILL.md) - severity: P1 - scope: module - frequency: 2x+ + severity: P1 + scope: module + frequency: 2x+ ## Blog -- [ ] No MDX before an approved website/blog/.plan.md exists (source: AGENTS.md → Blog Posts; .agents/skills/blog-planner/SKILL.md) - severity: P1 - scope: project - frequency: 2x+ +- [ ] No MDX before an approved website/blog/.plan.md exists (source: website/AGENTS.md → Blog posts; .agents/skills/blog-planner/SKILL.md) + severity: P1 + scope: project + frequency: 2x+ -- [ ] Prose carries no manufactured suspense: concede-then-swing, forward promise, meta-announcement, and withheld reveal are banned — figure captions included (source: AGENTS.md → Blog Posts) - severity: P2 - scope: project - frequency: chronic +- [ ] Prose carries no manufactured suspense: concede-then-swing, forward promise, meta-announcement, and withheld reveal are banned — figure captions included (source: website/AGENTS.md → Blog posts) + severity: P2 + scope: project + frequency: chronic ## Process - [ ] Branch off origin/main with --no-track; set tracking on first push with -u — auto-setup makes the new branch track origin/main and can misdirect pushes (source: AGENTS.md → Git Workflow) - severity: P2 - scope: project - frequency: 2x+ + severity: P2 + scope: project + frequency: 2x+ - [ ] shared/chain-utils is a local file dependency — build it before rebuilding any dependent package (source: AGENTS.md → Commands) - severity: P2 - scope: project - frequency: 2x+ + severity: P2 + scope: project + frequency: 2x+ diff --git a/.agents/skills/panda/SKILL.md b/.agents/skills/panda/SKILL.md new file mode 100644 index 000000000..b72c3f9b7 --- /dev/null +++ b/.agents/skills/panda/SKILL.md @@ -0,0 +1,123 @@ +--- +name: "panda" +description: "Use when: writing or changing any style in website/ — css({}) calls, recipes or patterns, adding a token to panda.config.ts, or debugging an element that renders a class name but no CSS. Carries the six ways Panda fails silently in this repo (the component renders, tsc and tests pass, only the CSS is missing), the recipe for verifying a bulk style change, and pointers to the design system and the tests that enforce the rules." +--- + +# Panda styling in `website/` + +Load this **before** writing or changing any style. The rules below are the ways Panda's +static compilation fails _silently_: the component still renders, the class name is still +emitted, `tsc` and the component tests pass, and only the CSS is wrong or missing. They +have all shipped here before. Structure follows the official Panda agent-skills pattern +(): general Panda knowledge lives in +the official skills and docs, this file teaches the project's own rules. The executable +checklist is `website/test/styleConventions.test.ts` (call-site rules) and +`website/test/territoryRule.test.ts` (CSS really generated). + +The design system — colours and their jobs, the button recipe, the scales, where styles +live — is in [`website/README.md`](../../../website/README.md); the reasoning behind it is +in [`IDENTITY.md`](../../../website/IDENTITY.md). Read both before adding any style; this +skill is not a summary of them. + +## The six silent-failure rules + +1. **Never pass a JS variable as a `css({})` value.** Panda resolves values statically at + build time; what it cannot trace without running the code emits no CSS at all. Since the + v2 engine, Panda _does_ trace a local constant and a named import + (`import { accent } from "./palette"`) — but the convention is still the token _name_, + spelled out at the call site: a traced raw value (`"#654321"`) silently goes off-token, + and a value that only exists at runtime (a prop, state, a default or namespace import) + still produces nothing, with no error anywhere. + + ```ts + css({ color: props.accent }); // ✗ runtime value — silently produces nothing + css({ color: importedHex }); // ✗ v2 emits this, but as a raw literal — off-token + css({ color: "essayAccent" }); // ✓ token name, resolved at build time + ``` + + When a value is needed in both CSS and JS (a Chart.js dataset, an SVG `fill`, an inline + `style={{}}`), define it once as a token in `panda.config.ts`, use the token _name_ inside + `css({})`, and read it via `token("colors.essayAccent")` everywhere else. Pattern: + `components/blog/palette.ts`. + +2. **Spacing tokens resolve for single values only, never inside a shorthand.** + + ```ts + padding: "4" // ✓ var(--spacing-4) = 16px + padding: "2 3" // ✗ emits `2px 3px` — NOT 8px/12px + padding: "8px 12px" // ✓ explicit + paddingY: "2", paddingX: "3" // ✓ longhands do tokenise + ``` + + This makes a bulk raw→token migration shrink every shorthand it touches by ~4×. + +3. **Only reference tokens that exist.** An unknown path is passed through as a literal string + and the browser discards the whole declaration — e.g. `token(colors.primary)` when the token + is named `brand` emits `color: colors.primary`. Token names come from `panda.config.ts` and + the generated `styled-system/`, not from guesswork; the official + `panda-css` skill reads that output before writing — do the same. + +4. **The `"token(…)"` string form works only inside `css({})`.** Panda resolves it at build + time; a JSX inline style never reaches Panda, so the literal ships and the browser drops + the declaration. + + ```tsx + style={{ border: "1px solid token(colors.danger)" }} // ✗ no border at all + style={{ border: `1px solid ${token("colors.danger")}` }} // ✓ imported function + className={css({ border: "1px solid token(colors.danger)" })} // ✓ build-time + ``` + + Rule 3 does not catch this — the path is valid, just unresolved. + +5. **`fontFamily` must name one of the three site faces** — `reading` (serif prose), `ui` + (sans chrome), `code` (mono), or `inherit`. Two ways to get this wrong, both silent: + + ```ts + fontFamily: "monospace"; // ✗ CSS generic — bypasses the token system + fontFamily: "mono"; // ✗ Panda's PRESET token — still valid, still the old + // system stack. No error anywhere. + fontFamily: "code"; // ✓ + ``` + + The preset's `sans`/`serif`/`mono` survive alongside the custom tokens, so a stale call + site resolves to a real value and simply renders the wrong font. See + [`website/README.md`](../../../website/README.md) → Typography. + +6. **A config recipe variant passed as a variable emits only the default.** Rule 1 one level up — + Panda resolves recipe variants statically too. + ```tsx + sectionRule({ territory }); // ✗ only defaultVariants gets CSS + sectionRule({ territory: "explore" }); // ✓ literal, extractable + ``` + The class name is still rendered, so the element gets the recipe's `base` and nothing else. + This shipped: the territory rule was an invisible 48×3px transparent block on all six + `territory="explore"` pages. The fix is not at the call site — list the recipe in `staticCss`: + ```ts + staticCss: { + recipes: { + sectionRule: [{ territory: ["*"] }]; + } + } + ``` + Only variants that _paint_ need this. A variant whose style object is empty (`active: { true: {} }` + on the button recipe) styles via `compoundVariants`, which Panda emits as atomic utilities. + `test/territoryRule.test.ts` asserts the CSS is really generated; rule 6 in + `test/styleConventions.test.ts` catches the call site. + +## Verifying a bulk style change + +`npm run typecheck` and `npm test` cannot see a wrong colour or a dropped declaration. Diff +the _emitted declarations_ instead — build before and after, resolve `var(--…)` back to +literals, and compare. For a pure rename the set must be identical; anything else is either +an intended change or a bug, and the list should be short enough to read. + +Note that Panda escapes `.`, `(`, `)` **and commas** in class names +(`.bg-c_rgba\(123\,_63\,_160\,_0\.04\)`), so a naive `grep -F` for an unescaped class reports +false misses. + +## Where the general knowledge lives + +This file teaches the project. General Panda questions — which primitive to use, theming, +the v1→v2 migration — go to the official skills (`npx skills add chakra-ui/panda`: +`panda-css`, `panda-css-theming`, `panda-css-migrate`) and the docs index at +. diff --git a/AGENTS.md b/AGENTS.md index f33b609f7..a89ed3b2d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -93,7 +93,7 @@ See [`.github/THREAT_MODEL.md`](.github/THREAT_MODEL.md) for the full asset inve Rules that apply everywhere: - Private keys go in the Hardhat keystore (`npx hardhat keystore set SEPOLIA_PRIVATE_KEY`) or a Scaleway secret — never in code, never committed. -- All serverless responses must include CORS headers (`Access-Control-Allow-Origin: *`). +- All serverless responses must include CORS headers. Public endpoints send `Access-Control-Allow-Origin: *`; `analytics` deliberately restricts to an origin whitelist instead — don't normalize it to `*`. ## Blog Posts diff --git a/scw_js/AGENTS.md b/scw_js/AGENTS.md index a6240a4ea..488d161f9 100644 --- a/scw_js/AGENTS.md +++ b/scw_js/AGENTS.md @@ -20,5 +20,4 @@ OpenAPI strictness rule) are in the root [`AGENTS.md`](../AGENTS.md) → \_x402 ## Adding a network Follow the checklist in [`README.md`](./README.md) → _Adding New Networks_ — it carries the -verified per-network EIP-712 domain name table (mainnet USDC is `"USD Coin"`, testnet is -`"USDC"`). Getting this wrong silently breaks payments. +verified per-network EIP-712 domain name table. Getting this wrong silently breaks payments. diff --git a/website/AGENTS.md b/website/AGENTS.md index 74e7a30a5..8de2ab4bb 100644 --- a/website/AGENTS.md +++ b/website/AGENTS.md @@ -5,107 +5,14 @@ the layout, and **the design system** — colours and their jobs, the button rec scales, where styles live. Read that before adding any style. The reasoning behind the system is in [`IDENTITY.md`](./IDENTITY.md). -The rules below are only the ways Panda fails _silently_; they are not a summary of the -system. - -## Styling rules (enforced by `test/styleConventions.test.ts`) - -Panda compiles `css({})` and recipe variants **statically at build time**. Six mistakes therefore -break styling _silently_ — the component still renders, the class name is still emitted, `tsc` and -the component tests all pass, and only the CSS is wrong or missing. They have all shipped here -before. The test file catches them; these are the rules it enforces. - -1. **Never pass a JS variable as a `css({})` value.** Panda resolves values statically at - build time; what it cannot trace without running the code emits no CSS at all. Since the - v2 engine, Panda _does_ trace a local constant and a named import - (`import { accent } from "./palette"`) — but the convention is still the token _name_, - spelled out at the call site: a traced raw value (`"#654321"`) silently goes off-token, - and a value that only exists at runtime (a prop, state, a default or namespace import) - still produces nothing, with no error anywhere. - - ```ts - css({ color: props.accent }); // ✗ runtime value — silently produces nothing - css({ color: importedHex }); // ✗ v2 emits this, but as a raw literal — off-token - css({ color: "essayAccent" }); // ✓ token name, resolved at build time - ``` - - When a value is needed in both CSS and JS (a Chart.js dataset, an SVG `fill`, an inline - `style={{}}`), define it once as a token in `panda.config.ts`, use the token _name_ inside - `css({})`, and read it via `token("colors.essayAccent")` everywhere else. Pattern: - `components/blog/palette.ts`. - -2. **Spacing tokens resolve for single values only, never inside a shorthand.** - - ```ts - padding: "4" // ✓ var(--spacing-4) = 16px - padding: "2 3" // ✗ emits `2px 3px` — NOT 8px/12px - padding: "8px 12px" // ✓ explicit - paddingY: "2", paddingX: "3" // ✓ longhands do tokenise - ``` - - This makes a bulk raw→token migration shrink every shorthand it touches by ~4×. - -3. **Only reference tokens that exist.** An unknown path is passed through as a literal string - and the browser discards the whole declaration — e.g. `token(colors.primary)` when the token - is named `brand` emits `color: colors.primary`. - -4. **The `"token(…)"` string form works only inside `css({})`.** Panda resolves it at build - time; a JSX inline style never reaches Panda, so the literal ships and the browser drops - the declaration. - - ```tsx - style={{ border: "1px solid token(colors.danger)" }} // ✗ no border at all - style={{ border: `1px solid ${token("colors.danger")}` }} // ✓ imported function - className={css({ border: "1px solid token(colors.danger)" })} // ✓ build-time - ``` - - Rule 3 does not catch this — the path is valid, just unresolved. - -5. **`fontFamily` must name one of the three site faces** — `reading` (serif prose), `ui` - (sans chrome), `code` (mono), or `inherit`. Two ways to get this wrong, both silent: - - ```ts - fontFamily: "monospace"; // ✗ CSS generic — bypasses the token system - fontFamily: "mono"; // ✗ Panda's PRESET token — still valid, still the old - // system stack. No error anywhere. - fontFamily: "code"; // ✓ - ``` - - The preset's `sans`/`serif`/`mono` survive alongside the custom tokens, so a stale call - site resolves to a real value and simply renders the wrong font. See - [`README.md`](./README.md) → Typography. - -6. **A config recipe variant passed as a variable emits only the default.** Rule 1 one level up — - Panda resolves recipe variants statically too. - ```tsx - sectionRule({ territory }); // ✗ only defaultVariants gets CSS - sectionRule({ territory: "explore" }); // ✓ literal, extractable - ``` - The class name is still rendered, so the element gets the recipe's `base` and nothing else. - This shipped: the territory rule was an invisible 48×3px transparent block on all six - `territory="explore"` pages. The fix is not at the call site — list the recipe in `staticCss`: - ```ts - staticCss: { - recipes: { - sectionRule: [{ territory: ["*"] }]; - } - } - ``` - Only variants that _paint_ need this. A variant whose style object is empty (`active: { true: {} }` - on the button recipe) styles via `compoundVariants`, which Panda emits as atomic utilities. - `test/territoryRule.test.ts` asserts the CSS is really generated; rule 6 in - `test/styleConventions.test.ts` catches the call site. - -## Verifying a bulk style change - -`npm run typecheck` and `npm test` cannot see a wrong colour or a dropped declaration. Diff -the _emitted declarations_ instead — build before and after, resolve `var(--…)` back to -literals, and compare. For a pure rename the set must be identical; anything else is either -an intended change or a bug, and the list should be short enough to read. - -Note that Panda escapes `.`, `(`, `)` **and commas** in class names -(`.bg-c_rgba\(123\,_63\,_160\,_0\.04\)`), so a naive `grep -F` for an unescaped class reports -false misses. +## Styling + +Use the **`panda`** skill before writing or changing any style — it carries the six ways +Panda fails _silently_ in this repo (the component renders, `tsc` and the tests pass, only +the CSS is missing) and the recipe for verifying a bulk style change. The one-line summary: +never pass a JS variable as a `css({})` value, and remember that typecheck and component +tests are blind to missing CSS. `test/styleConventions.test.ts` and `test/territoryRule.test.ts` +enforce the same rules executably. ## Other conventions diff --git a/x402_facilitator/AGENTS.md b/x402_facilitator/AGENTS.md index cf14b36d6..bfd12a822 100644 --- a/x402_facilitator/AGENTS.md +++ b/x402_facilitator/AGENTS.md @@ -18,7 +18,7 @@ are in the root [`AGENTS.md`](../AGENTS.md) → \_x402 payments*. ## Recipient whitelist (OR logic) -`/verify` accepts a recipient if **any** of: the manual list, a testnet-only test wallet, or -NFT-holder status. The composition and the reasons are in [`README.md`](./README.md) → -_Whitelist Architecture_ — read it before touching recipient gating, and keep the -batch-settlement test-wallet bypass honored on testnets only. +Recipient gating is **OR logic** across several sources, not a single list. The composition +and the reasons are in [`README.md`](./README.md) → _Whitelist Architecture_ — read it before +touching recipient gating, and keep the batch-settlement test-wallet bypass honored on +testnets only.