src/— TanStack Start app code (routes, components, styles).convex/— Convex backend (schema, queries/mutations/actions, HTTP routes).convex/_generated/— generated Convex API/types; committed for builds.docs/— product/spec docs (seedocs/spec.md).public/— static assets.
bun run dev— local app server athttp://localhost:3000.bun run build— production build (Vite + Nitro).bun run preview— preview built app.bunx convex dev— Convex dev deployment + function watcher.bunx convex codegen— regenerateconvex/_generated.bun run format:check— formatting check.bun run lint— Biome + oxlint (type-aware).bun run test— Vitest (unit tests).bun run coverage— coverage run; keep global >= 80%.
- TypeScript strict; ESM.
- Indentation: 2 spaces, single quotes (Biome).
- Lint/format: Biome + oxlint (type-aware).
- Convex function names: verb-first (
getBySlug,publishVersion).
- Framework: Vitest 4 + jsdom.
- Tests live in
src/**andconvex/lib/**. - Coverage threshold: 80% global (lines/functions/branches/statements).
- Example:
convex/lib/skills.test.ts.
- Commit messages: Conventional Commits (
feat:,fix:,chore:,docs:…). - Keep changes scoped; avoid repo-wide search/replace.
- Before commit/PR handoff, run
bun run format:checkandbun run lint; include commands run in the PR summary. - PRs: include summary + test commands run. Add screenshots for UI changes.
- Before merging any PR, verify TypeScript cleanly with
bunx tsc -p packages/schema/tsconfig.json --noEmitandbunx tsc -p packages/clawhub/tsconfig.json --noEmit; if Convex code changed, also run the repo typecheck path used by deploy sobunx convex deploywill not fail ontsc. - GitHub comments: for multiline
ghcomments/close messages, use--body-file,--input, or stdin/heredoc with real newlines; never pass literal\\nin shell strings. - Reject PRs that add skills into source code/repo content directly (for example under
skills/or seed-only additions intended as published skills). Skills must be uploaded/published via CLI. - Repo-local Convex developer skills under
.agents/skills/convex*/are allowed when they support working on this codebase; keep top-levelskills/reserved for installed/published skill content and ignored by git.
- Production deploys are manual-only. Merging to
maindoes not deploy. - To release production, start the GitHub Actions
Deployworkflow frommain:gh workflow run deploy.yml --repo openclaw/clawhub --ref main - The workflow supports
full,backend, andfrontendtargets. frontendcurrently means: wait for the Vercel production deploy for the selectedmainSHA, then run production smoke checks. It does not callvercel deploydirectly yet.- The workflow uses the GitHub
Productionenvironment for deploy secrets, but it does not require a separate approval step. - Prod deploy secrets live on the
Productionenvironment, not as ordinary repo secrets. Required:CONVEX_DEPLOY_KEY. Optional:PLAYWRIGHT_AUTH_STORAGE_STATE_JSON. - CLI npm releases are also manual-only and tag-based. Stable tags only:
vX.Y.Z. StartClawHub CLI NPM Releasefrommain, first withpreflight_only=true, then rerun it with the same tag and the successfulpreflight_run_id. - Real CLI publishes wait at the GitHub
npm-releaseenvironment and use npm trusted publishing. Required npm trusted publisher settings: repositoryopenclaw/clawhub, workflowclawhub-cli-npm-release.yml, environmentnpm-release.
- If
git branch -d/-D <branch>is policy-blocked, delete the local ref directly:git update-ref -d refs/heads/<branch>.
- Canonical site:
https://clawhub.ai(prefer this over legacy domains). - Skill page URL format:
https://clawhub.ai/<owner>/<slug>(owner handle preferred; falls back to owner id). - Skill API detail URL:
https://clawhub.ai/api/v1/skills/<slug>. - Skill file URL:
https://clawhub.ai/api/v1/skills/<slug>/file?path=SKILL.md. - For “full URL?” requests, return the canonical page URL first, then API URL if useful.
- Local env:
.env.local(never commit secrets). - Convex env holds JWT keys; Vercel only needs
VITE_CONVEX_URL+VITE_CONVEX_SITE_URL. - OAuth: GitHub OAuth App credentials required for login.
- New Convex functions must be pushed before
convex run: usebunx convex dev --once(dev) orbunx convex deploy(prod). - For non-interactive prod deploys, use
bunx convex deploy -yto skip confirmation. - If
bunx convex run --env-file .env.local ...returns401 MissingAccessTokendespitebunx convex login, workaround: omit--env-fileand use--deployment-name <name>/--prod.
- Always use
.withIndex()instead of.filter()for fields that can be indexed..filter()causes full table scans — every doc is read and billed. Even a single.filter()on a 16K-row table reads ~16 MB per call. - Convex reads entire documents — no field projections. If you only need a few fields from large docs (~6 KB+), denormalize a lightweight summary onto the parent doc or use a lookup table (see
embeddingSkillMap,skill.latestVersionSummary,skill.badgesfor examples). - Denormalization pattern: persist computed fields so they can be indexed. Every mutation that updates source fields must also update the denormalized field. Always write a cursor-based backfill for new fields (see
backfillIsSuspiciousInternal,backfillLatestVersionSummaryInternal,backfillDenormalizedBadgesInternalfor examples). - Cron jobs must never scan entire tables. Use indexed queries with equality filters. Use cursor-based pagination for large datasets. Prefer incremental/delta tracking over full recounts.
- 32K document limit per query. Split
.collect()calls by a partition field (e.g., one day at a time instead of a 7-day range). SeerebuildTrendingLeaderboardActioninconvex/leaderboards.tsfor an example. - Common mistakes:
.filter().collect()without an index;ctx.db.get()on large docs in a loop for list views; while loops that paginate the whole table to find filtered results. - Before writing or reviewing Convex queries, check deployment health. Run
bunx convex insightsto check for OCC conflicts,bytesReadLimit, anddocumentsReadLimiterrors. Runbunx convex logs --failureto see individual error messages and stack traces. This helps identify which functions are causing bandwidth issues so you can prioritize fixes.
This project uses Convex as its backend.
When working on Convex code, always read convex/_generated/ai/guidelines.md first for important guidelines on how to correctly use Convex APIs and patterns. The file contains rules that override what you may have learned about Convex from training data.
Convex agent skills for common tasks can be installed by running npx convex ai-files install.
The skills table maintains two parallel sets of stat fields as part of an in-progress field migration:
Legacy (nested, @deprecated) |
Top-level (source of truth, indexable) |
|---|---|
stats.downloads |
statsDownloads |
stats.stars |
statsStars |
stats.installsCurrent |
statsInstallsCurrent |
stats.installsAllTime |
statsInstallsAllTime |
Rules:
- Always use
readCanonicalStat(skill, field)(convex/lib/skillStats.ts) to read any of the four migrated fields. It prefers the top-level field and falls back to the nested field for pre-migration documents. Never accessskill.stats.downloads/.stars/.installsCurrent/.installsAllTimedirectly. - Always use
applySkillStatDeltas()to write stat deltas. It writes both the top-level and nested fields in the same patch to keep them in sync. - Both sets of fields must be written together in any patch that touches stat values (see the return shape of
applySkillStatDeltas). - Nested-only reads are acceptable only for
stats.commentsandstats.versions— no top-level field exists for these yet. - The four legacy nested fields are marked
@deprecatedinstatsValidator(schema.ts). Any IDE access toskill.stats.downloadsetc. will show a strikethrough warning — treat this as a signal to usereadCanonicalStat()instead. - When adding new stat fields, follow the same dual-write pattern and add a cursor-based backfill mutation (see
backfillSkillStatFieldsInternalfor an example).