Skip to content

Commit 1c94980

Browse files
authored
Merge pull request #70 from radcrew/refactor/cleanup-and-stabilize
Render pet art, restructure the repo, and close the CI gaps
2 parents 7d14957 + 35f71ec commit 1c94980

273 files changed

Lines changed: 4198 additions & 1893 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/image-generator.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
name: Image generator
22

33
# The service is deliberately not a pnpm workspace member (see
4-
# image-generator/README.md), so the root `pnpm lint` / `pnpm test` aggregates and
5-
# the Coverage workflow do not reach it. Without this workflow nothing runs its
4+
# services/image-generator/README.md), so the root `pnpm lint` / `pnpm test` aggregates
5+
# and the Coverage workflow do not reach it. Without this workflow nothing runs its
66
# suite at all.
77
# The path filter must also list the files `src/solanaLayout.test.ts` reads, not just
88
# this package. That suite pins the PetAccount byte layout against the Anchor IDL and
99
# `pet.rs`, so a Solana account change breaks it while touching nothing under
10-
# `image-generator/`. Filtered on this package alone the suite simply does not run, which
10+
# `services/image-generator/`. Filtered on this package alone the suite simply does not run, which
1111
# is how `open_to_challenges` was removed from the program while the decoder kept its
1212
# byte — every field after it misaligned, and a pet rendered as a different pet.
1313
#
@@ -17,17 +17,17 @@ on:
1717
pull_request:
1818
branches: [main]
1919
paths:
20-
- 'image-generator/**'
20+
- 'services/image-generator/**'
2121
- '.github/workflows/image-generator.yml'
2222
- 'contracts/solana/cryptopets/programs/cryptopets/src/state/**'
23-
- 'indexer-go/internal/solana/idl/**'
23+
- 'services/indexer-go/internal/solana/idl/**'
2424
push:
2525
branches: [main]
2626
paths:
27-
- 'image-generator/**'
27+
- 'services/image-generator/**'
2828
- '.github/workflows/image-generator.yml'
2929
- 'contracts/solana/cryptopets/programs/cryptopets/src/state/**'
30-
- 'indexer-go/internal/solana/idl/**'
30+
- 'services/indexer-go/internal/solana/idl/**'
3131

3232
permissions:
3333
contents: read
@@ -38,7 +38,7 @@ concurrency:
3838

3939
defaults:
4040
run:
41-
working-directory: image-generator
41+
working-directory: services/image-generator
4242

4343
jobs:
4444
check:
@@ -55,7 +55,7 @@ jobs:
5555
node-version: 22
5656
cache: pnpm
5757
# Its own lockfile, not the monorepo root's.
58-
cache-dependency-path: image-generator/pnpm-lock.yaml
58+
cache-dependency-path: services/image-generator/pnpm-lock.yaml
5959

6060
# --ignore-workspace, or pnpm walks up to the monorepo root and installs
6161
# that instead, leaving this package with no node_modules.

.github/workflows/mobile.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Mobile
2+
3+
# `mobile` is the one package with a test script that no workflow ran. Root `pnpm lint`
4+
# covers its ESLint, and `pnpm build` skips it entirely (React Native builds through the
5+
# `android`/`ios` scripts), so nothing executed its jest suite. That suite is a single
6+
# boot smoke test, but it is the only mechanical check that App.tsx's imports still
7+
# resolve and its provider tree renders, which is exactly what a rename in `shared` or a
8+
# moved module breaks silently here while every other package stays green.
9+
10+
on:
11+
pull_request:
12+
branches: [main]
13+
push:
14+
branches: [main]
15+
16+
permissions:
17+
contents: read
18+
19+
concurrency:
20+
group: mobile-${{ github.workflow }}-${{ github.ref }}
21+
cancel-in-progress: true
22+
23+
jobs:
24+
test:
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v4
28+
29+
- uses: pnpm/action-setup@v4
30+
31+
- uses: actions/setup-node@v4
32+
with:
33+
node-version: 20
34+
cache: pnpm
35+
36+
- name: Install dependencies
37+
run: pnpm install --frozen-lockfile
38+
39+
# Deliberately not path-filtered on `mobile/**`. The failure this catches comes
40+
# from changing `shared` or a module mobile imports, not from touching mobile
41+
# itself, so a filter would skip precisely the run that matters. See the
42+
# image-generator workflow for what path filtering has already cost here.
43+
- name: Mobile tests
44+
run: pnpm --filter mobile test

.github/workflows/parity.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ name: Combat parity
77
# neither:
88
#
99
# protocol tests/combat/goldenVectors.test.ts (the canonical TS engine)
10-
# indexer-go internal/combat/combat_golden_test.go (the independent Go port)
10+
# services/indexer-go internal/combat/combat_golden_test.go (the independent Go port)
1111
# contracts/ethereum test/XpFormula.test.ts (the XP fixture)
1212
#
1313
# §F's circuit breaker only has value while the TS and Go ports are independent and both
@@ -84,19 +84,19 @@ jobs:
8484

8585
- uses: actions/setup-go@v5
8686
with:
87-
go-version-file: indexer-go/go.mod
88-
cache-dependency-path: indexer-go/go.sum
87+
go-version-file: services/indexer-go/go.mod
88+
cache-dependency-path: services/indexer-go/go.sum
8989

9090
- name: Vet
91-
working-directory: indexer-go
91+
working-directory: services/indexer-go
9292
run: go vet ./...
9393

9494
# Unit tests only. The Postgres-backed tests are gated on TEST_DATABASE_URL and
9595
# truncate tables, so they are deliberately not given one here.
9696
- name: Test
97-
working-directory: indexer-go
97+
working-directory: services/indexer-go
9898
run: go test ./...
9999

100100
- name: Build
101-
working-directory: indexer-go
101+
working-directory: services/indexer-go
102102
run: go build -o /dev/null ./cmd/indexer
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Static checks
2+
3+
# The two whole-repo checks that nothing ran.
4+
#
5+
# Lint: the root `pnpm lint` aggregate ran in no workflow. `protocol` and `verifier`
6+
# are linted by their own workflows, and `image-generator` by its own, but `frontend`,
7+
# `backend`, `shared`, `website`, and `mobile` were linted only by hand. That includes
8+
# frontend's custom CSS-naming check (`lint:css`), which AGENTS.md lists under
9+
# Enforcement.
10+
#
11+
# Build: the only thing that typechecks `frontend`, `backend`, and `website`. None of
12+
# the three has a `typecheck` script — their type check *is* the build (`tsc -b &&
13+
# vite build`, `tsc`, `next build`) — and vitest does not typecheck, since esbuild
14+
# strips types without checking them. A type error on a path no test executes passed
15+
# every existing workflow and surfaced at deploy.
16+
#
17+
# One job rather than two: both need the same install, which dominates the runtime.
18+
# `if: always()` on the build step so a lint failure still reports the build result.
19+
#
20+
# Deliberately not path-filtered. These break from a change in `shared` or `protocol`
21+
# landing on a consumer that was not touched, so filtering on the consumer's own paths
22+
# would skip the run that matters.
23+
24+
on:
25+
pull_request:
26+
branches: [main]
27+
push:
28+
branches: [main]
29+
30+
permissions:
31+
contents: read
32+
33+
concurrency:
34+
group: static-checks-${{ github.workflow }}-${{ github.ref }}
35+
cancel-in-progress: true
36+
37+
jobs:
38+
checks:
39+
runs-on: ubuntu-latest
40+
steps:
41+
- uses: actions/checkout@v4
42+
43+
- uses: pnpm/action-setup@v4
44+
45+
- uses: actions/setup-node@v4
46+
with:
47+
# 22, not the 20 most workflows use: `pnpm build` starts with the contracts
48+
# compile, and Hardhat 3 needs >= 22.10. It calls `.flatMap` on the iterator
49+
# from `Map.values()`, an Iterator Helpers method absent before Node 22, so
50+
# `compile` dies with a TypeError rather than a version check. Same reason
51+
# parity.yml pins 22.
52+
node-version: 22
53+
cache: pnpm
54+
55+
- name: Install dependencies
56+
run: pnpm install --frozen-lockfile
57+
58+
# frontend (eslint + CSS naming), backend, protocol, verifier, shared,
59+
# website, mobile. Not contracts/ethereum, which has no lint script.
60+
- name: Lint
61+
run: pnpm lint
62+
63+
# Root aggregate: contracts compile, then backend, frontend, and website.
64+
# Runs unattended (no secrets), about a minute and a half.
65+
- name: Build
66+
if: always()
67+
run: pnpm build

.github/workflows/verifier.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ jobs:
3939
if: always()
4040
run: pnpm --filter @cryptopets/verifier lint
4141

42+
# Separate from the tests: vitest strips types rather than checking them, so a
43+
# type error in a path the suite does not execute reaches a third party's
44+
# checkout instead of CI. `protocol` is typechecked in the parity workflow for
45+
# the same reason; this package is consumed as raw TS too.
46+
- name: Verifier typecheck
47+
if: always()
48+
run: pnpm --filter @cryptopets/verifier typecheck
49+
4250
# The committed corpus, run through the actual CLI rather than the library, so the
4351
# thing a third party would run is the thing CI proves still works. No network
4452
# access: the ruleset these battles were fought under is pinned in the checkout.

.gitignore

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ dist
144144
vite.config.js.timestamp-*
145145
vite.config.ts.timestamp-*
146146

147-
# Hardhat files (scoped — a bare `cache/` also hid indexer-go/internal/cache/)
147+
# Hardhat files (scoped — a bare `cache/` also hid services/indexer-go/internal/cache/)
148148
contracts/**/cache/
149149
contracts/**/artifacts/
150150
typechain-types/
@@ -156,9 +156,6 @@ coverage.json
156156
.openzeppelin/
157157
deployments/
158158

159-
# EVM subgraph generated files (regenerated by pnpm configure)
160-
backend/indexing/evm/subgraph/src/addresses.ts
161-
162159
# Hardhat local network
163160
.openzeppelin/unknown-*.json
164161

AGENTS.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,28 @@ Root coordination contract for AI and human contributors in this repo. Detailed
44

55
## Scope
66

7-
- Applies to the whole monorepo: `frontend/`, `backend/`, `mobile/`, `website/`, `shared/`, `contracts/ethereum/`, `contracts/solana/`, `indexer-go/`, `proto/`, `image-generator/`.
7+
- Applies to the whole monorepo: `frontend/`, `backend/`, `mobile/`, `website/`, `shared/`, `contracts/ethereum/`, `contracts/solana/`, `services/indexer-go/`, `proto/`, `services/image-generator/`.
88
- No nested `AGENTS.md` files exist yet. If one is added under a package, it may tighten rules for that subtree but must not relax the rules here.
99

1010
Normative language: `MUST`/`MUST NOT` are mandatory. `SHOULD`/`SHOULD NOT` are expected by default; deviations should be explained in the PR. `MAY` is optional.
1111

1212
## Non-Negotiables
1313

1414
- `MUST NOT` change Solana's frozen combat port (`game/battle_sim.rs`, `game/xp.rs`). It has no caller left in the program, but its golden-vector tests are what still prove `contracts/test-vectors/{battle,xp}.json` describe what actually settled on that chain. A bug found there is fixed forward in the live ports below, under a new `rulesetVersion`, never by patching the frozen one. **The Solidity port is gone**: `CombatSim.sol` was deleted once it had no on-chain caller, which also removed `battle.json`'s Solidity generator and validator. `battle.json` itself is unchanged and still gates the live ports.
15-
- `MUST` keep the two **live** combat ports in step with each other and with the golden vectors: `protocol/src/combat/` (the canonical engine, re-exported from `shared/src/utils/combat` for existing importers) and `indexer-go/internal/combat/` (the independent verifier). Changing one without the other re-breaks the circuit breaker in §F, whose whole value is that the two were written to disagree if either drifts. This covers XP and level progression too (`protocol/src/combat/xp.ts`, validated against `contracts/test-vectors/xp.json`), so an XP or decay change is a both-ports change. `indexer-go/internal/combat/xp.go` still covers the formula and the decay but not level-up.
15+
- `MUST` keep the two **live** combat ports in step with each other and with the golden vectors: `protocol/src/combat/` (the canonical engine, re-exported from `shared/src/utils/combat` for existing importers) and `services/indexer-go/internal/combat/` (the independent verifier). Changing one without the other re-breaks the circuit breaker in §F, whose whole value is that the two were written to disagree if either drifts. This covers XP and level progression too (`protocol/src/combat/xp.ts`, validated against `contracts/test-vectors/xp.json`), so an XP or decay change is a both-ports change. `services/indexer-go/internal/combat/xp.go` still covers the formula and the decay but not level-up.
1616
- `MUST NOT` edit `contracts/test-vectors/{battle,xp}.json` to make a failing test pass — this holds more strongly now, not less. The vectors are the only mechanical link left between the frozen ports and the live ones. A live port that fails them has drifted away from the rules real battles were settled under.
17-
- `MUST NOT` assume the `ChainAdapter` interface (`shared/src/hooks/adapters/`) covers more than pet-action mutations and reads. It is a real, shared interface (`useEvmAdapter`/`useSolanaAdapter` both implement it) and every public pet-action hook consumes it chain-blind, but the low-level chain wiring in `frontend/src/chains/{ethereum,solana}/`, the async battle/breed VRF flows, and the combat simulator remain intentionally separate per chain. See CLAUDE.md's cross-chain interfaces section for the exact boundary.
18-
- `MUST` match the license of the package being edited when adding new files: `contracts/ethereum`, `contracts/solana`, `indexer-go`, `proto`, `protocol`, and `verifier` are MIT; everything else, `image-generator` included, is PolyForm Noncommercial 1.0.0 (root `LICENSE`). See the table in `README.md`. `protocol` is MIT on purpose (third parties have to be able to replay signed battle receipts), so it `MUST NOT` import from a PolyForm package; a test in that package enforces it. `verifier` is MIT for the same reason and depends on nothing but `protocol`.
19-
- `MUST NOT` assume the root `pnpm lint` / `pnpm test` cover `image-generator`, and `MUST NOT` verify it with `pnpm --filter image-generator <script>`. It is not a pnpm workspace member, so that command prints `No projects matched the filters` **and exits 0**: it reports success having run nothing. Run its scripts from `image-generator/` instead. It keeps its own lockfile, installs with `pnpm install --ignore-workspace`, and is checked by its own CI workflow.
17+
- `MUST NOT` assume the `ChainAdapter` interface (`shared/src/hooks/adapters/`) covers more than pet-action mutations and reads. It is a real, shared interface (`useEvmAdapter`/`useSolanaAdapter` both implement it) and every public pet-action hook consumes it for the mutation, but the low-level chain wiring in `frontend/src/chains/{ethereum,solana}/`, the async breed/mint randomness flows, and the combat simulator remain intentionally separate per chain. `useCreatePet` and `useBreedPets` are only chain-blind on the action: both carry the EVM settle lifecycle inline behind `isEvm` guards. See CLAUDE.md's cross-chain interfaces section for the exact boundary.
18+
- `MUST` match the license of the package being edited when adding new files: `contracts/ethereum`, `contracts/solana`, `services/indexer-go`, `proto`, `protocol`, and `verifier` are MIT; everything else, `services/image-generator` included, is PolyForm Noncommercial 1.0.0 (root `LICENSE`). See the table in `README.md`. `protocol` is MIT on purpose (third parties have to be able to replay signed battle receipts), so it `MUST NOT` import from a PolyForm package; a test in that package enforces it. `verifier` is MIT for the same reason and depends on nothing but `protocol`.
19+
- `MUST NOT` assume the root `pnpm lint` / `pnpm test` cover `image-generator`, and `MUST NOT` verify it with `pnpm --filter image-generator <script>`. It is not a pnpm workspace member, so that command prints `No projects matched the filters` **and exits 0**: it reports success having run nothing. Run its scripts from `services/image-generator/` instead. It keeps its own lockfile, installs with `pnpm install --ignore-workspace`, and is checked by its own CI workflow.
2020
- `MUST NOT` treat the v1 contract gaps documented in `contracts/plan-contract-upgrade.md` (no battle authorization, the `changeDna` cheat, client-supplied Solana starter-pet DNA) as bugs to silently patch. They are the known baseline the v2 rewrite is designed around.
2121
- `MUST` run the smallest scoped lint/test/build command for the package you touched (see Command Baseline below), not a full monorepo run, unless the change is broad.
2222
- `SHOULD NOT` trust `DEVELOPMENT.md`, `contracts/ethereum/README.md`, or the root `eth:deploy` / `eth:vrf:watch` scripts at face value. Several reference commands removed in a past refactor; see CLAUDE.md's Commands section for what is actually current.
2323

2424
## Command Baseline
2525

2626
- Install: `pnpm install` (root), or `pnpm install:all` (root + frontend + website + backend + mobile + contracts/ethereum)
27-
- Dev: `pnpm dev` (backend + frontend), or `pnpm dev:fe` / `pnpm dev:be` / `pnpm dev:mobile` / `pnpm dev:web` individually, or `pnpm fe:eth:local` / `pnpm fe:sol:local` for a full local chain + backend + frontend stack
28-
- Lint: `pnpm lint` (covers frontend, shared, website, mobile only, not backend or contracts/ethereum)
27+
- Dev: `pnpm dev` (backend + frontend + image-generator + indexer-go; no `--kill-others-on-fail`, so an unconfigured optional service dies alone), or `pnpm dev:fe` / `pnpm dev:be` / `pnpm dev:art` / `pnpm dev:idx` / `pnpm dev:mobile` / `pnpm dev:web` individually, or `pnpm fe:eth:local` / `pnpm fe:sol:local` for a full local chain + backend + frontend stack
28+
- Lint: `pnpm lint` (covers frontend, backend, shared, protocol, verifier, website, mobile, contracts/ethereum)
2929
- Test: `pnpm test` (equals `contracts/ethereum` test only; per-package test commands are in CLAUDE.md)
3030
- Build: `pnpm build`
3131

@@ -34,10 +34,10 @@ Full per-package lint/test/build matrix and single-test syntax: see [CLAUDE.md](
3434
## Where To Look
3535

3636
- Behavioral guidelines and full architecture: [CLAUDE.md](./CLAUDE.md)
37-
- Data flow and component map: [CLAUDE.md](./CLAUDE.md#architecture) (`docs/architecture.md` is referenced in places but does not exist)
37+
- Data flow and component map: [CLAUDE.md](./CLAUDE.md#architecture) (there is no `docs/architecture.md`)
3838
- Test suite conventions: [docs/testing.md](./docs/testing.md)
3939
- Backend API surface: [backend/API.md](./backend/API.md)
40-
- Indexer internals: [indexer-go/README.md](./indexer-go/README.md)
40+
- Indexer internals: [services/indexer-go/README.md](./services/indexer-go/README.md)
4141
- Contract v1-to-v2 migration plan: [contracts/plan-contract-upgrade.md](./contracts/plan-contract-upgrade.md)
4242
- Contribution workflow: [CONTRIBUTING.md](./CONTRIBUTING.md)
4343
- Reporting vulnerabilities: [SECURITY.md](./SECURITY.md)

0 commit comments

Comments
 (0)