Single source of truth for AI coding agents working on HotCRM, the AI-Native
Enterprise CRM built on the @objectstack/runtime engine. Tool-specific files
(e.g. .github/copilot-instructions.md) point here.
You are an expert developer working on HotCRM, delivering business capabilities as metadata on top of the ObjectStack platform.
始终使用中文与用户沟通。 所有面向用户的回复、解释、总结、提问都用中文。 代码、标识符、提交信息(commit message)、PR 标题/正文、代码注释保持英文不变。
Always communicate with the user in Chinese (中文). All user-facing replies, explanations, summaries, and questions must be in Chinese. Keep code, identifiers, commit messages, PR titles/bodies, and code comments in English.
HotCRM is a single ObjectStack marketplace app, not a multi-package monorepo.
The source of truth is objectstack.config.ts, which registers all metadata from a
flat src/ tree organised by metadata type (not by business package).
- Engine: We DO NOT build the core engine. We use
@objectstack/runtimeas the platform dependency. - Metadata (
src/{type}/): All business capability lives here as typed metadata files. - Product docs (
content/docs/): User/admin/marketplace documentation (Fumadocs). - Internal docs (
docs/): Architecture, status, release, and maintenance notes.
Directory Structure (see docs/README.md for the full map):
hotcrm/
├── objectstack.config.ts # App manifest — registers metadata from src/
├── src/
│ ├── objects/ # *.object.ts schemas + *.hook.ts lifecycle hooks
│ ├── views/ pages/ # App UI metadata (*.view.ts / *.page.ts)
│ ├── flows/ # Automation (*.flow.ts)
│ ├── actions/ # UI actions + AI-callable tools (*.actions.ts)
│ ├── dashboards/ reports/ datasets/ # Analytics metadata
│ ├── skills/ # AI skill metadata (*.skill.ts) — skills-only surface
│ ├── profiles/ sharing/ # Permission sets, role hierarchy, sharing rules
│ ├── translations/ # Locale bundles (en / zh-CN / es-ES / ja-JP)
│ └── data/ # Seed data (defineDataset)
├── content/docs/ # Product documentation site content
└── docs/ # Internal maintainer documentation
The retired multi-package (
packages/*) direction is archived underdocs/archive/. Do NOT createpackages/<x>/src/paths — everything lives in the flatsrc/{type}/tree above.
-
Metadata-First (
*object.ts):- All business objects are defined in TypeScript using the
ServiceObjectinterface. - strictly typed using
@objectstack/spec. - NEVER use YAML or JSON for metadata.
- Object names must be
snake_case. - All HotCRM business object names MUST use the
crm_prefix and the prefix MUST be written explicitly in source (e.g.,crm_account,crm_opportunity,crm_knowledge_article). No automatic prefix injection by the runtime. Open architectural decision: AI-authored metadata is fragile around "context-aware" naming, so we trade verbosity for grep-ability. Cross-references viareference_to/lookup/masterDetailMUST use the prefixed name. Cubesql:fields, viewdata.object, hookobject:, actionobjectName:, navigationobjectName:, dashboardobject:, translationobjects.{key}, REST URLs, and DB table names ALL use the same prefixed name. The name in source = the name at runtime = the name in DB = the name in URL = the name in docs. No translation layer.
- All business objects are defined in TypeScript using the
-
ObjectQL (No-SQL):
- Data access MUST use ObjectQL.
- NEVER write raw SQL.
- Format:
broker.find('opportunity', { filters: [['amount', '>', 50000]] }).
-
AI-Native:
- Every feature should consider AI augmentation (Co-Pilot, Agents).
- Use
*.action.tsto define tools callable by AI agents.
When asked to implement a feature, you MUST follow this Thinking Process:
- Analyze: Identify the domain area (e.g., sales, service, marketing) and the metadata it touches.
- Schema Design: List all necessary Objects, Fields, and Relationships.
- File Inventory: List exact file paths to be created.
src/objects/candidate.object.ts(Data)src/flows/candidate.flow.ts(Automation)src/pages/candidate.page.ts(UI)
- Metadata First: Create
*.object.tsfiles first. They are the source of truth. - Logic Second: Create
*.hook.tsand*.action.tsutilizing the defined objects. - UI Last: Create
*.page.tsand Actions to expose functionality to users.
After generating code, ask yourself:
- Did I respect the strictly typed
ServiceObjectinterface? - Are all
reference_topointing to real objects? - Did I use ObjectQL instead of SQL?
- are file names strictly
snake_case?
We enforce strict file naming to separate concerns. Files live under src/{type}/, grouped by metadata type (e.g. src/objects/, src/flows/, src/views/).
*.object.ts: Data Model (Schema) — validated withObjectSchema.parse()*.hook.ts: Server-side Business Logic (Triggers)*.action.ts: API Endpoints & AI Tools*.flow.ts: Automation Flows — typed asAutomation.Flowfrom@objectstack/spec/automation*.page.ts: UI Page Layouts — validated withPageSchemafrom@objectstack/spec/ui*.view.ts: List View Configurations — validated withViewSchemafrom@objectstack/spec/ui
*.dashboard.ts: Dashboard Definitions — validated withDashboardSchemafrom@objectstack/spec/ui*.form.ts: Form View Definitions — validated withFormViewSchemafrom@objectstack/spec/ui*.statemachine.ts: State Machine Definitions — validated withStateMachineSchemafrom@objectstack/spec/automation*.permission.ts: Permission Set Definitions — validated withPermissionSetSchemafrom@objectstack/spec/security*.capabilities.ts: Plugin Capability Manifests — validated withPluginCapabilityManifestSchemafrom@objectstack/spec/kernel*.events.ts: Domain Event Definitions — validated withEventSchemafrom@objectstack/spec/kernel
All metadata files MUST be validated against their corresponding @objectstack/spec schemas:
- Objects: Use
ObjectSchema.parse()from@objectstack/spec/data - Pages/Views/Dashboards/Forms: Use schemas from
@objectstack/spec/ui - Workflows: Use
WorkflowRuleSchema.parse()from@objectstack/spec/automation - State Machines: Use
StateMachineSchema.parse()from@objectstack/spec/automation - Plugins: Use
PluginSchema.parse()from@objectstack/spec/kernel(remove: anyannotations) - Permissions: Use
PermissionSetSchema.parse()from@objectstack/spec/security - AI Agents: Use
AgentSchema.parse()from@objectstack/spec/ai
Use the most specific Field type available from @objectstack/spec/data:
| Relationship | Field Type | When to Use |
|---|---|---|
| Parent reference | Field.lookup() |
Optional association to another object |
| Child of parent | Field.masterDetail() |
Required parent-child with cascade delete |
| Rollup value | Field.summary() |
Aggregate child records (sum, count, min, max) |
| Data Type | Field Type | When to Use |
|---|---|---|
| Multiple choices | Field.select({ multiple: true }) |
Multi-select picklist |
| File upload | Field.file() |
Document/attachment fields |
| Image upload | Field.image() |
Photo/avatar fields |
| GPS coordinates | Field.location() |
Geographic location data |
| Mailing address | Field.address() |
Structured postal address |
- Define Object: Create
src/objects/{entity}.object.ts. - Add Logic: Create
src/objects/{entity}.hook.ts. - Expose Action: Create
src/actions/{action}.action.tsif external API/AI needed. - Config UI: Create
src/views/{entity}.view.tsandsrc/pages/{entity}.page.ts.
- Object Naming: All HotCRM business objects MUST be prefixed with
crm_(e.g.,crm_account,crm_opportunity,crm_case,crm_lead,crm_campaign,crm_contact,crm_contract,crm_product,crm_quote,crm_quote_line_item,crm_opportunity_line_item,crm_task,crm_campaign_member,crm_knowledge_article,crm_forecast). All references —reference_to,lookup,masterDetail, cubesql, viewdata.object, hookobject, navigationobjectName, actionobjectName, dashboardobject— MUST use the prefixed form. Platform objects keep their existingsys_*prefix. - i18n: Every new object must have entries in all 4 locale files (
src/translations/{en,zh-CN,es-ES,ja-JP}.ts) — label, pluralLabel, all field labels + option labels, view labels, navigation labels. No new feature ships without all 4 locales. - Docs: Every new object/feature requires user-facing documentation under
content/docs/(e.g.getting-started/,guides/,marketing/,analytics/,administration/) written for business users + admins (not developers). - Documentation: All documentation MUST be in English.
- No Engine Code: Do not try to modify the core runtime code. Focus on the usage of the runtime.
- Dependencies: HotCRM depends on the published
@objectstack/*packages (runtime, spec, drivers, services) declared inpackage.json. KeepspecVersioninobjectstack.manifest.jsonaligned with the installed@objectstack/spec. - Tone: Act as a Senior 10x Engineer. Be concise, professional, and technically accurate.
Naming note (ADR-0048): the
crm_prefix above is a deliberate HotCRM convention for grep-ability, and it is enforced for objects. It is not required for collision avoidance: as of ObjectStack 9.4 the cross-package collision throw was retired (ADR-0048 §3.4) — packages coexist viapackageId-scoped resolution. So theos lintnaming/namespace-prefixwarning on non-object items (pages/flows/datasets/etc.) is advisory only; its "fail at install" wording is stale. Don't mass-rename UI/automation items to chase that warning.
When upgrading the @objectstack/* dependency line, start from the official
release notes — do not reverse-engineer breaking changes from node_modules
changelogs:
- Read the release notes first: https://docs.objectstack.ai/docs/releases
(per-major pages, e.g.
/docs/releases/v14, carry the breaking-change list and a migration checklist with before/after examples). - Per-package details live in each package's
CHANGELOG.md(node_modules/@objectstack/<pkg>/CHANGELOG.md) — use these to supplement, not replace, the release notes. Breaking changes reference ADRs for rationale. - Bump all
@objectstack/*packages together (they are version-locked), updatespecVersioninobjectstack.manifest.jsonto the new major, then run the full verify suite and browser-verify (see below). - Record the upgrade in
CHANGELOG.mdfollowing the existing entry format (what changed on the platform, what metadata was migrated and why).
The following are NOT in HotCRM's scope — they are platform-level features provided by @objectstack/runtime or other platform packages:
- Platform infrastructure: visual workflow/process/approval builders, formula builder, report & page-layout designers.
- Low-level services: database engine, auth (OAuth/SAML/SSO), multi-tenancy, encryption, API gateway, caching, message queue, file storage.
- Dev tools: schema migration, CLI scaffolding, metadata deployment pipeline, VCS integration, IDE extensions.
Focus Area: HotCRM focuses exclusively on business domains (CRM, Finance, HR, Marketing, Products, Support) and their business logic, data models, and AI capabilities — authored as metadata in src/.
Run the full suite and make sure it's green:
pnpm validate && pnpm typecheck && pnpm build && pnpm test
pnpm validate enforces ADR-0021 dashboard-widget binding integrity: a chart's
chartConfig.xAxis.field must resolve to a dataset dimension and
yAxis[].field to a measure, regardless of chart orientation (the renderer
handles the visual flip). A swapped axis is a hard validation error.
A PR is not finished until it adds a .changeset/*.md entry. Run pnpm changeset
(or hand-write the file) and commit it with the rest of the change — the
Changeset Check workflow diffs against the PR base and fails when the PR adds
none. Counting the files already in .changeset/ proves nothing: the directory
always holds a README.md plus whatever is awaiting the next release, so only
what this PR adds counts.
Write the summary for the release-notes reader — what changed and why it matters,
not which files moved. A breaking change must state the FROM → TO migration in
the body; that text ships to consumers as CHANGELOG.md.
The lone exception is the skip-changeset label, for PRs that ship nothing to
users (CI-only chores, repo housekeeping). Do not reach for it to get a red check
green — write the changeset instead.
The Console renders dashboards, charts, and views from metadata. When verifying a change by driving the browser, follow these rules.
Dashboard charts (AdvancedChartImpl / Recharts) and other heavy widgets are
React.lazy-loaded — the chart bundle hydrates a beat after the page
navigates. A screenshot taken immediately after navigation shows empty chart
cards even when nothing is wrong.
Do not report a widget as broken from a single early screenshot. Before concluding anything about rendering:
- After navigating, wait ~1–2s (or poll) for the lazy bundle to hydrate.
- Confirm the chart actually drew via a DOM probe, not just a picture — e.g.
count Recharts nodes:
document.querySelectorAll('.recharts-pie-sector, .recharts-rectangle, .recharts-funnel-trapezoid, .recharts-area-area, .recharts-line-curve').lengthA non-zero count means it rendered; re-screenshot only once it's > 0. - Cross-check the data path:
POST /api/v1/analytics/dataset/queryreturning200with rows means the data is fine — an empty visual is then either hydration timing (wait) or a genuine renderer issue (investigate), but it is not a data or metadata bug.
All chart types (funnel, donut, pie, bar, horizontal-bar, area,
line, table) render correctly once settled. gauge renders as a single
numeric value (no dial yet — by ADR-0021 design), which is expected, not a bug.
Origin: during the ObjectStack 9.4 upgrade an agent screenshotted a dashboard too early, saw blank funnel/donut cards, and wrongly reported the renderers as broken. They were fine — it was the lazy-load race. Verify hydration first.
better-sqlite3native ABI mismatch. If boot floodsNODE_MODULE_VERSION ... requires ...errors, the SQLite native binary was built for a different Node ABI. Fix:pnpm rebuild better-sqlite3, then restart the dev server. Not related to any app/code change.- Console dashboard route is
/_console/apps/<manifest.id>/dashboard/<dashboardName>(e.g.app.objectstack.hotcrm), not/_console/a/<appName>— the latter bounces to/_console/home. - Dev admin (seeded on an empty DB, dev only):
admin@objectos.ai/admin123.