Skip to content

Commit 93092f8

Browse files
Add AGENTS.md for src/core (elastic#253465)
## Summary Add an `AGENTS.md` to `src/core` documenting sub-package naming conventions (`-server`, `-server-internal`, `-browser`, `-browser-internal`, `-mocks`, `common`), visibility rules, mock patterns, and top-level re-export structure. Intended as a concise reference for agents working in core. Hopefully speeds agent based work up a little bit and saves some tokens. ## Test plan - N/A — documentation only. Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 5a7bdf6 commit 93092f8

3 files changed

Lines changed: 168 additions & 156 deletions

File tree

src/core/AGENTS.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# src/core
2+
3+
Code lives in `src/core/packages/`, organized by domain (e.g., `http`, `elasticsearch`, `saved-objects`). Each domain contains sub-packages following a naming convention.
4+
5+
## Sub-package suffixes
6+
7+
| Suffix | Role | Visibility |
8+
|---|---|---|
9+
| `-server` | Public server-side types and contracts | `shared` |
10+
| `-server-internal` | Server-side implementation | `private` |
11+
| `-server-mocks` | Jest mocks for server contracts | `shared`, `devOnly` |
12+
| `-browser` | Public browser-side types and contracts | `shared` |
13+
| `-browser-internal` | Browser-side implementation | `private` |
14+
| `-browser-mocks` | Jest mocks for browser contracts | `shared`, `devOnly` |
15+
| `common` | Types/utilities shared across server and browser | `shared` |
16+
17+
- `-server` / `-browser` packages export **types and pure utilities only**. No implementation.
18+
- `-internal` packages contain the implementation. They depend on the public API package, never the reverse.
19+
- `-mocks` packages depend on the public API types to produce typed mock objects.
20+
21+
## Visibility
22+
23+
Each package declares `visibility` in its `kibana.jsonc`:
24+
25+
- **`shared`** — any package in Kibana may import it. Used for public API types (`-server`, `-browser`, `common`) and mocks (`-mocks`).
26+
- **`private`** — only other `src/core` packages may import it. Used for all `-internal` packages since they contain implementation details that should not leak to plugins.
27+
28+
## KEEP THIS FILE UP TO DATE
29+
30+
If you (agent working in `src/core`) notice a discrepancy between this document and the actual code while working in `src/core`, include a fix to this AGENTS.md as part of your changeset.
31+
32+
## Related docs
33+
34+
- `src/core/CONVENTIONS.md` — plugin structure, API design, and mock patterns

src/core/CONVENTIONS.md

Lines changed: 134 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
- [Usage Collection](#usage-collection)
1010
- [Saved Objects Types](#saved-objects-types)
1111
- [Naming conventions](#naming-conventions)
12+
- [Core API Conventions](#core-api-conventions)
13+
- [Exposing API Types](#1-exposing-api-types)
14+
- [API Structure and nesting](#2-api-structure-and-nesting)
15+
- [Tests and mocks](#3-tests-and-mocks)
1216

1317
## Organisational Conventions
1418
### Definition of done
@@ -40,16 +44,16 @@ All Kibana plugins built at Elastic should follow the same structure.
4044
my_plugin/
4145
├── kibana.json
4246
├── public
43-
   ├── applications
44-
   │   ├── my_app
45-
   │   │ └── index.ts
46-
   │   └── index.ts
47-
  ├── services
48-
   │   ├── my_service
49-
   │   │ └── index.ts
50-
   │   └── index.ts
51-
   ├── index.ts
52-
   └── plugin.ts
47+
├── applications
48+
├── my_app
49+
│ └── index.ts
50+
└── index.ts
51+
├── services
52+
├── my_service
53+
│ └── index.ts
54+
└── index.ts
55+
├── index.ts
56+
└── plugin.ts
5357
└── server
5458
├── routes
5559
│ └── index.ts
@@ -58,12 +62,12 @@ my_plugin/
5862
├── saved_objects
5963
│ ├── index.ts
6064
│ └── my_type.ts
61-
   ├── services
62-
   │   ├── my_service
63-
   │   │ └── index.ts
64-
   │   └── index.ts
65-
   ├── index.ts
66-
   └── plugin.ts
65+
├── services
66+
├── my_service
67+
│ └── index.ts
68+
└── index.ts
69+
├── index.ts
70+
└── plugin.ts
6771
```
6872
- [Manifest file](/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md) should be defined on top level.
6973
- Both `server` and `public` should have an `index.ts` and a `plugin.ts` file:
@@ -363,3 +367,117 @@ Migration example from the legacy format is available in `src/core/MIGRATION_EXA
363367

364368
Export start and setup contracts as `MyPluginStart` and `MyPluginSetup`.
365369
This avoids naming clashes, if everyone exported them simply as `Start` and `Setup`.
370+
371+
## Core API Conventions
372+
373+
The following conventions apply to development inside `src/core`. Although many
374+
may be more widely applicable, adoption within the rest of Kibana is not the
375+
primary objective.
376+
377+
### 1. Exposing API Types
378+
379+
The following applies to types that describe the entire surface area of Core
380+
APIs and does not apply to internal types.
381+
382+
- 1.1 All API types must be exported from the top-level `server` or `public`
383+
directories.
384+
385+
```ts
386+
// -- good --
387+
import { IRouter } from 'src/core/server';
388+
389+
// -- bad --
390+
import { IRouter } from 'src/core/server/http/router.ts';
391+
```
392+
393+
> Why? This is required for generating documentation from our inline
394+
> typescript doc comments, makes it easier for API consumers to find the
395+
> relevant types and creates a clear distinction between external and
396+
> internal types.
397+
398+
- 1.2 Classes must not be exposed directly. Instead, use a separate type,
399+
prefixed with an 'I', to describe the public contract of the class.
400+
401+
```ts
402+
// -- good (alternative 1) --
403+
/**
404+
* @public
405+
* {@link UiSettingsClient}
406+
*/
407+
export type IUiSettingsClient = PublicContractOf<UiSettingsClient>;
408+
409+
/** internal only */
410+
export class UiSettingsClient {
411+
constructor(private setting: string) {}
412+
/** Retrieve all settings */
413+
public getSettings(): { return this.settings; }
414+
};
415+
416+
// -- good (alternative 2) --
417+
export interface IUiSettingsClient {
418+
/** Retrieve all settings */
419+
public getSettings(): string;
420+
}
421+
422+
export class UiSettingsClient implements IUiSettingsClient {
423+
public getSettings(): string;
424+
}
425+
426+
// -- bad --
427+
/** external */
428+
export class UiSettingsClient {
429+
constructor(private setting: string) {}
430+
public getSettings(): { return this.settings; }
431+
}
432+
```
433+
434+
> Why? Classes' private members form part of their type signature making it
435+
> impossible to mock a dependency typed as a `class`.
436+
437+
### 2. API Structure and nesting
438+
439+
- 2.1 Nest API methods into their own namespace only if we expect we will be
440+
adding additional methods to that namespace.
441+
442+
```ts
443+
// good
444+
core.overlays.openFlyout(...);
445+
core.overlays.openModal(...);
446+
core.overlays.banners.add(...);
447+
core.overlays.banners.remove(...);
448+
core.overlays.banners.replace(...);
449+
450+
// bad
451+
core.overlays.flyouts.open(...);
452+
core.overlays.modals.open(...);
453+
```
454+
455+
> Why? Nested namespaces should facilitate discovery and navigation for
456+
> consumers of the API. Having namespaces with a single method, effectively
457+
> hides the method under an additional layer without improving the
458+
> organization. However, introducing namespaces early on can avoid API
459+
> churn when we know related API methods will be introduced.
460+
461+
### 3. Tests and mocks
462+
463+
- 3.1 Declare Jest mocks with a temporary variable to ensure types are
464+
correctly inferred.
465+
466+
```ts
467+
// -- good --
468+
const createMock = () => {
469+
const mocked: jest.Mocked<IContextService> = {
470+
start: jest.fn(),
471+
};
472+
mocked.start.mockReturnValue(createStartContractMock());
473+
return mocked;
474+
};
475+
// -- bad --
476+
const createMock = (): jest.Mocked<ContextServiceContract> => ({
477+
start: jest.fn().mockReturnValue(createSetupContractMock()),
478+
});
479+
```
480+
481+
> Why? Without the temporary variable, Jest types the `start` function as
482+
> `jest<any, any>` and, as a result, doesn't typecheck the mock return
483+
> value.

src/core/CORE_CONVENTIONS.md

Lines changed: 0 additions & 140 deletions
This file was deleted.

0 commit comments

Comments
 (0)