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
24 changes: 24 additions & 0 deletions .changeset/docs-retired-agent-cube-directories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'hotcrm': patch
---

Stop documenting directories that no longer exist, and guard the class. `#512`
deleted `src/agents/` when the AI surface went skills-only, but seven maintainer
docs kept printing `src/agents/*.agent.ts` in their tree diagrams and
registration tables — `code_examples.md` still told authors to "add its name to
an agent in `src/agents/*.agent.ts`" after registering a skill. `src/cubes/` had
the same shape: dropped in favour of datasets (ADR-0021, noted in
`objectstack.config.ts`), still drawn in two trees. Also removes the skill
`permissions: [...]` key from the worked example — `SkillSchema` has no such
field and silently strips it (#511) — and corrects a stale flow count (20 → 23)
and the `*.action.ts` suffix (the convention is `*.actions.ts`).

Fills the gap left behind: the skill example now states which two sources a
`tools` name can resolve to (platform data tools, or `action_<name>` from an
`ai.exposed` Action), why `defineTool` is not a third one, and where the guard
lives. `ARCHITECTURE.md` gains the same note plus the missing `case_triage`
skill.

Adds a repo-tree guard to `test/docs-drift.test.ts`: every `src/<dir>/` path a
maintainer doc names must exist on disk (`docs/archive/` excluded — it is a
historical record). It caught a stray reference in this change's own first pass.
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ hotcrm/
│ ├── 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 (*.action.ts)
│ ├── dashboards/ reports/ datasets/ cubes/ # Analytics metadata
│ ├── agents/ skills/ # AI agent + skill metadata
│ ├── 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)
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,10 @@ hotcrm/
├── src/
│ ├── objects/ # *.object.ts — data model (15 objects)
│ ├── actions/ # *.actions.ts — server actions + AI tools (13)
│ ├── flows/ # *.flow.ts — visual flows (20): screen, record-change & scheduled
│ ├── flows/ # *.flow.ts — visual flows (23): screen, record-change & scheduled
│ ├── hooks/ # hook registry barrel
│ ├── agents/ # *.agent.ts — AI copilots (2)
│ ├── skills/ # *.skill.ts — AI skills (6)
│ ├── cubes/ # *.cube.ts — analytics cubes
│ ├── skills/ # *.skill.ts — AI skills (6) — skills-only surface, no agents
│ ├── datasets/ # *.dataset.ts — analytics semantic layer (8)
│ ├── dashboards/, reports/ # analytics UI
│ ├── pages/, views/, apps/ # UI definitions
│ ├── profiles/, sharing/ # security
Expand Down
20 changes: 14 additions & 6 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,9 @@ Runtime capabilities are declared in `requires`: `ai`, `automation`, `triggers`,
| Lifecycle hooks | `src/objects/*.hook.ts` via `src/hooks/index.ts` | `hooks` |
| UI actions | `src/actions/*.actions.ts` | `actions` |
| Automation | `src/flows/*.flow.ts` | `flows` |
| AI agents | `src/agents/*.agent.ts` | `agents` |
| AI skills | `src/skills/*.skill.ts` | `skills` |
| Apps, views, pages | `src/apps/`, `src/views/`, `src/pages/` | `apps`, `views`, `pages` |
| Analytics | `src/cubes/`, `src/dashboards/`, `src/reports/` | `analyticsCubes`, `dashboards`, `reports` |
| Analytics | `src/datasets/`, `src/dashboards/`, `src/reports/` | `datasets`, `dashboards`, `reports` |
| Security | `src/profiles/`, `src/sharing/` | `permissions`, `sharingRules`, `roles` |
| i18n | `src/translations/` | `translations`, `i18n` |
| Demo data | `src/data/` | `data` |
Expand Down Expand Up @@ -127,15 +126,24 @@ The UI is metadata-driven. Object field definitions, views, pages, actions, perm

## AI

AI is modeled as ObjectStack metadata:
AI is modeled as ObjectStack metadata. The surface is **skills-only**: the two
`*.agent.ts` copilots were retired in [#512](https://github.com/objectstack-ai/hotcrm/pull/512),
because ADR-0063 §2 closed agent records to third parties and the runtime
refuses non-platform ones. The capability lives in the skills, which attach to
the platform assistant by surface affinity.

| Layer | Files | Examples |
| --- | --- | --- |
| Agents | `src/agents/*.agent.ts` | `sales_copilot`, `service_copilot` |
| Skills | `src/skills/*.skill.ts` | `live_data`, `lead_qualification`, `email_drafting`, `revenue_forecasting`, `customer_360` |
| Skills | `src/skills/*.skill.ts` | `live_data`, `lead_qualification`, `email_drafting`, `revenue_forecasting`, `case_triage`, `customer_360` |
| Actions and flows | `src/actions/`, `src/flows/` | lead conversion, case triage, alerts |

The Sales Copilot instructions explicitly require live schema inspection before answering record questions, because admins can change metadata over time.
Skills declare no bespoke tools (ADR-0109). They compose the platform's data
tools with the `action_<name>` tools the runtime materialises from Actions that
opt in via `ai.exposed` (ADR-0011) — every name a skill declares must resolve to
one of those, which `test/skills-integrity.test.ts` enforces.

The `live_data` skill explicitly requires live schema inspection before
answering record questions, because admins can change metadata over time.

## Security

Expand Down
3 changes: 1 addition & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ hotcrm/
│ ├── objects/ # ObjectSchema.create metadata, object lifecycle hooks
│ ├── actions/ # UI actions and AI-callable action bodies
│ ├── flows/ # ObjectStack automation flows
│ ├── agents/ # AI agent definitions
│ ├── skills/ # AI skills used by agents
│ ├── skills/ # AI skill definitions (skills-only surface)
│ ├── dashboards/ # Dashboard metadata
│ ├── reports/ # Report metadata
│ ├── views/, pages/ # App UI metadata
Expand Down
3 changes: 1 addition & 2 deletions docs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ pnpm verify
| Object hooks | `src/objects/*.hook.ts`, collected by `src/hooks/index.ts` |
| Actions | `src/actions/*.actions.ts` |
| Flows | `src/flows/*.flow.ts` |
| Agents | `src/agents/*.agent.ts` |
| Skills | `src/skills/*.skill.ts` |
| Skills | `src/skills/*.skill.ts` (skills-only AI surface since #512 — the agent directory is gone) |
| Views and pages | `src/views/`, `src/pages/` |
| Dashboards and reports | `src/dashboards/`, `src/reports/` |
| Security | `src/profiles/`, `src/sharing/` |
Expand Down
32 changes: 27 additions & 5 deletions docs/developers/code_examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> Examples that match the current single-app HotCRM repository.

HotCRM metadata is registered from `src/` through [`objectstack.config.ts`](../../objectstack.config.ts). File names use the ObjectStack suffix convention: `.object.ts`, `.hook.ts`, `.actions.ts`, `.flow.ts`, `.agent.ts`, `.skill.ts`, `.view.ts`, `.page.ts`, `.dashboard.ts`, and `.report.ts`.
HotCRM metadata is registered from `src/` through [`objectstack.config.ts`](../../objectstack.config.ts). File names use the ObjectStack suffix convention: `.object.ts`, `.hook.ts`, `.actions.ts`, `.flow.ts`, `.skill.ts`, `.view.ts`, `.page.ts`, `.dashboard.ts`, and `.report.ts`.

## Define An Object

Expand Down Expand Up @@ -188,7 +188,7 @@ export const WarrantyExpirationFlow: Flow = {

Export flows from `src/flows/index.ts`. Record-change flows require the `triggers` capability, which is already declared in `objectstack.config.ts`.

## Add An Agent Skill
## Add An AI Skill

File: `src/skills/example.skill.ts`

Expand All @@ -204,11 +204,34 @@ summarize status, identify expired or soon-expiring warranties, and
recommend the next action.`,
tools: ['describe_object', 'query_records'],
triggerPhrases: ['summarize warranties', 'warranty risk'],
permissions: ['crm:account:read'],
});
```

Register the skill in `src/skills/index.ts`, then add its name to an agent in `src/agents/*.agent.ts` if the agent should use it.
Register the skill in `src/skills/index.ts`. There is no agent to attach it to —
the AI surface is skills-only ([#512](https://github.com/objectstack-ai/hotcrm/pull/512)),
and a skill binds to the platform assistant through its `surface` affinity.

**Every name in `tools` must resolve to something that actually runs.** The
runtime silently drops a tool it cannot resolve, so an invented name leaves the
model with instructions describing a capability it does not have — the defect
[#493](https://github.com/objectstack-ai/hotcrm/issues/493) catalogued. Two
sources resolve, and only two:

- **Platform data tools** — `describe_object`, `list_objects`, `query_records`,
`get_record`, `aggregate_data`.
- **`action_<name>`** — materialised from an Action that opts in with
`ai: { exposed: true, description }` (ADR-0011, default off) *and* has a
headless path. See `ConvertLeadAction` in `src/actions/lead.actions.ts`.

Authoring a `defineTool` record does **not** create a third source: `ToolSchema`
is a read-only projection for Studio discovery with no `implementation` field
and no executor. Reasoning — scoring, drafting, forecasting — belongs in
`instructions`, not in a tool (ADR-0109). `test/skills-integrity.test.ts`
enforces all of this at PR time.

Note there is no `permissions` key on a skill — `SkillSchema` has no such field,
so one is silently stripped ([#511](https://github.com/objectstack-ai/hotcrm/pull/511)).
Gate access on the Actions the skill calls instead.

## Common Registration Points

Expand All @@ -218,7 +241,6 @@ Register the skill in `src/skills/index.ts`, then add its name to an agent in `s
| Hook | `src/objects/` | `src/hooks/index.ts` |
| Action | `src/actions/` | `src/actions/index.ts` |
| Flow | `src/flows/` | `src/flows/index.ts` |
| Agent | `src/agents/` | `src/agents/index.ts` |
| Skill | `src/skills/` | `src/skills/index.ts` |
| View | `src/views/` | `src/views/index.ts` |
| Page | `src/pages/` | `src/pages/index.ts` |
Expand Down
44 changes: 43 additions & 1 deletion test/docs-drift.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';

/**
Expand Down Expand Up @@ -143,3 +143,45 @@ describe('package docs do not drift from the flows they document', () => {
});
}
});

/**
* Repo-tree drift — a doc must not advertise a directory that is gone.
*
* `src/agents/` outlived its deletion by three PRs. #512 removed the two
* copilots and the whole directory, but seven maintainer docs kept printing
* `src/agents/*.agent.ts` in their tree diagrams and registration tables, so
* the next reader (human or agent) was told to put a file somewhere that does
* not exist. `src/cubes/` had the same shape: dropped in favour of datasets
* (ADR-0021, see the note in objectstack.config.ts), still drawn in two trees.
*
* Nothing checked, because a path in prose is just prose. This walks the
* maintainer docs, pulls every `src/<dir>/` they mention, and resolves it
* against the real tree. `docs/archive/` is deliberately excluded — it is a
* historical record and is allowed to describe a repo that no longer exists.
*/
const TREE_DOCS = [
'README.md',
'AGENTS.md',
'docs/README.md',
'docs/STATUS.md',
'docs/ARCHITECTURE.md',
'docs/MAINTENANCE.md',
'docs/DEPLOYMENT.md',
'docs/developers/code_examples.md',
'docs/developers/api_reference.md',
];

describe('maintainer docs do not point at directories that no longer exist', () => {
for (const docFile of TREE_DOCS) {
it(`${docFile}: every src/<dir>/ it names exists`, () => {
const text = readFileSync(join(docFile), 'utf8');
const named = new Set([...text.matchAll(/\bsrc\/([a-z][a-z0-9_]*)\//g)].map((m) => m[1]));
const missing = [...named].filter((dir) => !existsSync(join('src', dir)));
expect(
missing,
`${docFile} advertises src/ directories that do not exist: ${missing.join(', ')}. ` +
'Delete the reference (or restore the directory) — a path in prose is still a promise.',
).toEqual([]);
});
}
});
Loading