@provartesting/provardx-cli 1.5.0-beta.13 → 1.5.0-beta.15

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 (60) hide show
  1. package/lib/commands/provar/mcp/start.d.ts +2 -0
  2. package/lib/commands/provar/mcp/start.js +14 -1
  3. package/lib/commands/provar/mcp/start.js.map +1 -1
  4. package/lib/mcp/docs/PROVAR_TEST_STEP_REFERENCE.md +2 -2
  5. package/lib/mcp/prompts/loopPrompts.js +18 -18
  6. package/lib/mcp/prompts/migrationPrompts.js +10 -10
  7. package/lib/mcp/server.d.ts +5 -0
  8. package/lib/mcp/server.js +14 -3
  9. package/lib/mcp/server.js.map +1 -1
  10. package/lib/mcp/tools/antTools.js +140 -129
  11. package/lib/mcp/tools/antTools.js.map +1 -1
  12. package/lib/mcp/tools/automationTools.js +125 -103
  13. package/lib/mcp/tools/automationTools.js.map +1 -1
  14. package/lib/mcp/tools/connectionTools.js +17 -13
  15. package/lib/mcp/tools/connectionTools.js.map +1 -1
  16. package/lib/mcp/tools/defectTools.js +35 -28
  17. package/lib/mcp/tools/defectTools.js.map +1 -1
  18. package/lib/mcp/tools/hierarchyValidate.d.ts +1 -1
  19. package/lib/mcp/tools/hierarchyValidate.js +127 -42
  20. package/lib/mcp/tools/hierarchyValidate.js.map +1 -1
  21. package/lib/mcp/tools/nitroXTools.js +161 -142
  22. package/lib/mcp/tools/nitroXTools.js.map +1 -1
  23. package/lib/mcp/tools/pageObjectGenerate.js +47 -40
  24. package/lib/mcp/tools/pageObjectGenerate.js.map +1 -1
  25. package/lib/mcp/tools/pageObjectValidate.js +136 -46
  26. package/lib/mcp/tools/pageObjectValidate.js.map +1 -1
  27. package/lib/mcp/tools/projectInspect.js +51 -30
  28. package/lib/mcp/tools/projectInspect.js.map +1 -1
  29. package/lib/mcp/tools/projectValidateFromPath.js +70 -49
  30. package/lib/mcp/tools/projectValidateFromPath.js.map +1 -1
  31. package/lib/mcp/tools/propertiesTools.js +64 -48
  32. package/lib/mcp/tools/propertiesTools.js.map +1 -1
  33. package/lib/mcp/tools/qualityHubApiTools.js +48 -44
  34. package/lib/mcp/tools/qualityHubApiTools.js.map +1 -1
  35. package/lib/mcp/tools/qualityHubTools.js +76 -52
  36. package/lib/mcp/tools/qualityHubTools.js.map +1 -1
  37. package/lib/mcp/tools/rcaTools.js +63 -55
  38. package/lib/mcp/tools/rcaTools.js.map +1 -1
  39. package/lib/mcp/tools/testCaseGenerate.js +46 -35
  40. package/lib/mcp/tools/testCaseGenerate.js.map +1 -1
  41. package/lib/mcp/tools/testCaseStepTools.js +38 -34
  42. package/lib/mcp/tools/testCaseStepTools.js.map +1 -1
  43. package/lib/mcp/tools/testCaseValidate.js +60 -41
  44. package/lib/mcp/tools/testCaseValidate.js.map +1 -1
  45. package/lib/mcp/tools/testPlanTools.js +198 -76
  46. package/lib/mcp/tools/testPlanTools.js.map +1 -1
  47. package/lib/mcp/tools/testPlanValidate.js +56 -18
  48. package/lib/mcp/tools/testPlanValidate.js.map +1 -1
  49. package/lib/mcp/tools/testSuiteValidate.js +37 -11
  50. package/lib/mcp/tools/testSuiteValidate.js.map +1 -1
  51. package/lib/mcp/update/updateChecker.d.ts +14 -0
  52. package/lib/mcp/update/updateChecker.js +228 -0
  53. package/lib/mcp/update/updateChecker.js.map +1 -0
  54. package/lib/services/projectValidation.d.ts +5 -2
  55. package/lib/services/projectValidation.js +83 -31
  56. package/lib/services/projectValidation.js.map +1 -1
  57. package/messages/sf.provar.auth.clear.md +1 -1
  58. package/messages/sf.provar.mcp.start.md +83 -48
  59. package/oclif.manifest.json +224 -212
  60. package/package.json +1 -1
@@ -90,16 +90,20 @@ function validateRootProperties(obj, issues) {
90
90
  // NX001: componentId must be present and a valid UUID
91
91
  if (obj['componentId'] === undefined || obj['componentId'] === null) {
92
92
  issues.push({
93
- rule_id: 'NX001', severity: 'ERROR',
93
+ rule_id: 'NX001',
94
+ severity: 'ERROR',
94
95
  message: 'componentId is required.',
95
- applies_to: 'root', field: 'componentId',
96
+ applies_to: 'root',
97
+ field: 'componentId',
96
98
  });
97
99
  }
98
100
  else if (typeof obj['componentId'] !== 'string' || !UUID_RE.test(obj['componentId'])) {
99
101
  issues.push({
100
- rule_id: 'NX001', severity: 'ERROR',
102
+ rule_id: 'NX001',
103
+ severity: 'ERROR',
101
104
  message: `componentId must be a valid UUID, got: "${String(obj['componentId'])}".`,
102
- applies_to: 'root', field: 'componentId',
105
+ applies_to: 'root',
106
+ field: 'componentId',
103
107
  });
104
108
  }
105
109
  // NX002: Root (no parentId) requires name, type, pageStructureElement, fieldDetailsElement
@@ -108,9 +112,11 @@ function validateRootProperties(obj, issues) {
108
112
  for (const field of ['name', 'type', 'pageStructureElement', 'fieldDetailsElement']) {
109
113
  if (obj[field] === undefined || obj[field] === null) {
110
114
  issues.push({
111
- rule_id: 'NX002', severity: 'ERROR',
115
+ rule_id: 'NX002',
116
+ severity: 'ERROR',
112
117
  message: `Root component requires "${field}".`,
113
- applies_to: 'root', field,
118
+ applies_to: 'root',
119
+ field,
114
120
  suggestion: `Add a "${field}" property to the root component object.`,
115
121
  });
116
122
  }
@@ -119,18 +125,22 @@ function validateRootProperties(obj, issues) {
119
125
  // NX003: tagName must not contain whitespace
120
126
  if (typeof obj['tagName'] === 'string' && /\s/.test(obj['tagName'])) {
121
127
  issues.push({
122
- rule_id: 'NX003', severity: 'ERROR',
128
+ rule_id: 'NX003',
129
+ severity: 'ERROR',
123
130
  message: 'tagName should not contain spaces.',
124
- applies_to: 'root', field: 'tagName',
131
+ applies_to: 'root',
132
+ field: 'tagName',
125
133
  suggestion: 'Remove whitespace from tagName.',
126
134
  });
127
135
  }
128
136
  // NX010: bodyTagName (if present) must not contain whitespace
129
137
  if (typeof obj['bodyTagName'] === 'string' && /\s/.test(obj['bodyTagName'])) {
130
138
  issues.push({
131
- rule_id: 'NX010', severity: 'INFO',
139
+ rule_id: 'NX010',
140
+ severity: 'INFO',
132
141
  message: 'bodyTagName should not contain spaces.',
133
- applies_to: 'root', field: 'bodyTagName',
142
+ applies_to: 'root',
143
+ field: 'bodyTagName',
134
144
  suggestion: 'Remove whitespace from bodyTagName.',
135
145
  });
136
146
  }
@@ -177,7 +187,8 @@ function validateElement(el, issues) {
177
187
  // NX007: Element should have type
178
188
  if (!el['type']) {
179
189
  issues.push({
180
- rule_id: 'NX007', severity: 'WARNING',
190
+ rule_id: 'NX007',
191
+ severity: 'WARNING',
181
192
  message: 'Element is missing required "type".',
182
193
  applies_to: 'element',
183
194
  suggestion: 'Add a "type" field to the element (e.g. "content" or "component::UUID").',
@@ -213,17 +224,21 @@ function validateInteraction(interaction, context, issues) {
213
224
  for (const field of ['defaultInteraction', 'interactionType', 'name', 'testStepTitlePattern', 'title']) {
214
225
  if (interaction[field] === undefined || interaction[field] === null) {
215
226
  issues.push({
216
- rule_id: 'NX004', severity: 'ERROR',
227
+ rule_id: 'NX004',
228
+ severity: 'ERROR',
217
229
  message: `Interaction in ${context} missing required field "${field}".`,
218
- applies_to: 'interaction', field,
230
+ applies_to: 'interaction',
231
+ field,
219
232
  });
220
233
  }
221
234
  }
222
235
  if (!Array.isArray(interaction['implementations']) || interaction['implementations'].length === 0) {
223
236
  issues.push({
224
- rule_id: 'NX004', severity: 'ERROR',
237
+ rule_id: 'NX004',
238
+ severity: 'ERROR',
225
239
  message: `Interaction in ${context} must have at least one implementation.`,
226
- applies_to: 'interaction', field: 'implementations',
240
+ applies_to: 'interaction',
241
+ field: 'implementations',
227
242
  });
228
243
  }
229
244
  else {
@@ -235,9 +250,11 @@ function validateInteraction(interaction, context, issues) {
235
250
  // NX009: name should match ^[A-Za-z0-9\s]*$
236
251
  if (typeof interaction['name'] === 'string' && !INTERACTION_NAME_RE.test(interaction['name'])) {
237
252
  issues.push({
238
- rule_id: 'NX009', severity: 'INFO',
253
+ rule_id: 'NX009',
254
+ severity: 'INFO',
239
255
  message: `Interaction name "${interaction['name']}" should contain only alphanumeric characters and spaces.`,
240
- applies_to: 'interaction', field: 'name',
256
+ applies_to: 'interaction',
257
+ field: 'name',
241
258
  suggestion: 'Remove special characters from the interaction name.',
242
259
  });
243
260
  }
@@ -246,9 +263,11 @@ function validateImplementation(impl, context, issues) {
246
263
  // NX005: must have javaScriptSnippet
247
264
  if (!impl['javaScriptSnippet']) {
248
265
  issues.push({
249
- rule_id: 'NX005', severity: 'ERROR',
266
+ rule_id: 'NX005',
267
+ severity: 'ERROR',
250
268
  message: `Implementation in ${context} missing required "javaScriptSnippet".`,
251
- applies_to: 'implementation', field: 'javaScriptSnippet',
269
+ applies_to: 'implementation',
270
+ field: 'javaScriptSnippet',
252
271
  });
253
272
  }
254
273
  }
@@ -256,9 +275,11 @@ function validateSelector(sel, issues) {
256
275
  // NX006: must have xpath
257
276
  if (!sel['xpath']) {
258
277
  issues.push({
259
- rule_id: 'NX006', severity: 'ERROR',
278
+ rule_id: 'NX006',
279
+ severity: 'ERROR',
260
280
  message: 'Selector missing required "xpath".',
261
- applies_to: 'selector', field: 'xpath',
281
+ applies_to: 'selector',
282
+ field: 'xpath',
262
283
  suggestion: 'Add an "xpath" property to the selector.',
263
284
  });
264
285
  }
@@ -267,9 +288,11 @@ function validateParameter(param, context, issues) {
267
288
  // NX008: comparisonType must be one of valid enum values
268
289
  if (param['comparisonType'] !== undefined && !VALID_COMPARISON_TYPES.includes(String(param['comparisonType']))) {
269
290
  issues.push({
270
- rule_id: 'NX008', severity: 'WARNING',
291
+ rule_id: 'NX008',
292
+ severity: 'WARNING',
271
293
  message: `Parameter in ${context} has invalid comparisonType "${String(param['comparisonType'])}". Must be one of: ${VALID_COMPARISON_TYPES.join(', ')}.`,
272
- applies_to: 'parameter', field: 'comparisonType',
294
+ applies_to: 'parameter',
295
+ field: 'comparisonType',
273
296
  suggestion: `Use one of: ${VALID_COMPARISON_TYPES.join(', ')}`,
274
297
  });
275
298
  }
@@ -338,40 +361,41 @@ function applyMergePatch(target, patch) {
338
361
  }
339
362
  // ── Tool Registrations ────────────────────────────────────────────────────────
340
363
  export function registerNitroXDiscover(server) {
341
- server.tool('provar.nitrox.discover', [
342
- 'Discover Provar projects containing NitroX (Hybrid Model) page objects.',
343
- 'Scans directories for .testproject marker files, then inventories nitroX/ and nitroXPackages/ directories.',
344
- 'NitroX is Provar\'s Hybrid Model for locators component-based page objects for LWC,',
345
- 'Screen Flow, Industry Components, Experience Cloud, and HTML5 components.',
346
- 'Results provide file paths and package info for use with provar.nitrox.read, validate, and generate.',
347
- ].join(' '), {
348
- search_roots: z
349
- .array(z.string())
350
- .optional()
351
- .describe('Directories to scan (default: cwd; if empty, falls back to ~/git and ~/Provar)'),
352
- max_depth: z
353
- .number()
354
- .int()
355
- .min(1)
356
- .max(20)
357
- .default(6)
358
- .describe('Maximum directory depth for .testproject search'),
359
- include_packages: z
360
- .boolean()
361
- .default(true)
362
- .describe('Include nitroXPackages/ package.json metadata in results'),
364
+ server.registerTool('provar_nitrox_discover', {
365
+ title: 'Discover NitroX Components',
366
+ description: [
367
+ 'Discover Provar projects containing NitroX (Hybrid Model) page objects.',
368
+ 'Scans directories for .testproject marker files, then inventories nitroX/ and nitroXPackages/ directories.',
369
+ "NitroX is Provar's Hybrid Model for locators component-based page objects for LWC,",
370
+ 'Screen Flow, Industry Components, Experience Cloud, and HTML5 components.',
371
+ 'Results provide file paths and package info for use with provar_nitrox_read, validate, and generate.',
372
+ ].join(' '),
373
+ inputSchema: {
374
+ search_roots: z
375
+ .array(z.string())
376
+ .optional()
377
+ .describe('Directories to scan (default: cwd; if empty, falls back to ~/git and ~/Provar)'),
378
+ max_depth: z
379
+ .number()
380
+ .int()
381
+ .min(1)
382
+ .max(20)
383
+ .default(6)
384
+ .describe('Maximum directory depth for .testproject search'),
385
+ include_packages: z
386
+ .boolean()
387
+ .default(true)
388
+ .describe('Include nitroXPackages/ package.json metadata in results'),
389
+ },
363
390
  }, ({ search_roots, max_depth, include_packages }) => {
364
391
  const requestId = makeRequestId();
365
- log('info', 'provar.nitrox.discover', { requestId, search_roots, max_depth });
392
+ log('info', 'provar_nitrox_discover', { requestId, search_roots, max_depth });
366
393
  try {
367
394
  let roots = search_roots && search_roots.length > 0 ? search_roots : [process.cwd()];
368
395
  let projects = findProvarProjects(roots, max_depth);
369
396
  // If no .testproject found in cwd, widen to home-dir defaults
370
397
  if (projects.length === 0 && (!search_roots || search_roots.length === 0)) {
371
- const fallbackRoots = [
372
- path.join(os.homedir(), 'git'),
373
- path.join(os.homedir(), 'Provar'),
374
- ];
398
+ const fallbackRoots = [path.join(os.homedir(), 'git'), path.join(os.homedir(), 'Provar')];
375
399
  const fallbackProjects = findProvarProjects(fallbackRoots, max_depth);
376
400
  if (fallbackProjects.length > 0) {
377
401
  projects = fallbackProjects;
@@ -426,32 +450,36 @@ export function registerNitroXDiscover(server) {
426
450
  catch (err) {
427
451
  const error = err;
428
452
  const errResult = makeError('DISCOVER_ERROR', error.message, requestId, false);
429
- log('error', 'provar.nitrox.discover failed', { requestId, error: error.message });
453
+ log('error', 'provar_nitrox_discover failed', { requestId, error: error.message });
430
454
  return { isError: true, content: [{ type: 'text', text: JSON.stringify(errResult) }] };
431
455
  }
432
456
  });
433
457
  }
434
458
  export function registerNitroXRead(server, config) {
435
- server.tool('provar.nitrox.read', [
436
- 'Read one or more NitroX .po.json (Hybrid Model page object) files and return their parsed content.',
437
- 'Use this to load examples before generating or validating.',
438
- 'Provide file_paths for specific files, or project_path to read all .po.json files from a project\'s nitroX/ directory.',
439
- ].join(' '), {
440
- file_paths: z.array(z.string()).optional().describe('Specific .po.json file paths to read'),
441
- project_path: z
442
- .string()
443
- .optional()
444
- .describe('Provar project path — reads all .po.json files from nitroX/ directory'),
445
- max_files: z
446
- .number()
447
- .int()
448
- .min(1)
449
- .max(100)
450
- .default(20)
451
- .describe('Maximum number of files to return (prevents context overflow)'),
459
+ server.registerTool('provar_nitrox_read', {
460
+ title: 'Read NitroX Files',
461
+ description: [
462
+ 'Read one or more NitroX .po.json (Hybrid Model page object) files and return their parsed content.',
463
+ 'Use this to load examples before generating or validating.',
464
+ "Provide file_paths for specific files, or project_path to read all .po.json files from a project's nitroX/ directory.",
465
+ ].join(' '),
466
+ inputSchema: {
467
+ file_paths: z.array(z.string()).optional().describe('Specific .po.json file paths to read'),
468
+ project_path: z
469
+ .string()
470
+ .optional()
471
+ .describe('Provar project path — reads all .po.json files from nitroX/ directory'),
472
+ max_files: z
473
+ .number()
474
+ .int()
475
+ .min(1)
476
+ .max(100)
477
+ .default(20)
478
+ .describe('Maximum number of files to return (prevents context overflow)'),
479
+ },
452
480
  }, ({ file_paths, project_path, max_files }) => {
453
481
  const requestId = makeRequestId();
454
- log('info', 'provar.nitrox.read', {
482
+ log('info', 'provar_nitrox_read', {
455
483
  requestId,
456
484
  file_count: file_paths?.length,
457
485
  project_path,
@@ -511,23 +539,27 @@ export function registerNitroXRead(server, config) {
511
539
  catch (err) {
512
540
  const error = err;
513
541
  const errResult = makeError(error instanceof PathPolicyError ? error.code : 'READ_ERROR', error.message, requestId, false);
514
- log('error', 'provar.nitrox.read failed', { requestId, error: error.message });
542
+ log('error', 'provar_nitrox_read failed', { requestId, error: error.message });
515
543
  return { isError: true, content: [{ type: 'text', text: JSON.stringify(errResult) }] };
516
544
  }
517
545
  });
518
546
  }
519
547
  export function registerNitroXValidate(server, config) {
520
- server.tool('provar.nitrox.validate', [
521
- 'Validate a NitroX .po.json (Hybrid Model component page object) against schema rules.',
522
- 'Works for any NitroX-mapped component type: LWC, Screen Flow, Industry Components, Experience Cloud, HTML5.',
523
- 'Returns a quality score (0–100) and a list of issues with rule IDs (NX001–NX010), severity, and suggestions.',
524
- 'Score formula: 100 (20 × errors) (5 × warnings) (1 × infos).',
525
- ].join(' '), {
526
- content: z.string().optional().describe('JSON string of the .po.json content to validate'),
527
- file_path: z.string().optional().describe('Path to a .po.json file to validate'),
548
+ server.registerTool('provar_nitrox_validate', {
549
+ title: 'Validate NitroX Component',
550
+ description: [
551
+ 'Validate a NitroX .po.json (Hybrid Model component page object) against schema rules.',
552
+ 'Works for any NitroX-mapped component type: LWC, Screen Flow, Industry Components, Experience Cloud, HTML5.',
553
+ 'Returns a quality score (0–100) and a list of issues with rule IDs (NX001–NX010), severity, and suggestions.',
554
+ 'Score formula: 100 − (20 × errors)(5 × warnings) (1 × infos).',
555
+ ].join(' '),
556
+ inputSchema: {
557
+ content: z.string().optional().describe('JSON string of the .po.json content to validate'),
558
+ file_path: z.string().optional().describe('Path to a .po.json file to validate'),
559
+ },
528
560
  }, ({ content, file_path }) => {
529
561
  const requestId = makeRequestId();
530
- log('info', 'provar.nitrox.validate', { requestId, has_content: !!content, file_path });
562
+ log('info', 'provar_nitrox_validate', { requestId, has_content: !!content, file_path });
531
563
  try {
532
564
  let source = content;
533
565
  if (!source && file_path) {
@@ -565,7 +597,7 @@ export function registerNitroXValidate(server, config) {
565
597
  catch (err) {
566
598
  const error = err;
567
599
  const errResult = makeError(error instanceof PathPolicyError ? error.code : 'VALIDATE_ERROR', error.message, requestId, false);
568
- log('error', 'provar.nitrox.validate failed', { requestId, error: error.message });
600
+ log('error', 'provar_nitrox_validate failed', { requestId, error: error.message });
569
601
  return { isError: true, content: [{ type: 'text', text: JSON.stringify(errResult) }] };
570
602
  }
571
603
  });
@@ -578,50 +610,36 @@ const ParameterInputSchema = z.object({
578
610
  });
579
611
  const ElementInputSchema = z.object({
580
612
  label: z.string().describe('Human-readable element label'),
581
- type_ref: z
582
- .string()
583
- .describe('Component type reference (e.g. "component::UUID" or "content")'),
613
+ type_ref: z.string().describe('Component type reference (e.g. "component::UUID" or "content")'),
584
614
  tag_name: z.string().optional().describe('Optional HTML/LWC tag name override'),
585
615
  parameters: z.array(ParameterInputSchema).optional(),
586
616
  selector_xpath: z.string().optional().describe('XPath selector for this element'),
587
617
  });
588
618
  export function registerNitroXGenerate(server, config) {
589
- server.tool('provar.nitrox.generate', [
590
- 'Generate a new NitroX .po.json (Hybrid Model page object) from a component description.',
591
- 'Applicable to any component type supported by Provar\'s Hybrid Model:',
592
- 'LWC, Screen Flow, Industry Components, Experience Cloud, HTML5.',
593
- 'All componentId fields are assigned fresh UUIDs. Returns JSON content;',
594
- 'writes to disk only when dry_run=false.',
595
- ].join(' '), {
596
- name: z
597
- .string()
598
- .describe('Path-like component name, e.g. /com/force/myapp/ButtonComponent'),
599
- tag_name: z
600
- .string()
601
- .describe('LWC or HTML tag name, e.g. lightning-button or c-my-component'),
602
- type: z.enum(['Block', 'Page']).default('Block').describe('Component type'),
603
- page_structure_element: z
604
- .boolean()
605
- .default(true)
606
- .describe('Whether this is a page structure element'),
607
- field_details_element: z
608
- .boolean()
609
- .default(false)
610
- .describe('Whether this is a field details element'),
611
- parameters: z.array(ParameterInputSchema).optional().describe('Component parameters/qualifiers'),
612
- elements: z.array(ElementInputSchema).optional().describe('Child elements'),
613
- output_path: z
614
- .string()
615
- .optional()
616
- .describe('File path to write (requires dry_run=false)'),
617
- overwrite: z.boolean().default(false).describe('Overwrite if output_path already exists'),
618
- dry_run: z
619
- .boolean()
620
- .default(true)
621
- .describe('Return JSON without writing to disk (default)'),
619
+ server.registerTool('provar_nitrox_generate', {
620
+ title: 'Generate NitroX Components',
621
+ description: [
622
+ 'Generate a new NitroX .po.json (Hybrid Model page object) from a component description.',
623
+ "Applicable to any component type supported by Provar's Hybrid Model:",
624
+ 'LWC, Screen Flow, Industry Components, Experience Cloud, HTML5.',
625
+ 'All componentId fields are assigned fresh UUIDs. Returns JSON content;',
626
+ 'writes to disk only when dry_run=false.',
627
+ ].join(' '),
628
+ inputSchema: {
629
+ name: z.string().describe('Path-like component name, e.g. /com/force/myapp/ButtonComponent'),
630
+ tag_name: z.string().describe('LWC or HTML tag name, e.g. lightning-button or c-my-component'),
631
+ type: z.enum(['Block', 'Page']).default('Block').describe('Component type'),
632
+ page_structure_element: z.boolean().default(true).describe('Whether this is a page structure element'),
633
+ field_details_element: z.boolean().default(false).describe('Whether this is a field details element'),
634
+ parameters: z.array(ParameterInputSchema).optional().describe('Component parameters/qualifiers'),
635
+ elements: z.array(ElementInputSchema).optional().describe('Child elements'),
636
+ output_path: z.string().optional().describe('File path to write (requires dry_run=false)'),
637
+ overwrite: z.boolean().default(false).describe('Overwrite if output_path already exists'),
638
+ dry_run: z.boolean().default(true).describe('Return JSON without writing to disk (default)'),
639
+ },
622
640
  }, (input) => {
623
641
  const requestId = makeRequestId();
624
- log('info', 'provar.nitrox.generate', { requestId, name: input.name, dry_run: input.dry_run });
642
+ log('info', 'provar_nitrox_generate', { requestId, name: input.name, dry_run: input.dry_run });
625
643
  try {
626
644
  const generated = buildNitroXJson({
627
645
  name: input.name,
@@ -645,7 +663,7 @@ export function registerNitroXGenerate(server, config) {
645
663
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
646
664
  fs.writeFileSync(filePath, content, 'utf-8');
647
665
  written = true;
648
- log('info', 'provar.nitrox.generate: wrote file', { requestId, filePath });
666
+ log('info', 'provar_nitrox_generate: wrote file', { requestId, filePath });
649
667
  }
650
668
  const result = { requestId, content, file_path: filePath, written, dry_run: input.dry_run };
651
669
  return {
@@ -655,34 +673,35 @@ export function registerNitroXGenerate(server, config) {
655
673
  }
656
674
  catch (err) {
657
675
  const error = err;
658
- const errResult = makeError(error instanceof PathPolicyError ? error.code : (error.code ?? 'GENERATE_ERROR'), error.message, requestId, false);
659
- log('error', 'provar.nitrox.generate failed', { requestId, error: error.message });
676
+ const errResult = makeError(error instanceof PathPolicyError ? error.code : error.code ?? 'GENERATE_ERROR', error.message, requestId, false);
677
+ log('error', 'provar_nitrox_generate failed', { requestId, error: error.message });
660
678
  return { isError: true, content: [{ type: 'text', text: JSON.stringify(errResult) }] };
661
679
  }
662
680
  });
663
681
  }
664
682
  export function registerNitroXPatch(server, config) {
665
- server.tool('provar.nitrox.patch', [
666
- 'Apply a JSON merge-patch (RFC 7396) to an existing NitroX .po.json file.',
667
- 'Reads the file, merges the patch (null values remove keys, other values replace or recurse into objects),',
668
- 'optionally validates the merged result, and writes back.',
669
- 'Use dry_run=true (default) to preview the merged output without writing.',
670
- ].join(' '), {
671
- file_path: z.string().describe('Path to the existing .po.json file to patch'),
672
- patch: z
673
- .record(z.unknown())
674
- .describe('JSON merge-patch to apply (RFC 7396: null removes key, any other value replaces)'),
675
- dry_run: z
676
- .boolean()
677
- .default(true)
678
- .describe('Return merged result without writing to disk (default)'),
679
- validate_after: z
680
- .boolean()
681
- .default(true)
682
- .describe('Run NX validation on merged result; blocks write if errors found'),
683
+ server.registerTool('provar_nitrox_patch', {
684
+ title: 'Patch NitroX Component',
685
+ description: [
686
+ 'Apply a JSON merge-patch (RFC 7396) to an existing NitroX .po.json file.',
687
+ 'Reads the file, merges the patch (null values remove keys, other values replace or recurse into objects),',
688
+ 'optionally validates the merged result, and writes back.',
689
+ 'Use dry_run=true (default) to preview the merged output without writing.',
690
+ ].join(' '),
691
+ inputSchema: {
692
+ file_path: z.string().describe('Path to the existing .po.json file to patch'),
693
+ patch: z
694
+ .record(z.unknown())
695
+ .describe('JSON merge-patch to apply (RFC 7396: null removes key, any other value replaces)'),
696
+ dry_run: z.boolean().default(true).describe('Return merged result without writing to disk (default)'),
697
+ validate_after: z
698
+ .boolean()
699
+ .default(true)
700
+ .describe('Run NX validation on merged result; blocks write if errors found'),
701
+ },
683
702
  }, ({ file_path, patch, dry_run, validate_after }) => {
684
703
  const requestId = makeRequestId();
685
- log('info', 'provar.nitrox.patch', { requestId, file_path, dry_run });
704
+ log('info', 'provar_nitrox_patch', { requestId, file_path, dry_run });
686
705
  try {
687
706
  assertPathAllowed(file_path, config.allowedPaths);
688
707
  const resolved = path.resolve(file_path);
@@ -717,7 +736,7 @@ export function registerNitroXPatch(server, config) {
717
736
  if (!dry_run) {
718
737
  fs.writeFileSync(resolved, content, 'utf-8');
719
738
  written = true;
720
- log('info', 'provar.nitrox.patch: wrote file', { requestId, filePath: resolved });
739
+ log('info', 'provar_nitrox_patch: wrote file', { requestId, filePath: resolved });
721
740
  }
722
741
  const result = {
723
742
  requestId,
@@ -734,8 +753,8 @@ export function registerNitroXPatch(server, config) {
734
753
  }
735
754
  catch (err) {
736
755
  const error = err;
737
- const errResult = makeError(error instanceof PathPolicyError ? error.code : (error.code ?? 'PATCH_ERROR'), error.message, requestId, false);
738
- log('error', 'provar.nitrox.patch failed', { requestId, error: error.message });
756
+ const errResult = makeError(error instanceof PathPolicyError ? error.code : error.code ?? 'PATCH_ERROR', error.message, requestId, false);
757
+ log('error', 'provar_nitrox_patch failed', { requestId, error: error.message });
739
758
  return { isError: true, content: [{ type: 'text', text: JSON.stringify(errResult) }] };
740
759
  }
741
760
  });