@eventcatalog/core 5.8.0 → 5.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-E636DOFG.js → chunk-3PWCUWQC.js} +1 -1
- package/dist/{chunk-DT63JMNP.js → chunk-A3256YCN.js} +3 -2
- package/dist/{chunk-WLXXZTLD.js → chunk-JDSFD7IC.js} +1 -1
- package/dist/{chunk-YU6FEGOK.js → chunk-KGFAIGOT.js} +1 -1
- package/dist/{chunk-Z6UUCTIB.js → chunk-YBM7MCOY.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/docs/api/02-config.md +1 -1
- package/dist/docs/development/00-why-eventcatalog.md +15 -8
- package/dist/docs/development/01-adopting-eventcatalog.md +51 -0
- package/dist/docs/development/{ask-your-architecture/03-install-skills.md → 01-getting-started/install-skills.md} +10 -4
- package/dist/docs/development/01-getting-started/installation.md +5 -1
- package/dist/docs/development/ask-your-architecture/01-intro.md +21 -6
- package/dist/docs/development/ask-your-architecture/03-mcp-server/getting-started.md +17 -3
- package/dist/docs/development/ask-your-architecture/03-mcp-server/introduction.md +3 -0
- package/dist/docs/development/ask-your-architecture/06-sync-docs-with-your-code.md +227 -0
- package/dist/docs/development/ask-your-architecture/07-catch-breaking-changes.md +149 -0
- package/dist/docs/development/ask-your-architecture/08-check-code-against-adrs.md +169 -0
- package/dist/docs/development/components/custom-components/02-create-your-first-component.md +4 -0
- package/dist/docs/development/customization/customize-visualizer/00-visualizer-nodes.md +7 -5
- package/dist/docs/development/developer-tools/api-catalog.md +1 -1
- package/dist/docs/development/{ask-your-architecture/02-llms.txt.md → developer-tools/llms.txt.md} +2 -0
- package/dist/docs/development/{ask-your-architecture/03-schemas.txt.md → developer-tools/schemas.txt.md} +1 -1
- package/dist/docs/development/developer-tools/ubiquitous-language.txt.md +42 -0
- package/dist/docs/development/guides/domains/03-ownership-and-language/02-adding-ubiquitous-language.md +3 -0
- package/dist/docs/development/guides/resources/adrs/01-introduction.md +1 -0
- package/dist/eventcatalog.cjs +6 -5
- package/dist/eventcatalog.js +8 -8
- package/dist/generate.cjs +3 -2
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/auth.config.ts +7 -9
- package/eventcatalog/src/components/Settings/LlmAccessSettingsForm.tsx +23 -3
- package/eventcatalog/src/features/ai/chat-api.ts +2 -4
- package/eventcatalog/src/features/auth/login.astro +2 -4
- package/eventcatalog/src/features/auth/middleware/middleware-auth.ts +1 -0
- package/eventcatalog/src/features/auth/middleware/middleware.ts +2 -3
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +1 -1
- package/eventcatalog/src/features/mcp/mcp-scoped-tools.ts +7 -4
- package/eventcatalog/src/features/mcp/mcp-server.ts +15 -2
- package/eventcatalog/src/features/tools/catalog-tools.ts +97 -30
- package/eventcatalog/src/pages/docs/llm/llms.txt.ts +24 -17
- package/eventcatalog/src/pages/docs/llm/schemas.txt.ts +1 -1
- package/eventcatalog/src/pages/docs/llm/ubiquitous-language.txt.ts +68 -0
- package/eventcatalog/src/pages/settings/llm-access.astro +2 -0
- package/eventcatalog/src/utils/catalog-modules.ts +16 -0
- package/eventcatalog/src/utils/collections/domains.ts +84 -0
- package/package.json +2 -2
- package/dist/docs/development/ask-your-architecture/04-agents/01-overview.md +0 -27
- package/dist/docs/development/ask-your-architecture/04-agents/02-code-to-docs.md +0 -124
- package/dist/docs/development/ask-your-architecture/04-agents/03-breaking-changes.md +0 -170
- package/dist/docs/development/ask-your-architecture/04-agents/_category_.json +0 -12
|
@@ -11,7 +11,12 @@ import {
|
|
|
11
11
|
processSpecifications,
|
|
12
12
|
versionMatches,
|
|
13
13
|
} from '@utils/collections/util';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
getDomains,
|
|
16
|
+
getTermsDefinedInSeveralDomains,
|
|
17
|
+
getUbiquitousLanguageGlossary,
|
|
18
|
+
getUbiquitousLanguageWithSubdomains,
|
|
19
|
+
} from '@utils/collections/domains';
|
|
15
20
|
import { getAbsoluteFilePathForAstroFile } from '@utils/files';
|
|
16
21
|
import fs from 'node:fs';
|
|
17
22
|
import { getArchitectureDiagramGraph } from '@utils/node-graphs/architecture-diagram';
|
|
@@ -910,12 +915,71 @@ export async function findMessageBySchemaId(params: {
|
|
|
910
915
|
// Ubiquitous Language (DDD)
|
|
911
916
|
// ============================================
|
|
912
917
|
|
|
918
|
+
const toUbiquitousLanguageTermResult = (
|
|
919
|
+
term: any,
|
|
920
|
+
domain: { id: string; name: string },
|
|
921
|
+
isDuplicate: boolean,
|
|
922
|
+
parentDomainId?: string
|
|
923
|
+
) => ({
|
|
924
|
+
id: term.id,
|
|
925
|
+
term: term.name,
|
|
926
|
+
summary: term.summary,
|
|
927
|
+
description: term.description || term.summary,
|
|
928
|
+
icon: term.icon,
|
|
929
|
+
domainId: domain.id,
|
|
930
|
+
domainName: domain.name,
|
|
931
|
+
...(parentDomainId ? { isSubdomain: true, parentDomainId } : {}),
|
|
932
|
+
isDuplicate,
|
|
933
|
+
});
|
|
934
|
+
|
|
913
935
|
/**
|
|
914
|
-
* Get ubiquitous language terms
|
|
915
|
-
*
|
|
936
|
+
* Get ubiquitous language terms, optionally filtered to the terms matching `term`.
|
|
937
|
+
* With a domainId, returns that domain's terms and its subdomains' terms.
|
|
938
|
+
* Without one, returns the terms of every domain (paginated), keeping each domain's meaning of a term separate.
|
|
916
939
|
*/
|
|
917
|
-
export async function explainUbiquitousLanguageTerms(params: {
|
|
918
|
-
|
|
940
|
+
export async function explainUbiquitousLanguageTerms(params: {
|
|
941
|
+
domainId?: string;
|
|
942
|
+
domainVersion?: string;
|
|
943
|
+
term?: string;
|
|
944
|
+
cursor?: string;
|
|
945
|
+
}) {
|
|
946
|
+
const search = params.term?.trim().toLowerCase();
|
|
947
|
+
const matchesSearch = (term: { id: string; term: string }) =>
|
|
948
|
+
!search || term.term.toLowerCase().includes(search) || term.id.toLowerCase().includes(search);
|
|
949
|
+
|
|
950
|
+
if (!params.domainId) {
|
|
951
|
+
const glossary = await getUbiquitousLanguageGlossary();
|
|
952
|
+
const definedInSeveralDomains = getTermsDefinedInSeveralDomains(glossary);
|
|
953
|
+
const isDuplicate = (term: { name: string }) => definedInSeveralDomains.has(term.name.toLowerCase());
|
|
954
|
+
|
|
955
|
+
const terms = glossary
|
|
956
|
+
.flatMap((domain) => [
|
|
957
|
+
...domain.terms.map((term) => toUbiquitousLanguageTermResult(term, domain, isDuplicate(term))),
|
|
958
|
+
...domain.subdomains.flatMap((subdomain) =>
|
|
959
|
+
subdomain.terms.map((term) => toUbiquitousLanguageTermResult(term, subdomain, isDuplicate(term), subdomain.parentId))
|
|
960
|
+
),
|
|
961
|
+
])
|
|
962
|
+
.filter(matchesSearch);
|
|
963
|
+
|
|
964
|
+
const page = paginate(terms, params.cursor);
|
|
965
|
+
if ('error' in page) return page;
|
|
966
|
+
|
|
967
|
+
return {
|
|
968
|
+
terms: page.items,
|
|
969
|
+
nextCursor: page.nextCursor,
|
|
970
|
+
totalCount: page.totalCount,
|
|
971
|
+
duplicateTerms: Array.from(new Set(page.items.filter((term) => term.isDuplicate).map((term) => term.term.toLowerCase()))),
|
|
972
|
+
message:
|
|
973
|
+
terms.length === 0
|
|
974
|
+
? search
|
|
975
|
+
? `No ubiquitous language terms match "${params.term}"`
|
|
976
|
+
: 'No ubiquitous language terms are defined in this catalog'
|
|
977
|
+
: undefined,
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// getDomains resolves subdomains to their entries, which is what finds the subdomains' terms
|
|
982
|
+
const domains = await getDomains({ getAllVersions: true });
|
|
919
983
|
|
|
920
984
|
// Use the existing utility to find the domain by id and version (or latest)
|
|
921
985
|
const matches = getItemsFromCollectionByIdAndSemverOrLatest(domains, params.domainId, params.domainVersion);
|
|
@@ -930,31 +994,32 @@ export async function explainUbiquitousLanguageTerms(params: { domainId: string;
|
|
|
930
994
|
// Get ubiquitous language including subdomains
|
|
931
995
|
const ubiquitousLanguageData = await getUbiquitousLanguageWithSubdomains(domain as any);
|
|
932
996
|
const { domain: domainUL, subdomains, duplicateTerms } = ubiquitousLanguageData;
|
|
997
|
+
const domainName = (domain.data as any).name || params.domainId;
|
|
933
998
|
|
|
934
999
|
// Extract terms from domain's ubiquitous language
|
|
935
|
-
const domainTerms =
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1000
|
+
const domainTerms = (domainUL?.data?.dictionary || [])
|
|
1001
|
+
.map((term: any) =>
|
|
1002
|
+
toUbiquitousLanguageTermResult(
|
|
1003
|
+
term,
|
|
1004
|
+
{ id: params.domainId!, name: domainName },
|
|
1005
|
+
duplicateTerms.has(term.name.toLowerCase())
|
|
1006
|
+
)
|
|
1007
|
+
)
|
|
1008
|
+
.filter(matchesSearch);
|
|
944
1009
|
|
|
945
1010
|
// Extract terms from subdomains
|
|
946
|
-
const subdomainTerms = subdomains
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1011
|
+
const subdomainTerms = subdomains
|
|
1012
|
+
.flatMap(({ subdomain, ubiquitousLanguage }) =>
|
|
1013
|
+
(ubiquitousLanguage?.data?.dictionary || []).map((term: any) =>
|
|
1014
|
+
toUbiquitousLanguageTermResult(
|
|
1015
|
+
term,
|
|
1016
|
+
{ id: (subdomain.data as any).id, name: (subdomain.data as any).name || (subdomain.data as any).id },
|
|
1017
|
+
duplicateTerms.has(term.name.toLowerCase()),
|
|
1018
|
+
params.domainId
|
|
1019
|
+
)
|
|
1020
|
+
)
|
|
1021
|
+
)
|
|
1022
|
+
.filter(matchesSearch);
|
|
958
1023
|
|
|
959
1024
|
const allTerms = [...domainTerms, ...subdomainTerms];
|
|
960
1025
|
|
|
@@ -962,8 +1027,10 @@ export async function explainUbiquitousLanguageTerms(params: { domainId: string;
|
|
|
962
1027
|
return {
|
|
963
1028
|
domainId: params.domainId,
|
|
964
1029
|
domainVersion: (domain.data as any).version,
|
|
965
|
-
domainName
|
|
966
|
-
message:
|
|
1030
|
+
domainName,
|
|
1031
|
+
message: search
|
|
1032
|
+
? `No ubiquitous language terms match "${params.term}" in this domain or its subdomains`
|
|
1033
|
+
: 'No ubiquitous language terms defined for this domain or its subdomains',
|
|
967
1034
|
terms: [],
|
|
968
1035
|
subdomainCount: subdomains.length,
|
|
969
1036
|
};
|
|
@@ -972,7 +1039,7 @@ export async function explainUbiquitousLanguageTerms(params: { domainId: string;
|
|
|
972
1039
|
return {
|
|
973
1040
|
domainId: params.domainId,
|
|
974
1041
|
domainVersion: (domain.data as any).version,
|
|
975
|
-
domainName
|
|
1042
|
+
domainName,
|
|
976
1043
|
terms: allTerms,
|
|
977
1044
|
totalCount: allTerms.length,
|
|
978
1045
|
domainTermCount: domainTerms.length,
|
|
@@ -1359,7 +1426,7 @@ export const toolDescriptions = {
|
|
|
1359
1426
|
findMessageBySchemaId:
|
|
1360
1427
|
'Use this tool when a user shares a schema file (Avro, JSON Schema, Protobuf) and wants to find it in EventCatalog. Look for "x-eventcatalog-id" and "x-eventcatalog-version" in the schema - these may be properties in the schema OR in comments (e.g. // x-eventcatalog-id: OrderCreated). Pass the id as messageId. If version exists, pass it as messageVersion, otherwise omit it to get the latest version. Returns the message resource along with its producers and consumers.',
|
|
1361
1428
|
explainUbiquitousLanguageTerms:
|
|
1362
|
-
|
|
1429
|
+
"Use this tool to find out what a business term means, or to get the ubiquitous language (Domain-Driven Design glossary) of a domain or of every domain. Pass domainId for a domain's terms and its subdomains' terms, term to find a term by name, or neither for the whole glossary. The same term can mean different things in different domains: each domain's meaning is returned separately and duplicates are flagged.",
|
|
1363
1430
|
getDataProductInputs:
|
|
1364
1431
|
'Use this tool to get the inputs (resources consumed) for a data product. Returns fully hydrated input resources with their id, version, name, summary, and collection type.',
|
|
1365
1432
|
getDataProductOutputs:
|
|
@@ -3,7 +3,7 @@ import config from '@config';
|
|
|
3
3
|
import type { APIRoute } from 'astro';
|
|
4
4
|
|
|
5
5
|
import { isLLMSTxtEnabled } from '@utils/feature';
|
|
6
|
-
import { getUbiquitousLanguage } from '@utils/collections/domains';
|
|
6
|
+
import { getTermSummary, getUbiquitousLanguage } from '@utils/collections/domains';
|
|
7
7
|
import { getResourceDocs } from '@utils/collections/resource-docs';
|
|
8
8
|
|
|
9
9
|
const events = await getCollection('events');
|
|
@@ -32,8 +32,8 @@ const ubiquitousLanguages: Record<string, { id: string; version: string; propert
|
|
|
32
32
|
for (const domain of domains) {
|
|
33
33
|
const ubiquitousLanguagesForDomain = await getUbiquitousLanguage(domain);
|
|
34
34
|
if (ubiquitousLanguagesForDomain.length > 0) {
|
|
35
|
-
ubiquitousLanguages[domain.id] = ubiquitousLanguagesForDomain.map((item) => ({
|
|
36
|
-
id: domain.id,
|
|
35
|
+
ubiquitousLanguages[domain.data.id] = ubiquitousLanguagesForDomain.map((item) => ({
|
|
36
|
+
id: domain.data.id,
|
|
37
37
|
version: domain.data.version,
|
|
38
38
|
properties: item.data.dictionary,
|
|
39
39
|
}));
|
|
@@ -44,15 +44,15 @@ for (const domain of domains) {
|
|
|
44
44
|
const renderUbiquitousLanguages = (baseUrl: string) => {
|
|
45
45
|
return Object.entries(ubiquitousLanguages)
|
|
46
46
|
.map(([domainId, items]) => {
|
|
47
|
-
const domainName = domains.find((domain) => domain.id === domainId)?.data.name || domainId;
|
|
47
|
+
const domainName = domains.find((domain) => domain.data.id === domainId)?.data.name || domainId;
|
|
48
48
|
const itemsList = items
|
|
49
49
|
.map((item) => {
|
|
50
50
|
// @ts-ignore
|
|
51
51
|
const propertiesList = Object.entries(item.properties)
|
|
52
|
-
.map(
|
|
53
|
-
(
|
|
54
|
-
|
|
55
|
-
)
|
|
52
|
+
.map(([key, value]: any) => {
|
|
53
|
+
const summary = getTermSummary(value);
|
|
54
|
+
return ` - [${value.name}${summary ? `: - ${summary}` : ''}](${baseUrl}/docs/domains/${domainId}/language.mdx)`;
|
|
55
|
+
})
|
|
56
56
|
.join('\n');
|
|
57
57
|
return propertiesList;
|
|
58
58
|
})
|
|
@@ -93,7 +93,8 @@ export const GET: APIRoute = async ({ params, request }) => {
|
|
|
93
93
|
|
|
94
94
|
const formatVersionedItem = (item: any, type: string, extraParams?: string | string[]) => {
|
|
95
95
|
const params = Array.isArray(extraParams) ? extraParams.join('&') : extraParams || '';
|
|
96
|
-
|
|
96
|
+
const summary = item.data.summary?.trim();
|
|
97
|
+
return `- [${item.data.name} - ${item.data.id} - ${item.data.version} ${params ? `- ${params}` : ''}](${baseUrl}/docs/${type}/${item.data.id}/${item.data.version}.mdx) ${summary ? `- ${summary}` : ''}`;
|
|
97
98
|
};
|
|
98
99
|
|
|
99
100
|
const formatSimpleItem = (item: any, type: string) =>
|
|
@@ -136,19 +137,19 @@ export const GET: APIRoute = async ({ params, request }) => {
|
|
|
136
137
|
`# ${config.organizationName} EventCatalog Documentation\n`,
|
|
137
138
|
`> ${config.tagline}\n`,
|
|
138
139
|
'## Events',
|
|
139
|
-
events.map((item) => formatVersionedItem(item, 'events')).join(''),
|
|
140
|
+
events.map((item) => formatVersionedItem(item, 'events')).join('\n'),
|
|
140
141
|
'\n## Commands',
|
|
141
|
-
commands.map((item) => formatVersionedItem(item, 'commands')).join(''),
|
|
142
|
+
commands.map((item) => formatVersionedItem(item, 'commands')).join('\n'),
|
|
142
143
|
'\n## Queries',
|
|
143
|
-
queries.map((item) => formatVersionedItem(item, 'queries')).join(''),
|
|
144
|
+
queries.map((item) => formatVersionedItem(item, 'queries')).join('\n'),
|
|
144
145
|
'\n## Agents',
|
|
145
|
-
agents.map((item) => formatVersionedItem(item, 'agents')).join(''),
|
|
146
|
+
agents.map((item) => formatVersionedItem(item, 'agents')).join('\n'),
|
|
146
147
|
'\n## Services',
|
|
147
|
-
services.map((item) => formatVersionedItem(item, 'services')).join(''),
|
|
148
|
+
services.map((item) => formatVersionedItem(item, 'services')).join('\n'),
|
|
148
149
|
'\n## Data Products',
|
|
149
150
|
dataProducts.map((item) => formatVersionedItem(item, 'data-products')).join('\n'),
|
|
150
151
|
'\n## Domains',
|
|
151
|
-
domains.map((item) => formatVersionedItem(item, 'domains')).join(''),
|
|
152
|
+
domains.map((item) => formatVersionedItem(item, 'domains')).join('\n'),
|
|
152
153
|
'\n## Flows',
|
|
153
154
|
flows.map((item) => formatVersionedItem(item, 'flows')).join('\n'),
|
|
154
155
|
'\n## Channels',
|
|
@@ -156,8 +157,14 @@ export const GET: APIRoute = async ({ params, request }) => {
|
|
|
156
157
|
.map((item) =>
|
|
157
158
|
formatVersionedItem(item, 'channels', item.data.protocols?.map((protocol) => `protocol - ${protocol}`).join('&'))
|
|
158
159
|
)
|
|
159
|
-
.join(''),
|
|
160
|
-
...(Object.keys(ubiquitousLanguages).length > 0
|
|
160
|
+
.join('\n'),
|
|
161
|
+
...(Object.keys(ubiquitousLanguages).length > 0
|
|
162
|
+
? [
|
|
163
|
+
'\n## Ubiquitous Language',
|
|
164
|
+
`The whole glossary, with terms defined in more than one domain listed together: ${baseUrl}/docs/llm/ubiquitous-language.txt`,
|
|
165
|
+
renderUbiquitousLanguages(baseUrl),
|
|
166
|
+
]
|
|
167
|
+
: []),
|
|
161
168
|
'\n## Containers (Databases, External Systems)',
|
|
162
169
|
containers.map((item) => formatVersionedItem(item, 'containers')).join('\n'),
|
|
163
170
|
'\n## Entities',
|
|
@@ -52,7 +52,7 @@ export const GET: APIRoute = async ({ params, request }) => {
|
|
|
52
52
|
|
|
53
53
|
const formatVersionedItem = (item: (typeof schemas)[number]) => {
|
|
54
54
|
const message = item.data.message;
|
|
55
|
-
return `- [${message.name || message.id} - ${message.id} - ${message.version}](${baseUrl}/api/schemas/${message.collectionName}/${message.id}/${message.version})
|
|
55
|
+
return `- [${message.name || message.id} - ${message.id} - ${message.version}](${baseUrl}/api/schemas/${message.collectionName}/${message.id}/${message.version}) ${message.summary ? `- ${message.summary.trim()}` : ''}`;
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
const formatServiceWithSchema = (item: ServiceWithSchema) => {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import config from '@config';
|
|
2
|
+
import type { APIRoute } from 'astro';
|
|
3
|
+
import { isLLMSTxtEnabled } from '@utils/feature';
|
|
4
|
+
import {
|
|
5
|
+
getTermsDefinedInSeveralDomains,
|
|
6
|
+
getTermSummary,
|
|
7
|
+
getUbiquitousLanguageGlossary,
|
|
8
|
+
type DomainGlossary,
|
|
9
|
+
type UbiquitousLanguageTerm,
|
|
10
|
+
} from '@utils/collections/domains';
|
|
11
|
+
|
|
12
|
+
const formatTerm = (term: UbiquitousLanguageTerm) => {
|
|
13
|
+
const summary = getTermSummary(term);
|
|
14
|
+
return `- ${term.name}${summary ? `: ${summary}` : ''}`;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const GET: APIRoute = async ({ request }) => {
|
|
18
|
+
if (!isLLMSTxtEnabled()) {
|
|
19
|
+
return new Response('ubiquitous-language.txt is not enabled for this Catalog.', { status: 404 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const url = new URL(request.url);
|
|
23
|
+
const baseUrl = process.env.LLMS_TXT_BASE_URL || `${url.origin}`;
|
|
24
|
+
|
|
25
|
+
const glossary = await getUbiquitousLanguageGlossary();
|
|
26
|
+
|
|
27
|
+
const header = [
|
|
28
|
+
`# ${config.organizationName} Ubiquitous Language`,
|
|
29
|
+
'> The business terms used across this organization, grouped by domain. A term only applies within its domain: the same word can mean something different in another domain.',
|
|
30
|
+
].join('\n');
|
|
31
|
+
|
|
32
|
+
if (glossary.length === 0) {
|
|
33
|
+
return new Response(`${header}\n\nNo ubiquitous language terms are defined in this catalog.`, {
|
|
34
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const formatDomain = (domain: DomainGlossary, heading: string) =>
|
|
39
|
+
[
|
|
40
|
+
`${heading} ${domain.name}`,
|
|
41
|
+
...(domain.terms.length > 0
|
|
42
|
+
? [`Full definitions: ${baseUrl}/docs/domains/${domain.id}/language.mdx`, ...domain.terms.map(formatTerm)]
|
|
43
|
+
: []),
|
|
44
|
+
].join('\n');
|
|
45
|
+
|
|
46
|
+
const termsInSeveralDomains = [...getTermsDefinedInSeveralDomains(glossary).values()].map((definitions) =>
|
|
47
|
+
[
|
|
48
|
+
`- ${definitions[0].term.name}`,
|
|
49
|
+
...definitions.map(({ domain, term }) => {
|
|
50
|
+
const summary = getTermSummary(term);
|
|
51
|
+
return ` - ${domain.name}${summary ? `: ${summary}` : ''}`;
|
|
52
|
+
}),
|
|
53
|
+
].join('\n')
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const sections = [
|
|
57
|
+
header,
|
|
58
|
+
...(termsInSeveralDomains.length > 0 ? [`## Terms defined in more than one domain\n${termsInSeveralDomains.join('\n')}`] : []),
|
|
59
|
+
...glossary.flatMap((domain) => [
|
|
60
|
+
formatDomain(domain, '##'),
|
|
61
|
+
...domain.subdomains.map((subdomain) => formatDomain(subdomain, '###')),
|
|
62
|
+
]),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
return new Response(sections.join('\n\n'), {
|
|
66
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
67
|
+
});
|
|
68
|
+
};
|
|
@@ -16,6 +16,7 @@ const apiBase = buildUrl('/api/settings');
|
|
|
16
16
|
const llmsTxtUrl = buildUrl('/llms.txt');
|
|
17
17
|
const llmsFullTxtUrl = buildUrl('/llms-full.txt');
|
|
18
18
|
const schemasTxtUrl = buildUrl('/docs/llm/schemas.txt');
|
|
19
|
+
const ubiquitousLanguageTxtUrl = buildUrl('/docs/llm/ubiquitous-language.txt');
|
|
19
20
|
---
|
|
20
21
|
|
|
21
22
|
<SettingsLayout title="LLM Access" active="llm-access">
|
|
@@ -27,5 +28,6 @@ const schemasTxtUrl = buildUrl('/docs/llm/schemas.txt');
|
|
|
27
28
|
llmsTxtUrl={llmsTxtUrl}
|
|
28
29
|
llmsFullTxtUrl={llmsFullTxtUrl}
|
|
29
30
|
schemasTxtUrl={schemasTxtUrl}
|
|
31
|
+
ubiquitousLanguageTxtUrl={ubiquitousLanguageTxtUrl}
|
|
30
32
|
/>
|
|
31
33
|
</SettingsLayout>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The file:// URL of a file in the catalog. Node's ES module loader only takes URLs: on Windows an absolute path
|
|
6
|
+
* (C:\catalog\eventcatalog.auth.js) reads as a URL with the scheme "c:", and the import fails.
|
|
7
|
+
*/
|
|
8
|
+
export const catalogModuleUrl = (file: string, directory = process.env.PROJECT_DIR || process.cwd()) =>
|
|
9
|
+
pathToFileURL(join(directory, file)).href;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Imports a module from the catalog (eventcatalog.auth.js, eventcatalog.chat.js, middleware.ts), at runtime rather
|
|
13
|
+
* than bundled into the build, so the catalog's own file is the one that runs.
|
|
14
|
+
*/
|
|
15
|
+
export const importCatalogModule = (file: string, directory?: string) =>
|
|
16
|
+
import(/* @vite-ignore */ catalogModuleUrl(file, directory));
|
|
@@ -436,6 +436,90 @@ export const getRootDomains = async (): Promise<Domain[]> => {
|
|
|
436
436
|
return domains.filter((d) => !allSubDomains.some((sd) => sd.data.id === d.data.id));
|
|
437
437
|
};
|
|
438
438
|
|
|
439
|
+
export type UbiquitousLanguageTerm = NonNullable<UbiquitousLanguage['data']['dictionary']>[number];
|
|
440
|
+
|
|
441
|
+
export type DomainGlossary = {
|
|
442
|
+
id: string;
|
|
443
|
+
version: string;
|
|
444
|
+
name: string;
|
|
445
|
+
terms: UbiquitousLanguageTerm[];
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
export type SubdomainGlossary = DomainGlossary & { parentId: string };
|
|
449
|
+
|
|
450
|
+
export type OrganizationGlossaryDomain = DomainGlossary & { subdomains: SubdomainGlossary[] };
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* The ubiquitous language of the whole catalog: every domain with terms (or with a subdomain that has
|
|
454
|
+
* them), with all of its subdomains, at any depth, listed under it. Terms stay with their domain and are
|
|
455
|
+
* never merged, because the same word can mean something different in another domain.
|
|
456
|
+
*/
|
|
457
|
+
export const getUbiquitousLanguageGlossary = async (): Promise<OrganizationGlossaryDomain[]> => {
|
|
458
|
+
const [domains, rootDomains, ubiquitousLanguages] = await Promise.all([
|
|
459
|
+
getDomains({ getAllVersions: false }),
|
|
460
|
+
getRootDomains(),
|
|
461
|
+
getCollection('ubiquitousLanguages'),
|
|
462
|
+
]);
|
|
463
|
+
const domainsById = new Map(domains.map((domain) => [domain.data.id, domain]));
|
|
464
|
+
|
|
465
|
+
const toGlossary = (domain: Domain): DomainGlossary => ({
|
|
466
|
+
id: domain.data.id,
|
|
467
|
+
version: domain.data.version,
|
|
468
|
+
name: domain.data.name || domain.data.id,
|
|
469
|
+
terms: getUbiquitousLanguageFromCollection(domain, ubiquitousLanguages).flatMap(
|
|
470
|
+
(ubiquitousLanguage) => ubiquitousLanguage.data.dictionary || []
|
|
471
|
+
),
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Each domain is only listed once, whether several domains share it or domains point at each other
|
|
475
|
+
const listedDomains = new Set<string>();
|
|
476
|
+
|
|
477
|
+
const getDescendants = (parent: Domain): SubdomainGlossary[] =>
|
|
478
|
+
((parent.data.domains as unknown as Domain[]) || []).flatMap((subdomain) => {
|
|
479
|
+
if (listedDomains.has(subdomain.data.id)) return [];
|
|
480
|
+
listedDomains.add(subdomain.data.id);
|
|
481
|
+
|
|
482
|
+
// Use the current version even when the parent pins an old one: only current versions have glossaries,
|
|
483
|
+
// and their own subdomains resolved
|
|
484
|
+
const currentSubdomain = domainsById.get(subdomain.data.id) || subdomain;
|
|
485
|
+
|
|
486
|
+
return [{ ...toGlossary(currentSubdomain), parentId: parent.data.id }, ...getDescendants(currentSubdomain)];
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Root domains go first. Any domain still not listed after them points in a loop, so has no root.
|
|
490
|
+
return [...rootDomains, ...domains]
|
|
491
|
+
.flatMap((domain) => {
|
|
492
|
+
if (listedDomains.has(domain.data.id)) return [];
|
|
493
|
+
listedDomains.add(domain.data.id);
|
|
494
|
+
|
|
495
|
+
return [
|
|
496
|
+
{
|
|
497
|
+
...toGlossary(domain),
|
|
498
|
+
subdomains: getDescendants(domain).filter((subdomain) => subdomain.terms.length > 0),
|
|
499
|
+
},
|
|
500
|
+
];
|
|
501
|
+
})
|
|
502
|
+
.filter((domain) => domain.terms.length > 0 || domain.subdomains.length > 0);
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
/** The terms defined in more than one domain, keyed by their lowercase name */
|
|
506
|
+
export const getTermsDefinedInSeveralDomains = (glossary: OrganizationGlossaryDomain[]) => {
|
|
507
|
+
const definitions = new Map<string, { domain: DomainGlossary; term: UbiquitousLanguageTerm }[]>();
|
|
508
|
+
|
|
509
|
+
for (const domain of glossary.flatMap((domain) => [domain, ...domain.subdomains])) {
|
|
510
|
+
for (const term of domain.terms) {
|
|
511
|
+
const key = term.name.toLowerCase();
|
|
512
|
+
definitions.set(key, [...(definitions.get(key) || []), { domain, term }]);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return new Map([...definitions].filter(([, termDefinitions]) => new Set(termDefinitions.map((d) => d.domain.id)).size > 1));
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
/** A term's definition on one line, for the text files we generate for LLMs */
|
|
520
|
+
export const getTermSummary = (term: UbiquitousLanguageTerm) =>
|
|
521
|
+
(term.summary || term.description || '').replace(/\s+/g, ' ').trim();
|
|
522
|
+
|
|
439
523
|
export const getDomainsForService = async (service: Service): Promise<Domain[]> => {
|
|
440
524
|
const domains = await getDomains({ getAllVersions: false });
|
|
441
525
|
return domains.filter((d) => {
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"license": "BUSL-1.1",
|
|
9
9
|
"type": "module",
|
|
10
|
-
"version": "5.
|
|
10
|
+
"version": "5.9.1",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
"@eventcatalog/exporters": "0.1.4",
|
|
145
145
|
"@eventcatalog/fitness": "0.1.1",
|
|
146
146
|
"@eventcatalog/sdk": "2.32.0",
|
|
147
|
-
"@eventcatalog/visualiser": "^5.2.
|
|
147
|
+
"@eventcatalog/visualiser": "^5.2.5",
|
|
148
148
|
"@eventcatalog/linter": "1.3.1"
|
|
149
149
|
},
|
|
150
150
|
"devDependencies": {
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
sidebar_position: 1
|
|
3
|
-
keywords:
|
|
4
|
-
- AI Agents
|
|
5
|
-
- documentation generation
|
|
6
|
-
- automation
|
|
7
|
-
sidebar_label: Overview
|
|
8
|
-
title: EventCatalog Agents
|
|
9
|
-
description: AI agents that help you manage and document your architecture with EventCatalog
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
[EventCatalog Agent](https://github.com/event-catalog/agents) is an agent that helps you manage your documentation through CI/CD and adds a governing layer to your pull requests.
|
|
13
|
-
|
|
14
|
-
Our Agent understands EventCatalog conventions (domains, systems and resources) and uses that understanding to keep your architecture documented for you, without you having to hand-write and maintain every catalog file yourself.
|
|
15
|
-
|
|
16
|
-
EventCatalog Agent lets you define and bring your own model.
|
|
17
|
-
|
|
18
|
-
### Available workflows
|
|
19
|
-
|
|
20
|
-
| Agent | What it does |
|
|
21
|
-
| --- | --- |
|
|
22
|
-
| [Code-to-Docs](/docs/development/ask-your-architecture/agents/code-to-docs) | The EventCatalog Agent will review your pull request code and update your catalog documentation for you. |
|
|
23
|
-
| [Breaking Changes](/docs/development/ask-your-architecture/agents/breaking-changes) | Detects breaking schema changes in a pull request and shows which catalog consumers could be affected. |
|
|
24
|
-
|
|
25
|
-
### Have an idea for a workflow?
|
|
26
|
-
|
|
27
|
-
More agents and AI workflows are on the way. If you have an idea for an agent or an AI workflow you'd like to see in EventCatalog, [open an issue on GitHub](https://github.com/event-catalog/agents/issues/new) and let us know. We use these ideas to decide what to build next.
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
sidebar_position: 2
|
|
3
|
-
keywords:
|
|
4
|
-
- Code to Docs
|
|
5
|
-
- AI Agents
|
|
6
|
-
- GitHub Action
|
|
7
|
-
- documentation generation
|
|
8
|
-
sidebar_label: Code-to-Docs Agent
|
|
9
|
-
title: Code-to-Docs Agent
|
|
10
|
-
description: Keep your EventCatalog documentation in sync with your code using the Code-to-Docs agent
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
The **Code-to-Docs** agent keeps your EventCatalog documentation in sync with your code.
|
|
14
|
-
|
|
15
|
-
When you open a pull request, the agent reviews the diff, works out which documentation should change, updates it in your catalog repository, and opens (or updates) a documentation pull request. It then comments back on your source pull request with a summary and a link.
|
|
16
|
-
|
|
17
|
-
## How it works
|
|
18
|
-
|
|
19
|
-
```mermaid
|
|
20
|
-
flowchart LR
|
|
21
|
-
PR[Source pull request] --> Agent[Code-to-Docs agent]
|
|
22
|
-
Agent --> Plan{Docs need<br/>updating?}
|
|
23
|
-
Plan -- No --> Comment[Comment on source PR]
|
|
24
|
-
Plan -- Yes --> Docs[Update catalog docs]
|
|
25
|
-
Docs --> CatalogPR[Open catalog pull request]
|
|
26
|
-
CatalogPR --> Comment
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
When a pull request is opened, the agent:
|
|
30
|
-
|
|
31
|
-
1. **Checks out your catalog** so it can see your existing documentation.
|
|
32
|
-
2. **Collects the changed source files** from the pull request.
|
|
33
|
-
3. **Plans the impact**. A read-only pass where the agent decides whether the diff requires any documentation changes, and if so, exactly which catalog resources should change. If nothing is needed, it stops here and says so.
|
|
34
|
-
4. **Applies the plan**. The agent updates the documentation, using EventCatalog conventions for frontmatter and folder structure, and a linter to validate its changes. It is only allowed to touch the resources approved in the plan.
|
|
35
|
-
5. **Opens a catalog pull request** with the changes for you to review.
|
|
36
|
-
6. **Comments on your source pull request** with a high-level summary and a link to the catalog pull request.
|
|
37
|
-
|
|
38
|
-
You stay in control: the agent never edits your catalog silently. Every change arrives as a pull request you can review, tweak, and merge.
|
|
39
|
-
|
|
40
|
-
## Getting started
|
|
41
|
-
|
|
42
|
-
The Code-to-Docs agent runs as a GitHub Action. Add it to your repository in three steps.
|
|
43
|
-
|
|
44
|
-
### 1. Add a new GitHub workflow
|
|
45
|
-
|
|
46
|
-
Create a new workflow in your source directory. Typically this is where your source code lives (e.g a service that publishes/consumes messages).
|
|
47
|
-
|
|
48
|
-
Create a `.github/workflows/eventcatalog.yml` file.
|
|
49
|
-
|
|
50
|
-
```yaml
|
|
51
|
-
on:
|
|
52
|
-
pull_request:
|
|
53
|
-
|
|
54
|
-
jobs:
|
|
55
|
-
eventcatalog:
|
|
56
|
-
runs-on: ubuntu-latest
|
|
57
|
-
permissions:
|
|
58
|
-
contents: read
|
|
59
|
-
issues: write
|
|
60
|
-
pull-requests: write
|
|
61
|
-
steps:
|
|
62
|
-
- uses: actions/checkout@v6
|
|
63
|
-
with:
|
|
64
|
-
fetch-depth: 0
|
|
65
|
-
|
|
66
|
-
- uses: event-catalog/agents@main
|
|
67
|
-
env:
|
|
68
|
-
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
69
|
-
with:
|
|
70
|
-
agent: code-to-docs
|
|
71
|
-
catalog-repo: your-org/your-catalog
|
|
72
|
-
catalog-token: ${{ secrets.EVENTCATALOG_TOKEN }}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
`fetch-depth: 0` is required so the agent can diff the pull request against its base.
|
|
76
|
-
|
|
77
|
-
### 2. Add your model provider key
|
|
78
|
-
|
|
79
|
-
Add the API key for your chosen model as a secret in your repository (**Settings → Secrets and variables → Actions**). Use the one that matches your model, for example `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or `OPENROUTER_API_KEY`.
|
|
80
|
-
|
|
81
|
-
You can see the list of [available models here](https://pi.dev/models).
|
|
82
|
-
|
|
83
|
-
### 3. Open a pull request
|
|
84
|
-
|
|
85
|
-
When you next open a pull request on your project, the EventCatalog Agent will run.
|
|
86
|
-
|
|
87
|
-
If your architecture changed, opens a documentation pull request in your catalog repository and comments back with a link.
|
|
88
|
-
|
|
89
|
-
## Configuration
|
|
90
|
-
|
|
91
|
-
### Inputs
|
|
92
|
-
|
|
93
|
-
| Input | Required | Default | Description |
|
|
94
|
-
| --- | --- | --- | --- |
|
|
95
|
-
| `catalog-repo` | Yes | | The location of your hosted EventCatalog. EventCatalog repository to document into, in `owner/repo` format. |
|
|
96
|
-
| `catalog-ref` | No | `main` | Branch checked out from the catalog repository and targeted by documentation pull requests. |
|
|
97
|
-
| `catalog-token` | No | `github.token` | Token used to check out the catalog repository and open documentation pull requests. |
|
|
98
|
-
| `model` | No | `anthropic/claude-sonnet-4-6` | Model specifier for the agent. See [available models](https://pi.dev/models). |
|
|
99
|
-
| `ignore-paths` | No | common build/output paths | Comma-separated paths or glob patterns to ignore in pull request diffs. |
|
|
100
|
-
| `agent` | No | `code-to-docs` | Which EventCatalog agent to run. Use `code-to-docs` for this workflow. |
|
|
101
|
-
|
|
102
|
-
### Provider API keys
|
|
103
|
-
|
|
104
|
-
The model provider's API key is passed as a normal workflow environment variable. Set the one that matches your `model`:
|
|
105
|
-
|
|
106
|
-
| Provider | Environment variable |
|
|
107
|
-
| --- | --- |
|
|
108
|
-
| Anthropic | `ANTHROPIC_API_KEY` |
|
|
109
|
-
| OpenAI | `OPENAI_API_KEY` |
|
|
110
|
-
| OpenRouter | `OPENROUTER_API_KEY` |
|
|
111
|
-
|
|
112
|
-
The agent supports models from many providers. See the full list of model specifiers at [pi.dev/models](https://pi.dev/models).
|
|
113
|
-
|
|
114
|
-
### Documenting into a separate catalog repository
|
|
115
|
-
|
|
116
|
-
When your catalog lives in a **different** repository from your source code, provide a `catalog-token` with permission to push branches and open pull requests in that catalog repository (the default `github.token` only has access to the current repository).
|
|
117
|
-
|
|
118
|
-
:::info Early access
|
|
119
|
-
The Code-to-Docs agent is in early access and free to evaluate. In the future, a license will be required to run EventCatalog Agents in production.
|
|
120
|
-
:::
|
|
121
|
-
|
|
122
|
-
## Found an issue or have feedback?
|
|
123
|
-
|
|
124
|
-
The Code-to-Docs agent is open on GitHub at [event-catalog/agents](https://github.com/event-catalog/agents). If you hit a problem, or the agent documents something in a way you didn't expect, [open an issue](https://github.com/event-catalog/agents/issues/new) and let us know. Your feedback during early access directly shapes how the agent works.
|