poker-mcp 1.0.0

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 (50) hide show
  1. package/README.md +241 -0
  2. package/config/mcp-manifest.json +1713 -0
  3. package/config/package-lock.json +1335 -0
  4. package/config/poker_mcp.code-workspace +8 -0
  5. package/package.json +51 -0
  6. package/src/config/ConfigManager.js +120 -0
  7. package/src/config/default.json +62 -0
  8. package/src/mcp/handlers/bodyHandlers.js +63 -0
  9. package/src/mcp/handlers/buildupFactorHandlers.js +43 -0
  10. package/src/mcp/handlers/calculationHandlers.js +265 -0
  11. package/src/mcp/handlers/detectorHandlers.js +201 -0
  12. package/src/mcp/handlers/index.js +43 -0
  13. package/src/mcp/handlers/sourceHandlers.js +25 -0
  14. package/src/mcp/handlers/transformHandlers.js +26 -0
  15. package/src/mcp/handlers/unitHandlers.js +305 -0
  16. package/src/mcp/handlers/zoneHandlers.js +83 -0
  17. package/src/mcp/middleware/errorHandler.js +49 -0
  18. package/src/mcp/middleware/requestValidator.js +129 -0
  19. package/src/mcp/server.js +76 -0
  20. package/src/mcp/tools/bodyTools.js +185 -0
  21. package/src/mcp/tools/buildupFactorTools.js +82 -0
  22. package/src/mcp/tools/calculationTools.js +58 -0
  23. package/src/mcp/tools/commonTools.js +22 -0
  24. package/src/mcp/tools/detectorTools.js +131 -0
  25. package/src/mcp/tools/index.js +34 -0
  26. package/src/mcp/tools/sourceTools.js +726 -0
  27. package/src/mcp/tools/sourceTools_fixed_part2.js +41 -0
  28. package/src/mcp/tools/transformTools.js +73 -0
  29. package/src/mcp/tools/unitTools.js +137 -0
  30. package/src/mcp/tools/zoneTools.js +65 -0
  31. package/src/mcp_server_stdio_v4.js +45 -0
  32. package/src/routes/restApi.js +184 -0
  33. package/src/services/CalculationService.js +525 -0
  34. package/src/services/DataManager.js +738 -0
  35. package/src/services/TaskManager.js +1622 -0
  36. package/src/utils/MaterialAlternatives.js +189 -0
  37. package/src/utils/errorHandler.js +68 -0
  38. package/src/utils/errors.js +37 -0
  39. package/src/utils/logger.js +24 -0
  40. package/src/utils/logger_no_color.js +28 -0
  41. package/src/utils/mcpErrors.js +225 -0
  42. package/src/validators/CMBValidator.js +191 -0
  43. package/src/validators/DetectorValidator.js +512 -0
  44. package/src/validators/ManifestValidator.js +249 -0
  45. package/src/validators/NuclideValidator.js +171 -0
  46. package/src/validators/PhysicsValidator.js +401 -0
  47. package/src/validators/PhysicsValidatorSimple.js +87 -0
  48. package/src/validators/SourceValidator.js +515 -0
  49. package/src/validators/TransformValidator.js +472 -0
  50. package/src/validators/UnitValidator.js +562 -0
@@ -0,0 +1,472 @@
1
+ // validators/TransformValidator.js
2
+ import { PokerMcpError } from '../utils/mcpErrors.js';
3
+ import { ManifestValidator } from './ManifestValidator.js';
4
+
5
+ /**
6
+ * Transform参照の検証を行うクラス(強化版)
7
+ * YAMLデータのtransformセクション内での名前存在確認と依存関係管理
8
+ */
9
+ export class TransformValidator {
10
+
11
+ /**
12
+ * Transform参照の存在確認
13
+ * @param {string} transformName - 参照するtransform名
14
+ * @param {Array} existingTransforms - YAMLファイル内のtransform配列
15
+ * @param {string} fieldName - フィールド名(エラー表示用)
16
+ */
17
+ static validateTransformReference(transformName, existingTransforms, fieldName = 'transform') {
18
+ // transform名自体のバリデーション
19
+ ManifestValidator.validateObjectName(transformName, fieldName);
20
+
21
+ // 既存transformsがnullまたは未定義の場合
22
+ if (!existingTransforms || !Array.isArray(existingTransforms)) {
23
+ throw PokerMcpError.transformNotFound(transformName);
24
+ }
25
+
26
+ // transform名の存在確認
27
+ const transformExists = existingTransforms.some(t => t && t.name === transformName);
28
+
29
+ if (!transformExists) {
30
+ throw PokerMcpError.invalidTransformReference(transformName);
31
+ }
32
+
33
+ return true;
34
+ }
35
+
36
+ /**
37
+ * Transform操作の基本バリデーション
38
+ * @param {Array} operations - 変換操作の配列
39
+ */
40
+ static validateTransformOperations(operations) {
41
+ if (!Array.isArray(operations) || operations.length === 0) {
42
+ throw PokerMcpError.validationError(
43
+ 'operations must be a non-empty array',
44
+ 'operations',
45
+ operations
46
+ );
47
+ }
48
+
49
+ for (let i = 0; i < operations.length; i++) {
50
+ const operation = operations[i];
51
+
52
+ if (!operation || typeof operation !== 'object') {
53
+ throw PokerMcpError.validationError(
54
+ `operation[${i}] must be an object`,
55
+ `operations[${i}]`,
56
+ operation
57
+ );
58
+ }
59
+
60
+ const keys = Object.keys(operation);
61
+ if (keys.length !== 1) {
62
+ throw PokerMcpError.validationError(
63
+ `operation[${i}] must have exactly one key`,
64
+ `operations[${i}]`,
65
+ operation
66
+ );
67
+ }
68
+
69
+ const operationType = keys[0];
70
+ const operationValue = operation[operationType];
71
+
72
+ switch (operationType) {
73
+ case 'translate':
74
+ ManifestValidator.validateCoordinateString(operationValue, `operations[${i}].translate`);
75
+ break;
76
+
77
+ case 'rotate_around_x':
78
+ case 'rotate_around_y':
79
+ case 'rotate_around_z':
80
+ ManifestValidator.validateRotationAngle(operationValue, `operations[${i}].${operationType}`);
81
+ break;
82
+
83
+ default:
84
+ throw PokerMcpError.validationError(
85
+ `Invalid operation type: ${operationType}. Valid types: translate, rotate_around_x, rotate_around_y, rotate_around_z`,
86
+ `operations[${i}]`,
87
+ operationType
88
+ );
89
+ }
90
+ }
91
+
92
+ return true;
93
+ }
94
+
95
+ /**
96
+ * Transform操作の物理的妥当性チェック
97
+ * @param {Array} operations - 変換操作の配列
98
+ * @param {string} transformName - Transform名(エラー表示用)
99
+ */
100
+ static validateTransformPhysics(operations, transformName = 'transform') {
101
+ let hasRotation = false;
102
+ let hasTranslation = false;
103
+ let totalRotationMagnitude = 0;
104
+
105
+ for (let i = 0; i < operations.length; i++) {
106
+ const operation = operations[i];
107
+ const operationType = Object.keys(operation)[0];
108
+ const operationValue = operation[operationType];
109
+
110
+ switch (operationType) {
111
+ case 'translate':
112
+ hasTranslation = true;
113
+ // 移動量の大きさチェック
114
+ const [dx, dy, dz] = operationValue.split(/\s+/).map(Number);
115
+ const translationMagnitude = Math.sqrt(dx*dx + dy*dy + dz*dz);
116
+ if (translationMagnitude > 10000) {
117
+ console.warn(`Transform '${transformName}': 非常に大きな移動量です (${translationMagnitude.toFixed(2)})`);
118
+ }
119
+ break;
120
+
121
+ case 'rotate_around_x':
122
+ case 'rotate_around_y':
123
+ case 'rotate_around_z':
124
+ hasRotation = true;
125
+ totalRotationMagnitude += Math.abs(operationValue);
126
+
127
+ // 異常な回転角度のチェック
128
+ if (Math.abs(operationValue) > 360) {
129
+ console.warn(`Transform '${transformName}': 360度を超える回転角度です (${operationValue}度)`);
130
+ }
131
+ break;
132
+ }
133
+ }
134
+
135
+ // 総回転量のチェック
136
+ if (totalRotationMagnitude > 720) {
137
+ console.warn(`Transform '${transformName}': 総回転量が非常に大きいです (${totalRotationMagnitude.toFixed(1)}度)`);
138
+ }
139
+
140
+ // Transformの意味チェック
141
+ if (!hasRotation && !hasTranslation) {
142
+ console.warn(`Transform '${transformName}': 回転も移動も含まれていません`);
143
+ }
144
+
145
+ return {
146
+ hasRotation,
147
+ hasTranslation,
148
+ totalRotationMagnitude,
149
+ operationCount: operations.length
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Transform名の一意性チェック
155
+ * @param {string} transformName - 新しいTransform名
156
+ * @param {Array} existingTransforms - 既存Transform配列
157
+ * @param {Array} pendingChanges - 保留中の変更配列
158
+ */
159
+ static validateTransformUniqueness(transformName, existingTransforms = [], pendingChanges = []) {
160
+ // 既存Transformとの重複チェック
161
+ if (existingTransforms.some(t => t && t.name === transformName)) {
162
+ throw PokerMcpError.duplicateName(transformName, 'transform');
163
+ }
164
+
165
+ // 保留中の変更との重複チェック
166
+ const pendingTransformChanges = pendingChanges.filter(
167
+ change => change.action === 'proposeTransform'
168
+ );
169
+
170
+ for (const change of pendingTransformChanges) {
171
+ if (change.data && change.data.name === transformName) {
172
+ throw PokerMcpError.duplicateName(transformName, 'transform (pending)');
173
+ }
174
+ }
175
+
176
+ return true;
177
+ }
178
+
179
+ /**
180
+ * Body/Source/DetectorのTransform参照を検証
181
+ * @param {string} transformName - 参照するtransform名
182
+ * @param {Object} yamlData - YAMLデータ全体
183
+ * @param {string} contextType - コンテキストタイプ(body/source/detector)
184
+ * @param {string} contextName - コンテキスト名
185
+ */
186
+ static validateContextTransformReference(transformName, yamlData, contextType, contextName) {
187
+ if (!transformName) {
188
+ return true; // transform参照はオプション
189
+ }
190
+
191
+ try {
192
+ this.validateTransformReference(transformName, yamlData.transform, 'transform');
193
+ } catch (error) {
194
+ // エラーメッセージにコンテキスト情報を追加
195
+ if (error instanceof PokerMcpError) {
196
+ throw new PokerMcpError(
197
+ error.code,
198
+ `${contextType} '${contextName}' references invalid transform: ${error.message}`,
199
+ 'transform',
200
+ transformName
201
+ );
202
+ }
203
+ throw error;
204
+ }
205
+
206
+ return true;
207
+ }
208
+
209
+ /**
210
+ * Transform依存関係チェック(詳細版)
211
+ * Transform削除時に、そのTransformを参照しているBody/Source/Detectorがないかチェック
212
+ * @param {string} transformName - 削除対象のtransform名
213
+ * @param {Object} yamlData - YAMLデータ全体
214
+ * @param {Array} pendingChanges - 保留中の変更配列
215
+ */
216
+ static checkTransformDependencies(transformName, yamlData, pendingChanges = []) {
217
+ const dependencies = [];
218
+
219
+ // 既存データの依存関係チェック
220
+ this._checkExistingDependencies(transformName, yamlData, dependencies);
221
+
222
+ // 保留中の変更の依存関係チェック
223
+ this._checkPendingDependencies(transformName, pendingChanges, dependencies);
224
+
225
+ if (dependencies.length > 0) {
226
+ throw PokerMcpError.dependencyExists(transformName, dependencies);
227
+ }
228
+
229
+ return {
230
+ dependencies: [],
231
+ canDelete: true,
232
+ checkedSections: ['body', 'source', 'detector', 'pending_changes']
233
+ };
234
+ }
235
+
236
+ /**
237
+ * 既存データの依存関係チェック
238
+ * @private
239
+ */
240
+ static _checkExistingDependencies(transformName, yamlData, dependencies) {
241
+ // Body内のtransform参照をチェック
242
+ if (yamlData.body && Array.isArray(yamlData.body)) {
243
+ for (const body of yamlData.body) {
244
+ if (body.transform === transformName) {
245
+ dependencies.push({
246
+ type: 'body',
247
+ name: body.name,
248
+ context: `Body '${body.name}' uses transform '${transformName}'`
249
+ });
250
+ }
251
+ }
252
+ }
253
+
254
+ // Source内のtransform参照をチェック
255
+ if (yamlData.source && Array.isArray(yamlData.source)) {
256
+ for (const source of yamlData.source) {
257
+ // geometry内のtransform参照をチェック
258
+ if (source.geometry && source.geometry.transform === transformName) {
259
+ dependencies.push({
260
+ type: 'source',
261
+ name: source.name,
262
+ context: `Source '${source.name}' geometry uses transform '${transformName}'`
263
+ });
264
+ }
265
+ }
266
+ }
267
+
268
+ // Detector内のtransform参照をチェック
269
+ if (yamlData.detector && Array.isArray(yamlData.detector)) {
270
+ for (const detector of yamlData.detector) {
271
+ if (detector.transform === transformName) {
272
+ dependencies.push({
273
+ type: 'detector',
274
+ name: detector.name,
275
+ context: `Detector '${detector.name}' uses transform '${transformName}'`
276
+ });
277
+ }
278
+ }
279
+ }
280
+ }
281
+
282
+ /**
283
+ * 保留中の変更の依存関係チェック
284
+ * @private
285
+ */
286
+ static _checkPendingDependencies(transformName, pendingChanges, dependencies) {
287
+ for (const change of pendingChanges) {
288
+ // Body関連の保留中変更
289
+ if (change.action === 'add_body' || change.action === 'updateBody') {
290
+ const bodyData = change.data.body || change.data;
291
+ if (bodyData && bodyData.transform === transformName) {
292
+ dependencies.push({
293
+ type: 'pending_body',
294
+ name: bodyData.name,
295
+ context: `Pending body '${bodyData.name}' will use transform '${transformName}'`
296
+ });
297
+ }
298
+ }
299
+
300
+ // Source関連の保留中変更
301
+ if (change.action === 'proposeSource' || change.action === 'updateSource') {
302
+ const sourceData = change.data;
303
+ if (sourceData && sourceData.geometry && sourceData.geometry.transform === transformName) {
304
+ dependencies.push({
305
+ type: 'pending_source',
306
+ name: sourceData.name,
307
+ context: `Pending source '${sourceData.name}' will use transform '${transformName}'`
308
+ });
309
+ }
310
+ }
311
+
312
+ // Detector関連の保留中変更
313
+ if (change.action === 'proposeDetector' || change.action === 'updateDetector') {
314
+ const detectorData = change.data;
315
+ if (detectorData && detectorData.transform === transformName) {
316
+ dependencies.push({
317
+ type: 'pending_detector',
318
+ name: detectorData.name,
319
+ context: `Pending detector '${detectorData.name}' will use transform '${transformName}'`
320
+ });
321
+ }
322
+ }
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Transform参照の完全性チェック
328
+ * システム全体で参照されているtransformが実際に存在するかチェック
329
+ * @param {Object} yamlData - YAMLデータ全体
330
+ */
331
+ static validateTransformIntegrity(yamlData) {
332
+ const issues = [];
333
+ const availableTransforms = new Set();
334
+
335
+ // 利用可能なTransform名を収集
336
+ if (yamlData.transform && Array.isArray(yamlData.transform)) {
337
+ for (const transform of yamlData.transform) {
338
+ if (transform && transform.name) {
339
+ availableTransforms.add(transform.name);
340
+ }
341
+ }
342
+ }
343
+
344
+ // Bodyのtransform参照をチェック
345
+ if (yamlData.body && Array.isArray(yamlData.body)) {
346
+ for (const body of yamlData.body) {
347
+ if (body.transform && !availableTransforms.has(body.transform)) {
348
+ issues.push({
349
+ type: 'missing_transform',
350
+ context: 'body',
351
+ name: body.name,
352
+ missingTransform: body.transform,
353
+ message: `Body '${body.name}' references non-existent transform '${body.transform}'`
354
+ });
355
+ }
356
+ }
357
+ }
358
+
359
+ // Sourceのtransform参照をチェック
360
+ if (yamlData.source && Array.isArray(yamlData.source)) {
361
+ for (const source of yamlData.source) {
362
+ if (source.geometry && source.geometry.transform && !availableTransforms.has(source.geometry.transform)) {
363
+ issues.push({
364
+ type: 'missing_transform',
365
+ context: 'source',
366
+ name: source.name,
367
+ missingTransform: source.geometry.transform,
368
+ message: `Source '${source.name}' references non-existent transform '${source.geometry.transform}'`
369
+ });
370
+ }
371
+ }
372
+ }
373
+
374
+ // Detectorのtransform参照をチェック
375
+ if (yamlData.detector && Array.isArray(yamlData.detector)) {
376
+ for (const detector of yamlData.detector) {
377
+ if (detector.transform && !availableTransforms.has(detector.transform)) {
378
+ issues.push({
379
+ type: 'missing_transform',
380
+ context: 'detector',
381
+ name: detector.name,
382
+ missingTransform: detector.transform,
383
+ message: `Detector '${detector.name}' references non-existent transform '${detector.transform}'`
384
+ });
385
+ }
386
+ }
387
+ }
388
+
389
+ return {
390
+ isValid: issues.length === 0,
391
+ issues,
392
+ availableTransforms: Array.from(availableTransforms),
393
+ checkedReferences: {
394
+ body: yamlData.body?.length || 0,
395
+ source: yamlData.source?.length || 0,
396
+ detector: yamlData.detector?.length || 0
397
+ }
398
+ };
399
+ }
400
+
401
+ /**
402
+ * Transform使用状況の統計情報を取得
403
+ * @param {Object} yamlData - YAMLデータ全体
404
+ */
405
+ static getTransformUsageStats(yamlData) {
406
+ const stats = new Map();
407
+ const allTransforms = new Set();
408
+
409
+ // 定義されているTransformを登録
410
+ if (yamlData.transform && Array.isArray(yamlData.transform)) {
411
+ for (const transform of yamlData.transform) {
412
+ if (transform && transform.name) {
413
+ allTransforms.add(transform.name);
414
+ stats.set(transform.name, {
415
+ name: transform.name,
416
+ operationCount: transform.operations?.length || 0,
417
+ usedBy: {
418
+ body: [],
419
+ source: [],
420
+ detector: []
421
+ },
422
+ totalUsage: 0
423
+ });
424
+ }
425
+ }
426
+ }
427
+
428
+ // Bodyでの使用状況を記録
429
+ if (yamlData.body && Array.isArray(yamlData.body)) {
430
+ for (const body of yamlData.body) {
431
+ if (body.transform && stats.has(body.transform)) {
432
+ const stat = stats.get(body.transform);
433
+ stat.usedBy.body.push(body.name);
434
+ stat.totalUsage++;
435
+ }
436
+ }
437
+ }
438
+
439
+ // Sourceでの使用状況を記録
440
+ if (yamlData.source && Array.isArray(yamlData.source)) {
441
+ for (const source of yamlData.source) {
442
+ if (source.geometry && source.geometry.transform && stats.has(source.geometry.transform)) {
443
+ const stat = stats.get(source.geometry.transform);
444
+ stat.usedBy.source.push(source.name);
445
+ stat.totalUsage++;
446
+ }
447
+ }
448
+ }
449
+
450
+ // Detectorでの使用状況を記録
451
+ if (yamlData.detector && Array.isArray(yamlData.detector)) {
452
+ for (const detector of yamlData.detector) {
453
+ if (detector.transform && stats.has(detector.transform)) {
454
+ const stat = stats.get(detector.transform);
455
+ stat.usedBy.detector.push(detector.name);
456
+ stat.totalUsage++;
457
+ }
458
+ }
459
+ }
460
+
461
+ return {
462
+ totalTransforms: allTransforms.size,
463
+ usedTransforms: Array.from(stats.values()).filter(s => s.totalUsage > 0).length,
464
+ unusedTransforms: Array.from(stats.values()).filter(s => s.totalUsage === 0).map(s => s.name),
465
+ detailedStats: Array.from(stats.values()),
466
+ summary: {
467
+ mostUsed: Array.from(stats.values()).sort((a, b) => b.totalUsage - a.totalUsage)[0],
468
+ averageUsage: Array.from(stats.values()).reduce((sum, s) => sum + s.totalUsage, 0) / allTransforms.size || 0
469
+ }
470
+ };
471
+ }
472
+ }