diff --git a/.changeset/docs-retired-agent-cube-directories.md b/.changeset/docs-retired-agent-cube-directories.md new file mode 100644 index 00000000..76425119 --- /dev/null +++ b/.changeset/docs-retired-agent-cube-directories.md @@ -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_` 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//` 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. diff --git a/AGENTS.md b/AGENTS.md index 55e85309..60e2f2f4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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) diff --git a/README.md b/README.md index 198a29e1..0799d67f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index bf9c597d..d278596f 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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` | @@ -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_` 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 diff --git a/docs/README.md b/docs/README.md index 0810acc4..a1f8f07a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 diff --git a/docs/STATUS.md b/docs/STATUS.md index 5c66ea0d..2a8f8562 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -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/` | diff --git a/docs/developers/code_examples.md b/docs/developers/code_examples.md index 04899667..24531d1f 100644 --- a/docs/developers/code_examples.md +++ b/docs/developers/code_examples.md @@ -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 @@ -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` @@ -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_`** — 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 @@ -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` | diff --git a/test/docs-drift.test.ts b/test/docs-drift.test.ts index ed335c76..66f36aa6 100644 --- a/test/docs-drift.test.ts +++ b/test/docs-drift.test.ts @@ -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'; /** @@ -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//` 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// 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([]); + }); + } +});