Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .changeset/agents-md-objectql-example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
'hotcrm': patch
---

Make `AGENTS.md`'s only ObjectQL example match the repo's real read path.

The example under §"Tech Stack & Protocol" item 2 read
`broker.find('opportunity', { filters: [['amount', '>', 50000]] })`, and every part of
it was wrong for this codebase:

- **`broker`** has zero occurrences in `src/`. The data surface an agent actually gets
is `ctx.api`: 43 call sites in `*.hook.ts` (cast once as `HookApi`, then
`api.object('crm_x')`) and 17 in action script bodies (`ctx.api.object('crm_x')`).
- **`filters`** is not a key of the in-process query object at all. It is the
*deprecated plural alias* of the `filter` HTTP query-param, whose value is a JSON
string. Used in process it fails silently, which is the failure mode this repo has
already paid for once — `.changeset/hook-query-where-not-filter.md` records seventeen
hook calls whose predicate vanished: `findOne` returned the object's first row and
`count` counted the whole object, with no error and no `null`. `HookQuery` in
`src/objects/_hook-api.ts` now omits the alias precisely so the compiler rejects it.
- **`'opportunity'`** violated the `crm_` prefix rule stated five lines above it in the
same file. The object is `crm_opportunity`.

The predicate *value* changed shape too: `[['amount', '>', 50000]]` is a legal filter
AST at the platform level, but it is not assignable to `HookQuery['where']`, and no
`ctx.api` call site uses it — every predicate on that surface is written as an object.
So the example now does: `where: { amount: { $gt: 50000 } }`.

The rule is stated *scoped to `ctx.api`* on purpose, because two other surfaces spell
their predicate differently and an unscoped rule would have invited the next agent to
"fix" them:

- A flow node's `config` is a schema-unvalidated bag (`config: z.record(z.string(),
z.unknown())`) and `*.flow.ts` query/update nodes use `filter:` — 44 occurrences across
17 of the 21 flow files, `where:` in none.
- A page component config uses `filter:` in the AST-array form, the one spelling of that
form in `src/` (`src/pages/lead_detail.page.ts:217`).

Documentation only — no runtime, metadata, or dependency change.
22 changes: 20 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,27 @@ hotcrm/
- **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 via `reference_to` / `lookup` / `masterDetail` MUST use the prefixed name. Cube `sql:` fields, view `data.object`, hook `object:`, action `objectName:`, navigation `objectName:`, dashboard `object:`, translation `objects.{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.

2. **ObjectQL (No-SQL)**:
- Data access MUST use **ObjectQL**.
- Data access MUST use **ObjectQL**, reached through the `ctx.api` surface. There is
**no `broker`** in this repo — the identifier has zero occurrences in `src/`.
- **NEVER** write raw SQL.
- Format: `broker.find('opportunity', { filters: [['amount', '>', 50000]] })`.
- Format: `ctx.api.object('crm_opportunity').find({ where: { amount: { $gt: 50000 } } })`.
Object names carry the `crm_` prefix, same as rule 1. In a `*.hook.ts` the surface is
cast once — `const api = ctx.api as HookApi | undefined`, from `src/objects/_hook-api.ts`
— and then called as `api.object(...)`; action script bodies call `ctx.api.object(...)`
directly.
- On `ctx.api`, the predicate key is **`where`**, and only `where`. `filter` (canonical)
and `filters` (deprecated alias) are *HTTP query-param* spellings carrying a JSON
**string**, not keys of the in-process query object — passing either in process fails
**silently**: `findOne` spreads the query into the AST without aliasing and returns the
object's **first row**, `count` reads `query.where` only and counts the **whole object**.
Neither throws. This repo has already paid for that once — see
`.changeset/hook-query-where-not-filter.md`. `HookQuery` in `src/objects/_hook-api.ts`
deliberately omits the alias so the mistake is a compile error.
Other surfaces are **not** governed by this rule and have their own spelling: a
`*.flow.ts` node `config` takes `filter:` (44 occurrences across 17 of the 21 flow
files; `where:` in none), and a page component config takes `filter:` in the
AST-array form (`src/pages/lead_detail.page.ts:217`). Do not "fix" one surface's
spelling into another's.

3. **AI-Native**:
- Every feature should consider AI augmentation (Co-Pilot, Agents).
Expand Down
Loading