@eventcatalog/core 4.0.1 → 4.0.3
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-CKG6A3D5.js → chunk-2CTETYS7.js} +1 -1
- package/dist/{chunk-INGKV23V.js → chunk-5OGYWSP3.js} +1 -1
- package/dist/{chunk-ZVPR7WWO.js → chunk-63J5GUOH.js} +1 -1
- package/dist/{chunk-NGNI5BQ6.js → chunk-K54LSZNF.js} +1 -1
- package/dist/{chunk-PO4KR6QE.js → chunk-UXP5LLH2.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/components/MDX/components.tsx +6 -1
- package/eventcatalog/src/enterprise/ai/chat-api.ts +36 -0
- package/eventcatalog/src/enterprise/custom-documentation/utils/docs-search.ts +144 -0
- package/eventcatalog/src/enterprise/mcp/mcp-server.ts +39 -0
- package/eventcatalog/src/enterprise/tools/catalog-tools.ts +137 -0
- package/eventcatalog/src/types/picomatch.d.ts +13 -0
- package/package.json +4 -3
|
@@ -140,7 +140,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
|
|
|
140
140
|
var import_os = __toESM(require("os"), 1);
|
|
141
141
|
|
|
142
142
|
// package.json
|
|
143
|
-
var version = "4.0.
|
|
143
|
+
var version = "4.0.3";
|
|
144
144
|
|
|
145
145
|
// src/constants.ts
|
|
146
146
|
var VERSION = version;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log_build_default
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-UXP5LLH2.js";
|
|
4
|
+
import "../chunk-5OGYWSP3.js";
|
|
5
5
|
import "../chunk-DAOXTQVS.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-K54LSZNF.js";
|
|
7
7
|
import "../chunk-6QENHZZP.js";
|
|
8
8
|
export {
|
|
9
9
|
log_build_default as default
|
package/dist/constants.cjs
CHANGED
package/dist/constants.js
CHANGED
package/dist/eventcatalog.cjs
CHANGED
|
@@ -144,7 +144,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
|
|
|
144
144
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
145
145
|
|
|
146
146
|
// package.json
|
|
147
|
-
var version = "4.0.
|
|
147
|
+
var version = "4.0.3";
|
|
148
148
|
|
|
149
149
|
// src/constants.ts
|
|
150
150
|
var VERSION = version;
|
package/dist/eventcatalog.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log_build_default
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-UXP5LLH2.js";
|
|
4
4
|
import {
|
|
5
5
|
runMigrations
|
|
6
6
|
} from "./chunk-XUAF2H54.js";
|
|
7
7
|
import "./chunk-CA4U2JP7.js";
|
|
8
8
|
import {
|
|
9
9
|
generate
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-63J5GUOH.js";
|
|
11
11
|
import {
|
|
12
12
|
logger
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-2CTETYS7.js";
|
|
14
14
|
import {
|
|
15
15
|
resolve_catalog_dependencies_default
|
|
16
16
|
} from "./chunk-LHR4G2UO.js";
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
import {
|
|
21
21
|
watch
|
|
22
22
|
} from "./chunk-O3Y2G6CY.js";
|
|
23
|
-
import "./chunk-
|
|
23
|
+
import "./chunk-5OGYWSP3.js";
|
|
24
24
|
import "./chunk-DAOXTQVS.js";
|
|
25
25
|
import {
|
|
26
26
|
catalogToAstro
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import "./chunk-VC6VUJIX.js";
|
|
29
29
|
import {
|
|
30
30
|
VERSION
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-K54LSZNF.js";
|
|
32
32
|
import {
|
|
33
33
|
linkCoreNodeModules,
|
|
34
34
|
resolveInstalledCoreNodeModules
|
package/dist/generate.cjs
CHANGED
|
@@ -108,7 +108,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
|
|
|
108
108
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
109
109
|
|
|
110
110
|
// package.json
|
|
111
|
-
var version = "4.0.
|
|
111
|
+
var version = "4.0.3";
|
|
112
112
|
|
|
113
113
|
// src/constants.ts
|
|
114
114
|
var VERSION = version;
|
package/dist/generate.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generate
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-63J5GUOH.js";
|
|
4
|
+
import "./chunk-2CTETYS7.js";
|
|
5
|
+
import "./chunk-K54LSZNF.js";
|
|
6
6
|
import "./chunk-6QENHZZP.js";
|
|
7
7
|
export {
|
|
8
8
|
generate
|
package/dist/utils/cli-logger.js
CHANGED
|
@@ -42,6 +42,11 @@ import { jsx } from 'astro/jsx-runtime';
|
|
|
42
42
|
import RemoteSchema from '@components/MDX/RemoteSchema.astro';
|
|
43
43
|
import Visibility from '@components/MDX/Visibility';
|
|
44
44
|
|
|
45
|
+
const getEntityMapCollection = (props: any, mdxProp: any) => {
|
|
46
|
+
const collection = mdxProp.collection ?? props.collection;
|
|
47
|
+
return collection === 'domains' || collection === 'services' ? collection : undefined;
|
|
48
|
+
};
|
|
49
|
+
|
|
45
50
|
const components = (props: any) => {
|
|
46
51
|
return {
|
|
47
52
|
Attachments: (mdxProp: any) => jsx(Attachments, { ...props, ...mdxProp }),
|
|
@@ -63,7 +68,7 @@ const components = (props: any) => {
|
|
|
63
68
|
ComponentDiagram: (mdxProp: any) => jsx(NodeGraphPortal, { ...props.data, ...mdxProp, props, mdxProp }),
|
|
64
69
|
ContextDiagram: (mdxProp: any) => jsx(ContextDiagramPortal, { ...props.data, ...mdxProp, props, mdxProp }),
|
|
65
70
|
SystemContextMap: (mdxProp: any) => jsx(SystemContextMapPortal, { ...mdxProp }),
|
|
66
|
-
EntityMap: (mdxProp: any) => jsx(EntityMap, { ...props, ...mdxProp }),
|
|
71
|
+
EntityMap: (mdxProp: any) => jsx(EntityMap, { ...props, ...mdxProp, collection: getEntityMapCollection(props, mdxProp) }),
|
|
67
72
|
OpenAPI,
|
|
68
73
|
Prompt: (mdxProp: any) => jsx(Prompt, { ...props, ...mdxProp }),
|
|
69
74
|
ResourceGroupTable: (mdxProp: any) => jsx(ResourceGroupTable, { ...props, ...mdxProp }),
|
|
@@ -18,6 +18,9 @@ import {
|
|
|
18
18
|
getDataProductInputs as getDataProductInputsImpl,
|
|
19
19
|
getDataProductOutputs as getDataProductOutputsImpl,
|
|
20
20
|
getArchitectureDiagramAsMermaid as getArchitectureDiagramImpl,
|
|
21
|
+
getCustomDocs as getCustomDocsImpl,
|
|
22
|
+
searchCustomDocs as searchCustomDocsImpl,
|
|
23
|
+
getCustomDoc as getCustomDocImpl,
|
|
21
24
|
collectionSchema,
|
|
22
25
|
resourceCollectionSchema,
|
|
23
26
|
messageCollectionSchema,
|
|
@@ -72,6 +75,9 @@ const builtInToolsMetadata = [
|
|
|
72
75
|
{ name: 'getDataProductInputs', description: 'Get the inputs (resources consumed) for a data product' },
|
|
73
76
|
{ name: 'getDataProductOutputs', description: 'Get the outputs (resources produced) for a data product with data contracts' },
|
|
74
77
|
{ name: 'getArchitectureDiagramAsMermaid', description: 'Get the architecture diagram for a resource as Mermaid code' },
|
|
78
|
+
{ name: 'getCustomDocs', description: 'List custom documentation pages (guides, runbooks, architecture notes)' },
|
|
79
|
+
{ name: 'searchCustomDocs', description: 'Search the full text of custom documentation pages' },
|
|
80
|
+
{ name: 'getCustomDoc', description: 'Get the markdown content of a custom documentation page' },
|
|
75
81
|
];
|
|
76
82
|
|
|
77
83
|
// Get extended tools metadata from user configuration
|
|
@@ -350,6 +356,36 @@ export const POST = async ({ request }: APIContext<{ question: string; messages:
|
|
|
350
356
|
return await getArchitectureDiagramImpl({ resourceId, resourceVersion, resourceCollection });
|
|
351
357
|
},
|
|
352
358
|
}),
|
|
359
|
+
getCustomDocs: tool({
|
|
360
|
+
description: toolDescriptions.getCustomDocs,
|
|
361
|
+
inputSchema: z.object({
|
|
362
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
363
|
+
search: z.string().optional().describe('Search term to filter docs by title, id, or summary (case-insensitive)'),
|
|
364
|
+
}),
|
|
365
|
+
execute: async ({ cursor, search }) => {
|
|
366
|
+
return await getCustomDocsImpl({ cursor, search });
|
|
367
|
+
},
|
|
368
|
+
}),
|
|
369
|
+
searchCustomDocs: tool({
|
|
370
|
+
description: toolDescriptions.searchCustomDocs,
|
|
371
|
+
inputSchema: z.object({
|
|
372
|
+
query: z.string().describe('Full-text search query, e.g. keywords describing the topic to find'),
|
|
373
|
+
limit: z.number().optional().describe('Maximum number of results to return (default 10)'),
|
|
374
|
+
}),
|
|
375
|
+
execute: async ({ query, limit }) => {
|
|
376
|
+
return await searchCustomDocsImpl({ query, limit });
|
|
377
|
+
},
|
|
378
|
+
}),
|
|
379
|
+
getCustomDoc: tool({
|
|
380
|
+
description: toolDescriptions.getCustomDoc,
|
|
381
|
+
inputSchema: z.object({
|
|
382
|
+
id: z.string().describe('The id or slug of the custom documentation page'),
|
|
383
|
+
section: z.string().optional().describe('Optional section heading to return only that section of the page'),
|
|
384
|
+
}),
|
|
385
|
+
execute: async ({ id, section }) => {
|
|
386
|
+
return await getCustomDocImpl({ id, section });
|
|
387
|
+
},
|
|
388
|
+
}),
|
|
353
389
|
suggestFollowUpQuestions: tool({
|
|
354
390
|
description:
|
|
355
391
|
'Use this tool after answering a question to suggest 2-3 relevant follow-up questions the user might want to ask. These will be displayed as clickable suggestions.',
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Licensed under the EventCatalog Commercial License.
|
|
3
|
+
* See /packages/core/eventcatalog/src/enterprise/LICENSE
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import MiniSearch from 'minisearch';
|
|
7
|
+
|
|
8
|
+
export type MarkdownChunk = {
|
|
9
|
+
heading: string | null;
|
|
10
|
+
content: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type DocForIndexing = {
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
summary?: string;
|
|
17
|
+
body?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type DocsSearchHit = {
|
|
21
|
+
docId: string;
|
|
22
|
+
title: string;
|
|
23
|
+
heading: string | null;
|
|
24
|
+
snippet: string;
|
|
25
|
+
score: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type DocsSearchIndex = {
|
|
29
|
+
search: (query: string, options?: { limit?: number }) => DocsSearchHit[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const DEFAULT_SEARCH_LIMIT = 10;
|
|
33
|
+
const SNIPPET_RADIUS = 100;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Splits markdown into section chunks at ATX headings (any level).
|
|
37
|
+
* Content before the first heading becomes a chunk with a null heading.
|
|
38
|
+
* Headings inside fenced code blocks are ignored.
|
|
39
|
+
*/
|
|
40
|
+
export const chunkMarkdownByHeadings = (markdown: string): MarkdownChunk[] => {
|
|
41
|
+
const chunks: MarkdownChunk[] = [];
|
|
42
|
+
let heading: string | null = null;
|
|
43
|
+
let buffer: string[] = [];
|
|
44
|
+
let insideCodeFence = false;
|
|
45
|
+
|
|
46
|
+
const flush = () => {
|
|
47
|
+
const content = buffer.join('\n').trim();
|
|
48
|
+
if (heading !== null || content) {
|
|
49
|
+
chunks.push({ heading, content });
|
|
50
|
+
}
|
|
51
|
+
buffer = [];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
for (const line of markdown.split('\n')) {
|
|
55
|
+
if (/^(```|~~~)/.test(line.trim())) {
|
|
56
|
+
insideCodeFence = !insideCodeFence;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const headingMatch = insideCodeFence ? null : line.match(/^#{1,6}\s+(.+)$/);
|
|
60
|
+
if (headingMatch) {
|
|
61
|
+
flush();
|
|
62
|
+
heading = headingMatch[1].trim();
|
|
63
|
+
} else {
|
|
64
|
+
buffer.push(line);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
flush();
|
|
69
|
+
return chunks;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Returns a short excerpt of the content centred on the first query term found.
|
|
74
|
+
* Falls back to the start of the content when no term matches.
|
|
75
|
+
*/
|
|
76
|
+
export const extractSnippet = (content: string, query: string, radius: number = SNIPPET_RADIUS): string => {
|
|
77
|
+
const normalized = content.replace(/\s+/g, ' ').trim();
|
|
78
|
+
const lowercased = normalized.toLowerCase();
|
|
79
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
80
|
+
|
|
81
|
+
let matchIndex = -1;
|
|
82
|
+
for (const term of terms) {
|
|
83
|
+
matchIndex = lowercased.indexOf(term);
|
|
84
|
+
if (matchIndex !== -1) break;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (matchIndex === -1) {
|
|
88
|
+
return normalized.length <= radius * 2 ? normalized : `${normalized.slice(0, radius * 2)}…`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const start = Math.max(0, matchIndex - radius);
|
|
92
|
+
const end = Math.min(normalized.length, matchIndex + radius);
|
|
93
|
+
return `${start > 0 ? '…' : ''}${normalized.slice(start, end)}${end < normalized.length ? '…' : ''}`;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Builds an in-memory BM25 search index over custom documentation.
|
|
98
|
+
* Each document is indexed as one entry per markdown section, so search hits
|
|
99
|
+
* point at the specific section of a document rather than the whole document.
|
|
100
|
+
*/
|
|
101
|
+
export const buildDocsSearchIndex = (docs: DocForIndexing[]): DocsSearchIndex => {
|
|
102
|
+
const miniSearch = new MiniSearch({
|
|
103
|
+
fields: ['title', 'summary', 'heading', 'content'],
|
|
104
|
+
storeFields: ['docId', 'title', 'heading', 'content'],
|
|
105
|
+
searchOptions: {
|
|
106
|
+
boost: { title: 3, heading: 2, summary: 2 },
|
|
107
|
+
prefix: true,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const sections = docs.flatMap((doc) => {
|
|
112
|
+
const chunks = chunkMarkdownByHeadings(doc.body ?? '');
|
|
113
|
+
// Documents with an empty body should still be searchable by title/summary
|
|
114
|
+
const indexableChunks = chunks.length > 0 ? chunks : [{ heading: null, content: '' }];
|
|
115
|
+
|
|
116
|
+
return indexableChunks.map((chunk, index) => ({
|
|
117
|
+
id: `${doc.id}#${index}`,
|
|
118
|
+
docId: doc.id,
|
|
119
|
+
title: doc.title,
|
|
120
|
+
summary: doc.summary,
|
|
121
|
+
heading: chunk.heading,
|
|
122
|
+
content: chunk.content,
|
|
123
|
+
}));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
miniSearch.addAll(sections);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
search: (query, options = {}) => {
|
|
130
|
+
const limit = options.limit ?? DEFAULT_SEARCH_LIMIT;
|
|
131
|
+
|
|
132
|
+
return miniSearch
|
|
133
|
+
.search(query)
|
|
134
|
+
.slice(0, limit)
|
|
135
|
+
.map((result) => ({
|
|
136
|
+
docId: result.docId,
|
|
137
|
+
title: result.title,
|
|
138
|
+
heading: result.heading ?? null,
|
|
139
|
+
snippet: extractSnippet(result.content ?? '', query),
|
|
140
|
+
score: result.score,
|
|
141
|
+
}));
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
};
|
|
@@ -26,6 +26,9 @@ import {
|
|
|
26
26
|
getUser,
|
|
27
27
|
findMessageBySchemaId,
|
|
28
28
|
explainUbiquitousLanguageTerms,
|
|
29
|
+
getCustomDocs,
|
|
30
|
+
searchCustomDocs,
|
|
31
|
+
getCustomDoc,
|
|
29
32
|
collectionSchema,
|
|
30
33
|
resourceCollectionSchema,
|
|
31
34
|
messageCollectionSchema,
|
|
@@ -296,6 +299,42 @@ function createMcpServer() {
|
|
|
296
299
|
createToolHandler(explainUbiquitousLanguageTerms, 'Failed to get ubiquitous language terms')
|
|
297
300
|
);
|
|
298
301
|
|
|
302
|
+
server.registerTool(
|
|
303
|
+
'getCustomDocs',
|
|
304
|
+
{
|
|
305
|
+
description: toolDescriptions.getCustomDocs,
|
|
306
|
+
inputSchema: z.object({
|
|
307
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
308
|
+
search: z.string().optional().describe('Search term to filter docs by title, id, or summary (case-insensitive)'),
|
|
309
|
+
}),
|
|
310
|
+
},
|
|
311
|
+
createToolHandler(getCustomDocs, 'Failed to get custom documentation pages')
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
server.registerTool(
|
|
315
|
+
'searchCustomDocs',
|
|
316
|
+
{
|
|
317
|
+
description: toolDescriptions.searchCustomDocs,
|
|
318
|
+
inputSchema: z.object({
|
|
319
|
+
query: z.string().describe('Full-text search query, e.g. keywords describing the topic to find'),
|
|
320
|
+
limit: z.number().optional().describe('Maximum number of results to return (default 10)'),
|
|
321
|
+
}),
|
|
322
|
+
},
|
|
323
|
+
createToolHandler(searchCustomDocs, 'Failed to search custom documentation')
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
server.registerTool(
|
|
327
|
+
'getCustomDoc',
|
|
328
|
+
{
|
|
329
|
+
description: toolDescriptions.getCustomDoc,
|
|
330
|
+
inputSchema: z.object({
|
|
331
|
+
id: z.string().describe('The id or slug of the custom documentation page'),
|
|
332
|
+
section: z.string().optional().describe('Optional section heading to return only that section of the page'),
|
|
333
|
+
}),
|
|
334
|
+
},
|
|
335
|
+
createToolHandler(getCustomDoc, 'Failed to get custom documentation page')
|
|
336
|
+
);
|
|
337
|
+
|
|
299
338
|
// Register extended tools from user configuration
|
|
300
339
|
for (const [toolName, toolConfig] of Object.entries(extendedTools)) {
|
|
301
340
|
if (!toolConfig || typeof toolConfig !== 'object') continue;
|
|
@@ -30,6 +30,11 @@ import { convertToMermaid } from '@utils/node-graphs/export-mermaid';
|
|
|
30
30
|
import config from '@config';
|
|
31
31
|
import { glob } from 'glob';
|
|
32
32
|
import path from 'node:path';
|
|
33
|
+
import {
|
|
34
|
+
buildDocsSearchIndex,
|
|
35
|
+
chunkMarkdownByHeadings,
|
|
36
|
+
type DocsSearchIndex,
|
|
37
|
+
} from '@enterprise/custom-documentation/utils/docs-search';
|
|
33
38
|
|
|
34
39
|
const MESSAGE_COLLECTIONS = new Set(['events', 'commands', 'queries']);
|
|
35
40
|
const LIKEC4_SOURCE_PATTERN = '**/*.{c4,likec4}';
|
|
@@ -1091,6 +1096,132 @@ export async function getArchitectureDiagramAsMermaid(params: {
|
|
|
1091
1096
|
}
|
|
1092
1097
|
}
|
|
1093
1098
|
|
|
1099
|
+
// ============================================
|
|
1100
|
+
// Custom Documentation Tools
|
|
1101
|
+
// ============================================
|
|
1102
|
+
|
|
1103
|
+
const CACHE_ENABLED = process.env.DISABLE_EVENTCATALOG_CACHE !== 'true';
|
|
1104
|
+
let cachedDocsSearchIndex: DocsSearchIndex | null = null;
|
|
1105
|
+
|
|
1106
|
+
const findCustomDocByIdOrSlug = (docs: any[], idOrSlug: string) => {
|
|
1107
|
+
const normalized = idOrSlug.toLowerCase().trim();
|
|
1108
|
+
return (
|
|
1109
|
+
docs.find((doc) => doc.id.toLowerCase() === normalized) ?? docs.find((doc) => doc.data.slug?.toLowerCase() === normalized)
|
|
1110
|
+
);
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
const getDocsSearchIndex = async (): Promise<DocsSearchIndex> => {
|
|
1114
|
+
if (CACHE_ENABLED && cachedDocsSearchIndex) {
|
|
1115
|
+
return cachedDocsSearchIndex;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const docs = await getCollection('customPages');
|
|
1119
|
+
cachedDocsSearchIndex = buildDocsSearchIndex(
|
|
1120
|
+
docs.map((doc: any) => ({
|
|
1121
|
+
id: doc.id,
|
|
1122
|
+
title: doc.data.title,
|
|
1123
|
+
summary: doc.data.summary,
|
|
1124
|
+
body: doc.body,
|
|
1125
|
+
}))
|
|
1126
|
+
);
|
|
1127
|
+
|
|
1128
|
+
return cachedDocsSearchIndex;
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* List custom documentation pages (metadata only, no page content)
|
|
1133
|
+
*/
|
|
1134
|
+
export async function getCustomDocs(params: { cursor?: string; search?: string }) {
|
|
1135
|
+
const docs = await getCollection('customPages');
|
|
1136
|
+
let allResults = docs.map((doc: any) => ({
|
|
1137
|
+
id: doc.id,
|
|
1138
|
+
title: doc.data.title,
|
|
1139
|
+
summary: doc.data.summary,
|
|
1140
|
+
...(doc.data.slug && { slug: doc.data.slug }),
|
|
1141
|
+
}));
|
|
1142
|
+
|
|
1143
|
+
if (params.search) {
|
|
1144
|
+
const searchTerm = params.search.toLowerCase().trim();
|
|
1145
|
+
allResults = allResults.filter(
|
|
1146
|
+
(doc: any) =>
|
|
1147
|
+
doc.id?.toLowerCase().includes(searchTerm) ||
|
|
1148
|
+
doc.title?.toLowerCase().includes(searchTerm) ||
|
|
1149
|
+
doc.summary?.toLowerCase().includes(searchTerm)
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
const paginatedResult = paginate(allResults, params.cursor);
|
|
1154
|
+
|
|
1155
|
+
if ('error' in paginatedResult) {
|
|
1156
|
+
return paginatedResult;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
return {
|
|
1160
|
+
docs: paginatedResult.items,
|
|
1161
|
+
nextCursor: paginatedResult.nextCursor,
|
|
1162
|
+
totalCount: paginatedResult.totalCount,
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Full-text search across custom documentation pages.
|
|
1168
|
+
* Matches at the section level so results point at the relevant part of a page.
|
|
1169
|
+
*/
|
|
1170
|
+
export async function searchCustomDocs(params: { query: string; limit?: number }) {
|
|
1171
|
+
const query = params.query?.trim();
|
|
1172
|
+
|
|
1173
|
+
if (!query) {
|
|
1174
|
+
return { error: 'A search query is required' };
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
const index = await getDocsSearchIndex();
|
|
1178
|
+
const results = index.search(query, { limit: params.limit });
|
|
1179
|
+
|
|
1180
|
+
return { results, totalCount: results.length };
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Get the full markdown content of a custom documentation page by id or slug.
|
|
1185
|
+
* Pass a section heading to get just that section of the page.
|
|
1186
|
+
*/
|
|
1187
|
+
export async function getCustomDoc(params: { id: string; section?: string }) {
|
|
1188
|
+
const docs = await getCollection('customPages');
|
|
1189
|
+
const doc = findCustomDocByIdOrSlug(docs, params.id);
|
|
1190
|
+
|
|
1191
|
+
if (!doc) {
|
|
1192
|
+
return { error: `Custom documentation page not found: ${params.id}` };
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
const { title, summary, slug, owners } = doc.data;
|
|
1196
|
+
const metadata = {
|
|
1197
|
+
id: doc.id,
|
|
1198
|
+
title,
|
|
1199
|
+
summary,
|
|
1200
|
+
...(slug && { slug }),
|
|
1201
|
+
...(owners && { owners }),
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
if (params.section) {
|
|
1205
|
+
const requestedSection = params.section.toLowerCase().trim();
|
|
1206
|
+
const sections = chunkMarkdownByHeadings(doc.body ?? '');
|
|
1207
|
+
const section = sections.find((chunk) => chunk.heading?.toLowerCase() === requestedSection);
|
|
1208
|
+
|
|
1209
|
+
if (!section) {
|
|
1210
|
+
const availableSections = sections
|
|
1211
|
+
.map((chunk) => chunk.heading)
|
|
1212
|
+
.filter(Boolean)
|
|
1213
|
+
.join(', ');
|
|
1214
|
+
return {
|
|
1215
|
+
error: `Section "${params.section}" not found in ${doc.id}. Available sections: ${availableSections || 'none'}`,
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
return { ...metadata, section: section.heading, markdown: section.content };
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
return { ...metadata, markdown: doc.body ?? '' };
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1094
1225
|
// ============================================
|
|
1095
1226
|
// Tool metadata (descriptions)
|
|
1096
1227
|
// ============================================
|
|
@@ -1131,4 +1262,10 @@ export const toolDescriptions = {
|
|
|
1131
1262
|
'Use this tool to get the outputs (resources produced) for a data product. Returns fully hydrated output resources with their id, version, name, summary, collection type, and data contracts (if defined). Data contracts include the contract name, path, format, type, and content.',
|
|
1132
1263
|
getArchitectureDiagramAsMermaid:
|
|
1133
1264
|
'Use this tool to get the architecture diagram for a resource as Mermaid flowchart code. This shows how the resource connects to other resources (agents, services, systems, events, channels, etc.) in the architecture. The mermaid code can be rendered to visualize the architecture or used to understand relationships. Supported collections: events, commands, queries, agents, services, domains, systems, flows, containers, data-products.',
|
|
1265
|
+
getCustomDocs:
|
|
1266
|
+
'Use this tool to list the custom documentation pages in EventCatalog (guides, runbooks, architecture notes, onboarding docs, etc.). Returns page ids, titles, and summaries without page content. Supports pagination via cursor and filtering by search term (searches title, id, and summary).',
|
|
1267
|
+
searchCustomDocs:
|
|
1268
|
+
'Use this tool to search the full text of custom documentation pages in EventCatalog. Returns the best matching sections, each with the page id, section heading, and a short snippet. To read more, call getCustomDoc with the returned page id (and optionally the section heading). Reformulate the query and search again if the first results are not relevant. If you are unsure where to find something, or the structured catalog tools have not answered the question, search here - custom documentation often contains guides, runbooks, and context not captured elsewhere in the catalog. An empty result may simply mean the catalog has no custom documentation on the topic.',
|
|
1269
|
+
getCustomDoc:
|
|
1270
|
+
'Use this tool to get the full markdown content of a custom documentation page by its id or slug. Optionally pass a section heading to get only that section - prefer this for large pages to keep responses small.',
|
|
1134
1271
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare module 'picomatch' {
|
|
2
|
+
export interface Options {
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface Picomatch {
|
|
7
|
+
(globs: string | string[], options?: Options): (input: string) => boolean;
|
|
8
|
+
isMatch(input: string | string[], glob: string | string[], options?: Options): boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const picomatch: Picomatch;
|
|
12
|
+
export default picomatch;
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"license": "SEE LICENSE IN LICENSE",
|
|
9
9
|
"type": "module",
|
|
10
|
-
"version": "4.0.
|
|
10
|
+
"version": "4.0.3",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"marked": "^15.0.6",
|
|
88
88
|
"mdast-util-find-and-replace": "^3.0.2",
|
|
89
89
|
"mermaid": "^11.12.3",
|
|
90
|
+
"minisearch": "^7.2.0",
|
|
90
91
|
"nanostores": "^1.1.0",
|
|
91
92
|
"pagefind": "^1.5.2",
|
|
92
93
|
"pako": "^2.1.0",
|
|
@@ -116,8 +117,8 @@
|
|
|
116
117
|
"uuid": "^10.0.0",
|
|
117
118
|
"zod": "^4.3.6",
|
|
118
119
|
"@eventcatalog/linter": "1.1.3",
|
|
119
|
-
"@eventcatalog/
|
|
120
|
-
"@eventcatalog/
|
|
120
|
+
"@eventcatalog/sdk": "2.25.0",
|
|
121
|
+
"@eventcatalog/visualiser": "^4.0.0"
|
|
121
122
|
},
|
|
122
123
|
"devDependencies": {
|
|
123
124
|
"@astrojs/check": "^0.9.9",
|