@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.
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(analytics_exports);
36
36
  var import_os = __toESM(require("os"), 1);
37
37
 
38
38
  // package.json
39
- var version = "4.0.1";
39
+ var version = "4.0.3";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-INGKV23V.js";
4
- import "../chunk-NGNI5BQ6.js";
3
+ } from "../chunk-5OGYWSP3.js";
4
+ import "../chunk-K54LSZNF.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -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.1";
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-PO4KR6QE.js";
4
- import "../chunk-INGKV23V.js";
3
+ } from "../chunk-UXP5LLH2.js";
4
+ import "../chunk-5OGYWSP3.js";
5
5
  import "../chunk-DAOXTQVS.js";
6
- import "../chunk-NGNI5BQ6.js";
6
+ import "../chunk-K54LSZNF.js";
7
7
  import "../chunk-6QENHZZP.js";
8
8
  export {
9
9
  log_build_default as default
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-NGNI5BQ6.js";
3
+ } from "./chunk-K54LSZNF.js";
4
4
 
5
5
  // src/utils/cli-logger.ts
6
6
  import pc from "picocolors";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-NGNI5BQ6.js";
3
+ } from "./chunk-K54LSZNF.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import os from "os";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-CKG6A3D5.js";
3
+ } from "./chunk-2CTETYS7.js";
4
4
  import {
5
5
  cleanup,
6
6
  getEventCatalogConfigFile
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "4.0.1";
2
+ var version = "4.0.3";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-INGKV23V.js";
3
+ } from "./chunk-5OGYWSP3.js";
4
4
  import {
5
5
  countResources,
6
6
  serializeCounts
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "4.0.1";
28
+ var version = "4.0.3";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-NGNI5BQ6.js";
3
+ } from "./chunk-K54LSZNF.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -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.1";
147
+ var version = "4.0.3";
148
148
 
149
149
  // src/constants.ts
150
150
  var VERSION = version;
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  log_build_default
3
- } from "./chunk-PO4KR6QE.js";
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-ZVPR7WWO.js";
10
+ } from "./chunk-63J5GUOH.js";
11
11
  import {
12
12
  logger
13
- } from "./chunk-CKG6A3D5.js";
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-INGKV23V.js";
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-NGNI5BQ6.js";
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.1";
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-ZVPR7WWO.js";
4
- import "./chunk-CKG6A3D5.js";
5
- import "./chunk-NGNI5BQ6.js";
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
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(cli_logger_exports);
36
36
  var import_picocolors = __toESM(require("picocolors"), 1);
37
37
 
38
38
  // package.json
39
- var version = "4.0.1";
39
+ var version = "4.0.3";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  logger
3
- } from "../chunk-CKG6A3D5.js";
4
- import "../chunk-NGNI5BQ6.js";
3
+ } from "../chunk-2CTETYS7.js";
4
+ import "../chunk-K54LSZNF.js";
5
5
  export {
6
6
  logger
7
7
  };
@@ -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.1",
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/visualiser": "^4.0.0",
120
- "@eventcatalog/sdk": "2.25.0"
120
+ "@eventcatalog/sdk": "2.25.0",
121
+ "@eventcatalog/visualiser": "^4.0.0"
121
122
  },
122
123
  "devDependencies": {
123
124
  "@astrojs/check": "^0.9.9",