This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
EspoCRM 9.x with a custom Nexus module that integrates the NEXUS agentic platform. The Nexus module lives entirely in two places:
application/Espo/Modules/Nexus/— PHP backendclient/modules/nexus/— JS frontend (AMD views)
Everything else is upstream EspoCRM. Touch only the Nexus module unless fixing an EspoCRM bug that blocks NEXUS functionality.
# Full suite (requires built client/lib/ — run npm run build-dev first if stale)
npx jasmine-browser-runner runSpecs --config=frontend/test/jasmine-browser.jsonJS tests require client/lib/espo.js and client/lib/transpiled/ to be current. Run npm run build-dev if tests fail to load AMD modules.
php command.php rebuild # clear caches + reload metadata after PHP module changesEspoCRM uses a DI container (Espo\Core\Container) and constructor injection throughout. The request lifecycle:
index.php→Application.php→ router matchesroutes.json- Route dispatches to a Controller (
action*methods, e.g.actionHealth) - Controllers call Services for business logic
- Services access the DB via Repositories (ORM layer in
Espo\ORM) - Hooks fire on entity lifecycle events (afterSave, beforeSave, etc.)
- Jobs (in
Jobs/) run async via the scheduler
Metadata (field definitions, layouts, entity defs) lives in Resources/metadata/ JSON files inside each module. After adding/changing metadata, always run php command.php rebuild.
EspoCRM's frontend is a custom AMD module system built on Backbone. Key facts:
client/src/contains source JS/TS filesgrunt devtranspiles TS → JS intoclient/lib/transpiled/client/lib/espo.jsis the bundled loader + lib- Views extend
Espo.View(Backbone-based); loaded via AMDrequire() - Module paths use
moduleName:path/to/viewsyntax (e.g.nexus:views/panels/nexus-assistant) - AMD loader unwraps default exports: in
require([id], Cls => ...)callbacks,Clsis already the class — never use.default
application/Espo/Modules/Nexus/
├── Controllers/NexusGateway.php # API actions: health, settings, chat, submit, status, result
├── Services/
│ ├── AgentClient.php # HTTP client to NEXUS /chat
│ ├── QueueClient.php # HTTP client to NEXUS /submit + polling
│ ├── RagClient.php # HTTP client to NEXUS RAG ingestion
│ ├── NexusAuth.php # Shared auth header builder
│ └── NexusService.php # Orchestrates the above
├── Hooks/Common/AfterSave.php # Triggers RAG push on entity save
├── Jobs/QueuePoller.php # Scheduled job for async queue polling
└── Resources/
├── routes.json # /api/v1/nexus/* route definitions
├── module.json # order: 20
└── metadata/ # Admin panel field defs (adminDefs, etc.)
client/modules/nexus/src/views/
├── admin/nexus-settings.js # Admin settings panel view
└── panels/nexus-assistant.js # Inline chat panel on record detail views
Runtime config lives in data/config.php (PHP array, not committed but present locally). Nexus-specific keys: nexusUrl, nexusUsername, nexusPassword, nexusEnabled, nexusRagEnabled. The nexusEnabled check uses !== false — null/missing is treated as enabled.
PHP: PHPUnit with ContainerMocker helper (tests/unit/ContainerMocker.php) for dependency injection in unit tests. Test classes under tests/unit/Espo/Modules/Nexus/ mirror the module namespace.
JS: Jasmine specs in frontend/test/spec/test.*.js. Each spec loads its target via the AMD loader in a beforeAll block:
beforeAll(done => {
require(['nexus:views/panels/nexus-assistant'], ViewClass => {
NexusAssistantView = ViewClass;
done();
});
});Stub files in frontend/test/stubs/ provide minimal DOM/dependency mocks for views that need them. Register stubs in frontend/test/jasmine-browser.json under srcFiles before the module under test.