vaspera 2.10.0 → 2.10.1

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 (173) hide show
  1. package/dist/__tests__/scanners/ai-code/ai-detector.test.d.ts +2 -0
  2. package/dist/__tests__/scanners/ai-code/ai-detector.test.d.ts.map +1 -0
  3. package/dist/__tests__/scanners/ai-code/ai-detector.test.js +188 -0
  4. package/dist/__tests__/scanners/ai-code/ai-detector.test.js.map +1 -0
  5. package/dist/__tests__/scanners/ai-code/confidence-scorer.test.d.ts +2 -0
  6. package/dist/__tests__/scanners/ai-code/confidence-scorer.test.d.ts.map +1 -0
  7. package/dist/__tests__/scanners/ai-code/confidence-scorer.test.js +363 -0
  8. package/dist/__tests__/scanners/ai-code/confidence-scorer.test.js.map +1 -0
  9. package/dist/__tests__/scanners/ai-code/hallucination-checker.test.d.ts +2 -0
  10. package/dist/__tests__/scanners/ai-code/hallucination-checker.test.d.ts.map +1 -0
  11. package/dist/__tests__/scanners/ai-code/hallucination-checker.test.js +226 -0
  12. package/dist/__tests__/scanners/ai-code/hallucination-checker.test.js.map +1 -0
  13. package/dist/__tests__/scanners/ai-code/index.test.d.ts +2 -0
  14. package/dist/__tests__/scanners/ai-code/index.test.d.ts.map +1 -0
  15. package/dist/__tests__/scanners/ai-code/index.test.js +214 -0
  16. package/dist/__tests__/scanners/ai-code/index.test.js.map +1 -0
  17. package/dist/__tests__/scanners/deploy/health-checker.test.d.ts +2 -0
  18. package/dist/__tests__/scanners/deploy/health-checker.test.d.ts.map +1 -0
  19. package/dist/__tests__/scanners/deploy/health-checker.test.js +67 -0
  20. package/dist/__tests__/scanners/deploy/health-checker.test.js.map +1 -0
  21. package/dist/__tests__/scanners/deploy/index.test.d.ts +2 -0
  22. package/dist/__tests__/scanners/deploy/index.test.d.ts.map +1 -0
  23. package/dist/__tests__/scanners/deploy/index.test.js +84 -0
  24. package/dist/__tests__/scanners/deploy/index.test.js.map +1 -0
  25. package/dist/__tests__/scanners/deploy/provider-detector.test.d.ts +2 -0
  26. package/dist/__tests__/scanners/deploy/provider-detector.test.d.ts.map +1 -0
  27. package/dist/__tests__/scanners/deploy/provider-detector.test.js +88 -0
  28. package/dist/__tests__/scanners/deploy/provider-detector.test.js.map +1 -0
  29. package/dist/__tests__/scanners/deploy/types.test.d.ts +2 -0
  30. package/dist/__tests__/scanners/deploy/types.test.d.ts.map +1 -0
  31. package/dist/__tests__/scanners/deploy/types.test.js +126 -0
  32. package/dist/__tests__/scanners/deploy/types.test.js.map +1 -0
  33. package/dist/__tests__/scanners/fp-feedback.test.js +1 -1
  34. package/dist/__tests__/scanners/fp-feedback.test.js.map +1 -1
  35. package/dist/__tests__/scanners/fp-tracker.test.js +1 -1
  36. package/dist/__tests__/scanners/fp-tracker.test.js.map +1 -1
  37. package/dist/__tests__/scanners/runtime/app-launcher.test.d.ts +2 -0
  38. package/dist/__tests__/scanners/runtime/app-launcher.test.d.ts.map +1 -0
  39. package/dist/__tests__/scanners/runtime/app-launcher.test.js +94 -0
  40. package/dist/__tests__/scanners/runtime/app-launcher.test.js.map +1 -0
  41. package/dist/__tests__/scanners/runtime/golden-path-runner.test.d.ts +2 -0
  42. package/dist/__tests__/scanners/runtime/golden-path-runner.test.d.ts.map +1 -0
  43. package/dist/__tests__/scanners/runtime/golden-path-runner.test.js +195 -0
  44. package/dist/__tests__/scanners/runtime/golden-path-runner.test.js.map +1 -0
  45. package/dist/__tests__/scanners/runtime/index.test.d.ts +2 -0
  46. package/dist/__tests__/scanners/runtime/index.test.d.ts.map +1 -0
  47. package/dist/__tests__/scanners/runtime/index.test.js +120 -0
  48. package/dist/__tests__/scanners/runtime/index.test.js.map +1 -0
  49. package/dist/__tests__/scanners/runtime/types.test.d.ts +2 -0
  50. package/dist/__tests__/scanners/runtime/types.test.d.ts.map +1 -0
  51. package/dist/__tests__/scanners/runtime/types.test.js +126 -0
  52. package/dist/__tests__/scanners/runtime/types.test.js.map +1 -0
  53. package/dist/__tests__/scanners/scale/bottleneck-detector.test.d.ts +2 -0
  54. package/dist/__tests__/scanners/scale/bottleneck-detector.test.d.ts.map +1 -0
  55. package/dist/__tests__/scanners/scale/bottleneck-detector.test.js +187 -0
  56. package/dist/__tests__/scanners/scale/bottleneck-detector.test.js.map +1 -0
  57. package/dist/__tests__/scanners/scale/index.test.d.ts +2 -0
  58. package/dist/__tests__/scanners/scale/index.test.d.ts.map +1 -0
  59. package/dist/__tests__/scanners/scale/index.test.js +87 -0
  60. package/dist/__tests__/scanners/scale/index.test.js.map +1 -0
  61. package/dist/__tests__/scanners/scale/load-profiler.test.d.ts +2 -0
  62. package/dist/__tests__/scanners/scale/load-profiler.test.d.ts.map +1 -0
  63. package/dist/__tests__/scanners/scale/load-profiler.test.js +122 -0
  64. package/dist/__tests__/scanners/scale/load-profiler.test.js.map +1 -0
  65. package/dist/__tests__/scanners/scale/types.test.d.ts +2 -0
  66. package/dist/__tests__/scanners/scale/types.test.d.ts.map +1 -0
  67. package/dist/__tests__/scanners/scale/types.test.js +129 -0
  68. package/dist/__tests__/scanners/scale/types.test.js.map +1 -0
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +874 -0
  71. package/dist/index.js.map +1 -1
  72. package/dist/install-skills.d.ts +11 -0
  73. package/dist/install-skills.d.ts.map +1 -0
  74. package/dist/install-skills.js +81 -0
  75. package/dist/install-skills.js.map +1 -0
  76. package/dist/scanners/ai-code/ai-detector.d.ts +25 -0
  77. package/dist/scanners/ai-code/ai-detector.d.ts.map +1 -0
  78. package/dist/scanners/ai-code/ai-detector.js +192 -0
  79. package/dist/scanners/ai-code/ai-detector.js.map +1 -0
  80. package/dist/scanners/ai-code/confidence-scorer.d.ts +40 -0
  81. package/dist/scanners/ai-code/confidence-scorer.d.ts.map +1 -0
  82. package/dist/scanners/ai-code/confidence-scorer.js +148 -0
  83. package/dist/scanners/ai-code/confidence-scorer.js.map +1 -0
  84. package/dist/scanners/ai-code/hallucination-checker.d.ts +36 -0
  85. package/dist/scanners/ai-code/hallucination-checker.d.ts.map +1 -0
  86. package/dist/scanners/ai-code/hallucination-checker.js +298 -0
  87. package/dist/scanners/ai-code/hallucination-checker.js.map +1 -0
  88. package/dist/scanners/ai-code/index.d.ts +30 -0
  89. package/dist/scanners/ai-code/index.d.ts.map +1 -0
  90. package/dist/scanners/ai-code/index.js +224 -0
  91. package/dist/scanners/ai-code/index.js.map +1 -0
  92. package/dist/scanners/ai-code/types.d.ts +192 -0
  93. package/dist/scanners/ai-code/types.d.ts.map +1 -0
  94. package/dist/scanners/ai-code/types.js +37 -0
  95. package/dist/scanners/ai-code/types.js.map +1 -0
  96. package/dist/scanners/deploy/health-checker.d.ts +38 -0
  97. package/dist/scanners/deploy/health-checker.d.ts.map +1 -0
  98. package/dist/scanners/deploy/health-checker.js +272 -0
  99. package/dist/scanners/deploy/health-checker.js.map +1 -0
  100. package/dist/scanners/deploy/index.d.ts +44 -0
  101. package/dist/scanners/deploy/index.d.ts.map +1 -0
  102. package/dist/scanners/deploy/index.js +208 -0
  103. package/dist/scanners/deploy/index.js.map +1 -0
  104. package/dist/scanners/deploy/provider-detector.d.ts +25 -0
  105. package/dist/scanners/deploy/provider-detector.d.ts.map +1 -0
  106. package/dist/scanners/deploy/provider-detector.js +177 -0
  107. package/dist/scanners/deploy/provider-detector.js.map +1 -0
  108. package/dist/scanners/deploy/types.d.ts +406 -0
  109. package/dist/scanners/deploy/types.d.ts.map +1 -0
  110. package/dist/scanners/deploy/types.js +58 -0
  111. package/dist/scanners/deploy/types.js.map +1 -0
  112. package/dist/scanners/deploy/vercel-integration.d.ts +52 -0
  113. package/dist/scanners/deploy/vercel-integration.d.ts.map +1 -0
  114. package/dist/scanners/deploy/vercel-integration.js +280 -0
  115. package/dist/scanners/deploy/vercel-integration.js.map +1 -0
  116. package/dist/scanners/runtime/app-launcher.d.ts +33 -0
  117. package/dist/scanners/runtime/app-launcher.d.ts.map +1 -0
  118. package/dist/scanners/runtime/app-launcher.js +419 -0
  119. package/dist/scanners/runtime/app-launcher.js.map +1 -0
  120. package/dist/scanners/runtime/golden-path-runner.d.ts +48 -0
  121. package/dist/scanners/runtime/golden-path-runner.d.ts.map +1 -0
  122. package/dist/scanners/runtime/golden-path-runner.js +373 -0
  123. package/dist/scanners/runtime/golden-path-runner.js.map +1 -0
  124. package/dist/scanners/runtime/index.d.ts +41 -0
  125. package/dist/scanners/runtime/index.d.ts.map +1 -0
  126. package/dist/scanners/runtime/index.js +164 -0
  127. package/dist/scanners/runtime/index.js.map +1 -0
  128. package/dist/scanners/runtime/playwright-executor.d.ts +50 -0
  129. package/dist/scanners/runtime/playwright-executor.d.ts.map +1 -0
  130. package/dist/scanners/runtime/playwright-executor.js +387 -0
  131. package/dist/scanners/runtime/playwright-executor.js.map +1 -0
  132. package/dist/scanners/runtime/types.d.ts +215 -0
  133. package/dist/scanners/runtime/types.d.ts.map +1 -0
  134. package/dist/scanners/runtime/types.js +40 -0
  135. package/dist/scanners/runtime/types.js.map +1 -0
  136. package/dist/scanners/scale/bottleneck-detector.d.ts +17 -0
  137. package/dist/scanners/scale/bottleneck-detector.d.ts.map +1 -0
  138. package/dist/scanners/scale/bottleneck-detector.js +250 -0
  139. package/dist/scanners/scale/bottleneck-detector.js.map +1 -0
  140. package/dist/scanners/scale/capacity-estimator.d.ts +17 -0
  141. package/dist/scanners/scale/capacity-estimator.d.ts.map +1 -0
  142. package/dist/scanners/scale/capacity-estimator.js +197 -0
  143. package/dist/scanners/scale/capacity-estimator.js.map +1 -0
  144. package/dist/scanners/scale/index.d.ts +37 -0
  145. package/dist/scanners/scale/index.d.ts.map +1 -0
  146. package/dist/scanners/scale/index.js +101 -0
  147. package/dist/scanners/scale/index.js.map +1 -0
  148. package/dist/scanners/scale/load-profiler.d.ts +48 -0
  149. package/dist/scanners/scale/load-profiler.d.ts.map +1 -0
  150. package/dist/scanners/scale/load-profiler.js +377 -0
  151. package/dist/scanners/scale/load-profiler.js.map +1 -0
  152. package/dist/scanners/scale/types.d.ts +529 -0
  153. package/dist/scanners/scale/types.d.ts.map +1 -0
  154. package/dist/scanners/scale/types.js +57 -0
  155. package/dist/scanners/scale/types.js.map +1 -0
  156. package/dist/scanners/secrets.d.ts.map +1 -1
  157. package/dist/scanners/secrets.js +13 -2
  158. package/dist/scanners/secrets.js.map +1 -1
  159. package/package.json +4 -2
  160. package/skills/vaspera-add-tests/SKILL.md +102 -0
  161. package/skills/vaspera-ai-verify/SKILL.md +166 -0
  162. package/skills/vaspera-audit/SKILL.md +67 -0
  163. package/skills/vaspera-certify/SKILL.md +130 -0
  164. package/skills/vaspera-deploy/SKILL.md +152 -0
  165. package/skills/vaspera-fix-critical/SKILL.md +52 -0
  166. package/skills/vaspera-fix-high/SKILL.md +81 -0
  167. package/skills/vaspera-fix-medium/SKILL.md +56 -0
  168. package/skills/vaspera-fix-rls/SKILL.md +85 -0
  169. package/skills/vaspera-harden/SKILL.md +102 -0
  170. package/skills/vaspera-help/SKILL.md +61 -0
  171. package/skills/vaspera-load-test/SKILL.md +167 -0
  172. package/skills/vaspera-verify/SKILL.md +70 -0
  173. package/skills/vaspera-verify-e2e/SKILL.md +117 -0
package/dist/index.js CHANGED
@@ -4593,6 +4593,880 @@ Calculates escalated severity based on chain impact.`,
4593
4593
  }
4594
4594
  });
4595
4595
  // ---------------------------------------------------------------------------
4596
+ // Runtime Verification Tools (M7)
4597
+ // ---------------------------------------------------------------------------
4598
+ import { detectFramework, launchApp, stopApp, runRuntimeVerification, quickHealthCheck, discoverFlows, generateSampleFlow, } from "./scanners/runtime/index.js";
4599
+ server.registerTool("runtime_launch", {
4600
+ description: `Launch a development server for runtime verification.
4601
+
4602
+ Automatically detects the framework (Next.js, Vite, Express, FastAPI, etc.)
4603
+ and starts the appropriate dev server. Returns when the app is healthy.
4604
+
4605
+ Supported frameworks:
4606
+ - Next.js (next dev)
4607
+ - Vite (vite)
4608
+ - Create React App (react-scripts start)
4609
+ - Express (npm run dev)
4610
+ - FastAPI (uvicorn)
4611
+ - Flask (flask run)
4612
+ - Django (manage.py runserver)
4613
+ - Rails (rails server)`,
4614
+ inputSchema: {
4615
+ project_path: z.string().describe("Path to the project"),
4616
+ port: z.number().optional().describe("Port to run on (auto-detected if not specified)"),
4617
+ timeout: z.number().optional().describe("Startup timeout in ms (default: 60000)"),
4618
+ },
4619
+ annotations: {
4620
+ readOnlyHint: false,
4621
+ destructiveHint: false,
4622
+ idempotentHint: false,
4623
+ openWorldHint: true,
4624
+ },
4625
+ }, async ({ project_path, port, timeout }) => {
4626
+ try {
4627
+ const detection = await detectFramework(project_path);
4628
+ const result = await launchApp({
4629
+ projectPath: project_path,
4630
+ framework: detection.framework,
4631
+ port,
4632
+ timeout,
4633
+ });
4634
+ return jsonResponse({
4635
+ success: result.success,
4636
+ framework: result.framework,
4637
+ url: result.url,
4638
+ port: result.port,
4639
+ pid: result.pid,
4640
+ healthStatus: result.healthStatus,
4641
+ startupTime: result.startupTime,
4642
+ error: result.error,
4643
+ detection: {
4644
+ confidence: detection.confidence,
4645
+ indicators: detection.indicators,
4646
+ },
4647
+ });
4648
+ }
4649
+ catch (error) {
4650
+ return errorResponse(error instanceof Error ? error.message : String(error));
4651
+ }
4652
+ });
4653
+ server.registerTool("runtime_stop", {
4654
+ description: "Stop a running development server.",
4655
+ inputSchema: {
4656
+ project_path: z.string().describe("Path to the project"),
4657
+ port: z.number().optional().describe("Port to stop (if multiple running)"),
4658
+ },
4659
+ annotations: {
4660
+ readOnlyHint: false,
4661
+ destructiveHint: false,
4662
+ idempotentHint: true,
4663
+ openWorldHint: false,
4664
+ },
4665
+ }, async ({ project_path, port }) => {
4666
+ try {
4667
+ const stopped = await stopApp(project_path, port);
4668
+ return jsonResponse({ stopped, projectPath: project_path, port });
4669
+ }
4670
+ catch (error) {
4671
+ return errorResponse(error instanceof Error ? error.message : String(error));
4672
+ }
4673
+ });
4674
+ server.registerTool("runtime_verify", {
4675
+ description: `Run full runtime verification on a project.
4676
+
4677
+ This tool:
4678
+ 1. Detects the framework
4679
+ 2. Launches the dev server
4680
+ 3. Runs golden path flows from .vaspera/flows/*.yaml
4681
+ 4. Calculates a runtime verification score
4682
+ 5. Stops the server
4683
+
4684
+ Returns a score from 0-100 indicating runtime verification success.
4685
+ Score >= 70 is considered passing.`,
4686
+ inputSchema: {
4687
+ project_path: z.string().describe("Path to the project"),
4688
+ port: z.number().optional().describe("Port to run on"),
4689
+ timeout: z.number().optional().describe("Startup timeout in ms"),
4690
+ priority: z.enum(["critical", "high", "medium", "low"]).optional()
4691
+ .describe("Only run flows at this priority or higher"),
4692
+ stop_after: z.boolean().optional().describe("Stop server after verification (default: true)"),
4693
+ },
4694
+ annotations: {
4695
+ readOnlyHint: false,
4696
+ destructiveHint: false,
4697
+ idempotentHint: true,
4698
+ openWorldHint: true,
4699
+ },
4700
+ }, async ({ project_path, port, timeout, priority, stop_after }) => {
4701
+ try {
4702
+ const result = await runRuntimeVerification(project_path, {
4703
+ port,
4704
+ timeout,
4705
+ priority,
4706
+ stopAfter: stop_after ?? true,
4707
+ });
4708
+ return jsonResponse({
4709
+ success: result.success,
4710
+ framework: result.framework,
4711
+ appUrl: result.appUrl,
4712
+ duration: result.duration,
4713
+ score: result.score,
4714
+ goldenPaths: result.goldenPaths.map(gp => ({
4715
+ name: gp.flowName,
4716
+ success: gp.success,
4717
+ passedSteps: gp.passedSteps,
4718
+ totalSteps: gp.totalSteps,
4719
+ duration: gp.duration,
4720
+ })),
4721
+ error: result.error,
4722
+ });
4723
+ }
4724
+ catch (error) {
4725
+ return errorResponse(error instanceof Error ? error.message : String(error));
4726
+ }
4727
+ });
4728
+ server.registerTool("runtime_health", {
4729
+ description: "Quick health check - launch app, verify it starts, and stop.",
4730
+ inputSchema: {
4731
+ project_path: z.string().describe("Path to the project"),
4732
+ port: z.number().optional().describe("Port to run on"),
4733
+ timeout: z.number().optional().describe("Timeout in ms (default: 30000)"),
4734
+ },
4735
+ annotations: {
4736
+ readOnlyHint: true,
4737
+ destructiveHint: false,
4738
+ idempotentHint: true,
4739
+ openWorldHint: true,
4740
+ },
4741
+ }, async ({ project_path, port, timeout }) => {
4742
+ try {
4743
+ const result = await quickHealthCheck(project_path, { port, timeout });
4744
+ return jsonResponse(result);
4745
+ }
4746
+ catch (error) {
4747
+ return errorResponse(error instanceof Error ? error.message : String(error));
4748
+ }
4749
+ });
4750
+ server.registerTool("runtime_flows_list", {
4751
+ description: "List all golden path flows defined in a project.",
4752
+ inputSchema: {
4753
+ project_path: z.string().describe("Path to the project"),
4754
+ },
4755
+ annotations: {
4756
+ readOnlyHint: true,
4757
+ destructiveHint: false,
4758
+ idempotentHint: true,
4759
+ openWorldHint: false,
4760
+ },
4761
+ }, async ({ project_path }) => {
4762
+ try {
4763
+ const flows = await discoverFlows(project_path);
4764
+ return jsonResponse({
4765
+ total: flows.length,
4766
+ flows: flows.map(f => ({
4767
+ path: f.path,
4768
+ name: f.flow.name,
4769
+ description: f.flow.description,
4770
+ priority: f.flow.priority,
4771
+ steps: f.flow.steps.length,
4772
+ tags: f.flow.tags,
4773
+ })),
4774
+ });
4775
+ }
4776
+ catch (error) {
4777
+ return errorResponse(error instanceof Error ? error.message : String(error));
4778
+ }
4779
+ });
4780
+ server.registerTool("runtime_flow_generate", {
4781
+ description: "Generate a sample golden path flow file.",
4782
+ inputSchema: {
4783
+ project_path: z.string().describe("Path to the project"),
4784
+ name: z.string().optional().describe("Flow name (default: sample)"),
4785
+ },
4786
+ annotations: {
4787
+ readOnlyHint: false,
4788
+ destructiveHint: false,
4789
+ idempotentHint: false,
4790
+ openWorldHint: false,
4791
+ },
4792
+ }, async ({ project_path, name }) => {
4793
+ try {
4794
+ const flowPath = await generateSampleFlow(project_path, name);
4795
+ return jsonResponse({
4796
+ created: true,
4797
+ path: flowPath,
4798
+ message: `Sample flow created at ${flowPath}. Edit to define your golden paths.`,
4799
+ });
4800
+ }
4801
+ catch (error) {
4802
+ return errorResponse(error instanceof Error ? error.message : String(error));
4803
+ }
4804
+ });
4805
+ server.registerTool("runtime_detect", {
4806
+ description: "Detect the framework used in a project without launching.",
4807
+ inputSchema: {
4808
+ project_path: z.string().describe("Path to the project"),
4809
+ },
4810
+ annotations: {
4811
+ readOnlyHint: true,
4812
+ destructiveHint: false,
4813
+ idempotentHint: true,
4814
+ openWorldHint: false,
4815
+ },
4816
+ }, async ({ project_path }) => {
4817
+ try {
4818
+ const detection = await detectFramework(project_path);
4819
+ return jsonResponse({
4820
+ framework: detection.framework,
4821
+ version: detection.version,
4822
+ confidence: detection.confidence,
4823
+ devCommand: detection.devCommand,
4824
+ buildCommand: detection.buildCommand,
4825
+ port: detection.port,
4826
+ healthEndpoint: detection.healthEndpoint,
4827
+ indicators: detection.indicators,
4828
+ });
4829
+ }
4830
+ catch (error) {
4831
+ return errorResponse(error instanceof Error ? error.message : String(error));
4832
+ }
4833
+ });
4834
+ // ---------------------------------------------------------------------------
4835
+ // Scale Assessment Tools (M8)
4836
+ // ---------------------------------------------------------------------------
4837
+ import { runScaleAssessment, quickBottleneckScan, discoverProfiles as discoverLoadProfiles, generateSampleProfile, detectLoadTool, isK6Available, } from "./scanners/scale/index.js";
4838
+ server.registerTool("scale_assess", {
4839
+ description: `Run full scale assessment on a project.
4840
+
4841
+ This tool:
4842
+ 1. Detects load testing tool (k6, Artillery)
4843
+ 2. Discovers and runs load profiles from .vaspera/load/*.yaml
4844
+ 3. Detects performance bottlenecks in code
4845
+ 4. Estimates capacity and breakpoint
4846
+ 5. Calculates a scale readiness score
4847
+
4848
+ Returns a score from 0-100 indicating scale readiness.
4849
+ Score >= 70 is considered passing.`,
4850
+ inputSchema: {
4851
+ project_path: z.string().describe("Path to the project"),
4852
+ base_url: z.string().describe("Base URL of running app"),
4853
+ profile_name: z.string().optional().describe("Specific profile to run"),
4854
+ skip_load_test: z.boolean().optional().describe("Skip load testing, only scan for bottlenecks"),
4855
+ target_concurrent: z.number().optional().describe("Target concurrent users for scoring"),
4856
+ timeout: z.number().optional().describe("Load test timeout in ms"),
4857
+ },
4858
+ annotations: {
4859
+ readOnlyHint: false,
4860
+ destructiveHint: false,
4861
+ idempotentHint: true,
4862
+ openWorldHint: true,
4863
+ },
4864
+ }, async ({ project_path, base_url, profile_name, skip_load_test, target_concurrent, timeout }) => {
4865
+ try {
4866
+ const result = await runScaleAssessment(project_path, base_url, {
4867
+ profileName: profile_name,
4868
+ skipLoadTest: skip_load_test,
4869
+ targetConcurrent: target_concurrent,
4870
+ timeout,
4871
+ });
4872
+ return jsonResponse({
4873
+ success: result.success,
4874
+ score: result.score,
4875
+ loadTest: result.loadTest ? {
4876
+ profile: result.loadTest.profile,
4877
+ passed: result.loadTest.passed,
4878
+ totalRequests: result.loadTest.summary.totalRequests,
4879
+ errorRate: result.loadTest.summary.errorRate,
4880
+ latencyP95: result.loadTest.summary.latency.p95,
4881
+ throughput: result.loadTest.summary.avgThroughput,
4882
+ } : null,
4883
+ bottlenecks: result.bottlenecks.slice(0, 10).map((b) => ({
4884
+ type: b.type,
4885
+ location: b.location,
4886
+ severity: b.severity,
4887
+ description: b.description,
4888
+ })),
4889
+ capacity: {
4890
+ maxConcurrent: result.capacity.maxConcurrentUsers,
4891
+ maxRPS: result.capacity.maxRequestsPerSecond,
4892
+ breakpoint: result.capacity.estimatedBreakpoint,
4893
+ },
4894
+ recommendations: result.capacity.recommendations,
4895
+ duration: result.duration,
4896
+ });
4897
+ }
4898
+ catch (error) {
4899
+ return errorResponse(error instanceof Error ? error.message : String(error));
4900
+ }
4901
+ });
4902
+ server.registerTool("scale_bottlenecks", {
4903
+ description: "Scan for performance bottlenecks without running load tests.",
4904
+ inputSchema: {
4905
+ project_path: z.string().describe("Path to the project"),
4906
+ },
4907
+ annotations: {
4908
+ readOnlyHint: true,
4909
+ destructiveHint: false,
4910
+ idempotentHint: true,
4911
+ openWorldHint: false,
4912
+ },
4913
+ }, async ({ project_path }) => {
4914
+ try {
4915
+ const result = await quickBottleneckScan(project_path);
4916
+ return jsonResponse({
4917
+ score: result.score,
4918
+ total: result.bottlenecks.length,
4919
+ bottlenecks: result.bottlenecks.map((b) => ({
4920
+ type: b.type,
4921
+ location: b.location,
4922
+ severity: b.severity,
4923
+ description: b.description,
4924
+ recommendation: b.recommendation,
4925
+ })),
4926
+ });
4927
+ }
4928
+ catch (error) {
4929
+ return errorResponse(error instanceof Error ? error.message : String(error));
4930
+ }
4931
+ });
4932
+ server.registerTool("scale_profiles_list", {
4933
+ description: "List available load testing profiles in a project.",
4934
+ inputSchema: {
4935
+ project_path: z.string().describe("Path to the project"),
4936
+ },
4937
+ annotations: {
4938
+ readOnlyHint: true,
4939
+ destructiveHint: false,
4940
+ idempotentHint: true,
4941
+ openWorldHint: false,
4942
+ },
4943
+ }, async ({ project_path }) => {
4944
+ try {
4945
+ const profiles = await discoverLoadProfiles(project_path);
4946
+ return jsonResponse({
4947
+ total: profiles.length,
4948
+ profiles: profiles.map((p) => ({
4949
+ name: p.profile.name,
4950
+ path: p.path,
4951
+ tool: p.profile.tool,
4952
+ scenarios: p.profile.scenarios.map((s) => s.name),
4953
+ endpoints: p.profile.endpoints?.length || 0,
4954
+ })),
4955
+ });
4956
+ }
4957
+ catch (error) {
4958
+ return errorResponse(error instanceof Error ? error.message : String(error));
4959
+ }
4960
+ });
4961
+ server.registerTool("scale_profile_generate", {
4962
+ description: "Generate a sample load testing profile.",
4963
+ inputSchema: {
4964
+ project_path: z.string().describe("Path to the project"),
4965
+ name: z.string().optional().describe("Profile name (default: 'production')"),
4966
+ },
4967
+ annotations: {
4968
+ readOnlyHint: false,
4969
+ destructiveHint: false,
4970
+ idempotentHint: true,
4971
+ openWorldHint: false,
4972
+ },
4973
+ }, async ({ project_path, name }) => {
4974
+ try {
4975
+ const profilePath = await generateSampleProfile(project_path, name);
4976
+ return jsonResponse({
4977
+ created: true,
4978
+ path: profilePath,
4979
+ message: `Load profile created. Edit ${profilePath} to define your endpoints and scenarios.`,
4980
+ });
4981
+ }
4982
+ catch (error) {
4983
+ return errorResponse(error instanceof Error ? error.message : String(error));
4984
+ }
4985
+ });
4986
+ server.registerTool("scale_tools_check", {
4987
+ description: "Check which load testing tools are available.",
4988
+ inputSchema: {},
4989
+ annotations: {
4990
+ readOnlyHint: true,
4991
+ destructiveHint: false,
4992
+ idempotentHint: true,
4993
+ openWorldHint: false,
4994
+ },
4995
+ }, async () => {
4996
+ try {
4997
+ const k6 = await isK6Available();
4998
+ const tool = await detectLoadTool();
4999
+ return jsonResponse({
5000
+ k6Available: k6,
5001
+ recommendedTool: tool,
5002
+ installInstructions: !tool ? {
5003
+ k6: "brew install k6 # macOS\nchoco install k6 # Windows\nsudo apt install k6 # Linux",
5004
+ artillery: "npm install -g artillery",
5005
+ } : null,
5006
+ });
5007
+ }
5008
+ catch (error) {
5009
+ return errorResponse(error instanceof Error ? error.message : String(error));
5010
+ }
5011
+ });
5012
+ // ---------------------------------------------------------------------------
5013
+ // Deployment Verification Tools (M9)
5014
+ // ---------------------------------------------------------------------------
5015
+ import { runDeployVerification, quickDeployCheck, detectProvider as detectDeployProvider, loadDeployConfig, generateDeployConfig, isVercelAvailable, listDeployments, promoteToProduction, rollback as vercelRollback, } from "./scanners/deploy/index.js";
5016
+ server.registerTool("deploy_verify", {
5017
+ description: `Run deployment verification against a deployed app.
5018
+
5019
+ This tool:
5020
+ 1. Detects the deployment provider (Vercel, AWS, etc.)
5021
+ 2. Runs health checks against configured endpoints
5022
+ 3. Executes smoke tests from .vaspera/deploy.yaml
5023
+ 4. Optionally runs canary analysis
5024
+ 5. Calculates a deploy readiness score
5025
+
5026
+ Returns a score from 0-100 indicating deployment health.
5027
+ Score >= 70 is considered passing.`,
5028
+ inputSchema: {
5029
+ project_path: z.string().describe("Path to the project"),
5030
+ deployment_url: z.string().describe("URL of the deployed app"),
5031
+ skip_health_check: z.boolean().optional().describe("Skip health checks"),
5032
+ skip_smoke_tests: z.boolean().optional().describe("Skip smoke tests"),
5033
+ run_canary: z.boolean().optional().describe("Run canary analysis"),
5034
+ canary_duration: z.number().optional().describe("Canary duration in ms"),
5035
+ },
5036
+ annotations: {
5037
+ readOnlyHint: true,
5038
+ destructiveHint: false,
5039
+ idempotentHint: true,
5040
+ openWorldHint: true,
5041
+ },
5042
+ }, async ({ project_path, deployment_url, skip_health_check, skip_smoke_tests, run_canary, canary_duration }) => {
5043
+ try {
5044
+ const result = await runDeployVerification(project_path, deployment_url, {
5045
+ skipHealthCheck: skip_health_check,
5046
+ skipSmokeTests: skip_smoke_tests,
5047
+ runCanary: run_canary,
5048
+ canaryDuration: canary_duration,
5049
+ });
5050
+ return jsonResponse({
5051
+ success: result.success,
5052
+ provider: result.provider,
5053
+ score: result.score,
5054
+ deployment: result.deployment ? {
5055
+ id: result.deployment.id,
5056
+ environment: result.deployment.environment,
5057
+ status: result.deployment.status,
5058
+ url: result.deployment.url,
5059
+ } : null,
5060
+ healthChecks: result.healthChecks.map((h) => ({
5061
+ endpoint: h.endpoint,
5062
+ status: h.status,
5063
+ responseTime: h.responseTime,
5064
+ })),
5065
+ smokeTests: result.smokeTests.map((t) => ({
5066
+ name: t.name,
5067
+ passed: t.passed,
5068
+ responseTime: t.responseTime,
5069
+ })),
5070
+ canary: result.canary ? {
5071
+ phase: result.canary.phase,
5072
+ recommendation: result.canary.recommendation,
5073
+ errorRate: result.canary.metrics.errorRate,
5074
+ p95Latency: result.canary.metrics.p95Latency,
5075
+ } : null,
5076
+ duration: result.duration,
5077
+ });
5078
+ }
5079
+ catch (error) {
5080
+ return errorResponse(error instanceof Error ? error.message : String(error));
5081
+ }
5082
+ });
5083
+ server.registerTool("deploy_health", {
5084
+ description: "Quick health check against a deployed app.",
5085
+ inputSchema: {
5086
+ deployment_url: z.string().describe("URL of the deployed app"),
5087
+ endpoints: z.array(z.string()).optional().describe("Endpoints to check"),
5088
+ },
5089
+ annotations: {
5090
+ readOnlyHint: true,
5091
+ destructiveHint: false,
5092
+ idempotentHint: true,
5093
+ openWorldHint: true,
5094
+ },
5095
+ }, async ({ deployment_url, endpoints }) => {
5096
+ try {
5097
+ const result = await quickDeployCheck(deployment_url, endpoints);
5098
+ return jsonResponse({
5099
+ healthy: result.healthy,
5100
+ score: result.score,
5101
+ checks: result.checks.map((c) => ({
5102
+ endpoint: c.endpoint,
5103
+ status: c.status,
5104
+ statusCode: c.statusCode,
5105
+ responseTime: c.responseTime,
5106
+ error: c.error,
5107
+ })),
5108
+ });
5109
+ }
5110
+ catch (error) {
5111
+ return errorResponse(error instanceof Error ? error.message : String(error));
5112
+ }
5113
+ });
5114
+ server.registerTool("deploy_detect", {
5115
+ description: "Detect the deployment provider for a project.",
5116
+ inputSchema: {
5117
+ project_path: z.string().describe("Path to the project"),
5118
+ },
5119
+ annotations: {
5120
+ readOnlyHint: true,
5121
+ destructiveHint: false,
5122
+ idempotentHint: true,
5123
+ openWorldHint: false,
5124
+ },
5125
+ }, async ({ project_path }) => {
5126
+ try {
5127
+ const detection = await detectDeployProvider(project_path);
5128
+ const config = await loadDeployConfig(project_path);
5129
+ return jsonResponse({
5130
+ provider: detection.provider,
5131
+ confidence: detection.confidence,
5132
+ indicators: detection.indicators,
5133
+ projectId: detection.projectId,
5134
+ hasConfig: !!config,
5135
+ vercelAvailable: isVercelAvailable(),
5136
+ });
5137
+ }
5138
+ catch (error) {
5139
+ return errorResponse(error instanceof Error ? error.message : String(error));
5140
+ }
5141
+ });
5142
+ server.registerTool("deploy_config_generate", {
5143
+ description: "Generate a deployment verification config file.",
5144
+ inputSchema: {
5145
+ project_path: z.string().describe("Path to the project"),
5146
+ provider: z.enum(["vercel", "aws", "gcp", "railway", "render", "fly"]).optional()
5147
+ .describe("Deployment provider"),
5148
+ },
5149
+ annotations: {
5150
+ readOnlyHint: false,
5151
+ destructiveHint: false,
5152
+ idempotentHint: true,
5153
+ openWorldHint: false,
5154
+ },
5155
+ }, async ({ project_path, provider }) => {
5156
+ try {
5157
+ const configPath = await generateDeployConfig(project_path, provider);
5158
+ return jsonResponse({
5159
+ created: true,
5160
+ path: configPath,
5161
+ message: `Deploy config created. Edit ${configPath} to customize health checks and smoke tests.`,
5162
+ });
5163
+ }
5164
+ catch (error) {
5165
+ return errorResponse(error instanceof Error ? error.message : String(error));
5166
+ }
5167
+ });
5168
+ server.registerTool("deploy_vercel_list", {
5169
+ description: "List recent Vercel deployments for a project.",
5170
+ inputSchema: {
5171
+ project_id: z.string().describe("Vercel project ID"),
5172
+ limit: z.number().optional().describe("Number of deployments to list"),
5173
+ target: z.enum(["production", "preview"]).optional().describe("Filter by target"),
5174
+ },
5175
+ annotations: {
5176
+ readOnlyHint: true,
5177
+ destructiveHint: false,
5178
+ idempotentHint: true,
5179
+ openWorldHint: true,
5180
+ },
5181
+ }, async ({ project_id, limit, target }) => {
5182
+ try {
5183
+ if (!isVercelAvailable()) {
5184
+ return errorResponse("VERCEL_TOKEN not configured");
5185
+ }
5186
+ const deployments = await listDeployments(project_id, { limit, target });
5187
+ return jsonResponse({
5188
+ total: deployments.length,
5189
+ deployments: deployments.map((d) => ({
5190
+ id: d.id,
5191
+ environment: d.environment,
5192
+ status: d.status,
5193
+ url: d.url,
5194
+ createdAt: d.createdAt,
5195
+ commit: d.meta?.commit,
5196
+ branch: d.meta?.branch,
5197
+ })),
5198
+ });
5199
+ }
5200
+ catch (error) {
5201
+ return errorResponse(error instanceof Error ? error.message : String(error));
5202
+ }
5203
+ });
5204
+ server.registerTool("deploy_vercel_promote", {
5205
+ description: "Promote a Vercel preview deployment to production.",
5206
+ inputSchema: {
5207
+ project_id: z.string().describe("Vercel project ID"),
5208
+ deployment_id: z.string().describe("Deployment ID to promote"),
5209
+ },
5210
+ annotations: {
5211
+ readOnlyHint: false,
5212
+ destructiveHint: true,
5213
+ idempotentHint: false,
5214
+ openWorldHint: true,
5215
+ },
5216
+ }, async ({ project_id, deployment_id }) => {
5217
+ try {
5218
+ if (!isVercelAvailable()) {
5219
+ return errorResponse("VERCEL_TOKEN not configured");
5220
+ }
5221
+ const result = await promoteToProduction(deployment_id, project_id);
5222
+ return jsonResponse({
5223
+ success: result.success,
5224
+ error: result.error,
5225
+ message: result.success
5226
+ ? `Deployment ${deployment_id} promoted to production`
5227
+ : `Promotion failed: ${result.error}`,
5228
+ });
5229
+ }
5230
+ catch (error) {
5231
+ return errorResponse(error instanceof Error ? error.message : String(error));
5232
+ }
5233
+ });
5234
+ server.registerTool("deploy_vercel_rollback", {
5235
+ description: "Rollback to a previous Vercel deployment.",
5236
+ inputSchema: {
5237
+ project_id: z.string().describe("Vercel project ID"),
5238
+ deployment_id: z.string().describe("Deployment ID to rollback to"),
5239
+ },
5240
+ annotations: {
5241
+ readOnlyHint: false,
5242
+ destructiveHint: true,
5243
+ idempotentHint: false,
5244
+ openWorldHint: true,
5245
+ },
5246
+ }, async ({ project_id, deployment_id }) => {
5247
+ try {
5248
+ if (!isVercelAvailable()) {
5249
+ return errorResponse("VERCEL_TOKEN not configured");
5250
+ }
5251
+ const result = await vercelRollback(project_id, deployment_id);
5252
+ return jsonResponse({
5253
+ success: result.success,
5254
+ error: result.error,
5255
+ message: result.success
5256
+ ? `Rolled back to deployment ${deployment_id}`
5257
+ : `Rollback failed: ${result.error}`,
5258
+ });
5259
+ }
5260
+ catch (error) {
5261
+ return errorResponse(error instanceof Error ? error.message : String(error));
5262
+ }
5263
+ });
5264
+ // ---------------------------------------------------------------------------
5265
+ // M10: AI Code Verification Tools
5266
+ // ---------------------------------------------------------------------------
5267
+ import { runAIVerification, quickAICheck, generateAIVerifyConfig, scanDirectoryForAIPatterns, scanDirectoryForHallucinations, } from "./scanners/ai-code/index.js";
5268
+ server.registerTool("ai_code_verify", {
5269
+ description: `Run full AI code verification on a project.
5270
+
5271
+ This tool:
5272
+ 1. Scans for AI-generated code patterns
5273
+ 2. Checks for hallucinated imports and APIs
5274
+ 3. Scores each file for AI likelihood
5275
+ 4. Identifies changes requiring human review
5276
+
5277
+ Returns a comprehensive verification result with scores:
5278
+ - Detection Score: Lower AI likelihood = better
5279
+ - Hallucination Score: Fewer hallucinations = better
5280
+ - Review Score: Fewer reviews needed = better
5281
+ - Overall Score: Weighted average (0-100)`,
5282
+ inputSchema: {
5283
+ project_path: z.string().describe("Path to the project"),
5284
+ extensions: z.array(z.string()).optional().describe("File extensions to analyze"),
5285
+ exclude: z.array(z.string()).optional().describe("Patterns to exclude"),
5286
+ files: z.array(z.string()).optional().describe("Specific files to analyze"),
5287
+ },
5288
+ annotations: {
5289
+ readOnlyHint: true,
5290
+ destructiveHint: false,
5291
+ idempotentHint: true,
5292
+ openWorldHint: false,
5293
+ },
5294
+ }, async ({ project_path, extensions, exclude, files }) => {
5295
+ try {
5296
+ const result = await runAIVerification(project_path, {
5297
+ extensions,
5298
+ exclude,
5299
+ files,
5300
+ });
5301
+ return jsonResponse({
5302
+ success: result.success,
5303
+ filesAnalyzed: result.filesAnalyzed,
5304
+ summary: result.summary,
5305
+ score: result.score,
5306
+ duration: result.duration,
5307
+ hallucinations: result.hallucinations.slice(0, 20),
5308
+ changesRequiringReview: result.changesScored
5309
+ .filter((c) => c.requiresReview)
5310
+ .slice(0, 20)
5311
+ .map((c) => ({
5312
+ file: c.file,
5313
+ aiLikelihood: c.aiLikelihood,
5314
+ confidence: c.confidence,
5315
+ reason: c.reviewReason,
5316
+ })),
5317
+ });
5318
+ }
5319
+ catch (error) {
5320
+ return errorResponse(error instanceof Error ? error.message : String(error));
5321
+ }
5322
+ });
5323
+ server.registerTool("ai_code_quick", {
5324
+ description: `Quick AI code check for a single file.
5325
+
5326
+ Returns AI likelihood, hallucination count, and whether review is required.
5327
+ Use this for fast feedback on individual files.`,
5328
+ inputSchema: {
5329
+ project_path: z.string().describe("Path to the project"),
5330
+ file_path: z.string().describe("Path to the file to check"),
5331
+ },
5332
+ annotations: {
5333
+ readOnlyHint: true,
5334
+ destructiveHint: false,
5335
+ idempotentHint: true,
5336
+ openWorldHint: false,
5337
+ },
5338
+ }, async ({ project_path, file_path }) => {
5339
+ try {
5340
+ const fullPath = file_path.startsWith("/") ? file_path : join(project_path, file_path);
5341
+ const result = await quickAICheck(fullPath, project_path);
5342
+ return jsonResponse({
5343
+ file: file_path,
5344
+ aiLikelihood: result.aiLikelihood,
5345
+ hallucinations: result.hallucinations,
5346
+ requiresReview: result.requiresReview,
5347
+ indicatorCount: result.indicators.length,
5348
+ indicators: result.indicators.slice(0, 10).map((i) => ({
5349
+ type: i.type,
5350
+ line: i.location.line,
5351
+ description: i.description,
5352
+ confidence: i.confidence,
5353
+ })),
5354
+ });
5355
+ }
5356
+ catch (error) {
5357
+ return errorResponse(error instanceof Error ? error.message : String(error));
5358
+ }
5359
+ });
5360
+ server.registerTool("ai_code_config_generate", {
5361
+ description: "Generate a sample AI code verification config file.",
5362
+ inputSchema: {
5363
+ project_path: z.string().describe("Path to the project"),
5364
+ },
5365
+ annotations: {
5366
+ readOnlyHint: false,
5367
+ destructiveHint: false,
5368
+ idempotentHint: true,
5369
+ openWorldHint: false,
5370
+ },
5371
+ }, async ({ project_path }) => {
5372
+ try {
5373
+ const configPath = await generateAIVerifyConfig(project_path);
5374
+ return jsonResponse({
5375
+ success: true,
5376
+ configPath,
5377
+ message: `AI verification config created at ${configPath}`,
5378
+ });
5379
+ }
5380
+ catch (error) {
5381
+ return errorResponse(error instanceof Error ? error.message : String(error));
5382
+ }
5383
+ });
5384
+ server.registerTool("ai_code_patterns", {
5385
+ description: "Scan a directory for AI code generation patterns.",
5386
+ inputSchema: {
5387
+ project_path: z.string().describe("Path to the project"),
5388
+ extensions: z.array(z.string()).optional().describe("File extensions to scan"),
5389
+ exclude: z.array(z.string()).optional().describe("Patterns to exclude"),
5390
+ },
5391
+ annotations: {
5392
+ readOnlyHint: true,
5393
+ destructiveHint: false,
5394
+ idempotentHint: true,
5395
+ openWorldHint: false,
5396
+ },
5397
+ }, async ({ project_path, extensions, exclude }) => {
5398
+ try {
5399
+ const result = await scanDirectoryForAIPatterns(project_path, {
5400
+ extensions,
5401
+ exclude,
5402
+ });
5403
+ const byFile = new Map();
5404
+ for (const match of result.matches) {
5405
+ byFile.set(match.file, (byFile.get(match.file) ?? 0) + 1);
5406
+ }
5407
+ return jsonResponse({
5408
+ filesScanned: result.files,
5409
+ totalMatches: result.matches.length,
5410
+ byConfidence: {
5411
+ high: result.indicators.filter((i) => i.confidence === "high").length,
5412
+ medium: result.indicators.filter((i) => i.confidence === "medium").length,
5413
+ low: result.indicators.filter((i) => i.confidence === "low").length,
5414
+ },
5415
+ topFiles: Array.from(byFile.entries())
5416
+ .sort((a, b) => b[1] - a[1])
5417
+ .slice(0, 10)
5418
+ .map(([file, count]) => ({ file, matches: count })),
5419
+ recentMatches: result.matches.slice(0, 20).map((m) => ({
5420
+ pattern: m.pattern,
5421
+ file: m.file,
5422
+ line: m.line,
5423
+ confidence: m.confidence,
5424
+ })),
5425
+ });
5426
+ }
5427
+ catch (error) {
5428
+ return errorResponse(error instanceof Error ? error.message : String(error));
5429
+ }
5430
+ });
5431
+ server.registerTool("ai_code_hallucinations", {
5432
+ description: "Scan for hallucinated imports, APIs, and deprecated patterns.",
5433
+ inputSchema: {
5434
+ project_path: z.string().describe("Path to the project"),
5435
+ extensions: z.array(z.string()).optional().describe("File extensions to scan"),
5436
+ exclude: z.array(z.string()).optional().describe("Patterns to exclude"),
5437
+ },
5438
+ annotations: {
5439
+ readOnlyHint: true,
5440
+ destructiveHint: false,
5441
+ idempotentHint: true,
5442
+ openWorldHint: false,
5443
+ },
5444
+ }, async ({ project_path, extensions, exclude }) => {
5445
+ try {
5446
+ const result = await scanDirectoryForHallucinations(project_path, {
5447
+ extensions,
5448
+ exclude,
5449
+ });
5450
+ return jsonResponse({
5451
+ filesScanned: result.files,
5452
+ totalFindings: result.findings.length,
5453
+ byType: result.summary.byType,
5454
+ bySeverity: result.summary.bySeverity,
5455
+ findings: result.findings.slice(0, 30).map((f) => ({
5456
+ type: f.type,
5457
+ severity: f.severity,
5458
+ file: f.location.file,
5459
+ line: f.location.line,
5460
+ description: f.description,
5461
+ suggestion: f.suggestion,
5462
+ })),
5463
+ });
5464
+ }
5465
+ catch (error) {
5466
+ return errorResponse(error instanceof Error ? error.message : String(error));
5467
+ }
5468
+ });
5469
+ // ---------------------------------------------------------------------------
4596
5470
  // Exports (for HTTP server and client integration)
4597
5471
  // ---------------------------------------------------------------------------
4598
5472
  export { server, textResponse, jsonResponse, errorResponse };