levy-street/world-of-claudecraft
GitHub将大型功能拆解为多阶段实施计划,适配Opus 4.8架构。通过子代理和跨会话状态管理保存上下文,确保模拟核心、服务器与RL环境的一致性、确定性及各语言本地化要求。
Install All Skills
npx skills add levy-street/world-of-claudecraft --all -g -y
More Options
List skills in collection
npx skills add levy-street/world-of-claudecraft --list
Skills in Collection (5)
.claude/skills/feature-plan/SKILL.md
npx skills add levy-street/world-of-claudecraft --skill feature-plan -g -y
SKILL.md
Frontmatter
{
"name": "feature-plan",
"description": "Break a big feature into a phased implementation plan with starter prompts, progress tracking, and cross-session state. Opus 4.8 native - each phase runs as its own session with context-saving subagents, agent teams, and (for batch-heavy work) deterministic Workflows, plus a web-research pass for any third-party surface. Use when a feature is too large for one session.",
"user-invocable": true,
"disable-model-invocation": true
}
Feature Plan: Multi-Phase Implementation Planning (Opus 4.8)
Break a large feature into a phased implementation plan designed for multiple Claude Code sessions. Every phase runs as its own fresh session and uses Opus 4.8 at xhigh effort (ultracode where the phase warrants deterministic multi-agent orchestration). The whole point is to save context per phase: the orchestrator delegates reading and fan-out to subagents and keeps only conclusions, so each session stays sharp.
The user will provide a feature description either inline (e.g., /feature-plan add a guild bank) or you will ask them to describe it.
Before doing anything else: scan memory. If you use Claude Code's memory feature, check your MEMORY.md index and project memory entries for prior decisions or feedback relevant to this feature's domain. Past incidents encoded there are cheaper than rediscovering them.
What this repo is (so the plan fits it)
World of ClaudeCraft is a classic-style micro-MMO and a headless RL environment, both driven by one deterministic TypeScript simulation core. The load-bearing ideas the plan must respect:
- One sim, three hosts. The exact same
src/sim/code runs the offline browser world, the online authoritative server (server/), and the RL env (headless/). Behavior must be identical everywhere. A feature added to the offlineSimis not done until it is mirrored online (viaClientWorld) and, where relevant, exposed to the env. IWorldis the only seam (src/world_api.ts).render/andui/talk only toIWorld; the offlineSimsatisfies it structurally and the onlineClientWorldimplements it by mirroring server snapshots. New feature: extendIWorldfirst, then implement it in both worlds.- The server is authoritative. Clients stream intent + commands at 20 Hz; the server runs the one shared
Simand returns interest-scoped snapshots (radius defined inserver/game.ts) + per-player events. All combat, loot, quest credit, and economy resolve server-side. The client never decides outcomes. - Determinism is non-negotiable. Fixed 20 Hz tick (
DT = 1/20,src/sim/types.ts); all randomness goes throughRng(src/sim/rng.ts). NeverMath.random,Date.now, orperformance.nowin sim logic. Same seed gives the same world. - i18n is a release gate. Every player-visible string is a
t()key present in every locale. Sim/server stay language-agnostic (not(), no DOM) but emit player text that is re-localized at the client boundary via the matchers insrc/ui/sim_i18n.ts/src/ui/server_i18n.ts. The S3 guard (tests/localization_fixes.test.ts) enforces it.
Orchestration Toolbox (Opus 4.8) - choose deliberately before running any phase
Opus 4.8 self-initiates fewer subagents than prior models, so you MUST request fan-out explicitly. Pick the lightest tool that fits; escalate only when the work demands it.
| Tool | What it is | Use it when | Context effect |
|---|---|---|---|
Explore subagent (subagent_type: "Explore") |
Read-only search agent; reads excerpts, returns conclusions | Mapping the codebase, locating files/patterns, "where is X used" | Raw file reads stay in the subagent; only the summary returns. Default for all recon. |
Parallel Agent fan-out (multiple Agent calls in one message) |
Several independent agents in one turn | Independent vertical slices in a phase (sim + server + ui + tests); parallel reviews | Each agent's work stays in its own context; you keep the results. Cap at ~5 manual agents. |
Agent teams (name: + SendMessage) |
Addressable, longer-lived agents you can hand more work to with context intact | Multi-round collaboration where an agent needs its prior context (iterative implement, fix, re-review) | Reuses a warm agent instead of re-priming a fresh one. Never mode: "plan" on teammates (they stall). |
Workflow (the Workflow tool) |
A JS script orchestrating dozens to hundreds of subagents deterministically (pipeline / parallel / loop-until-dry / adversarial-verify), with structured-output schemas | A phase is batch-heavy or needs scale + verification: mass edits, content sweeps across many tables, exhaustive audits, adversarially-verified review. Requires explicit ultracode opt-in in the running prompt. |
Intermediate results live in script variables, not your context. Scales far past manual fan-out (16 concurrent / 1000 total). |
| ToolSearch / deferred tools | On-demand tool-schema loading | A phase needs an MCP/integration tool not loaded by default | Keeps unused tool schemas out of context until needed. |
Hard rules (Opus 4.8 + this repo):
- Subagents inherit the parent model. No need to set model unless you deliberately want a cheaper tier; when unsure, omit.
- Request fan-out explicitly - say "spawn N agents in parallel", name the split. 4.8 will not infer it.
- Manual parallel fan-out caps at ~5 agents. Beyond that, coordination overhead wins - use a Workflow instead.
- Workflow is opt-in. Only reach for it when the running phase prompt includes
ultracode(or the user asked for a workflow). For batch-heavy phases, the starter prompt should TELL the runner to addultracodeand orchestrate via a Workflow. - Shared working tree. A concurrent session may share this exact checkout. Commit sequentially with EXPLICIT paths, never
git add -A; often the right move is to commit nothing that is not yours. (See memory: shared-worktree-commit-care.)
Opus 4.8 Prompting Discipline (apply to EVERY prompt this skill emits)
- State scope literally and exhaustively. 4.8 follows instructions literally and will NOT generalize from one example. Write "all three hosts", "every locale in
translations", "each of the nine classes" - never "the sections" or "the files". - Reserve ALL-CAPS / NON-NEGOTIABLE for genuine determinism, server-authority, security, and data-integrity gates. If everything is emphasized, nothing is. Routine conventions read fine in plain voice.
- For review/QA agents, the finding stage is COVERAGE, not filtering. 4.8 honors "be conservative / don't nitpick" literally and reports fewer real issues. Always prompt: "report every issue including low-severity and uncertain ones; ranking happens in a later step."
- Review agents truncate mid-analysis. Budget 2-3 SendMessage rounds; resume with: "Stop reading more files. Output the full report now based on what you've already seen. No more tool calls. Format: BLOCKING / SHOULD-FIX / NICE-TO-HAVE / VERDICT."
- Demand structured handoffs. A phase ends by writing its state to
progress.md/state.mdand (for big packets) a per-phase resume file - that IS the cross-session memory. The next session reads the summary, not the transcript.
Context Discipline (how each phase stays cheap)
- The orchestrator (main loop) does not read large docs or sprawl across source files. It spawns an Explore agent that returns a focused summary. (
src/ui/hud.ts~10k,src/sim/sim.ts~7.5k,src/main.ts~6.4k; the big i18n surface is nowsrc/ui/i18n.catalog/*plus the generated overlays, noti18n.ts; never read these whole in the main loop.) - Give each implementation agent ONLY the slice of context it needs (the Explore summary + its own files), never the raw planning docs.
- Delegate web/doc lookups to a subagent (classic-era MMO formula references, a Three.js or GLB asset technique, the
pgPostgres driver, Cloudflare Turnstile, any third-party surface); keep raw docs out of the main context. - For 12+ phase packets, use per-phase resume files so a fresh session resumes from a checkpoint, not from scratch.
- Use ToolSearch to pull in deferred tool schemas only when a phase actually needs them.
Step 1: Understand the Feature
If the user did not provide a feature description inline, ask them to describe what they want to build. Get enough detail to understand scope, but do not over-interrogate.
Step 2: Explore the Codebase + Research the External Surface (parallel agents)
Spawn these in parallel in a single message. Do NOT read these files yourself - save your context window. Each returns a summary, not raw dumps.
Codebase Explore agents (subagent_type: "Explore")
Adapt the split to the feature (a content-only feature needs the sim/content explorer, not the net explorer; add a headless-explorer for RL-env work):
sim-explorer - src/sim/ core: tick loop, combat/abilities/threat, mob AI, parties/duels/arena/trade/market/dungeons, the RL observation surface, Rng usage, and overlapping content in src/sim/content/ (classes, abilities, zones, dungeons, items, talents). Note relevant test patterns in tests/.
server-explorer - server/: GameServer loop and command dispatch (server/game.ts), interest-scoped snapshots (wireEntity/selfWireJson), Postgres persistence and the inline SCHEMA (server/db.ts) + SOCIAL_SCHEMA (server/social_db.ts), auth (server/auth.ts), social/moderation, rate limiting.
client-explorer - src/render/ (Three.js renderer; reads world, never mutates), src/ui/ (HUD, windows, tooltips, map, FCT, i18n), and src/game/ (input, camera, keybinds, mobile/touch controls). Note which IWorld members the feature will read or call.
net-explorer - src/net/ (ClientWorld implements IWorld, REST auth, WS mirror) and the wire-protocol lockstep with server/game.ts (applySnapshot/applyWire, SimEvent handling). Use when the feature changes anything that crosses the network.
admin-explorer - src/admin/ (the separate admin dashboard SPA, its own admin.html entry and admin/api) and its dedicated i18n catalog src/admin/i18n.ts. Use when the feature has an admin, moderation, or operator surface. Operators are users, so admin strings localize too.
(Add headless-explorer for headless/ + python/ RL-env work. One focused agent per surface, up to the ~5-agent cap.)
Web-research agent (REQUIRED when the feature touches any third-party surface)
If the feature involves a third-party API, SDK, library, framework, cloud service, or a real classic-era MMO formula you need to get exactly right, spawn a web-research agent (general-purpose, or claude-code-guide for Claude tooling; both have WebSearch/WebFetch). Instruct it to pull current, primary-source data (prefer official docs over blogs), since APIs and references drift. Ask for: exact endpoints/auth/schemas (for an API), the canonical formula/constants (for balance math), version/migration notes, and licensing for any asset. It must return a tight brief with citations and mark anything unverifiable as OPEN rather than guessing. Fold OPEN items into the plan as blockers, never as assumptions. (Gameplay math must follow real classic-era formulas; do not invent balance numbers.)
For sweeping research (many sources cross-checked), the running session can use an ultracode Workflow (multi-modal sweep + adversarial-verify) instead of a single agent.
Step 3: Brainstorm with the User
Present findings from the Explore + research agents (summarized, not raw) and brainstorm:
- What systems/content already exist vs what is new
- What
IWorldsurface exists vs what needs adding - Creative ideas that leverage existing infrastructure (the sim already has parties, duels, arena, trade, market, dungeons, talents, pets)
- What would make this feature stand out while staying classic-faithful
- Any OPEN items from research that need a human/credential/design decision before phasing
Get user buy-in on the overall vision before planning phases.
Step 4: Create Planning Documents
Create docs/{feature-name}/ with these files:
README.md- packet entry point and index, links to every phase file plus the cross-cutting docs.brainstorm.md- feature vision, approved ideas, current state summary, reusable systems/IWorldmembers, new work needed, research findings + OPEN items.implementation-plan.md- TOC + canonical workflow + phase summary table.progress.md- status table + per-phase deliverables/acceptance checklists.state.md- cross-phase cheat sheet (locked decisions, validation matrix, file paths, newIWorldmethods /SimEvents / wire fields / endpoints / tables / i18n keys).qa-checklist.md- whole-feature integration QA matrix (three-host parity, determinism, i18n completeness, classic fidelity, persistence, performance, deploy verification).
For packets with 12+ phases (or when each phase is non-trivial), prefer per-phase resume files instead of inlining everything into implementation-plan.md:
phase-XX-{slug}.md- implementation prompt for phase XX (self-contained; a fresh-context Claude can paste it into a new session and execute without referring back to the TOC).phase-XX-qa.md- QA prompt for phase XX.implementation-plan.mdthen becomes a TOC + canonical workflow + summary table that references the per-phase files.
README.md
The packet entry point: a one-paragraph feature summary, an index linking every phase and QA file in order, and links to the cross-cutting docs (brainstorm.md, implementation-plan.md, progress.md, state.md, qa-checklist.md). A newcomer should be able to orient from here alone.
brainstorm.md
- Feature vision and approved ideas
- Current state summary
- Existing systems /
IWorldmembers / content tables that can be reused - New work needed (sim / server / net / render / ui / headless)
- Web-research findings (with citations) and OPEN items
- Open questions for design decisions
implementation-plan.md
Split the feature into phases. Each implementation phase is followed by a dedicated QA phase. Both are separate Claude Code sessions. The numbering is: Phase 1 (implement), Phase 1 QA (verify), Phase 2 (implement), Phase 2 QA (verify), etc.
Phase sizing (critical): Prefer many small phases over fewer large ones. A phase that tries to do too much burns context, produces sloppier output, and misses details. Each implementation phase should be completable in a single focused session without exhausting the context window. Rules of thumb:
- One phase = one logical slice (e.g., "add the ability to the sim + content data + tests" or "wire the HUD window to the new
IWorldmembers"). If you find yourself writing "and also..." in the phase description, split it. - 2-4 deliverables per phase is ideal. More than 5 is a sign the phase is too big.
- When in doubt, split. Two small phases with QA passes will always produce better work than one large phase that rushes through.
Phase ordering principles:
- Phase 1 is always architecture/foundation: extend
IWorldand the sim data model, establish the pattern all later phases follow. - Phase 1 QA verifies Phase 1.
- Next phases implement sim behavior server-side and mirror it in
ClientWorld(keep the offline and online worlds in lockstep as you go, not at the end). - Then phases that add server persistence (extend the
SCHEMADDL, save/load round-trip, back-compat for existing JSONB saves). - Then renderer/HUD/i18n surface, then polish/optimization last.
- Every implementation phase gets a QA phase immediately after it.
- The final QA phase closes the packet: once it passes, it offers Packet teardown (below), deleting
docs/{feature-name}/only on explicit user confirmation so the PR does not ship the planning scaffolding.
Team Workflow section (include at top of plan):
Every phase runs on Opus 4.8 at xhigh effort (1m context variant where the file load demands it; ultracode for batch-heavy phases). Include this standard workflow:
- Step 0 - Pre-flight: Verify
git statusis clean (and that no concurrent session is mid-change in your files). Scan your Claude Code memory (theMEMORY.mdindex and any entries matching the phase domain), if you use it. - Step 1 - Load Context: Spawn an Explore agent to read planning docs and relevant source files. The main agent does NOT read large docs directly. The Explore agent returns a focused summary.
- Step 2 - Choose Orchestration + Execute: Pick the lightest tool from the Orchestration Toolbox. Default: parallel Agent fan-out, one agent per vertical slice (give each ONLY the Explore summary, not raw planning docs). For batch-heavy/audit/content-sweep phases, run an
ultracodeWorkflow (pipeline + adversarial-verify) instead. Useisolation: "worktree"only when agents mutate overlapping files in parallel. - Step 3 - Validation + Multi-Agent Review Dispatch:
- Run validation (see the matrix in
state.md):npx tsc --noEmit;npx vitest run tests/<affected>.ts(ornpm testfor broad changes). Ifsrc/sim/changed, runnpx vitest run tests/architecture.test.ts(the sim-purity guard: no render/ui/game/net/three imports, no DOM globals, no nondeterminism). If any player-visible text was added or an emit changed, runnpx vitest run tests/localization_fixes.test.ts(the S3 i18n drift guard). If the wire protocol / snapshots changed, runnpx vitest run tests/snapshots.test.ts tests/env_protocol.test.ts tests/bandwidth.test.ts. If assets changed,npm run asset:budget. Before a big merge, mirror CI:npm test && npx tsc --noEmit && npm run build:env && npm run build:server && npm run build. - Spawn review agents using the Review Dispatch Matrix below. Spawn ONLY the agents whose surface this change actually touches. Most phases trigger one or two, not all four; a docs/test-only change triggers none. (Each agent also self-gates and exits cheaply if mis-dispatched, but spawning an out-of-scope agent still costs tokens, so gate at dispatch too.)
- Prompt every review agent you DO spawn for COVERAGE not filtering ("report every issue including low-severity and uncertain ones"). Do not commit until each reports no BLOCKING issues. Resume any agent that truncates with: "Stop reading more files. Output the full report now based on what you've already seen. No more tool calls. Format: BLOCKING / SHOULD-FIX / NICE-TO-HAVE / VERDICT."
- Run validation (see the matrix in
Review Dispatch Matrix (single source of truth - keep the starter-prompt copies below in sync)
Match the change surface to the agent. Spawn an agent ONLY when its row matches the diff:
| Agent | Spawn ONLY when the diff touches | Skip it for |
|---|---|---|
privacy-security-review |
server/, src/admin/, src/net/, a deploy/secret file (Docker/compose/env/CI yml/DEPLOY.md), OR introduces SQL / auth / a secret / ALLOW_DEV_COMMANDS / a new Math.random|Date.now|performance.now in src/sim/ |
a pure src/ui / src/render / src/game / src/sim/content / docs / test change |
migration-safety |
server/db.ts, server/social_db.ts, a server/*_db.ts, or a characters.state JSONB serialize/deserialize path |
any diff with no DDL and no persisted-state shape change |
cross-platform-sync |
src/world_api.ts (IWorld), src/sim/ behavior/obs/SimEvent, src/net/online.ts, server/game.ts wire/dispatch, the matchers src/ui/sim_i18n.ts|src/ui/server_i18n.ts, or the RL surface (headless/, python/) |
a pure i18n catalog refactor (only src/ui/i18n.ts + locale data, t() keys unchanged) - tsc (: typeof en) + the resolved-equivalence test already cover it |
architecture-reviewer |
a src/sim/ change: determinism, rng draw-order, tick-phase order, the SimContext seam, or a move-not-rewrite relocation |
a non-sim change, or a pure data/content/test change |
qa-checklist |
a phase / deliverable set is COMPLETE (it self-scales via its per-category Skip rules) | per-commit / mid-phase work, or a docs/test-only change |
If NO row matches (e.g. a docs-only, test-only, or comment change), spawn NO review agent.
Do not default to "run privacy-security-review anyway."
5. Step 4 - Update Docs + Memory: Update progress.md (mark phase complete; note deferrals) and state.md (new IWorld members, SimEvents, wire fields, endpoints, tables, i18n keys, locked decisions). If you use Claude Code memory, record any surprising rules learned or current-state notes there for the next session. Commit doc updates in the same logical commit as the implementation (EXPLICIT paths).
Agent Scaling Guidelines (include in Team Workflow): The starter prompts suggest a default split (sim + server + client + tests), but the orchestrator MUST assess the actual workload and scale accordingly:
- Split large sim work across multiple agents when a single agent would handle 4+ independent concerns (e.g., a new ability system + threat changes + content tables + RL obs surface). Signs a split is needed: the deliverable list has 10+ items, the work spans 4+ modules, or the concerns are independent.
- Merge small work into a single agent when one side has only 1-2 trivial changes (e.g., adding one
IWorldgetter, adding one i18n key). Do not spawn a dedicated agent for work that takes five minutes. - Split large client work when a phase adds 3+ HUD windows plus renderer changes plus input wiring. One agent for HUD/i18n, one for renderer, one for input/camera/mobile.
- Use dedicated test agents when a phase has complex test requirements across multiple suites. A test agent can run in parallel after implementation agents commit their code.
- Escalate to a Workflow past the manual cap. Hand-orchestrated parallel fan-out tops out at ~5 agents. When a phase has 10+ independent, uniform tasks (e.g., add a new field to every zone's mob table, register 30 new i18n keys across 14 locales, transform many content entries), do not hand-spawn - write an
ultracodeWorkflow that pipelines them with structured outputs and verifies each. - Each agent should own complete vertical slices - do not split by file type (one for types, another for tests). Split by domain (one for the sim behavior + its tests, another for the HUD surface + its tests). Each agent writes its own tests for the code it creates.
Code Hygiene section (include in Team Workflow): Every phase must enforce:
- Module-first: new self-contained behavior goes in its own focused module behind an existing seam (
IWorld, asrc/sim/content/record, asrc/render/<thing>.ts), not appended to a monolith (sim.ts,hud.ts,renderer.ts). Do not split a monolith for line count. Fix bugs test-first. See theextract-and-testskill and the root Modularity section. - New code gets tests: Every new ability, system, command,
IWorldmember, server endpoint, query, and behavior gets unit tests. Sim: combat math, abilities, AI, economy. Server: command dispatch, persistence, snapshots. Net: wire round-trip. UI: data transforms, frame logic. E2E (scripts/*.mjs): user flows where applicable. If you wrote it, test it. - Determinism tests: For sim changes, assert same-seed-same-result; never introduce
Math.random/Date.now/performance.nowintosrc/sim/. - Test maintenance: Update/remove tests when modifying existing code. When placeholder content is replaced with real content, update the tests. Never leave orphaned or broken tests.
- Dead code removal: Delete fully replaced abilities, systems, components, helpers, and types. No commented-out code, unused imports, or deprecated functions.
- Import cleanup: Zero unused imports. And uphold the import invariant:
src/sim/imports nothing fromrender/,ui/,game/,net/, and has no DOM/Three.js imports. - Type cleanup: Remove unreferenced interfaces/types. Consolidate evolved types, do not accumulate.
- No generated-file hand-edits: Never hand-edit generated output (e.g.
src/render/assets/manifest.generated.tsor the media manifest emitted bynpm run build); regenerate via the build instead.
Every phase starter prompt must follow this structure:
### Starter Prompt
```
This is Phase N of the {Feature Name} feature: {Phase Title}.
Model: Opus 4.8, xhigh effort (reserve max for genuinely frontier problems), 1m context variant where the file load demands it.
Harness: Claude Code.
ULTRACODE: add the keyword `ultracode` to this prompt if this phase is batch-heavy
(content sweeps across many tables, many-locale i18n additions, exhaustive audit) so you
orchestrate via a Workflow (pipeline + adversarial-verify) instead of hand-spawning agents.
Goal: {one sentence}
STEP 0 - PRE-FLIGHT:
- Verify `git status` is clean before starting. If not, ask the user (a concurrent
session may share this checkout).
- Memory scan (if you use Claude Code memory): check your `MEMORY.md` index and any
entries relevant to this phase's domain (suggested topics: {phase-specific patterns}).
STEP 1 - LOAD CONTEXT (do NOT read planning docs directly, save your context):
Spawn an Explore agent to read and summarize:
- docs/{feature-name}/state.md (locked decisions, validation matrix, file paths)
- docs/{feature-name}/progress.md (Phase N status + deliverable checklist)
- docs/{feature-name}/phase-N-{slug}.md (this prompt) - verify the agent has the same understanding
- {relevant source files for this phase, listed individually}
- CLAUDE.md (root) + the relevant sub-CLAUDE.md files
({e.g. src/sim/CLAUDE.md, server/CLAUDE.md, src/ui/CLAUDE.md, src/net/CLAUDE.md})
The agent should return: {specific info this phase needs}.
{If this phase integrates a third-party API/SDK or needs an exact classic-era formula:
also spawn a web-research agent for current primary-source docs/constants; mark
unverifiable facts OPEN rather than guessing.}
STEP 2 - CHOOSE ORCHESTRATION + EXECUTE:
Pick the lightest tool that fits (Explore for recon, then parallel Agent fan-out for
independent slices, then Workflow for batch/scale). Request fan-out EXPLICITLY (Opus 4.8
will not self-initiate it). Give each agent ONLY the Explore summary, not raw planning
docs. Never `mode: "plan"` on teammates. Use `isolation: "worktree"` only if agents edit
overlapping files in parallel.
{Agent A} deliverables:
- {bullet}
- {bullet}
{Agent B} deliverables:
- {bullet}
- {bullet}
INVARIANTS THIS PHASE MUST KEEP (call out the ones in play):
- Determinism: all randomness via `Rng`; no `Math.random` / `Date.now` / `performance.now` in `src/sim/`.
- Seam: extend `IWorld` first, then implement in BOTH `Sim` and `ClientWorld`.
- Server authority: the client never decides combat/loot/quest/economy outcomes.
- i18n: every new player string is a `t()` key in every locale; sim/server emit English
re-localized via the matchers (`src/ui/sim_i18n.ts` / `src/ui/server_i18n.ts`).
- Classic-era formulas only; do not invent balance numbers.
Out of scope (do NOT do in this phase):
- {explicit exclusions to prevent scope creep}
STEP 3 - VALIDATION + MULTI-AGENT REVIEW:
- Run: {phase-specific validation commands referencing the state.md matrix}
(baseline: `npx tsc --noEmit` + `npx vitest run tests/<affected>.ts`; add
`npx vitest run tests/localization_fixes.test.ts` if any player text changed; add the
wire/snapshot suites if the protocol changed).
- Spawn review agents in parallel, but ONLY the ones whose surface this diff touches
(check `git diff --name-only` against the phase-start commit). Spawning an out-of-scope
agent wastes tokens; most phases trigger one or two, not all four.
- `privacy-security-review` - ONLY if the diff touches `server/`, `src/admin/`, `src/net/`,
a deploy/secret file (Docker/compose/env/CI), or introduces SQL / auth / a secret /
`ALLOW_DEV_COMMANDS` / a new `Math.random`|`Date.now`|`performance.now` in `src/sim/`.
NOT for a pure `src/ui` / `src/render` / `src/game` / content / docs / test change.
- `migration-safety` - ONLY if `server/db.ts`, `server/social_db.ts`, a `server/*_db.ts`,
or a `characters.state` JSONB serialize/deserialize path changed.
- `cross-platform-sync` - ONLY if `src/world_api.ts`, `src/sim/` behavior/obs/`SimEvent`,
`src/net/online.ts`, `server/game.ts` wire/dispatch, the matchers `src/ui/sim_i18n.ts`
|`src/ui/server_i18n.ts`, or the RL surface (`headless/`, `python/`) changed. A pure
i18n catalog refactor (only `src/ui/i18n.ts` + locale data, keys unchanged) is NOT in
scope - `tsc` + the resolved-equivalence test cover it.
- `architecture-reviewer` - ONLY if `src/sim/` changed (determinism, rng draw-order,
tick-phase order, the `SimContext` seam, or a move-not-rewrite relocation).
- `qa-checklist` - when this phase completes a deliverable set.
- If none of the above match, spawn no review agent.
- Prompt each agent you spawn for COVERAGE not filtering. Resume any that truncates with the
"Stop reading. Output verdict now." message.
- Do not commit until each reports no BLOCKING issues.
STEP 4 - COMMIT CADENCE:
Aim for {2-5} commits with these headlines (Conventional Commits with a scope, e.g.
`feat(sim): ...`, `fix(net): ...`; EXPLICIT paths, never `git add -A`; no em dashes/emojis):
- {commit 1 headline}
- {commit 2 headline}
- ...
STEP 5 - ACCEPTANCE CRITERIA (verifiable checklist; do not mark complete until all check):
- [ ] {acceptance item}
- [ ] {acceptance item}
- ...
STEP 6 - DOC UPDATES + MEMORY:
- Update docs/{feature-name}/progress.md (mark Phase N status; note deferrals).
- Update docs/{feature-name}/state.md (new IWorld members, SimEvents, wire fields,
endpoints, tables, i18n keys; locked decisions).
- If you use Claude Code memory, record any surprising rules or current-state notes for
the next session.
STEP 7 - FINAL RESPONSE FORMAT:
End your turn with: phase status, files touched, validation results, review-agent verdicts,
any deferred items, and a one-line handoff for the QA session.
STOPPING RULES:
- {explicit stop conditions: "stop if determinism cannot be preserved", "stop and ask if
Y changes the wire protocol in a backwards-incompatible way", etc.}
```
Every QA phase starter prompt must follow this structure:
### QA Starter Prompt
```
This is Phase N QA of the {Feature Name} feature: Verify {Phase Title}.
Model: Opus 4.8, xhigh effort (reserve max for genuinely frontier problems), 1m context variant where the file load demands it.
Harness: Claude Code.
ULTRACODE: for a large or high-risk phase, add `ultracode` so you can run an
adversarial-verify Workflow (each finding independently confirmed by a skeptic agent
before it counts).
Goal: Audit Phase N implementation for correctness, missing tests, dead code, determinism,
three-host parity, and i18n completeness.
STEP 0 - PRE-FLIGHT:
- Verify `git status` is clean (Phase N implementation should already be committed). If dirty, ask the user.
- Memory scan (if you use Claude Code memory): check entries from the Phase N domain.
STEP 1 - LOAD CONTEXT (do NOT read planning docs directly, save your context):
Spawn an Explore agent to read and summarize:
- docs/{feature-name}/state.md (new IWorld members, SimEvents, wire fields, endpoints, tables from Phase N)
- docs/{feature-name}/progress.md (Phase N deliverables checklist + acceptance criteria)
- docs/{feature-name}/phase-N-{slug}.md (the implementation prompt - what was promised)
- All files created or modified in Phase N (use `git diff` against the phase start commit)
- CLAUDE.md (root) and relevant sub-CLAUDE.md files
The agent should return: full list of Phase N deliverables, all new/modified files, and any known issues.
STEP 2 - QA AUDIT (spawn parallel review agents using the Explore summary; prompt each for COVERAGE not filtering):
Correctness agent:
- Verify every deliverable from Phase N was actually implemented
- Verify every acceptance criterion in the phase prompt is met
- Check for logic bugs, off-by-one errors, missing error handling
- Verify classic-era formulas match the cited references; no invented constants
- Verify the offline `Sim` path and the online `ClientWorld` path behave identically
- Test edge cases: empty states, error states, boundary values, concurrent access
- Run the affected tests and (where useful) an E2E script (`scripts/*.mjs`) and verify behavior matches intent
Test coverage agent:
- Identify new code paths without tests
- Add missing unit tests for new sim behavior, commands, IWorld members, endpoints, queries
- Add a determinism test (same seed, same result) for new sim logic
- Update existing tests broken by Phase N changes
- Remove orphaned tests for deleted/replaced code
- Verify test assertions are meaningful (not just "it runs")
Dead code & cleanup agent:
- Find unused imports, functions, types, components left behind
- Verify the import invariant holds (`src/sim/` imports nothing from render/ui/game/net; no DOM/Three)
- Remove commented-out code
- Consolidate duplicate or near-duplicate logic
- Verify no TODO/FIXME items were left unresolved
- Check for inconsistent naming or patterns vs the rest of the codebase
Multi-agent review dispatch (spawn ONLY the agents whose surface the Phase N diff touches;
check `git diff --name-only` against the phase-start commit - do not run all four by default):
- `privacy-security-review` - ONLY if the diff touches `server/`, `src/admin/`, `src/net/`,
a deploy/secret file, or introduces SQL / auth / a secret / `ALLOW_DEV_COMMANDS` / a new
`Math.random`|`Date.now`|`performance.now` in `src/sim/`. NOT for a pure UI/render/game/
content/docs/test change.
- `migration-safety` - ONLY if server schema/DDL or a `characters.state` persisted-state
shape changed.
- `cross-platform-sync` - ONLY if IWorld / `src/sim` behavior/obs/`SimEvent` / `ClientWorld`
/ `wireEntity` dispatch / the `sim_i18n`|`server_i18n` matchers / the RL surface changed.
A pure i18n catalog refactor (keys unchanged) is NOT in scope.
- `architecture-reviewer` - ONLY if `src/sim/` changed (determinism, rng draw-order,
tick-phase, the `SimContext` seam, or a move-not-rewrite relocation).
- `qa-checklist` - yes (this is the phase-completion QA gate).
Resume any review agent that truncates mid-analysis with: *"Stop reading more files. Output the full report now. No more tool calls. Format: BLOCKING / SHOULD-FIX / NICE-TO-HAVE / VERDICT."*
STEP 3 - FIX: Apply all BLOCKING and SHOULD-FIX items. Run the full validation matrix from state.md
(at minimum `npx tsc --noEmit` and the relevant vitest files; the S3 i18n guard if player text changed).
Commit fixes (separate commits from the QA verdicts themselves so the history is reviewable; EXPLICIT paths).
STEP 4 - UPDATE DOCS + MEMORY:
- Update docs/{feature-name}/progress.md (mark Phase N QA complete; note any items deferred to follow-up).
- Update docs/{feature-name}/state.md (any drift discovered during QA).
- If you use Claude Code memory, record any surprising rules learned during QA.
STEP 5 - PACKET TEARDOWN (final phase only; skip entirely otherwise):
If this is the LAST phase in the packet and everything is complete and green, ask the user
for explicit confirmation to delete the planning scaffolding before the PR, e.g.:
"All phases are complete and green. OK to delete docs/{feature-name}/ before the PR?"
- Surface any deferred follow-ups first so nothing tracked only in the packet is lost.
- On confirmation, delete ONLY that directory with an explicit path: `git rm -r
docs/{feature-name}/` (if it was committed) then commit
`docs: remove {feature-name} planning scaffolding`, or `rm -rf docs/{feature-name}/`
(if it was never committed).
- If the user declines, leave it in place. Never delete anything outside that directory and
never `git add -A`.
STEP 6 - FINAL RESPONSE FORMAT:
End your turn with: QA verdict (PASS / PASS-WITH-FOLLOWUPS / FAIL), counts of
BLOCKING/SHOULD-FIX/NICE-TO-HAVE found and fixed, deferred items, whether the planning
packet was removed, and a one-line handoff for the next implementation phase (or
"packet complete" if this was the final phase).
STOPPING RULES:
- Stop and surface to the user if any BLOCKING item cannot be fixed without changing the phase scope.
```
Every phase that changes server persistence MUST also:
- Extend the DDL additively: edit
server/db.tsSCHEMA(orserver/social_db.tsSOCIAL_SCHEMA) usingCREATE TABLE IF NOT EXISTS/ALTER TABLE ... ADD COLUMN IF NOT EXISTSso existing realms upgrade in place under the boot advisory lock. There is no migrations directory; the inline DDL is the schema. - Guarantee back-compat for existing JSONB character state: loading a character saved before this change must not throw or lose data (default any missing fields).
- Add appropriate indexes for any new query predicate.
- Add a persistence round-trip test under
tests/(save then load, assert equality).
Mobile in every client phase (the game ships touch controls):
- Any HUD/input change must work with the touch controls in
src/game/and respect mobile safe areas. - Verify with the mobile screenshot scripts (e.g.
node scripts/mobile_visual.mjs, themobile_*_shot.mjsfamily) against a phone viewport withnpm run devrunning. - Keep tap targets comfortable; do not rely on hover for essential info.
Performance in every phase (the sim is a fixed 20 Hz budget):
- Sim work per tick must stay within the 20 Hz frame budget; avoid per-tick allocations in hot paths.
- Keep snapshots interest-scoped and delta-guarded; do not send unchanged heavy fields (see the interest radius and
wireEntity/selfWireJsoninserver/game.ts). - The renderer reads the world and never mutates it; keep draw-call and texture budgets in check (
npm run asset:budget,npm run perf:tour). - Keep the dependency set tiny; do not add packages without a clear need.
Deploy gates (any phase that ships server or client changes to production):
- Production runs in Docker (see
DEPLOY.md). Standalone update path: ssh to the host,cd /opt/eastbrook,sudo git pull,sudo docker compose up -d --build. Players are saved on shutdown and briefly disconnect. - Health check after deploy:
curl -s localhost:8787/api/statusreturns{"ok":true,"players_online":N,...}. - Before any deploy, the CI-equivalent gate must be green locally:
npm test && npx tsc --noEmit && npm run build:env && npm run build:server && npm run build(this mirrors.github/workflows/ci.yml). - Never set
ALLOW_DEV_COMMANDS=1in production (it enables level/teleport/item cheats). - Most phases do not deploy; deploys are manual and infrequent. Treat deploy as a deliberate, separate step, not part of every phase.
Packet teardown (final phase only):
The planning packet in docs/{feature-name}/ is cross-session scaffolding, not a shipping artifact. The final QA phase, once every phase is complete and the build / CI-equivalent gate is green, MUST offer to remove it before a PR is opened:
- Ask the user explicitly, in plain language, for example: "All phases are complete and green. OK to delete
docs/{feature-name}/(the planning scaffolding) before the PR?" Do not delete on assumption. - Surface any deferred follow-up items FIRST, so nothing tracked only inside the packet is lost when it goes.
- Delete ONLY on explicit confirmation, and ONLY that one directory, with an explicit path:
- If the docs were committed during the packet:
git rm -r docs/{feature-name}/, then commitdocs: remove {feature-name} planning scaffolding. - If they were never committed:
rm -rf docs/{feature-name}/.
- If the docs were committed during the packet:
- If the user declines or wants to keep them, leave the directory in place and say so; delete nothing.
- Never delete anything outside
docs/{feature-name}/, never fold the deletion into an unrelated commit, and nevergit add -A(a concurrent session may share this checkout).
progress.md
- Overall status table (phase | status | date started | date completed) - includes both implementation and QA phases (e.g., "Phase 1", "Phase 1 QA", "Phase 2", "Phase 2 QA")
- Per-phase checklist matching deliverables from implementation-plan.md
- Per-QA-phase checklist (fixes applied, tests added, dead code removed)
- Notes section per phase (filled in after completion)
state.md
Cross-phase cheat sheet. Contains ONLY what the next session needs:
- Current phase number + status
- Locked design decisions (record once, reference forever)
- Non-negotiable constraints (determinism, server authority,
IWorld-first, i18n in all locales, no generated-file edits, shared-worktree commit care) - Validation matrix by change type:
- sim-only:
npx tsc --noEmit+npx vitest run tests/sim.test.ts(and the relevant command suites); determinism check. - content-only:
npx tsc --noEmit+npx vitest run tests/progression.test.ts tests/talents.test.ts(referential integrity); i18n if new names. - server-only: relevant server suites +
npx tsc --noEmit+npm run build:server. - net/wire:
npx vitest run tests/snapshots.test.ts tests/env_protocol.test.ts tests/bandwidth.test.ts+ parity check. - ui/render:
npx tsc --noEmit+npx vitest run tests/localization_fixes.test.ts(if text) + a mobile screenshot script. - headless/RL:
npm run build:env+npx vitest run tests/env_protocol.test.ts+ a shortnpm run bench. - full-stack / pre-merge:
npm test && npx tsc --noEmit && npm run build:env && npm run build:server && npm run build. - any code change (Biome / CI ratchet):
npm run ci:changed(Biome on the files you changed, what the.githooks/pre-pushfloor runs). Fix formatting with a SCOPEDnpx @biomejs/biome check --write <file>, never a whole-tree--write.
- sim-only:
- Key file paths (existing + created by this feature)
- New files created per phase
- New
IWorldmembers added per phase - New
SimEvents and wire/snapshot fields added per phase - API endpoints created per phase
- Database tables/columns added per phase (in the inline
SCHEMA) - New i18n keys + matcher rules added per phase
- Architecture decisions (locked once made)
- OPEN research items + known issues / gotchas
qa-checklist.md
Whole-feature integration matrix verified once at packet completion:
- Three-host parity: the offline browser
Sim, the onlineClientWorld, and the headless env behave consistently for this feature. - Determinism: same seed gives the same world; no
Math.random/Date.now/performance.nowinsrc/sim/; determinism tests pass. - i18n completeness: every new player-visible string is a
t()key present in all locales intranslations; sim/server emits have matcher rules insim_i18n.ts/server_i18n.ts;npx tsc --noEmitandnpx vitest run tests/localization_fixes.test.ts(S3 guard) are green; numbers/money/dates go throughformatNumber/formatMoney/formatDateTime. - Classic-era fidelity: formulas match the cited references; no invented balance numbers.
- Server authority: no client-trusted outcomes; WS commands validated server-side.
- Persistence: characters saved before this feature still load; save/load round-trip verified; DDL changes are additive and idempotent.
- Performance / budgets: snapshot bandwidth sane;
npm run asset:budgetandnpm run perf:tourwithin budget. - Copy review: no em dashes or emojis in player-facing text (raw emojis as in-game icons are also disallowed by the aesthetic rule).
- Build gate: the CI-equivalent gate is green (
npm test && npx tsc --noEmit && npm run build:env && npm run build:server && npm run build). - Deploy verification (only if deployed):
curl -s localhost:8787/api/statusreturns ok with the expected build.
Step 5: Commit
Stage and commit all planning docs (EXPLICIT paths, Conventional Commit, short message, no em dashes/emojis):
docs: add {feature-name} phased implementation plan
Present a summary to the user:
- Number of phases (implementation + QA)
- What each phase covers (one line each, showing the implement/QA pairs)
- How to start Phase 1 (copy the starter prompt from
phase-01-{slug}.mdor the implementation-plan.md Phase 1 section) - Locked design decisions captured in state.md
- OPEN research items still needing a human/credential/design answer
- Cross-cutting workflow blocks the user can edit centrally (memory scan, orchestration choice, multi-agent dispatch, validation matrix, deploy gate)
- That the final QA phase will offer packet teardown (delete
docs/{feature-name}/) before the PR, on your explicit confirmation, so the planning scaffolding does not ship
.claude/skills/file-issue/SKILL.md
npx skills add levy-street/world-of-claudecraft --skill file-issue -g -y
SKILL.md
Frontmatter
{
"name": "file-issue",
"description": "Create a GitHub issue for World of ClaudeCraft in the maintainer's house format. Use when asked to file, open, or create a GitHub issue or bug report from a description, a screenshot, or a rough placeholder. Rewrites the input into a clean, professional issue (Problem, Steps to reproduce, Expected, Actual, Scope, Acceptance criteria) and includes a Screenshot section only for UI\/UX issues. Posts to levy-street\/world-of-claudecraft via gh.",
"user-invocable": true
}
File an issue (World of ClaudeCraft house style)
Turn a rough description, screenshot, or placeholder into a clean, professional GitHub
issue that matches how the maintainer writes them, then create it on the canonical repo.
Reference issues for the voice and shape: #1050 (feature/behavior) and #1051
(UI/UX bug with a screenshot section).
Voice and rules
- Plain, professional, calm. No marketing, no preamble, no AI voice.
- No em dashes, en dashes, or emojis anywhere in the title or body. Use commas, colons, parentheses, or "to" for ranges. (This repo bans them; do not introduce them.)
- Be specific and factual. Rewrite vague placeholder copy into concrete, testable statements. If a detail is unknown, leave a clearly marked HTML comment rather than guessing a fact.
- Title: short and descriptive, stating the problem (for example, "Unable to scroll the
world market UI on mobile"). A severity prefix ("Critical: ...") is optional and only
for genuinely high-severity issues, matching the maintainer's style in
#1050.
Step 1: Gather and classify
From the user's input work out:
- The core problem in one or two sentences.
- Whether it is a bug (something is broken) or a request (new or changed behavior). Bugs get Steps to reproduce plus Actual behavior; requests usually do not.
- Whether it is UI/UX related (visual, layout, HUD, mobile, input, rendering). Only UI/UX issues get the Screenshot section.
- Whether it is platform or device specific (mobile, landscape, a browser). If so, include an Environment section.
If the core problem or the expected behavior is genuinely unclear, ask one short clarifying question before drafting. Do not stall on details you can reasonably infer from a screenshot or the description, note inferred specifics back to the user instead.
Step 2: Draft the body
Use this section set. Include the core sections always; include the situational ones only when they apply. Keep each section tight.
Core (always):
## Problem
<One to three short paragraphs: what is wrong or missing, and why it matters.>
## Expected behavior
- <Bulleted, concrete, observable statements of what should happen.>
## Scope
<What to investigate or change. A short bulleted list of the affected areas/tabs/flows.>
## Acceptance criteria
- <Checkable conditions that mean this is done. Mirror the Expected behavior, made testable.>
- <For UI/mobile bugs, include "the desktop experience is unchanged" or the equivalent.>
Situational (include only when they apply):
## Steps to reproduce # bugs only
1. <step>
2. <step>
3. <observe the failure>
## Actual behavior # bugs only
- <What actually happens today, the mirror of Expected behavior.>
## Environment # platform/device-specific only
- Platform: <mobile web landscape / desktop / etc.>
- Surface: <in-game HUD window, guide page, admin, etc.>
## Notes # optional: likely root cause, non-goals, related work
Step 3: Screenshot section (UI/UX issues only)
If, and only if, the issue is UI/UX related, append a Screenshot section as a placeholder
the user fills in later. If the user already provided an image, still leave the
placeholder (issues are created from text via gh; the user attaches the image in the
GitHub web UI afterward) and tell them where to drop it.
## Screenshot
<!-- Only add a screenshot if this issue is UI/UX related. Drag the image in here. -->
For a non-UI issue (logic, server, balance, content), omit the Screenshot section entirely.
Step 4: Create it
Write the body to a temp file and create the issue on the canonical repo. Always
levy-street/world-of-claudecraft, never a fork.
gh issue create \
--repo levy-street/world-of-claudecraft \
--title "<title>" \
--body-file <path-to-body.md>
Use --body-file (not --body) so headings, lists, and the HTML comment survive shell
quoting. Add --label/--assignee only if the user asked for them.
Creating an issue is outward-facing, but invoking this skill IS the request to create it, so create directly. Then report back: the issue URL, the title, and a one-line note of any specifics you inferred (so the user can correct them) plus, for UI/UX issues, a reminder that the Screenshot section is waiting for their image.
.claude/skills/extract-and-test/SKILL.md
npx skills add levy-street/world-of-claudecraft --skill extract-and-test -g -y
SKILL.md
Frontmatter
{
"name": "extract-and-test",
"description": "Build features and fix bugs the clean, scalable way for World of ClaudeCraft. Use when adding a feature, refactoring logic out of a large file (sim.ts, hud.ts, renderer.ts, main.ts), or fixing a bug, especially when the change would otherwise append a block of new logic to an existing big file. Extracts self-contained behavior into a small, well-named, unit-tested module behind one of this repo's existing seams instead of growing a monolith, and fixes bugs test-first (reproduce with a failing test, then make the smallest change that turns it green). Keeps merge conflicts small and the codebase scalable for many contributors.",
"user-invocable": true
}
Extract and test: module-first features, test-first fixes
This repo is built and maintained almost entirely by AI agents and grows by many small contributions. The thing that keeps it scalable, and keeps open-source merge conflicts small, is that new behavior lands as a focused module behind a known seam, not as another block appended to a 10k-line file. This skill is the detailed how-to behind the root CLAUDE.md "Modularity" section. Apply it whenever you implement a feature or fix a bug.
The one decision: sibling module or monolith edit
The four logic monoliths (src/ui/hud.ts ~10k, src/sim/sim.ts ~7.5k, src/main.ts
~6.4k, src/render/renderer.ts ~4.5k) are coordinators, not a license to grow them: do
not split them to hit a line count, and do not rewrite them as a side effect of your task,
but never GROW one either. src/main.ts especially is a firewall, not a home (its
client-bootstrap helpers belong in src/game/ or src/ui/ siblings). Before you add a
block of new logic to one, ask:
Does this behavior need the monolith's private mutable state (the live
Simentity loop, theHudDOM and per-frame state, the renderer's scene graph)?
- No: it is a sibling module. Write it as its own file with a named export and a Vitest, then wire it in with a few lines (a call, a registration, a consume).
- Yes, partly: extract the pure part (the math, the formatting, the id/state
resolution) into a host-agnostic module a test imports directly, and leave the
stateful side a thin consumer that calls it. This is the pure-core + thin-consumer
split (reference:
src/ui/unit_portrait.tscore +src/ui/unit_portrait_painter.ts, shared by the player and target frames;src/ui/xp_bar.tsis a purexpBarView()that a snapshot test drives with no DOM).
If your edit to a monolith is more than a thin wiring of something defined elsewhere, you are probably appending behavior that wants its own module.
Use the seams this repo already has
Do not invent a new architecture. Pick the seam that matches the work:
- render or ui needs new data or an action: extend
IWorldinsrc/world_api.tsfirst, implement it in BOTH the offlineSim(src/sim/sim.ts) and the onlineClientWorld(src/net/online.ts), then consume it throughIWorld. render and ui never import a concrete world. This is the load-bearing parity rule; thecross-platform-syncagent audits it. - New game content (mob, quest, item, ability, zone, talent): a declarative
record in
src/sim/content/, merged into the flat tables bysrc/sim/data.ts. Never inline a content table insim.ts. - New visual system: a new
src/render/<thing>.tsexporting abuild*()that returns a*Viewthe renderer owns and calls. Not a new method bank onrenderer.ts(templates:terrain.ts,props.ts,foliage.ts). - New self-contained HUD window or panel: its own module the HUD composes,
rather than a new banner section inside
hud.ts. This is the direction the HUD modularization is heading; follow it for new windows. - New server command: validate every field in
dispatchMessage(server/game.ts), then call thesim.*method that owns the rule. The outcome resolves in theSim, never on the server outside it. - A multi-file subsystem: a directory with an
index.tsbarrel that exports only its public surface, plus its own shortCLAUDE.md(templates:src/render/characters/,src/ui/i18n.catalog/).
When to extract, and when not to
- Extract on the rule of three. Two similar blocks: leave them. A third copy, or a single block whose responsibility you can name in one sentence with no "and", earns its own module.
- Do not abstract ahead of need. No helper, base class, options bag, or indirection for a single caller or a hypothetical future requirement. The right amount of structure is the minimum the current task needs. A wrong abstraction is more expensive than a little duplication.
- Name for the behavior, not the layer.
threat_table.ts,loot_roll.ts,coords.ts, nothelpers.tsorutils.ts. The file name should tell a reader what one thing it owns. - Keep new modules host-aware. Anything reused by
src/sim/must stay DOM-free and Three-free (thetests/architecture.test.tsguard enforces this forsrc/sim/). Pure logic that both the sim and the UI need lives sim-side or in a neutral module both can import without breaking the import direction insrc/CLAUDE.md.
Build a new module
- Create
src/<area>/<behavior>.tswith a small, explicit public surface (one or a few named exports). Keep internals private. - Add a Vitest at
tests/<behavior>.test.tsthat imports the module directly and asserts real behavior (not "it runs"). Tests live intests/, not beside the source (seetests/CLAUDE.mdfor the idioms). For sim logic, add a determinism assertion: same seed gives the same result (expect(run()).toEqual(run())). - Wire it into its consumer with the smallest possible edit (a call, a registration, a barrel re-export). The consumer stays thin.
- If the module is the public face of a new directory, add an
index.tsbarrel and a localCLAUDE.mddescribing only that directory's conventions.
Fix bugs test-first
- Reproduce in a failing test before touching the fix. Write a Vitest that exercises the real code path and fails, and confirm it fails for the reason the bug describes, not an unrelated setup error. If the buggy logic is buried in a monolith and hard to test in place, that is the signal to extract the unit under test into its own module first, then test it.
- Make the smallest change that turns the test green. Fix the root cause, not the symptom. Never special-case the test inputs or hard-code the expected value into the implementation.
- Generalize the assertion, not the fix. Add a couple of nearby cases (boundary, empty, the mirror host) so the test pins the behavior, not one example.
- For a high-risk or subtle fix, isolate the grader from the implementer: have one subagent write the reproducing test, a second implement the fix, and a fresh subagent review the diff for coverage (every correctness and requirement gap), so the fix is not validated by the same reasoning that produced it.
Verify, and keep the diff honest
After an extraction or fix, these stay green (run the subset your change touches):
npx tsc --noEmitnpx vitest run tests/<affected>.test.ts(ornpm testfor broad changes)npx vitest run tests/architecture.test.tsif you touchedsrc/sim/, or added / renamed asrc/uiorsrc/render*_view/*_corepure core (the completeness sweep also checksUI_PURE_CORES/RENDER_PURE_CORESregistration)npx vitest run tests/localization_fixes.test.tsif any player-visible text or asrc/sim/serveremit changed (the S3 i18n guard)npm run ci:changed(Biome on the files you changed; this is what the.githooks/pre-pushfloor runs, so clear it here, not at push time). If it flags formatting on your own files, fix with a SCOPEDnpx @biomejs/biome check --write <file>per touched file, never a whole-tree--write(the repo defers global Biome debt, so a whole-tree write buries your change in thousands of unrelated reformats).npm run buildbefore a merge
When you extract, the diff should read as move plus import, not rewrite. If you "improved" the moved code in the same change, that is scope creep: split it into a follow-up so the extraction stays reviewable. Delete the code you replaced; leave no dead duplicate, commented-out block, or unused import behind.
Effort by model (the doctrine here is identical, only the effort scales): on Opus 4.8,
after the extraction fan out a fresh subagent (or the architecture-reviewer for a
src/sim/ move) to review your move-diff for COVERAGE, every parity and correctness gap,
before calling it done. On the Sonnet baseline, take small verifiable steps and lean on
one investigator.
Repo anti-patterns to avoid
- Appending a new system as another
// ----banner section insim.tsorhud.tswhen it does not need that file's private state. - Reaching past
IWorldintoSim/ClientWorldfromrender/orui/. - Adding a content table or balance number inline in
sim.tsinstead ofsrc/sim/content/and the tuning const blocks. - A
helpers.ts/utils.tsgrab-bag, or an abstraction with exactly one caller. - Splitting a monolith purely to reduce its line count, with no seam and no test.
.claude/skills/release-malware-audit/SKILL.md
npx skills add levy-street/world-of-claudecraft --skill release-malware-audit -g -y
SKILL.md
Frontmatter
{
"name": "release-malware-audit",
"description": "Release-gate scan for deliberately planted malicious code (crypto miners, data\/secret exfiltration, backdoors, RCE\/obfuscation, install hooks) across the whole working tree of World of ClaudeCraft. Use before tagging or shipping a release, or whenever you want to confirm the tree is free of malicious code. Runs the deterministic scanner, fans the read-only release-malware-audit agent across categories, and returns a single PASS \/ BLOCK verdict with confirmed findings and dismissed false positives. Distinct from privacy-security-review, which catches accidental security mistakes.",
"user-invocable": true
}
Release malware audit: whole-tree check for planted malicious code
This is a release gate. Its one job is to answer: does this tree contain code that was
deliberately written to harm users, operators, or the supply chain? Crypto miners,
data/secret exfiltration, backdoors and auth bypasses, RCE/obfuscation, web3 wallet-drain /
key theft (the class that would steal a user's $WOC), prompt-injection planted in the
agent/skill instruction files, and package.json install hooks or risky dependencies. It
does NOT look for accidental security bugs - that is privacy-security-review's job - and it
never modifies code. A human acts on a BLOCK.
The work is split so each half does what it is good at:
scripts/malware_scan.mjsis a deterministic, high-recall FLAGGER. It greps a curated signature catalog and emits structured findings. It is deliberately noisy: a regex hit is a question, not a verdict.- The
release-malware-auditagent is the JUDGE. It reads the flagged code in context, knows this repo's legitimate patterns (the$WOCcrypto-wallet,child_processin build scripts, real auth comparisons), and decides real-vs-false-positive.
Steps
-
Run the scanner over the whole tree:
mkdir -p tmp && node scripts/malware_scan.mjs --json --quiet > tmp/malware_scan.jsonIt exits non-zero whenever there are findings (expected; most are false positives). Note
totalFindingsand the per-category counts. A scan-failure exit (2) means fix the run before trusting any result - never report PASS off a scan that did not complete.npm run security:scanis the same human-readable flagger. CI andnpm run security:gaterun--gate, which exits non-zero only on a HIGH-severity finding that survives the path-aware priors (tests/malware_scan.test.tsasserts the tree is HIGH-clean, so a planted signature breaksnpm test). This skill is the deeper, agent-judged pass on top of that. -
Triage by fanning out the agent. Group the findings by
category. Dispatch the read-onlyrelease-malware-auditagent, one invocation per category that has findings (categories are independent, so run them in parallel in a single message). Give each agent its category's findings (or point it attmp/malware_scan.jsonand the category) and have it read the flagged files and return confirmed-vs-dismissed for that category. For a small number of total findings, a single agent invocation over all of them is fine. -
Synthesize one verdict. Combine the agents' results into a single report:
- PASS only if every flag is explained by legitimate behavior.
- BLOCK if there is even one confirmed malicious finding, or an uncertain finding that cannot be cleared. The gate fails safe: when in doubt, BLOCK and explain.
Report confirmed findings (
file:line, category, why), uncertain findings needing human judgement, and a one-line dismissal summary per category so a human can spot-check. Never silently drop a flag.
What is in scope (and the seams it deliberately covers)
- Whole source tree, including instruction markdown that an LLM executes:
.claude/agents/**,.claude/skills/**,CLAUDE.md, andAGENTS.mdare scanned for prompt injection / exfiltration / permission-escalation directives. Prose docs (docs/**,README.md) are out of scope - they are not executed..envis never read. package.jsoncontent check: install lifecycle hooks and newly-added transaction/web3/miner or non-registry (git/url/tarball) dependencies.- Path-aware priors:
child_process/execinscripts/,headless/, andtests/is dev tooling and demoted below the gate threshold, so the HIGH band tracks real risk in shippedsrc/**andserver/**. A--gaterun (andnpm test) fails only on a surviving HIGH finding, so a clean tree is green and a planted drainer is not.
Scope and limits (say these in the report, do not pretend otherwise)
- The static line scan cannot see: aliased or multi-line or dynamically-built call forms
(e.g.
const f = fetch; f(url, {body: secret})), an exfil URL assembled from variables or fetched at runtime, semantically paraphrased instruction injection (theai-*rules catch phrasings, not meaning), and anything behindeval/runtime indirection. The agent compensates by READING context, but neither runs the code in a sandbox - say so. - Dependencies are checked by content, not by diff or depth.
node_modulesis NOT walked and the lockfile's transitive tree is NOT audited. The manifest check catches a direct risky or non-registry dep and install hooks, but a malicious TRANSITIVE dependency, or a compromised version of an allowed one, is invisible here. The realistic token-theft path is new wallet-transaction code (which theweb3/key-exfilrules cover) or a new dependency - pair this gate withnpm audit, a lockfile diff, and a review of anypackage.jsonchange. - Read-only. This skill never edits, reverts, or quarantines code. It produces a verdict; remediation is a human decision.
Relationship to the other reviewers
| Concern | Owner |
|---|---|
| Deliberately planted malicious code | this skill |
| Accidental security/privacy mistakes (auth, SQLi, leaked secrets) | privacy-security-review |
| Schema / persisted-state safety | migration-safety |
| Three-host / IWorld parity | cross-platform-sync |
| Sim determinism | tests/architecture.test.ts |
.claude/skills/review-pr/SKILL.md
npx skills add levy-street/world-of-claudecraft --skill review-pr -g -y
SKILL.md
Frontmatter
{
"name": "review-pr",
"description": "Review a GitHub pull request for World of ClaudeCraft the way the maintainer does. Use when asked to review a PR, look at a PR, or check a PR (a github.com\/...\/pull\/<n> link or a PR number). Fetches the PR, classifies the change by domain (sim, wire\/parity, render, ui, server, i18n), verifies the repo's invariants against the real code (not the PR description), checks the merge-conflict scope, independently confirms any consequential finding, then posts a short, plain, friendly GitHub review with severity-tagged findings. Not for reviewing the local working tree (that is \/code-review).",
"user-invocable": true
}
Review a PR (World of ClaudeCraft house style)
This repo takes many small AI-authored contributions, so review is the gate that keeps the invariants intact. A good review here is not a vibe check of the diff: it verifies the load-bearing claims against the actual code, names issues with file:line evidence and a severity, and reads like a calm human wrote it. Work through the steps below.
/code-review reviews the local working tree; THIS skill reviews a GitHub PR end to
end and posts the result. They are different jobs.
What a good review looks like (the voice)
- Short, calm, plain GitHub style. Write like a person, not an AI: no preamble, no summary-of-a-summary, no "Great work!" throat-clearing, no bulleted restatement of the diff, no hedging boilerplate. If a sentence sounds like a model wrote it, cut it.
- No em dashes or en dashes, and no emojis. Use commas, colons, parentheses, or "to" for ranges. (You are reviewing a repo that bans them; do not introduce them yourself.)
- Lead with what is genuinely good when it is good, then the issues. Do not flatter.
- Every finding carries a severity and evidence:
blocking/should-fix/nit, with afile:linepointer and a one-line why. - i18n: review English only. The maintainer does every other locale at release time.
Check only that new player-visible strings are English
t()keys in the right catalog. Never raise a missing-translation finding, never draft a locale string, and never ask the contributor about any non-English locale (the non-Latin overlays and the M16 gate included). All of that is release-time maintainer work. - Post as a plain comment review (not approve / request-changes) unless told otherwise.
- Match the depth to the change: a one-file fix gets a tight note; a sim/wire/auth change earns the full invariant pass.
Step 1: Gather and scope
gh pr view <n> --json title,body,author,headRefName,baseRefName,state,additions,deletions,changedFiles,mergeable,mergeStateStatus,commits
gh pr diff <n> --name-only # which files, which domains
gh pr diff <n> # the diff (skip the generated i18n blocks)
git fetch origin pull/<n>/head:pr-<n>-review # local ref at the PR head
git fetch origin <baseRefName>
Conflict scope (do not trust the GitHub CONFLICTING flag for WHICH files):
git merge-tree --write-tree --name-only pr-<n>-review origin/<baseRefName>
Files printed at the top / marked CONFLICT are the true conflicts; Auto-merging
lines are clean. Read files at the PR head with git show pr-<n>-review:<path>.
Step 2: Classify the change, then run that domain's invariant pass
Pick the domains the diff touches (a PR can hit several) and check each. The root and
sub CLAUDE.md files are the source of truth; this is the checklist.
Sim (src/sim/**) -> determinism + purity + three-host parity.
- Determinism: ALL randomness through
Rng(src/sim/rng.ts). NeverMath.random,Date.now, orperformance.now. Same seed gives the same world. Watch the sneaky ones:Map/Setiteration order, object-key order, unstable sorts. A sort must be a total order (tiebreak on a stable id), not lean on input order or engine sort stability. Pathfinding/AI are classic nondeterminism hiding spots. - Purity: zero DOM/Three/browser imports; never imports
render/,ui/,game/,net/. (tests/architecture.test.tsguards this; confirm the diff respects it.) - Tick-based timers count
tickCount/DT, not wall-clock. Session-only fields (e.g.joinedAt) are re-derived on load, never persisted with a stale tick epoch.
Wire / parity (server/game.ts, src/net/online.ts, types.ts, entity.ts).
- A new
Entityfield either round-trips BOTH ways (encoded inwireEntity/dynamicFields, decoded inapplyWire) OR is deliberately server-local. If server-local, it must still be added toblankEntityinonline.tsso the offlineSimand theClientWorldmirror keep identical entity shapes (precedent:chargePath,petPath). Drift (one host has it, the other does not) is a bug. - Server authority: movement, combat, loot, economy resolve server-side. The client is a renderer. A client-side movement/combat decision is a finding, not a feature.
- When in doubt, hand this to the
cross-platform-syncagent (.claude/agents/).
Render (src/render/**) -> reads the world, never mutates it.
- Thin
IWorldconsumer; no concreteSim/ClientWorldcoupling beyond what the file already has. New visual system is its ownsrc/render/<thing>.ts, not a method bank onrenderer.ts. Pure geometry/math extracted and unit-tested without Three. - Watch per-frame cost (samples/allocations in the hot path).
UI (src/ui/**, index.html) -> seam + modularity + i18n + parity + a11y.
IWorldonly; never reach into a concrete world. A new window/panel is its own module the HUD composes (chat_window.tspattern), not a new banner section inhud.ts. Pure clamping/geometry/formatting extracted and tested.- play.html CSS parity:
play.htmlis a separate build entry that carries the same chrome DOM. CSS added toindex.htmlfor shared chrome must be mirrored intoplay.htmlor it breaks on/play. Check this explicitly (a recurring miss). - Persistence (window pos, settings) restores clamped to the current viewport; drag / resize no-op on mobile; check keyboard operability + aria on new controls.
Server (server/**) -> authority + auth + SQL + migration + privacy.
- Every new route: bearer-authed and scoped to the account FROM THE TOKEN, not from
client input. It must never read or mutate another account's data. Double-gate
ownership (route check + the UPDATE/SELECT
WHERE account_idclause). - SQL: parameterized ($1,$2,...). Flag any interpolation of user input.
- Migration: schema is inline DDL re-applied every boot. Changes must be additive +
idempotent (
ADD COLUMN IF NOT EXISTS); JSONB load stays back-compat (old rows lack the field). Flag destructive/non-idempotent DDL. Hand tomigration-safety/privacy-security-reviewagents for anything non-trivial. - Privacy: no
password_hashor other accounts' data returned to the client. - CLAUDE.md requires tests for sim/server behavior changes. A server change with no test is a finding.
i18n (any player-visible string). Review English only; the maintainer does every other locale at release time.
- Check only that new player-visible strings are English
t()keys in the rightsrc/ui/i18n.catalog/<domain>.ts, rendered viat().hud_chromeis the English-only catalog domain. Numbers/money/dates/percents go through the formatters, never string concat. - Do NOT raise any non-English i18n finding. Missing translations, the five non-Latin
overlays (
zh_CN,zh_TW,ja_JP,ko_KR,ru_RU), Latin-scriptpendingrows, and the M16 completeness gate (tests/i18n_completeness.test.ts) are all release-time maintainer work, not a review finding and not a contributor ask. From the contributor's side a PR is English-only. shell.tsand some catalog modules carry inline per-locale blocks that need all locales present fortsc; that is structural, not a policy violation, and still not something to flag.
The standard stale-base i18n conflict. When the only true conflicts are
src/ui/i18n.resolved.generated/pending.ts, src/ui/i18n.resolved.sha256, and
src/ui/i18n.status.summary.json, that is mechanical regen churn, not a design problem.
Say so, and note the fix: merge the base and re-run the i18n build
(i18n:build / i18n:admin / i18n:scan / i18n:hash --write). A hand-edited or
malformed generated artifact (e.g. an i18n.resolved.sha256 that is not exactly one
line) is a different thing and IS a blocker.
Step 3: Verify against the real code, and confirm before you accuse
- Do not review the PR description, review the code. Read the actual files at the head
ref, grep the surrounding conventions (the facing vector, the persistence path,
whether a field is serialized), and run the targeted test (
npx vitest run <file>) when you can. - Independently confirm any consequential or negative finding before you post it.
"This is dead code", "this duplicates base", "this leaks data", "this is
nondeterministic": verify each with your own grep/read/run, because a wrong strong
claim is worse than a missed nit. (Example: confirm a "feature already in base" claim
by checking the base branch has the routes/files; confirm "not on the wire" by
grepping
wireEntity.) - Distinguish what the diff CHANGED from what it inherited: do not flag pre-existing code the PR merely sits next to.
Step 4: Tests
Failing-first where it is a bug fix (a test that reproduces it, then the fix). Sim tests are seeded and deterministic. Pure logic is extracted so it tests without DOM/Three/GL. Note meaningful gaps (an untested branch, gesture math, the stamping half of a gate) as nits, not blockers, unless the untested thing is the actual fix.
Step 5: Write and post
Assemble the review in the voice above: a short positive opening when earned, then
findings as a tight list, each with severity + file:line + one-line why, then any
non-blocking notes. Then post:
gh pr review <n> --comment --body-file <path-to-review.md>
gh api repos/<owner>/<repo>/pulls/<n>/reviews --jq '.[-1] | {author:.user.login, state:.state, url:.html_url}'
Reviews post AS THE MAINTAINER. Only post to origin (levy-street), never a fork.
Posting is outward-facing: if the task did not clearly ask you to post, present the draft
and ask first.
Scaling up: many PRs, or one deep review
Multiple PRs, or a security/sim/wire change that deserves an adversarial pass: pre-fetch
the head refs, then fan out one read-only investigator per PR (or per dimension) with a
domain-tailored prompt that says READ-ONLY, do not post, return evidence-backed findings
with severity. Then YOU verify the consequential findings (Step 3) and write/post each
review in your own voice. Do not let a subagent author the final review prose or post it,
the voice and the verify-before-accuse bar are yours to hold. Prefer the purpose-built
agents (architecture-reviewer for src/sim/ determinism + the SimContext seam,
cross-platform-sync, migration-safety, privacy-security-review, qa-checklist) for
their domains.
Quick reference: domain -> first things to check
| Touches | Check first |
|---|---|
src/sim/** |
Rng-only, no wall-clock, total-order sorts, no DOM/Three import |
server/game.ts + online.ts + types.ts |
wire round-trip or server-local + blankEntity parity; server authority |
src/render/** |
reads not mutates; own module; per-frame cost |
src/ui/** + index.html |
IWorld seam; own module; play.html CSS parity; a11y/mobile; i18n |
server/** routes/db |
token-scoped auth; parameterized SQL; additive idempotent DDL; no oversharing; tests |
| i18n strings | English t() in catalog only; all other locales are release-time maintainer work, do not flag; generated-trio conflict = regen |


