vaspera 2.12.0 → 2.13.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.
- package/CHANGELOG.md +46 -0
- package/dist/__tests__/siem-integration.test.d.ts +7 -0
- package/dist/__tests__/siem-integration.test.d.ts.map +1 -0
- package/dist/__tests__/siem-integration.test.js +285 -0
- package/dist/__tests__/siem-integration.test.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +296 -16
- package/dist/index.js.map +1 -1
- package/dist/persistence/__tests__/json-fallback.test.d.ts +5 -0
- package/dist/persistence/__tests__/json-fallback.test.d.ts.map +1 -0
- package/dist/persistence/__tests__/json-fallback.test.js +249 -0
- package/dist/persistence/__tests__/json-fallback.test.js.map +1 -0
- package/dist/persistence/__tests__/persistence.test.js.map +1 -1
- package/dist/persistence/db.d.ts +15 -0
- package/dist/persistence/db.d.ts.map +1 -1
- package/dist/persistence/db.js +59 -10
- package/dist/persistence/db.js.map +1 -1
- package/dist/persistence/index.d.ts +13 -4
- package/dist/persistence/index.d.ts.map +1 -1
- package/dist/persistence/index.js +139 -14
- package/dist/persistence/index.js.map +1 -1
- package/dist/persistence/json-fallback.d.ts +52 -0
- package/dist/persistence/json-fallback.d.ts.map +1 -0
- package/dist/persistence/json-fallback.js +283 -0
- package/dist/persistence/json-fallback.js.map +1 -0
- package/dist/scanners/ai-code/index.d.ts.map +1 -1
- package/dist/scanners/ai-code/index.js +90 -2
- package/dist/scanners/ai-code/index.js.map +1 -1
- package/dist/scanners/ai-code/types.d.ts +12 -0
- package/dist/scanners/ai-code/types.d.ts.map +1 -1
- package/dist/scanners/eslint.d.ts.map +1 -1
- package/dist/scanners/eslint.js +45 -3
- package/dist/scanners/eslint.js.map +1 -1
- package/dist/scanners/scale/bottleneck-detector.d.ts +13 -2
- package/dist/scanners/scale/bottleneck-detector.d.ts.map +1 -1
- package/dist/scanners/scale/bottleneck-detector.js +199 -72
- package/dist/scanners/scale/bottleneck-detector.js.map +1 -1
- package/dist/scanners/types.d.ts +18 -1
- package/dist/scanners/types.d.ts.map +1 -1
- package/dist/scanners/types.js.map +1 -1
- package/dist/scanners/typescript.d.ts.map +1 -1
- package/dist/scanners/typescript.js +36 -4
- package/dist/scanners/typescript.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -56,6 +56,8 @@ import { validateProjectPath, PathValidationError } from "./util/paths.js";
|
|
|
56
56
|
// Telemetry and scan registry
|
|
57
57
|
import { trackCertificationStarted, trackCertificationCompleted, trackScannerRun, } from "./telemetry/usage.js";
|
|
58
58
|
import { getRegistry } from "./telemetry/registry.js";
|
|
59
|
+
// False positive feedback
|
|
60
|
+
import { submitFeedback, generateFeedbackReport, getSuppressionSuggestions, FP_REASON_DESCRIPTIONS, } from "./scanners/fp-feedback.js";
|
|
59
61
|
// ---------------------------------------------------------------------------
|
|
60
62
|
// Config
|
|
61
63
|
// ---------------------------------------------------------------------------
|
|
@@ -871,6 +873,64 @@ server.registerTool("certification_scan", {
|
|
|
871
873
|
};
|
|
872
874
|
});
|
|
873
875
|
// ---------------------------------------------------------------------------
|
|
876
|
+
// Tool: Diff-Aware Scanning (CI Integration)
|
|
877
|
+
// ---------------------------------------------------------------------------
|
|
878
|
+
server.registerTool("certification_scan_diff", {
|
|
879
|
+
title: "Diff-Aware Certification Scan",
|
|
880
|
+
description: `Scan only changed files for faster CI/PR workflows.
|
|
881
|
+
|
|
882
|
+
Uses \`git diff\` to detect files changed since a base commit (e.g., origin/main).
|
|
883
|
+
Reduces scan time by 50-90% in typical PR scenarios.
|
|
884
|
+
|
|
885
|
+
**Security-critical files** (auth, middleware, API routes) are always included
|
|
886
|
+
regardless of changes when \`always_scan_security\` is true.`,
|
|
887
|
+
inputSchema: {
|
|
888
|
+
project_path: z.string().describe("Absolute path to the project root"),
|
|
889
|
+
base_sha: z.string().optional().describe("Base SHA for comparison (default: origin/main)"),
|
|
890
|
+
head_sha: z.string().optional().describe("Head SHA to compare (default: HEAD)"),
|
|
891
|
+
always_scan_security: z.boolean().optional().default(true).describe("Always include security-critical files"),
|
|
892
|
+
exclude: z.array(z.string()).optional().describe("Regex patterns to exclude from scanning"),
|
|
893
|
+
},
|
|
894
|
+
annotations: {
|
|
895
|
+
readOnlyHint: true,
|
|
896
|
+
destructiveHint: false,
|
|
897
|
+
idempotentHint: true,
|
|
898
|
+
openWorldHint: false,
|
|
899
|
+
},
|
|
900
|
+
}, async ({ project_path, base_sha, head_sha, always_scan_security, exclude }) => {
|
|
901
|
+
const { configureIncrementalScan } = await import("./action/diff-mode.js");
|
|
902
|
+
const result = await configureIncrementalScan({
|
|
903
|
+
projectPath: project_path,
|
|
904
|
+
baseSha: base_sha,
|
|
905
|
+
headSha: head_sha,
|
|
906
|
+
alwaysScanSecurityFiles: always_scan_security,
|
|
907
|
+
exclude,
|
|
908
|
+
});
|
|
909
|
+
const fileList = result.filesToScan.map((f) => f.path);
|
|
910
|
+
const securityFiles = fileList.filter((f) => f.includes("auth") ||
|
|
911
|
+
f.includes("security") ||
|
|
912
|
+
f.includes("middleware") ||
|
|
913
|
+
f.includes("api/"));
|
|
914
|
+
return jsonResponse({
|
|
915
|
+
isIncremental: result.isIncremental,
|
|
916
|
+
filesToScan: fileList,
|
|
917
|
+
fileCount: fileList.length,
|
|
918
|
+
securityFileCount: securityFiles.length,
|
|
919
|
+
removedFiles: result.removedFiles,
|
|
920
|
+
estimatedSavings: `${result.estimatedSavingsPercent}%`,
|
|
921
|
+
diff: {
|
|
922
|
+
baseSha: result.diff.baseSha,
|
|
923
|
+
headSha: result.diff.headSha,
|
|
924
|
+
additions: result.diff.additions,
|
|
925
|
+
deletions: result.diff.deletions,
|
|
926
|
+
changedFilesTotal: result.diff.changedFiles.length,
|
|
927
|
+
},
|
|
928
|
+
note: result.isIncremental
|
|
929
|
+
? `Scanning ${fileList.length} changed files (${result.estimatedSavingsPercent}% reduction)`
|
|
930
|
+
: "Full scan recommended (diff detection failed or no changes)",
|
|
931
|
+
});
|
|
932
|
+
});
|
|
933
|
+
// ---------------------------------------------------------------------------
|
|
874
934
|
// Tool: Check Scanner Availability
|
|
875
935
|
// ---------------------------------------------------------------------------
|
|
876
936
|
server.registerTool("certification_scanners_available", {
|
|
@@ -1789,11 +1849,17 @@ server.registerTool("certification_dashboard", {
|
|
|
1789
1849
|
// ---------------------------------------------------------------------------
|
|
1790
1850
|
server.registerTool("autofix_preview", {
|
|
1791
1851
|
title: "Preview Auto-Fix for Finding",
|
|
1792
|
-
description: `Preview an automatic fix
|
|
1852
|
+
description: `Preview an automatic fix without applying it. Can be used two ways:
|
|
1853
|
+
1. With certification_id + finding_id to preview a fix from a certification scan
|
|
1854
|
+
2. Standalone with file + pattern_id to preview a fix without running a full scan first`,
|
|
1793
1855
|
inputSchema: {
|
|
1794
1856
|
project_path: z.string().describe("Absolute path to the project root"),
|
|
1795
|
-
certification_id: z.string().describe("Certification ID"),
|
|
1796
|
-
finding_id: z.string().describe("Finding ID to preview fix for"),
|
|
1857
|
+
certification_id: z.string().optional().describe("Certification ID (optional if using standalone mode)"),
|
|
1858
|
+
finding_id: z.string().optional().describe("Finding ID to preview fix for (required with certification_id)"),
|
|
1859
|
+
file: z.string().optional().describe("File path relative to project root (standalone mode)"),
|
|
1860
|
+
line: z.number().optional().describe("Line number where the issue is located (standalone mode)"),
|
|
1861
|
+
pattern_id: z.string().optional().describe("Pattern ID to apply (e.g., 'perf-async-foreach', 'sec-hardcoded-secret'). Use autofix_list_patterns to see available patterns."),
|
|
1862
|
+
category: z.string().optional().describe("Finding category (alternative to pattern_id, e.g., 'async-foreach', 'hardcoded')"),
|
|
1797
1863
|
},
|
|
1798
1864
|
annotations: {
|
|
1799
1865
|
readOnlyHint: true,
|
|
@@ -1801,24 +1867,51 @@ server.registerTool("autofix_preview", {
|
|
|
1801
1867
|
idempotentHint: true,
|
|
1802
1868
|
openWorldHint: false,
|
|
1803
1869
|
},
|
|
1804
|
-
}, async ({ project_path, certification_id, finding_id }) => {
|
|
1805
|
-
const certification = await getCertification(project_path, certification_id);
|
|
1806
|
-
if (!certification) {
|
|
1807
|
-
return errorResponse(`Certification ${certification_id} not found`);
|
|
1808
|
-
}
|
|
1809
|
-
// Find the finding
|
|
1870
|
+
}, async ({ project_path, certification_id, finding_id, file, line, pattern_id, category }) => {
|
|
1810
1871
|
let finding = null;
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1872
|
+
if (certification_id && finding_id) {
|
|
1873
|
+
const certification = await getCertification(project_path, certification_id);
|
|
1874
|
+
if (!certification) {
|
|
1875
|
+
return errorResponse(`Certification ${certification_id} not found`);
|
|
1876
|
+
}
|
|
1877
|
+
for (const agentData of Object.values(certification.agents)) {
|
|
1878
|
+
if (agentData) {
|
|
1879
|
+
finding = agentData.findings.find((f) => f.id === finding_id) || null;
|
|
1880
|
+
if (finding)
|
|
1881
|
+
break;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
if (!finding) {
|
|
1885
|
+
return errorResponse(`Finding ${finding_id} not found`);
|
|
1816
1886
|
}
|
|
1817
1887
|
}
|
|
1818
|
-
if (
|
|
1819
|
-
|
|
1888
|
+
else if (file) {
|
|
1889
|
+
if (!pattern_id && !category) {
|
|
1890
|
+
return errorResponse("Standalone mode requires either pattern_id or category. Use autofix_list_patterns to see available patterns.");
|
|
1891
|
+
}
|
|
1892
|
+
finding = {
|
|
1893
|
+
id: pattern_id || `standalone-${category || "unknown"}`,
|
|
1894
|
+
severity: "medium",
|
|
1895
|
+
category: category || pattern_id || "unknown",
|
|
1896
|
+
file,
|
|
1897
|
+
line: line || 1,
|
|
1898
|
+
description: "Standalone autofix preview",
|
|
1899
|
+
evidence: "",
|
|
1900
|
+
confidence: 100,
|
|
1901
|
+
verifications: [],
|
|
1902
|
+
created_at: new Date().toISOString(),
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
else {
|
|
1906
|
+
return errorResponse("Provide either (certification_id + finding_id) or (file + pattern_id/category) for standalone mode.");
|
|
1820
1907
|
}
|
|
1821
1908
|
const preview = await previewFix(project_path, finding);
|
|
1909
|
+
if (!preview.canAutoFix && !certification_id) {
|
|
1910
|
+
return jsonResponse({
|
|
1911
|
+
...preview,
|
|
1912
|
+
hint: "No matching pattern found. Use autofix_list_patterns to see available fix patterns and their IDs.",
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1822
1915
|
return jsonResponse({ ...preview });
|
|
1823
1916
|
});
|
|
1824
1917
|
// ---------------------------------------------------------------------------
|
|
@@ -6094,6 +6187,193 @@ server.registerTool("ai_code_hallucinations", {
|
|
|
6094
6187
|
}
|
|
6095
6188
|
});
|
|
6096
6189
|
// ---------------------------------------------------------------------------
|
|
6190
|
+
// False Positive Feedback Tools
|
|
6191
|
+
// ---------------------------------------------------------------------------
|
|
6192
|
+
server.registerTool("feedback_submit", {
|
|
6193
|
+
description: "Submit feedback marking a finding as true positive (TP) or false positive (FP). Helps improve scanner accuracy over time.",
|
|
6194
|
+
inputSchema: {
|
|
6195
|
+
project_path: z.string().describe("Path to the project"),
|
|
6196
|
+
scanner: z.enum([
|
|
6197
|
+
"semgrep", "npm-audit", "gitleaks", "tsc", "eslint",
|
|
6198
|
+
"bandit", "gosec", "brakeman", "trivy", "binary-analysis",
|
|
6199
|
+
"memory-safety", "race-condition", "healthcare", "logic",
|
|
6200
|
+
"dast", "zap", "nuclei", "terraform", "tfsec", "checkov",
|
|
6201
|
+
"openapi", "spectral", "rust", "cargo-audit", "clippy",
|
|
6202
|
+
"detection", "plugin"
|
|
6203
|
+
]).describe("Scanner that generated the finding"),
|
|
6204
|
+
rule_id: z.string().describe("Rule ID (e.g., 'semgrep:owasp.sql-injection')"),
|
|
6205
|
+
file: z.string().describe("File path where the finding was reported"),
|
|
6206
|
+
line: z.number().optional().describe("Line number of the finding"),
|
|
6207
|
+
verdict: z.enum(["tp", "fp"]).describe("Your verdict: 'tp' for true positive, 'fp' for false positive"),
|
|
6208
|
+
reason: z.enum([
|
|
6209
|
+
"test-code", "false-pattern-match", "sanitized-elsewhere",
|
|
6210
|
+
"intentional", "vendor-code", "generated-code",
|
|
6211
|
+
"example-code", "configuration", "other"
|
|
6212
|
+
]).optional().describe("Reason for marking as FP (required if verdict is 'fp')"),
|
|
6213
|
+
details: z.string().optional().describe("Additional context or notes"),
|
|
6214
|
+
},
|
|
6215
|
+
annotations: {
|
|
6216
|
+
readOnlyHint: false,
|
|
6217
|
+
destructiveHint: false,
|
|
6218
|
+
idempotentHint: true,
|
|
6219
|
+
openWorldHint: false,
|
|
6220
|
+
},
|
|
6221
|
+
}, async ({ project_path, scanner, rule_id, file, line, verdict, reason, details }) => {
|
|
6222
|
+
try {
|
|
6223
|
+
const validatedPath = await validateProjectPath(project_path);
|
|
6224
|
+
const finding = {
|
|
6225
|
+
scanner: scanner,
|
|
6226
|
+
ruleId: rule_id,
|
|
6227
|
+
file,
|
|
6228
|
+
line: line ?? 0,
|
|
6229
|
+
message: "",
|
|
6230
|
+
severity: "medium",
|
|
6231
|
+
confidence: 100,
|
|
6232
|
+
};
|
|
6233
|
+
const entry = await submitFeedback(validatedPath, finding, verdict, {
|
|
6234
|
+
reason: reason,
|
|
6235
|
+
details,
|
|
6236
|
+
});
|
|
6237
|
+
return jsonResponse({
|
|
6238
|
+
success: true,
|
|
6239
|
+
feedbackId: entry.id,
|
|
6240
|
+
scanner: entry.scanner,
|
|
6241
|
+
ruleId: entry.ruleId,
|
|
6242
|
+
file: entry.file,
|
|
6243
|
+
verdict: entry.verdict,
|
|
6244
|
+
reason: entry.reason,
|
|
6245
|
+
submittedAt: entry.submittedAt,
|
|
6246
|
+
message: verdict === "fp"
|
|
6247
|
+
? `Marked as false positive. Reason: ${FP_REASON_DESCRIPTIONS[reason] || reason}`
|
|
6248
|
+
: "Marked as true positive. This helps confirm the finding is valid.",
|
|
6249
|
+
});
|
|
6250
|
+
}
|
|
6251
|
+
catch (error) {
|
|
6252
|
+
if (error instanceof PathValidationError) {
|
|
6253
|
+
return errorResponse(`Path validation failed: ${error.message}`);
|
|
6254
|
+
}
|
|
6255
|
+
return errorResponse(error instanceof Error ? error.message : String(error));
|
|
6256
|
+
}
|
|
6257
|
+
});
|
|
6258
|
+
server.registerTool("feedback_report", {
|
|
6259
|
+
description: "Generate a report of all feedback submitted for a project, including FP rates by scanner and top FP reasons.",
|
|
6260
|
+
inputSchema: {
|
|
6261
|
+
project_path: z.string().describe("Path to the project"),
|
|
6262
|
+
},
|
|
6263
|
+
annotations: {
|
|
6264
|
+
readOnlyHint: true,
|
|
6265
|
+
destructiveHint: false,
|
|
6266
|
+
idempotentHint: true,
|
|
6267
|
+
openWorldHint: false,
|
|
6268
|
+
},
|
|
6269
|
+
}, async ({ project_path }) => {
|
|
6270
|
+
try {
|
|
6271
|
+
const validatedPath = await validateProjectPath(project_path);
|
|
6272
|
+
const report = await generateFeedbackReport(validatedPath);
|
|
6273
|
+
return jsonResponse({
|
|
6274
|
+
overview: {
|
|
6275
|
+
totalFeedback: report.overview.totalFeedback,
|
|
6276
|
+
truePositives: report.overview.tpCount,
|
|
6277
|
+
falsePositives: report.overview.fpCount,
|
|
6278
|
+
overallFPRate: `${(report.overview.overallFPRate * 100).toFixed(1)}%`,
|
|
6279
|
+
},
|
|
6280
|
+
byScanner: report.byScanner.map((s) => ({
|
|
6281
|
+
scanner: s.scanner,
|
|
6282
|
+
total: s.total,
|
|
6283
|
+
tp: s.tp,
|
|
6284
|
+
fp: s.fp,
|
|
6285
|
+
fpRate: `${(s.fpRate * 100).toFixed(1)}%`,
|
|
6286
|
+
})),
|
|
6287
|
+
topFPReasons: report.topFPReasons.map((r) => ({
|
|
6288
|
+
reason: r.reason,
|
|
6289
|
+
description: FP_REASON_DESCRIPTIONS[r.reason],
|
|
6290
|
+
count: r.count,
|
|
6291
|
+
percentage: `${r.percentage.toFixed(1)}%`,
|
|
6292
|
+
})),
|
|
6293
|
+
recentFeedback: report.recentFeedback.slice(0, 5).map((f) => ({
|
|
6294
|
+
scanner: f.scanner,
|
|
6295
|
+
ruleId: f.ruleId,
|
|
6296
|
+
file: f.file,
|
|
6297
|
+
verdict: f.verdict,
|
|
6298
|
+
reason: f.reason,
|
|
6299
|
+
submittedAt: f.submittedAt,
|
|
6300
|
+
})),
|
|
6301
|
+
suppressionSuggestions: report.suppressionSuggestions.length,
|
|
6302
|
+
message: report.overview.totalFeedback === 0
|
|
6303
|
+
? "No feedback submitted yet. Use feedback_submit to mark findings as TP or FP."
|
|
6304
|
+
: `Analyzed ${report.overview.totalFeedback} feedback entries. FP rate: ${(report.overview.overallFPRate * 100).toFixed(1)}%`,
|
|
6305
|
+
});
|
|
6306
|
+
}
|
|
6307
|
+
catch (error) {
|
|
6308
|
+
if (error instanceof PathValidationError) {
|
|
6309
|
+
return errorResponse(`Path validation failed: ${error.message}`);
|
|
6310
|
+
}
|
|
6311
|
+
return errorResponse(error instanceof Error ? error.message : String(error));
|
|
6312
|
+
}
|
|
6313
|
+
});
|
|
6314
|
+
server.registerTool("feedback_suppressions", {
|
|
6315
|
+
description: "Get suggestions for rules to disable based on accumulated false positive feedback. Rules with >50% FP rate (min 5 samples) are flagged.",
|
|
6316
|
+
inputSchema: {
|
|
6317
|
+
project_path: z.string().describe("Path to the project"),
|
|
6318
|
+
min_fp_rate: z.number().min(0).max(1).optional().default(0.5).describe("Minimum FP rate to suggest suppression (0.0-1.0, default: 0.5)"),
|
|
6319
|
+
min_sample_size: z.number().min(1).optional().default(5).describe("Minimum number of feedback entries required (default: 5)"),
|
|
6320
|
+
},
|
|
6321
|
+
annotations: {
|
|
6322
|
+
readOnlyHint: true,
|
|
6323
|
+
destructiveHint: false,
|
|
6324
|
+
idempotentHint: true,
|
|
6325
|
+
openWorldHint: false,
|
|
6326
|
+
},
|
|
6327
|
+
}, async ({ project_path, min_fp_rate, min_sample_size }) => {
|
|
6328
|
+
try {
|
|
6329
|
+
const validatedPath = await validateProjectPath(project_path);
|
|
6330
|
+
const suggestions = await getSuppressionSuggestions(validatedPath, {
|
|
6331
|
+
minFPRate: min_fp_rate,
|
|
6332
|
+
minSampleSize: min_sample_size,
|
|
6333
|
+
});
|
|
6334
|
+
if (suggestions.length === 0) {
|
|
6335
|
+
return jsonResponse({
|
|
6336
|
+
suggestions: [],
|
|
6337
|
+
message: "No rules meet the criteria for suppression. Either not enough feedback or FP rates are acceptable.",
|
|
6338
|
+
criteria: {
|
|
6339
|
+
minFPRate: `${(min_fp_rate ?? 0.5) * 100}%`,
|
|
6340
|
+
minSampleSize: min_sample_size ?? 5,
|
|
6341
|
+
},
|
|
6342
|
+
});
|
|
6343
|
+
}
|
|
6344
|
+
return jsonResponse({
|
|
6345
|
+
suggestions: suggestions.map((s) => ({
|
|
6346
|
+
scanner: s.scanner,
|
|
6347
|
+
ruleId: s.ruleId,
|
|
6348
|
+
fpRate: `${(s.fpRate * 100).toFixed(1)}%`,
|
|
6349
|
+
sampleSize: s.sampleSize,
|
|
6350
|
+
recommendation: s.suggestion,
|
|
6351
|
+
recommendationText: s.suggestion === "disable"
|
|
6352
|
+
? "High FP rate (≥80%) - consider disabling this rule"
|
|
6353
|
+
: s.suggestion === "review"
|
|
6354
|
+
? "Moderate FP rate (≥50%) - review rule configuration"
|
|
6355
|
+
: "FP rate acceptable - keep rule enabled",
|
|
6356
|
+
commonReasons: s.commonReasons.map((r) => ({
|
|
6357
|
+
reason: r,
|
|
6358
|
+
description: FP_REASON_DESCRIPTIONS[r],
|
|
6359
|
+
})),
|
|
6360
|
+
})),
|
|
6361
|
+
summary: {
|
|
6362
|
+
totalSuggestions: suggestions.length,
|
|
6363
|
+
disableRecommendations: suggestions.filter((s) => s.suggestion === "disable").length,
|
|
6364
|
+
reviewRecommendations: suggestions.filter((s) => s.suggestion === "review").length,
|
|
6365
|
+
},
|
|
6366
|
+
message: `Found ${suggestions.length} rule(s) with high false positive rates based on your feedback.`,
|
|
6367
|
+
});
|
|
6368
|
+
}
|
|
6369
|
+
catch (error) {
|
|
6370
|
+
if (error instanceof PathValidationError) {
|
|
6371
|
+
return errorResponse(`Path validation failed: ${error.message}`);
|
|
6372
|
+
}
|
|
6373
|
+
return errorResponse(error instanceof Error ? error.message : String(error));
|
|
6374
|
+
}
|
|
6375
|
+
});
|
|
6376
|
+
// ---------------------------------------------------------------------------
|
|
6097
6377
|
// Exports (for HTTP server and client integration)
|
|
6098
6378
|
// ---------------------------------------------------------------------------
|
|
6099
6379
|
export { server, textResponse, jsonResponse, errorResponse };
|