Primary AI instruction file for this repo. Read natively by Claude Code, GitHub Copilot (coding agent + CLI, since Aug 2025), and other agents — no separate .github/copilot-instructions.md mirror needed.
v5.0 breaking rename:
project→environmenteverywhere (CLI-e,/api/v1/environments/:id, headerX-Environment-Id,OS_ENVIRONMENT_ID, DB columnenvironment_id). No aliases. See ADR-0006. "Project" now only means the npm/monorepo sense.
- 与维护者沟通时一律使用中文(对话回复、PR/issue 讨论中的解释性文字)。
- 代码、标识符、提交信息(commit messages)、ADR/文档正文等仓库产物保持现有语言惯例(以英文为主),不要因本条而改写。
pnpm install # deps
pnpm setup # first-time: install + build spec
pnpm build # turbo build (excludes docs)
pnpm test # turbo test
pnpm docs:dev # docs site| Scenario | Command | Notes |
|---|---|---|
Frontend debug (UI in ../objectui calls backend) |
PORT=3000 pnpm dev |
pnpm dev = the showcase kitchen-sink app (default; best for exercising the platform). Port must be 3000 (UI hard-wired); persistent state; leave running. For the minimal CRM app instead: PORT=3000 pnpm dev:crm. |
| Backend-only debug | pnpm dev -- --fresh -p <random> |
Random high port; ephemeral tempdir; you must kill it when done |
--fresh: ephemeral tempdir (auto-deleted on exit) + --seed-admin (POSTs sign-up, prints creds — default admin@objectos.ai / admin123, override via --admin-email/--admin-password). The seeded admin is auto-promoted to platform admin (the system seed identity usr_system is skipped), so Setup/Studio are reachable on first login.
Rules: never run two backends on port 3000; for backend tasks pick a random port and tear it down; never kill a server you didn't start (other agents/the user may be using it — see Multi-agent discipline §8); always use a pnpm dev/dev:crm/dev:showcase script (flags after -- are forwarded), not raw pnpm --filter.
pnpm dev:crm -- --fresh -p 38421 # start; debug via curl
kill $(lsof -ti tcp:38421) # tear down — tempdir auto-deletesThis repo ships backend only. All Studio/Console UI work happens in ../objectui (separate repo, checked out next to framework/). Workflow: edit + commit + push in ../objectui, then in framework/ run pnpm objectui:refresh to pull its build into packages/console/.
Other scripts: objectui:bump (pull only), objectui:build, objectui:clean. packages/console/dist/ or .cache/objectui-*/ — regenerated.
Fast iteration on ../objectui src (no commit/refresh loop): run objectui's own console dev server — cd ../objectui && pnpm --filter @object-ui/console dev (Vite on :5180, HMR). Its /api proxy targets DEV_PROXY_TARGET || http://localhost:3000, so run the backend you're testing on :3000 (PORT=3000 pnpm dev for showcase) and browse :5180. Note :3001/_console (or whatever the backend serves) is the published console, not your ../objectui src — only :5180 reflects local UI edits. See ../objectui/AGENTS.md for the app-id / localStorage / auth gotchas.
-
Zod First. All schemas start as Zod. Types via
z.infer<typeof X>. JSON Schemas generated from Zod. -
No business logic in
packages/spec. Spec = schemas/types/constants only. Runtime logic goes incore,runtime, orservices/*. -
Naming:
- TS config keys →
camelCase(maxLength,defaultValue) - Machine names (data values) →
snake_case(name: 'first_name') - Metadata type names → singular (
'agent','view','flow') — matchesMetadataTypeSchemainpackages/spec/src/kernel/metadata-plugin.zod.ts - REST endpoints → plural (
/api/v1/ai/agents)
- TS config keys →
-
Imports: Use
@objectstack/specnamespaces or subpaths. Never relative../../packages/spec. -
No workarounds. Adopt sustainable, well-architected solutions — not temporary patches.
-
Object name = table name. The object
nameis the canonical id everywhere (API, ObjectQL, REST, SDK, DB table). Never setnamespace(deprecated) ortableName(always equalsname). For module prefixes, embed in the name (sys_user,ai_conversations). -
One Zod source per metadata type. Each type (
view,flow,agent, …) has exactly one schema inpackages/spec/src/{domain}/. Org overlay opt-in lives only inallowOrgOverrideonDEFAULT_METADATA_TYPE_REGISTRY— no parallel whitelists. See ADR-0005. -
North Star alignment. Read
content/docs/concepts/north-star.mdxbefore structural changes. If a change doesn't advance §7 Built, shrink Drift, or unlock Missing — it probably shouldn't ship. -
OS_env-var prefix + structure. All ObjectStack-owned env vars MUST start withOS_, then followOS_{DOMAIN}_{FEATURE}[_QUALIFIER]whereDOMAINis the subsystem (AUTH,SEARCH,CORS,CLOUD,DATABASE,CLUSTER,MCP,SSO, …) so related vars group together (cf.OS_AUTH_*,OS_CORS_*). Pick the shape by what the var is:- Boolean feature flag → suffix
_ENABLED, default-off / opt-in:OS_{DOMAIN}_{FEATURE}_ENABLED(OS_SSO_ENABLED,OS_SCIM_ENABLED,OS_SEARCH_PINYIN_ENABLED). Never a bareOS_PINYIN_SEARCH— bare names read as config, not toggles. - Config value (URL / path / secret / level / count) →
OS_{DOMAIN}_{NAME}(OS_CLOUD_URL,OS_DATABASE_URL,OS_LOG_LEVEL,OS_AUTH_SECRET). - Escape hatch / dangerous override →
OS_ALLOW_{X}— deliberately ungrouped and scary-looking (OS_ALLOW_MAIN_EDITS,OS_ALLOW_MEMORY_CLUSTER_MULTINODE). - Opt-out →
OS_SKIP_{X}/OS_DISABLE_{X}. Test/CI-only →OS_TEST_*/OS_EXPECT_*. - Pre-existing vars that don't fit (
OS_METADATA_WRITABLE,OS_EAGER_SCHEMAS,OS_SERVER_TIMING) are debt, not precedent — new vars follow this rule; rename old ones via the deprecation helper below when touched.
When renaming a legacy var, use
readEnvWithDeprecation('OS_NEW', 'LEGACY')from@objectstack/types(keeps legacy working one release). Third-party exceptions kept as-is:NODE_ENV,HOME,OPENAI_API_KEY,TURSO_*, OAuth*_CLIENT_ID/SECRET,RESEND_API_KEY,POSTMARK_TOKEN,AI_GATEWAY_*,SMTP_*. See #1382. - Boolean feature flag → suffix
-
File issues for out-of-scope findings — don't silently expand scope or leave them buried. When you hit a bug, gap, or unenforced capability that's unrelated to the current task, or too large to fix in scope, open a GitHub issue (
gh issue create) with a clear repro/decision and link it from your PR. Corollary: never advertise or demo a capability the runtime doesn't actually deliver (declared ≠ enforced) — fix it, trim it, or file an issue, but don't fake coverage. Example: the spec declares 9 validation-rule types but the write-path validator enforces only 3 (state_machine/script/cross_field); the other 6 are tracked in #1475 rather than demoed in the showcase. -
Worktree-first — never edit on the shared
maincheckout. This repo is edited by multiple agents at once; the sharedmaintree has its HEAD switched and reset under you, silently clobbering uncommitted work. Before your first file edit, you MUST be in a dedicated worktree on a feature branch:git worktree add ../framework-<task> -b <branch> main && cd ../framework-<task> && pnpm install. A PreToolUse hook (.claude/hooks/guard-main-checkout.sh) enforces this — it blocksEdit/Write/NotebookEditwhenever HEAD is onmain(override for a deliberate non-task fix withOS_ALLOW_MAIN_EDITS=1). Full playbook below. -
Contract-first — fix the metadata, not the runtime. This is a metadata-driven framework:
packages/specis the one contract between metadata producers and the runtime/renderers that consume it. When a piece of metadata "doesn't work," ask first: is it spec-compliant? is this the long-term-correct direction? If the metadata is wrong, fix it at the producer and reject it at authoring/publish (validation / lint) so the error surfaces loudly — do not add a lenient alias or??fallback in the consumer (a node executor, the REST layer, a renderer) to tolerate off-spec input. A tolerant fallback fossilizes the wrong convention into a second de-facto contract, dilutes the spec, and hides the producer's bug — one strict contract beats N dialects. This is an internal contract (we own both ends), so "be liberal in what you accept" (Postel) does not apply — that's for untrusted boundaries. Change the spec only when the spec itself is genuinely wrong, and then deliberately (edit the Zod schema + migrate), never by accreting consumer-side fallbacks. The existingcfg.filter ?? cfg.filters/cfg.objectName ?? cfg.objectin the flow executors are debt to pay down, not a pattern to copy. Worked example: an AI-authoredcreate_recordusedfieldValues/today()/{{trigger.record.id}}while the executor readsfields/{TODAY()}/{record.id}→ the fix was correcting the authoring skill + a publish-gate lint that rejects the wrong shape (cloud#688), not acfg.fields ?? cfg.fieldValuesruntime alias (framework#2419, rejected). Strengthens #5.
This repo is worked on by multiple agents in parallel. Use one git
worktree per agent/task (git worktree add ../framework-<task> -b <branch>;
run pnpm install in the new tree) so file systems are physically isolated —
this is mandatory, not a preference (Prime Directive #11), and a PreToolUse hook
blocks edits made while on the shared main branch. Working in the shared main
checkout is not a supported fallback: branches get switched and shared files —
including ones you just wrote — get reset under you mid-task (a full session's
work was silently reverted twice before this rule was enforced). Even inside your
own worktree, operate defensively:
- Only touch the files your task needs. Don't "fix" unrelated diffs, reverts, or other agents' in-flight edits, and don't try to manage the whole working tree. If a file you didn't change shows as modified, leave it.
- One feature branch + one PR per task. Branch off
main. Never commit task work straight tomain. - Never
git push --force/--force-with-lease, and never pushmain. A force-push can clobber a parallel agent's work;mainis shared — land everything via PR. - Verify the current branch before every commit/push
(
git rev-parse --abbrev-ref HEAD). HEAD may have been switched by another agent — if it isn't your feature branch, stop and re-checkout before pushing. - Shared files (barrels/registries like
builtin/index.ts): edit →git add→ commit atomically, then confirm the commit really contains your lines (git show HEAD:<file> | grep <yourChange>). A concurrent edit can revert your working-tree change between the edit and the commit. On a real conflict, re-apply only your lines and let the PR merge integrate the rest. - Don't rebase or force-update shared branches to tidy other agents' commits.
- Merge only after remote CI is fully green. Never
gh pr merge --auto. Auto-merge can land a still-red PR onto sharedmainand break it for every parallel agent (see #1475). Merge serially; rebase other open branches before merging the next one. - Testing needs a server? Start your own temporary one — never stop someone
else's. A running dev server you didn't start probably belongs to another
agent or the user; killing it (or its port) breaks their in-flight work. Spin
up your own instance on a random high port (
pnpm dev -- --fresh -p <random>) and shut it down yourself when the task is done (kill $(lsof -ti tcp:<port>)). Don't leave orphan servers behind.
packages/
spec/ # 🏛️ Protocol schemas, types, constants (Zod source of truth)
core/ # ⚙️ ObjectKernel, DI, EventBus
types/ # 📦 Shared TS utilities
metadata/ # 📋 Metadata loading & persistence
objectql/ # 🔍 Query engine
runtime/ # 🏃 Bootstrap (Driver/App plugins)
rest/ # 🌐 Auto-generated REST layer
client/ # 📡 Framework-agnostic SDK
client-react/ # ⚛️ React hooks
cli/ # 🖥️ CLI
create-objectstack/ # 🚀 Scaffolding
vscode-objectstack/ # 🧩 VS Code extension
adapters/ # 🔌 express/fastify/hono/nestjs/nextjs/nuxt/sveltekit
plugins/ # 🧱 Official plugins & drivers
services/ # 🔧 Kernel-managed services
apps/docs/ # 📖 Fumadocs site
examples/ # 📚 Reference implementations
skills/ # 🤖 Domain skill definitions
content/docs/ # 📝 Docs content
Studio UI: ../objectui (sibling repo).
| Namespace | Path | Responsibility |
|---|---|---|
Data |
data/ |
Object, Field, FieldType, Query, Filter, Sort |
UI |
ui/ |
App, View (grid/kanban/calendar/gantt), Dashboard, Report, Action |
System |
system/ |
Manifest, Datasource, API endpoints, Translation (i18n) |
Automation |
automation/ |
Flow, Workflow, Trigger registry |
AI |
ai/ |
Agent, Tool, Skill, RAG, Model registry |
API |
api/ |
REST/GraphQL contract, Endpoint, Realtime |
Identity |
identity/ |
User, Organization, Profile |
Security |
security/ |
Permission, Role, Policy |
Kernel |
kernel/ |
Plugin lifecycle (PluginContext) |
Cloud |
cloud/ |
Multi-tenant, deployment, environment |
QA |
qa/ |
Test, validation |
Contracts |
contracts/ |
Cross-package interfaces |
Integration |
integration/ |
External integrations |
Studio |
studio/ |
Studio UI metadata |
Shared |
shared/ |
Error maps, normalization utilities |
Root also exports: defineStack, composeStacks, defineView, defineApp, defineFlow, defineAgent, defineTool, defineSkill.
| Kernel | Use For |
|---|---|
ObjectKernel |
Default production runtime. Full DI / EventBus / Plugin lifecycle. |
LiteKernel |
Tests (vitest), serverless, edge (Workers). |
EnhancedObjectKernel is deprecated — do not use.
| Path | Type | Rule |
|---|---|---|
content/docs/references/ |
AUTO-GEN | ❌ Never hand-edit. Regenerated by packages/spec/scripts/build-docs.ts. |
content/docs/guides/ |
hand-written | ✅ Update meta.json when adding pages. |
content/docs/concepts/ |
hand-written | ✅ |
content/docs/getting-started/ |
hand-written | ✅ |
content/docs/protocol/ |
hand-written | ✅ |
| Path | Role | Key Constraints |
|---|---|---|
**/objectstack.config.ts |
Project Architect | defineStack, driver/adapter selection |
packages/spec/src/data/** |
Data Architect | Zod-first, snake_case, TSDoc every prop |
packages/spec/src/ui/** |
UI Protocol Designer | View types, SDUI patterns |
packages/spec/src/automation/** |
Automation Architect | Flow/Workflow state machines |
packages/spec/src/ai/** |
AI Protocol Designer | Agent/Tool/Skill schemas |
packages/spec/src/system/** |
System Architect | Manifest, datasource, i18n |
packages/spec/src/kernel/** |
Kernel Engineer | Plugin lifecycle, PluginContext |
packages/spec/src/security/** |
Security Architect | RBAC, policies |
packages/core/** |
Kernel Engineer | Runtime logic OK here |
packages/runtime/** |
Runtime Engineer | Bootstrap, plugin registration |
packages/rest/** |
API Engineer | Route gen, middleware |
packages/plugins/** |
Plugin Developer | Implements spec contracts |
packages/services/** |
Service Engineer | Kernel-managed services |
packages/adapters/** |
Integration Engineer | Framework bindings, zero business logic |
packages/client*/** |
SDK Engineer | Public API, DX, type safety |
apps/docs/** |
Docs Engineer | Fumadocs + Next.js, MDX |
examples/** |
Example Author | Minimal, runnable, uses defineStack |
content/docs/** |
Technical Writer | Respect auto-gen boundaries |
../objectui/** (sibling repo) |
Studio UI Engineer | React + Shadcn + Tailwind, dark mode default |
Consult the matching SKILL.md when working in its domain: objectstack-platform, objectstack-data, objectstack-query, objectstack-api, objectstack-ui, objectstack-automation, objectstack-ai, objectstack-i18n, objectstack-formula (CEL).
Zod schema:
export const FieldSchema = z.object({
name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Machine name (snake_case)'),
label: z.string().describe('Display label'),
type: FieldTypeSchema,
maxLength: z.number().optional(),
defaultValue: z.any().optional(),
});
export type Field = z.infer<typeof FieldSchema>;Plugin:
export default {
async onInstall(ctx: PluginContext) { /* migrations */ },
async onEnable(ctx: PluginContext) { /* register routes/services */ },
async onDisable(ctx: PluginContext) { /* cleanup */ },
};pnpm test— verify nothing broke.- Land it — don't leave passing work in the working tree. Once tests pass,
create a feature branch, commit, push, open a PR, and merge it after remote
CI is fully green (see Multi-agent discipline: never straight to
main, nevergh pr merge --auto). A finished task = a merged PR, not a dirty working tree. - Add a changeset for feature work. When the change is a feature or functional improvement, run
pnpm changeset(or add a.changeset/*.mdentry) describing it before committing. Pure bug fixes do not require a changeset. - Update
CHANGELOG.md/ROADMAP.mdif user-facing or architectural. - Delete temporary artifacts — screenshots, traces, scratch logs,
.playwright-mcp/, throwawaytmp*.ts, ad-hoc scripts. Repo must look identical to before, minus intended changes.
Keep single edit/create payloads under ~20KB. Split larger changes into multiple sequential edits.