- npm workspaces monorepo; use npm, not pnpm/yarn. Root build is
tsc --buildoverpackages/core,packages/server,packages/cli,packages/enclave, andscriptsvia TS project references. - Package boundaries matter:
coreowns protocol/auth/grants/storage/keys/gateway/sync;serverowns the Hono HTTP app and runtime bootstrap;cliis only a facade re-exporting core config and server bootstrap;enclaveowns the dstack port, deterministic identity, sealing and the node agent HTTP, and imports nothing fromcore/server.packages/core/src/legacy-projectionis the internal, pure PDPP→legacy scope projection (not a published package); it must stay browser-safe, so ESLint rejectsnode:imports there. - Public API is controlled by package
exports; updatepackages/*/package.jsonwhen moving exported files or subpaths.
- Install like CI:
npm ci. - Typecheck is named
npm run lint(tsc --noEmit); ESLint is separate:npm run lint:eslint. - Full local validation:
npm run lint && npm run lint:eslint && npm run format:check && npm test. - CI also requires
npm run build && npm teston Node 20 and 22. - Unit/integration tests:
npm test(packages/*/src/**/*.test.ts). Focus with Vitest args, e.g.npm test -- packages/server/src/routes/data.test.ts. - E2E tests are separate:
npm run test:e2e(tests/e2e/**/*.e2e.test.ts, 15s timeout). npm run devperforms a fulltsc --buildbeforetsx watch;npm startrebuilds before runningpackages/server/dist/index.js.- Server package build has an extra UI artifact step:
packages/serverrunscopy-uisosrc/ui/index.htmllands indist/ui.
- Default root namespace is
${PERSONAL_SERVER_ROOT_PATH:-~/personal-server}; runtime writesdata/,config.json,index.db,key.json,logs/,tokens.json, and possiblybin/frpc*under it. loadConfig()auto-creates/rewritesconfig.jsonwith defaults, so simply starting the server can mutate the configured root.- Cloud overrides (
SERVER_PORT,SERVER_ORIGIN,TUNNEL_ENABLED,DEV_UI_ENABLED) only apply whenCLOUD_MODE=true. .env.examplecontains a dev/testVANA_MASTER_KEY_SIGNATURE; without this, owner-restricted endpoints return server-not-configured errors and sync/tunnel are disabled.- Dev UI is enabled by default in config, but it is mounted only when an ephemeral startup token is generated; restart rotates the token.
- Non-cloud startup may bind two listeners: the main server port and a loopback auth listener on
LOCAL_AUTH_PORTorserver.port + 1. - Startup serves HTTP before slow background services finish; gateway registration and tunnel connection happen after listen.
- Tunnel requires
config.tunnel.enabled, owner key material, and an on-demand frpc download. Missing prereqs degrade to local-only warnings; an unregistered server makes an established tunnel unroutable.
- Composition root is
packages/server/src/bootstrap.ts; route mounting and global JSON error/404 shape live inpackages/server/src/app.ts. - Prefer adding route deps to
AppDeps/route factory deps instead of importing singletons; tests instantiate routes with mocked deps. - Auth is not only
Web3Signed: owner routes can also authenticate via dev token,PS_ACCESS_TOKEN, or persisted CLI session tokens intokens.json. - Two storage ports: readers (data route, MCP, derivatives, job worker) get
withLegacyProjection(raw), which serves legacy scope bodies projected from stored PDPP{records}; sync upload/download must get the raw port so stored bytes and hashes never change.?view=storedgives the owner the stored file's body (no stored-record projection and no canonical view); ownerPOST ?supersede=legacydeletes older legacy-form versions locally only (not on the gateway). - Body limits are endpoint-specific: default helper is 1 MB, but data ingest uses 50 MB and structured 413 JSON.
- Tests are co-located as
foo.test.ts; test utilities live underpackages/core/src/test-utilsand route tests build mock gateways/deps directly. - Use red/green TDD for behavior changes and bug fixes: add one focused failing test through the public route/core API, make the minimal code change to pass, then repeat; do not batch-write imagined tests up front.
- For Personal Server UI/API work, test through the debug UI whenever applicable, end to end as far as local/runtime prerequisites allow.
- ESLint enforces type-only imports and errors on unused vars unless prefixed with
_;no-explicit-anyis only a warning in source and disabled in tests. - Root
.editorconfigis 2-space, LF, final newline; Markdown allows trailing whitespace. - Husky runs
lint-stagedpre-commit and commitlint on commit messages; PR titles are semantic-convention checked in GitHub Actions.
- Stable release:
release.ymlruns semantic-release on push tomainand publishespackages/core→lite→server→cli(see.releaserc.yaml). Workspace*deps are pinned to the release version before publish viascripts/semantic-release-pin-workspace-deps.cjs(same idea as canary). - Canary prereleases:
prerelease.ymlondev/develop/feat/*rewrites package versions/pins thennpm publish --tag canaryfor core/lite/server/cli. - Docker build intentionally replaces root
tsconfig.jsonwith references to onlycoreandserver; container defaults setCLOUD_MODE=true,PERSONAL_SERVER_ROOT_PATH=/data,TUNNEL_ENABLED=false, andDEV_UI_ENABLED=false. - Use conventional commits for commit messages