@maxisoft/instantcms-mcp 1.2.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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/dist/__tests__/artifact-tool.test.js +196 -0
  4. package/dist/__tests__/db-tool.test.js +152 -0
  5. package/dist/__tests__/define-tool.test.js +106 -0
  6. package/dist/__tests__/find-tool.test.js +185 -0
  7. package/dist/__tests__/get-component-api.test.js +144 -0
  8. package/dist/__tests__/hardening.test.js +73 -0
  9. package/dist/__tests__/hooks-tool.test.js +173 -0
  10. package/dist/__tests__/mcp-integration-extra.test.js +166 -0
  11. package/dist/__tests__/mcp-integration.test.js +53 -0
  12. package/dist/__tests__/pagination.test.js +114 -0
  13. package/dist/__tests__/performance-baseline.test.js +56 -0
  14. package/dist/__tests__/project-patch.test.js +144 -0
  15. package/dist/__tests__/project-source.test.js +44 -0
  16. package/dist/__tests__/project-workflows-extra.test.js +165 -0
  17. package/dist/__tests__/project-workflows.test.js +51 -0
  18. package/dist/__tests__/scaffold-addon-roundtrip.test.js +287 -0
  19. package/dist/__tests__/serialization.test.js +170 -0
  20. package/dist/__tests__/source-knowledge-parsers.test.js +42 -0
  21. package/dist/__tests__/template-development.test.js +78 -0
  22. package/dist/__tests__/template-productivity.test.js +87 -0
  23. package/dist/__tests__/tools.test.js +2143 -0
  24. package/dist/data/components.js +2818 -0
  25. package/dist/data/controllers-map.js +7571 -0
  26. package/dist/data/core-api.js +9356 -0
  27. package/dist/data/database-schema.js +4555 -0
  28. package/dist/data/events-map.js +1339 -0
  29. package/dist/data/fields-map.js +3692 -0
  30. package/dist/data/hooks.js +2257 -0
  31. package/dist/data/js-api.js +123 -0
  32. package/dist/data/libs-api.js +264 -0
  33. package/dist/data/routes-map.js +203 -0
  34. package/dist/data/schemas-validation.js +255 -0
  35. package/dist/data/schemas.js +1404 -0
  36. package/dist/data/traits-map.js +1004 -0
  37. package/dist/data/version-profiles.js +41 -0
  38. package/dist/data/widgets-map.js +131 -0
  39. package/dist/data/wysiwyg-map.js +486 -0
  40. package/dist/generated/components-source.js +14748 -0
  41. package/dist/generated/hooks-source.js +3399 -0
  42. package/dist/generated/knowledge-meta.js +101 -0
  43. package/dist/index.js +13 -0
  44. package/dist/registry/database-tools.js +112 -0
  45. package/dist/registry/extension-tools.js +473 -0
  46. package/dist/registry/generator-tools.js +506 -0
  47. package/dist/registry/knowledge-tools.js +441 -0
  48. package/dist/registry/language-tools.js +99 -0
  49. package/dist/registry/meta-tools.js +152 -0
  50. package/dist/registry/project-tools.js +48 -0
  51. package/dist/registry/resources.js +98 -0
  52. package/dist/registry/source-tools.js +211 -0
  53. package/dist/registry/template-development-tools.js +79 -0
  54. package/dist/server.js +32 -0
  55. package/dist/tools/addon-tool.js +1437 -0
  56. package/dist/tools/admin-partial-tool.js +498 -0
  57. package/dist/tools/api-tool.js +294 -0
  58. package/dist/tools/artifact-tool.js +95 -0
  59. package/dist/tools/cache-tool.js +360 -0
  60. package/dist/tools/component-tool.js +415 -0
  61. package/dist/tools/controllers-tool.js +125 -0
  62. package/dist/tools/cron-tool.js +197 -0
  63. package/dist/tools/crud-tool.js +659 -0
  64. package/dist/tools/db-tool.js +198 -0
  65. package/dist/tools/email-tool.js +222 -0
  66. package/dist/tools/external-api-tool.js +596 -0
  67. package/dist/tools/filter-tool.js +341 -0
  68. package/dist/tools/form-tool.js +275 -0
  69. package/dist/tools/grid-tool.js +189 -0
  70. package/dist/tools/hooks-tool.js +104 -0
  71. package/dist/tools/import-export-tool.js +548 -0
  72. package/dist/tools/lang-tool.js +251 -0
  73. package/dist/tools/layout-override-tool.js +125 -0
  74. package/dist/tools/layout-tool.js +548 -0
  75. package/dist/tools/maria-tool.js +127 -0
  76. package/dist/tools/mariadb.js +201 -0
  77. package/dist/tools/migration-tool.js +329 -0
  78. package/dist/tools/oauth-tool.js +520 -0
  79. package/dist/tools/parser/components-parser.js +76 -0
  80. package/dist/tools/parser/controllers-parser.js +313 -0
  81. package/dist/tools/parser/core-parser.js +294 -0
  82. package/dist/tools/parser/coverage-generator.js +424 -0
  83. package/dist/tools/parser/events-parser.js +127 -0
  84. package/dist/tools/parser/fields-parser.js +382 -0
  85. package/dist/tools/parser/hooks-parser.js +106 -0
  86. package/dist/tools/parser/sql-parser.js +197 -0
  87. package/dist/tools/parser/traits-parser.js +161 -0
  88. package/dist/tools/parser/widgets-parser.js +150 -0
  89. package/dist/tools/permission-tool.js +348 -0
  90. package/dist/tools/project-patch-tool.js +73 -0
  91. package/dist/tools/project-source-tool.js +188 -0
  92. package/dist/tools/project-workflow-tool.js +186 -0
  93. package/dist/tools/requirement-tool.js +212 -0
  94. package/dist/tools/scaffold-tool.js +815 -0
  95. package/dist/tools/seo-tool.js +412 -0
  96. package/dist/tools/source-tool.js +155 -0
  97. package/dist/tools/template-development-tool.js +271 -0
  98. package/dist/tools/template-overrides-tool.js +294 -0
  99. package/dist/tools/template-productivity-tool.js +319 -0
  100. package/dist/tools/template-tool.js +665 -0
  101. package/dist/tools/test-tool.js +183 -0
  102. package/dist/tools/webhook-tool.js +427 -0
  103. package/dist/tools/widget-tool.js +331 -0
  104. package/dist/tools/wysiwyg-tool.js +137 -0
  105. package/dist/types/scaffold.js +68 -0
  106. package/dist/utils/define-tool.js +37 -0
  107. package/dist/utils/find-tool.js +97 -0
  108. package/dist/utils/mcp-result.js +18 -0
  109. package/dist/utils/pagination.js +27 -0
  110. package/dist/utils/serialization.js +27 -0
  111. package/package.json +94 -0
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const fc = __importStar(require("fast-check"));
37
+ const hooks_tool_js_1 = require("../tools/hooks-tool.js");
38
+ describe('hooks-tool knowledge lookups', () => {
39
+ describe('listHooks', () => {
40
+ test('возвращает структуру с total и hooks', () => {
41
+ const result = (0, hooks_tool_js_1.listHooks)();
42
+ expect(typeof result.total).toBe('number');
43
+ expect(result.total).toBeGreaterThan(0);
44
+ expect(Array.isArray(result.hooks)).toBe(true);
45
+ expect(Array.isArray(result.categories)).toBe(true);
46
+ });
47
+ test('фильтр по category оставляет только эту категорию', () => {
48
+ const result = (0, hooks_tool_js_1.listHooks)('content');
49
+ for (const hook of result.hooks) {
50
+ expect(hook.category).toBe('content');
51
+ }
52
+ });
53
+ test('фильтр по type оставляет только этот type (или содержит подстроку)', () => {
54
+ const result = (0, hooks_tool_js_1.listHooks)(undefined, 'filter');
55
+ for (const hook of result.hooks) {
56
+ expect(hook.type === 'filter' || hook.type.includes('filter')).toBe(true);
57
+ }
58
+ });
59
+ test('пагинация через cursor работает', () => {
60
+ const first = (0, hooks_tool_js_1.listHooks)(undefined, undefined, { limit: 5 });
61
+ expect(first.hooks).toHaveLength(5);
62
+ expect(first.page.returned).toBe(5);
63
+ expect(first.page.next_cursor).toBeTruthy();
64
+ const second = (0, hooks_tool_js_1.listHooks)(undefined, undefined, {
65
+ limit: 5,
66
+ cursor: first.page.next_cursor,
67
+ });
68
+ expect(second.hooks).toHaveLength(5);
69
+ const names = new Set([...first.hooks.map(h => h.name), ...second.hooks.map(h => h.name)]);
70
+ expect(names.size).toBe(10);
71
+ });
72
+ test('несуществующая категория → пустой результат', () => {
73
+ const result = (0, hooks_tool_js_1.listHooks)('this-category-does-not-exist');
74
+ expect(result.hooks).toHaveLength(0);
75
+ expect(result.total).toBe(0);
76
+ });
77
+ test('hook items всегда содержат name, type, category, parameters_count, return_type', () => {
78
+ const result = (0, hooks_tool_js_1.listHooks)(undefined, undefined, { limit: 100 });
79
+ for (const h of result.hooks) {
80
+ expect(typeof h.name).toBe('string');
81
+ expect(typeof h.type).toBe('string');
82
+ expect(typeof h.category).toBe('string');
83
+ expect(typeof h.parameters_count).toBe('number');
84
+ }
85
+ });
86
+ });
87
+ describe('getHookDetails', () => {
88
+ test('известный хук возвращает полную структуру', () => {
89
+ const candidates = ['content_after_add_approve', 'user_registered', 'admin_login'];
90
+ let resolved = null;
91
+ for (const name of candidates) {
92
+ const r = (0, hooks_tool_js_1.getHookDetails)(name);
93
+ if (!r.code && r.name === name) {
94
+ resolved = name;
95
+ break;
96
+ }
97
+ }
98
+ if (!resolved)
99
+ return; // ни один из тестовых имён не существует в базе
100
+ const result = (0, hooks_tool_js_1.getHookDetails)(resolved);
101
+ expect(result.name).toBe(resolved);
102
+ expect(result.implementation.class_name).toContain('on{AddonName}');
103
+ expect(result.implementation.file_path).toContain(resolved);
104
+ expect(result.manifest_xml).toContain(`name="${resolved}"`);
105
+ });
106
+ test('точный case-insensitive поиск', () => {
107
+ const first = (0, hooks_tool_js_1.listHooks)(undefined, undefined, { limit: 1 });
108
+ const realName = first.hooks[0].name;
109
+ const upper = (0, hooks_tool_js_1.getHookDetails)(realName.toUpperCase());
110
+ // Зависит от того, есть ли такой хук; если точный — должен найтись.
111
+ if (!upper.code) {
112
+ expect(upper.name).toBe(realName);
113
+ }
114
+ });
115
+ test('частичное совпадение → возвращает AMBIGUOUS_HOOK или null без произвольного выбора', () => {
116
+ const result = (0, hooks_tool_js_1.getHookDetails)('content');
117
+ // Если совпало несколько — должны быть перечислены все кандидаты.
118
+ if (result.code === 'AMBIGUOUS_HOOK') {
119
+ expect(Array.isArray(result.candidates)).toBe(true);
120
+ expect(result.candidates.length).toBeGreaterThan(1);
121
+ }
122
+ else {
123
+ // Если один или ноль — должен быть name или HOOK_NOT_FOUND.
124
+ expect(['name', 'code']).toContain(Object.keys(result)[0]);
125
+ }
126
+ });
127
+ test('несуществующий хук → HOOK_NOT_FOUND с similar_hooks', () => {
128
+ const result = (0, hooks_tool_js_1.getHookDetails)('nonexistent_hook_xyzqwerty');
129
+ expect(result.code).toBe('HOOK_NOT_FOUND');
130
+ expect(Array.isArray(result.similar_hooks)).toBe(true);
131
+ });
132
+ });
133
+ describe('searchHooks', () => {
134
+ test('поиск по имени', () => {
135
+ const candidates = (0, hooks_tool_js_1.listHooks)(undefined, undefined, { limit: 3 });
136
+ const someName = candidates.hooks[0].name;
137
+ const part = someName.split('_')[0];
138
+ const result = (0, hooks_tool_js_1.searchHooks)(part);
139
+ expect(result.total).toBeGreaterThan(0);
140
+ expect(result.results.some(h => h.name.includes(part))).toBe(true);
141
+ });
142
+ test('поиск по описанию (case-insensitive)', () => {
143
+ const result = (0, hooks_tool_js_1.searchHooks)('событие');
144
+ expect(result.total).toBeGreaterThanOrEqual(0);
145
+ // Если есть совпадения — проверяем структуру.
146
+ for (const r of result.results) {
147
+ expect(typeof r.name).toBe('string');
148
+ expect(typeof r.type).toBe('string');
149
+ expect(typeof r.category).toBe('string');
150
+ }
151
+ });
152
+ test('уникальные результаты (no duplicates)', () => {
153
+ const list = (0, hooks_tool_js_1.listHooks)(undefined, undefined, { limit: 10 });
154
+ const someName = list.hooks[0].name;
155
+ const result = (0, hooks_tool_js_1.searchHooks)(someName);
156
+ const names = result.results.map(r => r.name);
157
+ expect(new Set(names).size).toBe(names.length);
158
+ });
159
+ test('пустой запрос → 0 результатов', () => {
160
+ const result = (0, hooks_tool_js_1.searchHooks)('zzz_nonexistent_xyzqwerty_abc_999');
161
+ expect(result.total).toBe(0);
162
+ expect(result.results).toHaveLength(0);
163
+ });
164
+ test('property-based: search для произвольных строк не выбрасывает', () => {
165
+ fc.assert(fc.property(fc.string({ minLength: 0, maxLength: 100 }), query => {
166
+ const result = (0, hooks_tool_js_1.searchHooks)(query);
167
+ expect(Array.isArray(result.results)).toBe(true);
168
+ expect(typeof result.total).toBe('number');
169
+ return true;
170
+ }), { numRuns: 30 });
171
+ });
172
+ });
173
+ });
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
4
+ const inMemory_js_1 = require("@modelcontextprotocol/sdk/inMemory.js");
5
+ const server_js_1 = require("../server.js");
6
+ const schemas_js_1 = require("../data/schemas.js");
7
+ const hooks_js_1 = require("../data/hooks.js");
8
+ const components_js_1 = require("../data/components.js");
9
+ describe('MCP integration (extra)', () => {
10
+ async function withClient(fn) {
11
+ const [clientTransport, serverTransport] = inMemory_js_1.InMemoryTransport.createLinkedPair();
12
+ const server = (0, server_js_1.createServer)();
13
+ const client = new index_js_1.Client({ name: 'integration-test', version: '1.0.0' });
14
+ await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
15
+ try {
16
+ return await fn(client);
17
+ }
18
+ finally {
19
+ await client.close();
20
+ await server.close();
21
+ }
22
+ }
23
+ test('get_server_capabilities: число tools соответствует фактическому', async () => {
24
+ await withClient(async (client) => {
25
+ const listed = await client.listTools();
26
+ const caps = await client.callTool({ name: 'get_server_capabilities', arguments: {} });
27
+ const data = caps.structuredContent;
28
+ // Подтверждаем, что tools_count в capabilities соответствует длине каталога.
29
+ expect(data.tools_count).toBe(listed.tools.length);
30
+ // И в частности — это все реально зарегистрированные tools.
31
+ expect(data.tools_count).toBe(100);
32
+ });
33
+ });
34
+ test('find_tool: известный запрос → matches не пустой', async () => {
35
+ await withClient(async (client) => {
36
+ const r = await client.callTool({
37
+ name: 'find_tool',
38
+ arguments: { query: 'создай дополнение' },
39
+ });
40
+ const data = r.structuredContent;
41
+ expect(Array.isArray(data.matches)).toBe(true);
42
+ expect(data.matches.length).toBeGreaterThan(0);
43
+ });
44
+ });
45
+ test('find_tool: ranked содержит информацию о релевантности', async () => {
46
+ await withClient(async (client) => {
47
+ const r = await client.callTool({
48
+ name: 'find_tool',
49
+ arguments: { query: 'audit project upgrade' },
50
+ });
51
+ const data = r.structuredContent;
52
+ expect(Array.isArray(data.ranked)).toBe(true);
53
+ });
54
+ });
55
+ test('get_workflow: каждый известный workflow отдаёт последовательность', async () => {
56
+ await withClient(async (client) => {
57
+ const workflows = ['addon', 'widget', 'template', 'audit', 'repair', 'upgrade'];
58
+ for (const name of workflows) {
59
+ const r = await client.callTool({ name: 'get_workflow', arguments: { workflow: name } });
60
+ const data = r.structuredContent;
61
+ expect(data.workflow).toBe(name);
62
+ expect(Array.isArray(data.tools)).toBe(true);
63
+ expect(data.tools.length).toBeGreaterThan(0);
64
+ }
65
+ });
66
+ });
67
+ test('explain_validation_error: известный код → success, неизвестный → error', async () => {
68
+ await withClient(async (client) => {
69
+ const ok = await client.callTool({
70
+ name: 'explain_validation_error',
71
+ arguments: { code: 'MISSING_REQUIRED_FILE' },
72
+ });
73
+ expect(ok.isError).toBeFalsy();
74
+ const bad = await client.callTool({
75
+ name: 'explain_validation_error',
76
+ arguments: { code: 'UNDEFINED_CODE_XYZ' },
77
+ });
78
+ expect(bad.isError).toBe(true);
79
+ });
80
+ });
81
+ test('compare_instantcms_versions: known и unknown', async () => {
82
+ await withClient(async (client) => {
83
+ const r = await client.callTool({
84
+ name: 'compare_instantcms_versions',
85
+ arguments: { from: '2.17', to: '2.18.2' },
86
+ });
87
+ expect(r.isError).toBeFalsy();
88
+ const r2 = await client.callTool({
89
+ name: 'compare_instantcms_versions',
90
+ arguments: { from: '99.99.99', to: '2.18.2' },
91
+ });
92
+ // Не isError — просто возвращает warnings.
93
+ expect(r2.isError).toBeFalsy();
94
+ });
95
+ });
96
+ test('get_project_health: возвращает status=ready', async () => {
97
+ await withClient(async (client) => {
98
+ const r = await client.callTool({ name: 'get_project_health', arguments: {} });
99
+ const data = r.structuredContent;
100
+ expect(data.status).toBe('ready');
101
+ });
102
+ });
103
+ test('validate_generated_artifacts: валидный набор → is_valid=true', async () => {
104
+ await withClient(async (client) => {
105
+ const r = await client.callTool({
106
+ name: 'validate_generated_artifacts',
107
+ arguments: {
108
+ files: {
109
+ 'manifest.xml': '<?xml version="1.0"?><root/>',
110
+ 'layout.yaml': 'layout:\n rows: []',
111
+ },
112
+ },
113
+ });
114
+ const data = r.structuredContent;
115
+ expect(data.is_valid).toBe(true);
116
+ });
117
+ });
118
+ test('invalid input на list_hooks → isError', async () => {
119
+ await withClient(async (client) => {
120
+ const r = await client.callTool({
121
+ name: 'list_hooks',
122
+ arguments: { limit: -1 },
123
+ });
124
+ // Schema отвергает <0 и >200; Zod или no-throw поведение зависит от реализации.
125
+ // Минимум — не должно throw.
126
+ expect(r).toBeDefined();
127
+ });
128
+ });
129
+ test('tools/list contains весь critical tool set', async () => {
130
+ await withClient(async (client) => {
131
+ const listed = await client.listTools();
132
+ const names = new Set(listed.tools.map(t => t.name));
133
+ // Подмножество, которое ОБЯЗАНО быть для всех наших workflows.
134
+ const critical = [
135
+ 'get_server_capabilities',
136
+ 'find_tool',
137
+ 'get_workflow',
138
+ 'diagnose_request',
139
+ 'list_hooks',
140
+ 'get_hook_details',
141
+ 'search_hooks',
142
+ 'list_components',
143
+ 'get_component_api',
144
+ 'scaffold_addon',
145
+ 'get_addon_structure',
146
+ 'validate_addon',
147
+ 'validate_generated_artifacts',
148
+ 'build_addon_archive',
149
+ 'inspect_addon_archive',
150
+ 'get_field_types',
151
+ ];
152
+ for (const name of critical) {
153
+ expect(names.has(name)).toBe(true);
154
+ }
155
+ });
156
+ });
157
+ test('knowledge volume count matches встроенным справочникам', async () => {
158
+ await withClient(async (client) => {
159
+ const r = await client.callTool({ name: 'get_server_capabilities', arguments: {} });
160
+ const data = r.structuredContent;
161
+ expect(data.knowledge.hooks).toBe(hooks_js_1.hooks.length);
162
+ expect(data.knowledge.components).toBe(components_js_1.components.length);
163
+ expect(data.knowledge.addon_types.length).toBe(Object.keys(schemas_js_1.addonStructures).length);
164
+ });
165
+ });
166
+ });
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
4
+ const inMemory_js_1 = require("@modelcontextprotocol/sdk/inMemory.js");
5
+ const server_js_1 = require("../server.js");
6
+ describe('MCP integration', () => {
7
+ test('lists tools and returns structured capabilities', async () => {
8
+ const [clientTransport, serverTransport] = inMemory_js_1.InMemoryTransport.createLinkedPair();
9
+ const server = (0, server_js_1.createServer)();
10
+ const client = new index_js_1.Client({ name: 'integration-test', version: '1.0.0' });
11
+ await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
12
+ try {
13
+ const listed = await client.listTools();
14
+ expect(listed.tools.some(tool => tool.name === 'get_server_capabilities')).toBe(true);
15
+ expect(listed.tools.some(tool => tool.name === 'validate_generated_artifacts')).toBe(true);
16
+ expect(listed.tools.some(tool => tool.name === 'audit_instantcms_project')).toBe(true);
17
+ expect(listed.tools.some(tool => tool.name === 'plan_instantcms_upgrade')).toBe(true);
18
+ expect(listed.tools.some(tool => tool.name === 'load_instantcms_project')).toBe(true);
19
+ expect(listed.tools.some(tool => tool.name === 'create_project_patch')).toBe(true);
20
+ expect(listed.tools.some(tool => tool.name === 'scaffold_complete_template')).toBe(true);
21
+ expect(listed.tools.some(tool => tool.name === 'analyze_instantcms_template')).toBe(true);
22
+ expect(listed.tools.some(tool => tool.name === 'check_template_override_compatibility')).toBe(true);
23
+ expect(listed.tools.some(tool => tool.name === 'merge_template_overrides')).toBe(true);
24
+ expect(listed.tools.some(tool => tool.name === 'audit_template_frontend')).toBe(true);
25
+ expect(listed.tools.some(tool => tool.name === 'scaffold_template_e2e_environment')).toBe(true);
26
+ expect(listed.tools.some(tool => tool.name === 'index_upstream_template_sources')).toBe(true);
27
+ expect(listed.tools.some(tool => tool.name === 'scaffold_template_php_quality')).toBe(true);
28
+ expect(listed.tools).toHaveLength(100);
29
+ const result = await client.callTool({ name: 'get_server_capabilities', arguments: {} });
30
+ expect(result.structuredContent).toMatchObject({ server_version: '1.2.3' });
31
+ }
32
+ finally {
33
+ await client.close();
34
+ await server.close();
35
+ }
36
+ });
37
+ test('supports pagination and rejects invalid input at protocol boundary', async () => {
38
+ const [clientTransport, serverTransport] = inMemory_js_1.InMemoryTransport.createLinkedPair();
39
+ const server = (0, server_js_1.createServer)();
40
+ const client = new index_js_1.Client({ name: 'integration-test', version: '1.0.0' });
41
+ await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
42
+ try {
43
+ const page = await client.callTool({ name: 'list_hooks', arguments: { limit: 2 } });
44
+ expect(page.structuredContent.page.returned).toBe(2);
45
+ const invalid = await client.callTool({ name: 'list_hooks', arguments: { limit: 5000 } });
46
+ expect(invalid.isError).toBe(true);
47
+ }
48
+ finally {
49
+ await client.close();
50
+ await server.close();
51
+ }
52
+ });
53
+ });
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const fc = __importStar(require("fast-check"));
37
+ const pagination_js_1 = require("../utils/pagination.js");
38
+ describe('pagination property-based', () => {
39
+ test('возвращённых элементов никогда не больше limit', () => {
40
+ fc.assert(fc.property(fc.array(fc.string(), { maxLength: 500 }), fc.integer({ min: -10, max: 300 }), (items, rawLimit) => {
41
+ const limit = rawLimit; // функция сама зажимает.
42
+ const page = (0, pagination_js_1.paginate)(items, { limit });
43
+ return page.items.length <= Math.max(1, Math.min(limit === 0 ? 50 : limit, 200));
44
+ }), { numRuns: 100 });
45
+ });
46
+ test('limit автоматически clamp в диапазон [1, 200]', () => {
47
+ fc.assert(fc.property(fc.array(fc.string(), { maxLength: 50 }), fc.integer({ min: -50, max: 1000 }), (items, limit) => {
48
+ const page = (0, pagination_js_1.paginate)(items, { limit });
49
+ return page.page.limit >= 1 && page.page.limit <= 200;
50
+ }), { numRuns: 100 });
51
+ });
52
+ test('limit по умолчанию равен 50', () => {
53
+ fc.assert(fc.property(fc.array(fc.string(), { maxLength: 100 }), items => {
54
+ const page = (0, pagination_js_1.paginate)(items);
55
+ return page.page.limit === 50;
56
+ }), { numRuns: 20 });
57
+ });
58
+ test('total отражает размер исходного массива', () => {
59
+ fc.assert(fc.property(fc.array(fc.string(), { maxLength: 200 }), fc.integer({ min: 1, max: 50 }), (items, limit) => {
60
+ const page = (0, pagination_js_1.paginate)(items, { limit });
61
+ return page.page.total === items.length;
62
+ }), { numRuns: 100 });
63
+ });
64
+ test('returned элементов равно длине страницы', () => {
65
+ fc.assert(fc.property(fc.array(fc.string(), { maxLength: 200 }), fc.integer({ min: 1, max: 100 }), (items, limit) => {
66
+ const page = (0, pagination_js_1.paginate)(items, { limit });
67
+ return page.page.returned === page.items.length;
68
+ }), { numRuns: 100 });
69
+ });
70
+ test('невалидный/повреждённый cursor безопасно стартует с 0', () => {
71
+ const cursors = ['', '!!not-base64!!', 'AAAA', '!!!!', '__$$', '\u0000\u0000', ' '];
72
+ for (const c of cursors) {
73
+ const result = (0, pagination_js_1.paginate)(['a', 'b', 'c'], { cursor: c, limit: 10 });
74
+ expect(result.items.length).toBeGreaterThanOrEqual(0);
75
+ expect(Array.isArray(result.items)).toBe(true);
76
+ }
77
+ });
78
+ test('cursor за пределами массива возвращает пустой список и next_cursor=null', () => {
79
+ const huge = (0, pagination_js_1.paginate)([1, 2, 3], { limit: 5, cursor: 'Zm9vYmFy' /* arbitrary */ });
80
+ // arbitrary base64 может распарситься в мусор; cursor никогда не должен сломать результат
81
+ expect(Array.isArray(huge.items)).toBe(true);
82
+ });
83
+ test('объединение всех страниц с корректными курсорами даёт исходный массив', () => {
84
+ fc.assert(fc.property(fc.array(fc.string({ maxLength: 50 }), { maxLength: 30 }), fc.integer({ min: 1, max: 7 }), (items, limit) => {
85
+ const result = [];
86
+ let cursor;
87
+ let safety = 100; // защита от бесконечного цикла
88
+ while (safety-- > 0) {
89
+ const page = (0, pagination_js_1.paginate)(items, { cursor, limit });
90
+ result.push(...page.items);
91
+ if (!page.page.next_cursor)
92
+ break;
93
+ cursor = page.page.next_cursor;
94
+ }
95
+ return JSON.stringify(result) === JSON.stringify(items);
96
+ }), { numRuns: 50 });
97
+ });
98
+ test('пустой массив → пустой page с next_cursor=null', () => {
99
+ const page = (0, pagination_js_1.paginate)([]);
100
+ expect(page.items).toEqual([]);
101
+ expect(page.page.returned).toBe(0);
102
+ expect(page.page.total).toBe(0);
103
+ expect(page.page.next_cursor).toBeNull();
104
+ expect(page.page.limit).toBe(50);
105
+ });
106
+ test('массив короче limit → одна страница, next_cursor=null', () => {
107
+ fc.assert(fc.property(fc.array(fc.string(), { maxLength: 10 }), fc.integer({ min: 20, max: 200 }), (items, limit) => {
108
+ const page = (0, pagination_js_1.paginate)(items, { limit });
109
+ expect(page.items.length).toBe(items.length);
110
+ expect(page.page.next_cursor).toBeNull();
111
+ return true;
112
+ }), { numRuns: 30 });
113
+ });
114
+ });
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const hooks_tool_js_1 = require("../tools/hooks-tool.js");
4
+ const addon_tool_js_1 = require("../tools/addon-tool.js");
5
+ const pagination_js_1 = require("../utils/pagination.js");
6
+ const find_tool_js_1 = require("../utils/find-tool.js");
7
+ describe('performance baseline', () => {
8
+ test('listHooks: < 50ms для одного вызова без пагинации', () => {
9
+ const start = Date.now();
10
+ (0, hooks_tool_js_1.listHooks)();
11
+ const elapsed = Date.now() - start;
12
+ expect(elapsed).toBeLessThan(50);
13
+ });
14
+ test('listHooks с пагинацией: < 50ms', () => {
15
+ const start = Date.now();
16
+ (0, hooks_tool_js_1.listHooks)(undefined, undefined, { limit: 100 });
17
+ const elapsed = Date.now() - start;
18
+ expect(elapsed).toBeLessThan(50);
19
+ });
20
+ test('listComponents: < 100ms', () => {
21
+ const start = Date.now();
22
+ (0, addon_tool_js_1.listComponents)();
23
+ const elapsed = Date.now() - start;
24
+ expect(elapsed).toBeLessThan(100);
25
+ });
26
+ test('paginate вызов: < 5ms', () => {
27
+ const arr = Array.from({ length: 1000 }, (_, i) => `item-${i}`);
28
+ const start = Date.now();
29
+ (0, pagination_js_1.paginate)(arr, { limit: 100 });
30
+ const elapsed = Date.now() - start;
31
+ expect(elapsed).toBeLessThan(5);
32
+ });
33
+ test('rankToolCategories: < 20ms на 10 категориях', () => {
34
+ const cats = Array.from({ length: 10 }, (_, i) => ({
35
+ category: `cat${i}`,
36
+ keywords: [`kw_${i}`, `kw_shared`],
37
+ tools: ['t1'],
38
+ }));
39
+ const start = Date.now();
40
+ (0, find_tool_js_1.rankToolCategories)('test query with multiple tokens', cats);
41
+ const elapsed = Date.now() - start;
42
+ expect(elapsed).toBeLessThan(20);
43
+ });
44
+ test('100 последовательных listHooks — без утечек', () => {
45
+ const startMem = process.memoryUsage().heapUsed;
46
+ for (let i = 0; i < 100; i += 1) {
47
+ (0, hooks_tool_js_1.listHooks)(undefined, undefined, { limit: 10 });
48
+ }
49
+ if (global.gc)
50
+ global.gc();
51
+ const endMem = process.memoryUsage().heapUsed;
52
+ const diffMb = (endMem - startMem) / 1024 / 1024;
53
+ // Достаточно грубая проверка — не растёт бесконтрольно.
54
+ expect(diffMb).toBeLessThan(5);
55
+ });
56
+ });