@jigyasudham/veto 0.8.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +209 -52
- package/dist/agents/executor.js +36 -3
- package/dist/cli.js +350 -51
- package/dist/context/reader.js +113 -0
- package/dist/council/index.js +3 -1
- package/dist/plugins/loader.js +49 -0
- package/dist/router/index.js +2 -2
- package/dist/router/learning-updater.js +45 -1
- package/dist/server.js +478 -14
- package/dist/watcher/index.js +77 -0
- package/dist/workflow/pipeline.js +64 -0
- package/package.json +12 -3
- package/.claude/settings.local.json +0 -9
- package/src/adapters/claude.ts +0 -70
- package/src/adapters/codex.ts +0 -71
- package/src/adapters/gemini.ts +0 -71
- package/src/adapters/index.ts +0 -217
- package/src/agents/development/api.ts +0 -120
- package/src/agents/development/backend.ts +0 -85
- package/src/agents/development/coder.ts +0 -213
- package/src/agents/development/database.ts +0 -83
- package/src/agents/development/debugger.ts +0 -238
- package/src/agents/development/devops.ts +0 -86
- package/src/agents/development/frontend.ts +0 -85
- package/src/agents/development/migration.ts +0 -144
- package/src/agents/development/performance.ts +0 -144
- package/src/agents/development/refactor.ts +0 -86
- package/src/agents/development/reviewer.ts +0 -268
- package/src/agents/development/tester.ts +0 -151
- package/src/agents/executor.ts +0 -158
- package/src/agents/memory/context-manager.ts +0 -171
- package/src/agents/memory/decision-logger.ts +0 -160
- package/src/agents/memory/knowledge-base.ts +0 -124
- package/src/agents/memory/pattern-learner.ts +0 -143
- package/src/agents/memory/project-mapper.ts +0 -118
- package/src/agents/quality/accessibility.ts +0 -99
- package/src/agents/quality/code-quality.ts +0 -115
- package/src/agents/quality/compatibility.ts +0 -58
- package/src/agents/quality/documentation.ts +0 -105
- package/src/agents/quality/error-handling.ts +0 -96
- package/src/agents/research/competitor-analyzer.ts +0 -45
- package/src/agents/research/cost-analyzer.ts +0 -54
- package/src/agents/research/estimator.ts +0 -60
- package/src/agents/research/ethics-bias.ts +0 -113
- package/src/agents/research/researcher.ts +0 -114
- package/src/agents/research/risk-assessor.ts +0 -63
- package/src/agents/research/tech-advisor.ts +0 -55
- package/src/agents/security/auth.ts +0 -287
- package/src/agents/security/dependency-audit.ts +0 -337
- package/src/agents/security/penetration.ts +0 -262
- package/src/agents/security/privacy.ts +0 -285
- package/src/agents/security/scanner.ts +0 -322
- package/src/agents/security/secrets.ts +0 -249
- package/src/agents/types.ts +0 -66
- package/src/agents/workflow/automation.ts +0 -59
- package/src/agents/workflow/file-manager.ts +0 -52
- package/src/agents/workflow/git-agent.ts +0 -55
- package/src/agents/workflow/reporter.ts +0 -51
- package/src/agents/workflow/search-agent.ts +0 -40
- package/src/agents/workflow/task-coordinator.ts +0 -41
- package/src/agents/workflow/task-planner.ts +0 -47
- package/src/cli.ts +0 -135
- package/src/council/decision-engine.ts +0 -171
- package/src/council/devil-advocate.ts +0 -116
- package/src/council/index.ts +0 -44
- package/src/council/lead-developer.ts +0 -118
- package/src/council/legal-compliance.ts +0 -152
- package/src/council/product-manager.ts +0 -102
- package/src/council/security.ts +0 -172
- package/src/council/system-architect.ts +0 -132
- package/src/council/types.ts +0 -33
- package/src/council/ux-designer.ts +0 -121
- package/src/memory/local.ts +0 -305
- package/src/memory/schema.ts +0 -174
- package/src/memory/sync.ts +0 -274
- package/src/router/complexity-scorer.ts +0 -96
- package/src/router/context-compressor.ts +0 -74
- package/src/router/index.ts +0 -60
- package/src/router/learning-updater.ts +0 -271
- package/src/router/model-selector.ts +0 -83
- package/src/router/rate-monitor.ts +0 -103
- package/src/server.ts +0 -1038
- package/src/skills/development/skill-api-design.ts +0 -329
- package/src/skills/development/skill-auth.ts +0 -271
- package/src/skills/development/skill-ci-cd.ts +0 -0
- package/src/skills/development/skill-crud.ts +0 -209
- package/src/skills/development/skill-db-schema.ts +0 -0
- package/src/skills/development/skill-docker.ts +0 -0
- package/src/skills/development/skill-env-setup.ts +0 -0
- package/src/skills/development/skill-scaffold.ts +0 -323
- package/src/skills/intelligence/skill-complexity-score.ts +0 -69
- package/src/skills/intelligence/skill-cost-track.ts +0 -39
- package/src/skills/intelligence/skill-learning-loop.ts +0 -69
- package/src/skills/intelligence/skill-pattern-detect.ts +0 -38
- package/src/skills/intelligence/skill-rate-watch.ts +0 -61
- package/src/skills/memory/skill-context-compress.ts +0 -98
- package/src/skills/memory/skill-cross-sync.ts +0 -104
- package/src/skills/memory/skill-decision-log.ts +0 -119
- package/src/skills/memory/skill-session-restore.ts +0 -59
- package/src/skills/memory/skill-session-save.ts +0 -94
- package/src/skills/quality/skill-accessibility.ts +0 -0
- package/src/skills/quality/skill-code-review.ts +0 -84
- package/src/skills/quality/skill-docs-gen.ts +0 -0
- package/src/skills/quality/skill-perf-audit.ts +0 -0
- package/src/skills/quality/skill-security-scan.ts +0 -91
- package/src/skills/quality/skill-test-suite.ts +0 -290
- package/src/skills/workflow/skill-deploy.ts +0 -0
- package/src/skills/workflow/skill-git-workflow.ts +0 -0
- package/src/skills/workflow/skill-rollback.ts +0 -0
- package/src/skills/workflow/skill-task-breakdown.ts +0 -0
- package/tsconfig.json +0 -20
package/dist/server.js
CHANGED
|
@@ -5,17 +5,25 @@
|
|
|
5
5
|
process.removeAllListeners('warning');
|
|
6
6
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
7
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
8
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { buildContextString } from './context/reader.js';
|
|
9
10
|
import { saveSession, restoreSession, listSessions, getDbPath, saveCouncilOutcome, storeKnowledge, searchKnowledge, deleteKnowledge, updateProjectMap, getProjectMap, upsertPattern, getPatterns, } from './memory/local.js';
|
|
10
11
|
import { exportMemory, importMemory } from './memory/sync.js';
|
|
11
12
|
import { runDebate } from './council/index.js';
|
|
12
|
-
import { routeTask, getRateStatus, recordOutcome, getLearningStats, applyLearnedThresholds, getAgentPerformanceStats, getTaskTypeBreakdown, getCouncilInsights } from './router/index.js';
|
|
13
|
+
import { routeTask, getRateStatus, recordOutcome, getLearningStats, applyLearnedThresholds, getAgentPerformanceStats, getTaskTypeBreakdown, getCouncilInsights, getRecommendedAgent } from './router/index.js';
|
|
13
14
|
import { executeParallel, executeOne } from './agents/executor.js';
|
|
14
15
|
import { handoff, continueSession, getPlatformSetup } from './adapters/index.js';
|
|
15
|
-
|
|
16
|
+
import { startWatch, pollWatch, stopWatch } from './watcher/index.js';
|
|
17
|
+
import { runPipeline } from './workflow/pipeline.js';
|
|
18
|
+
import { loadPlugins, listPlugins } from './plugins/loader.js';
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
import { extname, basename } from 'node:path';
|
|
21
|
+
const VERSION = '1.0.0';
|
|
16
22
|
const server = new Server({ name: 'veto', version: VERSION }, {
|
|
17
23
|
capabilities: {
|
|
18
24
|
tools: {},
|
|
25
|
+
resources: {},
|
|
26
|
+
prompts: {},
|
|
19
27
|
},
|
|
20
28
|
});
|
|
21
29
|
// ─── Tool Definitions ─────────────────────────────────────────────────────────
|
|
@@ -158,6 +166,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
158
166
|
type: 'string',
|
|
159
167
|
description: 'Optional: additional context such as codebase state, prior decisions, or constraints.',
|
|
160
168
|
},
|
|
169
|
+
project_dir: {
|
|
170
|
+
type: 'string',
|
|
171
|
+
description: 'Optional: absolute path to the project directory. Veto will auto-read package.json, git diff, and stack info to give the council real project context.',
|
|
172
|
+
},
|
|
161
173
|
session_id: {
|
|
162
174
|
type: 'string',
|
|
163
175
|
description: 'Optional: session ID to associate this council outcome with an active session.',
|
|
@@ -179,6 +191,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
179
191
|
},
|
|
180
192
|
task: { type: 'string', description: 'The task for the agent to plan.' },
|
|
181
193
|
context: { type: 'string', description: 'Optional additional context.' },
|
|
194
|
+
project_dir: { type: 'string', description: 'Optional: absolute path to the project directory. Auto-injects package.json, git diff, and stack info into the agent context.' },
|
|
182
195
|
},
|
|
183
196
|
required: ['agent', 'task'],
|
|
184
197
|
},
|
|
@@ -195,6 +208,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
195
208
|
required: ['code'],
|
|
196
209
|
},
|
|
197
210
|
},
|
|
211
|
+
{
|
|
212
|
+
name: 'veto_diff_review',
|
|
213
|
+
description: 'Reviews a git diff — runs code review, security scan, and secrets scan in parallel across all changed files. Returns a structured verdict (pass/warn/fail), per-file findings, and a CI-ready summary. Pass diff directly or let Veto read it from project_dir automatically.',
|
|
214
|
+
inputSchema: {
|
|
215
|
+
type: 'object',
|
|
216
|
+
properties: {
|
|
217
|
+
diff: { type: 'string', description: 'The git diff to review. If omitted, Veto runs git diff HEAD in project_dir.' },
|
|
218
|
+
project_dir: { type: 'string', description: 'Absolute project path. Used to auto-read git diff if diff is not provided, and to inject codebase context.' },
|
|
219
|
+
context: { type: 'string', description: 'Optional: PR description, ticket number, or focus area.' },
|
|
220
|
+
},
|
|
221
|
+
required: [],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
198
224
|
{
|
|
199
225
|
name: 'veto_security_scan',
|
|
200
226
|
description: 'Runs the Security Scanner (OWASP Top 10) on provided code. Returns vulnerabilities with severity, CWE/OWASP category, and remediation steps.',
|
|
@@ -235,10 +261,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
235
261
|
task: { type: 'string', description: 'Task description for this agent.' },
|
|
236
262
|
code: { type: 'string', description: 'Optional code to analyze (triggers analyze() instead of plan()).' },
|
|
237
263
|
context: { type: 'string', description: 'Optional additional context.' },
|
|
264
|
+
project_dir: { type: 'string', description: 'Optional: per-task project dir override.' },
|
|
238
265
|
},
|
|
239
266
|
required: ['id', 'agent', 'task'],
|
|
240
267
|
},
|
|
241
268
|
},
|
|
269
|
+
project_dir: { type: 'string', description: 'Optional: project directory applied to all tasks (per-task project_dir overrides this). Auto-injects codebase context.' },
|
|
242
270
|
},
|
|
243
271
|
required: ['tasks'],
|
|
244
272
|
},
|
|
@@ -391,6 +419,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
391
419
|
output_quality: { type: 'number', description: 'Output quality score 0–100. 90–100=excellent, 70–89=good, 50–69=acceptable, 30–49=poor, 0–29=failed.' },
|
|
392
420
|
agent: { type: 'string', description: 'The worker agent type used (optional but useful for agent performance tracking).' },
|
|
393
421
|
tokens_used: { type: 'number', description: 'Approximate tokens used (optional).' },
|
|
422
|
+
file_ext: { type: 'string', description: 'File extension of the primary file worked on (e.g. ".ts", ".sql", ".tsx"). Enables predictive agent routing — next time you work on the same extension, veto_route_task will recommend the best agent.' },
|
|
394
423
|
},
|
|
395
424
|
required: ['task_type', 'complexity', 'model_tier', 'output_quality'],
|
|
396
425
|
},
|
|
@@ -457,6 +486,88 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
457
486
|
required: ['platform', 'veto_server_path'],
|
|
458
487
|
},
|
|
459
488
|
},
|
|
489
|
+
{
|
|
490
|
+
name: 'veto_watch',
|
|
491
|
+
description: 'Starts a file watcher on a project directory. Returns a watch_id. Call veto_watch_poll to collect file-change events with recommended agents. Call veto_watch_stop when done.',
|
|
492
|
+
inputSchema: {
|
|
493
|
+
type: 'object',
|
|
494
|
+
properties: {
|
|
495
|
+
project_dir: { type: 'string', description: 'Absolute path to the project directory to watch.' },
|
|
496
|
+
},
|
|
497
|
+
required: ['project_dir'],
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: 'veto_watch_poll',
|
|
502
|
+
description: 'Polls for file-change events from an active watcher. Returns accumulated events since last poll (events are cleared on read). Each event includes the file, recommended agent, and suggested veto tool to call.',
|
|
503
|
+
inputSchema: {
|
|
504
|
+
type: 'object',
|
|
505
|
+
properties: {
|
|
506
|
+
watch_id: { type: 'string', description: 'The watch_id returned by veto_watch.' },
|
|
507
|
+
},
|
|
508
|
+
required: ['watch_id'],
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
name: 'veto_watch_stop',
|
|
513
|
+
description: 'Stops an active file watcher.',
|
|
514
|
+
inputSchema: {
|
|
515
|
+
type: 'object',
|
|
516
|
+
properties: {
|
|
517
|
+
watch_id: { type: 'string', description: 'The watch_id returned by veto_watch.' },
|
|
518
|
+
},
|
|
519
|
+
required: ['watch_id'],
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: 'veto_workflow',
|
|
524
|
+
description: 'Runs a sequential agent pipeline with optional pass/fail gates between steps. Each step runs a worker agent; if a gate score is set and the step confidence falls below it, the pipeline stops. Returns per-step results plus an overall verdict (passed/partial/failed).',
|
|
525
|
+
inputSchema: {
|
|
526
|
+
type: 'object',
|
|
527
|
+
properties: {
|
|
528
|
+
steps: {
|
|
529
|
+
type: 'array',
|
|
530
|
+
description: 'Ordered pipeline steps.',
|
|
531
|
+
items: {
|
|
532
|
+
type: 'object',
|
|
533
|
+
properties: {
|
|
534
|
+
id: { type: 'string', description: 'Step identifier.' },
|
|
535
|
+
agent: { type: 'string', description: 'Worker agent type.' },
|
|
536
|
+
task: { type: 'string', description: 'Task description for this step.' },
|
|
537
|
+
code: { type: 'string', description: 'Optional code to analyze.' },
|
|
538
|
+
context: { type: 'string', description: 'Optional context.' },
|
|
539
|
+
gate: { type: 'number', description: 'Optional minimum confidence % (0–100) required to proceed to the next step.' },
|
|
540
|
+
},
|
|
541
|
+
required: ['id', 'agent', 'task'],
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
project_dir: { type: 'string', description: 'Optional project directory — auto-injects codebase context into all steps.' },
|
|
545
|
+
},
|
|
546
|
+
required: ['steps'],
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: 'veto_explain',
|
|
551
|
+
description: 'Reads a file and returns an expert explanation from the most appropriate agent (auto-detected from file extension and name). Pass depth to control detail level.',
|
|
552
|
+
inputSchema: {
|
|
553
|
+
type: 'object',
|
|
554
|
+
properties: {
|
|
555
|
+
file_path: { type: 'string', description: 'Absolute path to the file to explain.' },
|
|
556
|
+
depth: { type: 'string', enum: ['overview', 'detailed', 'line-by-line'], description: 'Explanation depth. Default: overview.' },
|
|
557
|
+
context: { type: 'string', description: 'Optional additional context about what you want explained.' },
|
|
558
|
+
},
|
|
559
|
+
required: ['file_path'],
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: 'veto_plugins',
|
|
564
|
+
description: 'Lists all custom agents loaded from ~/.veto/agents/. Drop a .js file there that exports plan(task, context?) to register a new agent available in veto_agent_plan and veto_execute_parallel.',
|
|
565
|
+
inputSchema: {
|
|
566
|
+
type: 'object',
|
|
567
|
+
properties: {},
|
|
568
|
+
required: [],
|
|
569
|
+
},
|
|
570
|
+
},
|
|
460
571
|
],
|
|
461
572
|
}));
|
|
462
573
|
// ─── Tool Handlers ────────────────────────────────────────────────────────────
|
|
@@ -563,20 +674,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
563
674
|
};
|
|
564
675
|
}
|
|
565
676
|
case 'veto_route_task': {
|
|
566
|
-
const
|
|
677
|
+
const routeTaskStr = String(args?.task ?? '');
|
|
678
|
+
const fileExt = args?.file_ext ? String(args.file_ext) : undefined;
|
|
679
|
+
const result = routeTask(routeTaskStr, {
|
|
567
680
|
agentType: args?.agent_type ? String(args.agent_type) : undefined,
|
|
568
681
|
filesAffected: typeof args?.files_affected === 'number' ? args.files_affected : undefined,
|
|
569
682
|
forceCouncil: args?.force_council === true,
|
|
570
683
|
context: args?.context ? String(args.context) : undefined,
|
|
571
684
|
preferredPlatform: args?.preferred_platform ? String(args.preferred_platform) : 'claude',
|
|
572
685
|
});
|
|
686
|
+
const recommended_agent = getRecommendedAgent(routeTaskStr, fileExt);
|
|
573
687
|
return {
|
|
574
|
-
content: [
|
|
575
|
-
{
|
|
688
|
+
content: [{
|
|
576
689
|
type: 'text',
|
|
577
|
-
text: JSON.stringify(result, null, 2),
|
|
578
|
-
},
|
|
579
|
-
],
|
|
690
|
+
text: JSON.stringify({ ...result, recommended_agent }, null, 2),
|
|
691
|
+
}],
|
|
580
692
|
};
|
|
581
693
|
}
|
|
582
694
|
case 'veto_rate_status': {
|
|
@@ -600,6 +712,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
600
712
|
const result = await runDebate({
|
|
601
713
|
task,
|
|
602
714
|
context: args?.context ? String(args.context) : undefined,
|
|
715
|
+
project_dir: args?.project_dir ? String(args.project_dir) : undefined,
|
|
603
716
|
});
|
|
604
717
|
const outcomeId = saveCouncilOutcome({
|
|
605
718
|
session_id: args?.session_id ? String(args.session_id) : undefined,
|
|
@@ -645,11 +758,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
645
758
|
if (!task) {
|
|
646
759
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'task is required.' }) }], isError: true };
|
|
647
760
|
}
|
|
648
|
-
const result = await executeOne({
|
|
761
|
+
const result = await executeOne({
|
|
762
|
+
id: 'plan-1',
|
|
763
|
+
agent: agentType,
|
|
764
|
+
task,
|
|
765
|
+
context: args?.context ? String(args.context) : undefined,
|
|
766
|
+
project_dir: args?.project_dir ? String(args.project_dir) : undefined,
|
|
767
|
+
});
|
|
649
768
|
if (result.error) {
|
|
650
769
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: result.error }) }], isError: true };
|
|
651
770
|
}
|
|
652
|
-
return { content: [{ type: 'text', text: JSON.stringify(result.plan ?? result.analysis, null, 2) }] };
|
|
771
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ...(result.plan ?? result.analysis), output: result.output }, null, 2) }] };
|
|
653
772
|
}
|
|
654
773
|
case 'veto_code_review': {
|
|
655
774
|
const code = String(args?.code ?? '').trim();
|
|
@@ -662,6 +781,92 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
662
781
|
}
|
|
663
782
|
return { content: [{ type: 'text', text: JSON.stringify(result.analysis, null, 2) }] };
|
|
664
783
|
}
|
|
784
|
+
case 'veto_diff_review': {
|
|
785
|
+
const projectDir = args?.project_dir ? String(args.project_dir) : undefined;
|
|
786
|
+
const userContext = args?.context ? String(args.context) : undefined;
|
|
787
|
+
// Resolve diff — use provided or read from git
|
|
788
|
+
let diff = args?.diff ? String(args.diff).trim() : '';
|
|
789
|
+
if (!diff && projectDir) {
|
|
790
|
+
const { execSync: execSyncDiff } = await import('node:child_process');
|
|
791
|
+
try {
|
|
792
|
+
diff = execSyncDiff('git diff HEAD --no-color', {
|
|
793
|
+
cwd: projectDir, timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
794
|
+
}).toString().trim();
|
|
795
|
+
if (!diff) {
|
|
796
|
+
diff = execSyncDiff('git diff --cached --no-color', {
|
|
797
|
+
cwd: projectDir, timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
798
|
+
}).toString().trim();
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
catch { /* not a git repo or no changes */ }
|
|
802
|
+
}
|
|
803
|
+
if (!diff) {
|
|
804
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'No diff provided and no git changes detected. Pass diff or point to a project_dir with uncommitted changes.' }) }], isError: true };
|
|
805
|
+
}
|
|
806
|
+
// Parse changed files from diff header lines
|
|
807
|
+
const changedFiles = [...diff.matchAll(/^diff --git a\/.+ b\/(.+)$/gm)].map(m => m[1]);
|
|
808
|
+
const diffChunks = diff.split(/^diff --git /m).filter(Boolean);
|
|
809
|
+
// Run code-review, security-scan, secrets-scan in parallel across the full diff
|
|
810
|
+
const context = buildContextString(projectDir, userContext);
|
|
811
|
+
const [reviewResult, secResult, secretsResult] = await Promise.all([
|
|
812
|
+
executeOne({ id: 'diff-review', agent: 'reviewer', task: 'Review this git diff for code quality issues', code: diff, context }),
|
|
813
|
+
executeOne({ id: 'diff-sec', agent: 'security-scanner', task: 'Scan this git diff for security vulnerabilities', code: diff, context }),
|
|
814
|
+
executeOne({ id: 'diff-secrets', agent: 'secrets', task: 'Scan this git diff for exposed secrets or credentials', code: diff }),
|
|
815
|
+
]);
|
|
816
|
+
// Derive overall verdict
|
|
817
|
+
const hasBlocking = (reviewResult.analysis?.critical_count ?? 0) > 0
|
|
818
|
+
|| (secResult.analysis?.critical_count ?? 0) > 0
|
|
819
|
+
|| (secretsResult.analysis?.critical_count ?? 0) > 0;
|
|
820
|
+
const hasWarnings = (reviewResult.analysis?.high_count ?? 0) > 0
|
|
821
|
+
|| (secResult.analysis?.high_count ?? 0) > 0;
|
|
822
|
+
const verdict = hasBlocking ? 'fail' : hasWarnings ? 'warn' : 'pass';
|
|
823
|
+
const verdictEmoji = verdict === 'pass' ? '✅ PASS' : verdict === 'warn' ? '⚠️ WARN' : '❌ FAIL';
|
|
824
|
+
// Per-file finding counts (approximate from line refs)
|
|
825
|
+
const fileFindings = {};
|
|
826
|
+
for (const f of changedFiles)
|
|
827
|
+
fileFindings[f] = 0;
|
|
828
|
+
for (const finding of [...(reviewResult.analysis?.findings ?? []), ...(secResult.analysis?.findings ?? [])]) {
|
|
829
|
+
const match = changedFiles.find(f => finding.location?.includes(f));
|
|
830
|
+
if (match)
|
|
831
|
+
fileFindings[match]++;
|
|
832
|
+
}
|
|
833
|
+
return {
|
|
834
|
+
content: [{
|
|
835
|
+
type: 'text',
|
|
836
|
+
text: JSON.stringify({
|
|
837
|
+
verdict,
|
|
838
|
+
verdict_label: verdictEmoji,
|
|
839
|
+
files_changed: changedFiles.length,
|
|
840
|
+
files: changedFiles,
|
|
841
|
+
file_findings: fileFindings,
|
|
842
|
+
code_review: {
|
|
843
|
+
score: reviewResult.analysis?.score ?? null,
|
|
844
|
+
verdict: reviewResult.analysis?.verdict ?? null,
|
|
845
|
+
critical: reviewResult.analysis?.critical_count ?? 0,
|
|
846
|
+
high: reviewResult.analysis?.high_count ?? 0,
|
|
847
|
+
findings: reviewResult.analysis?.findings ?? [],
|
|
848
|
+
},
|
|
849
|
+
security: {
|
|
850
|
+
score: secResult.analysis?.score ?? null,
|
|
851
|
+
verdict: secResult.analysis?.verdict ?? null,
|
|
852
|
+
critical: secResult.analysis?.critical_count ?? 0,
|
|
853
|
+
high: secResult.analysis?.high_count ?? 0,
|
|
854
|
+
findings: secResult.analysis?.findings ?? [],
|
|
855
|
+
},
|
|
856
|
+
secrets: {
|
|
857
|
+
verdict: secretsResult.analysis?.verdict ?? null,
|
|
858
|
+
findings: secretsResult.analysis?.findings ?? [],
|
|
859
|
+
},
|
|
860
|
+
summary: [
|
|
861
|
+
`${verdictEmoji} — ${changedFiles.length} file(s) changed`,
|
|
862
|
+
`Code: ${reviewResult.analysis?.verdict ?? 'n/a'} (score ${reviewResult.analysis?.score ?? '?'}/100)`,
|
|
863
|
+
`Security: ${secResult.analysis?.verdict ?? 'n/a'} — ${secResult.analysis?.critical_count ?? 0} critical, ${secResult.analysis?.high_count ?? 0} high`,
|
|
864
|
+
`Secrets: ${(secretsResult.analysis?.findings?.length ?? 0) > 0 ? '🔴 Exposed credentials detected' : '✅ Clean'}`,
|
|
865
|
+
].join('\n'),
|
|
866
|
+
}, null, 2),
|
|
867
|
+
}],
|
|
868
|
+
};
|
|
869
|
+
}
|
|
665
870
|
case 'veto_security_scan': {
|
|
666
871
|
const code = String(args?.code ?? '').trim();
|
|
667
872
|
if (!code) {
|
|
@@ -689,12 +894,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
689
894
|
if (rawTasks.length === 0) {
|
|
690
895
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'tasks array is required and must not be empty.' }) }], isError: true };
|
|
691
896
|
}
|
|
897
|
+
const parallelProjectDir = args?.project_dir ? String(args.project_dir) : undefined;
|
|
692
898
|
const tasks = rawTasks.map((t) => ({
|
|
693
899
|
id: String(t.id ?? ''),
|
|
694
900
|
agent: String(t.agent ?? ''),
|
|
695
901
|
task: String(t.task ?? ''),
|
|
696
902
|
code: t.code ? String(t.code) : undefined,
|
|
697
903
|
context: t.context ? String(t.context) : undefined,
|
|
904
|
+
project_dir: t.project_dir ? String(t.project_dir) : parallelProjectDir,
|
|
698
905
|
}));
|
|
699
906
|
const results = await executeParallel(tasks);
|
|
700
907
|
return {
|
|
@@ -708,7 +915,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
708
915
|
agent: r.agent,
|
|
709
916
|
duration_ms: r.duration_ms,
|
|
710
917
|
error: r.error,
|
|
711
|
-
output: r.plan ?? r.analysis,
|
|
918
|
+
output: { ...(r.plan ?? r.analysis), structured: r.output },
|
|
712
919
|
})),
|
|
713
920
|
}, null, 2),
|
|
714
921
|
}],
|
|
@@ -892,7 +1099,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
892
1099
|
if (!task_type) {
|
|
893
1100
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'task_type is required.' }) }], isError: true };
|
|
894
1101
|
}
|
|
895
|
-
recordOutcome(task_type, complexity, model_tier, args?.agent ? String(args.agent) : 'dynamic', output_quality, typeof args?.tokens_used === 'number' ? args.tokens_used : 0);
|
|
1102
|
+
recordOutcome(task_type, complexity, model_tier, args?.agent ? String(args.agent) : 'dynamic', output_quality, typeof args?.tokens_used === 'number' ? args.tokens_used : 0, args?.file_ext ? String(args.file_ext) : undefined);
|
|
896
1103
|
const stats = getLearningStats();
|
|
897
1104
|
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: 'Outcome recorded.', total_outcomes: stats.total_tasks, next_step: stats.total_tasks >= 20 ? 'You have 20+ outcomes. Call veto_learning_apply to update router thresholds.' : `Need ${20 - stats.total_tasks} more outcomes before veto_learning_apply can adjust thresholds.` }, null, 2) }] };
|
|
898
1105
|
}
|
|
@@ -931,15 +1138,272 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
931
1138
|
const result = importMemory(args?.input_path ? String(args.input_path) : undefined);
|
|
932
1139
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
933
1140
|
}
|
|
1141
|
+
case 'veto_watch': {
|
|
1142
|
+
const dir = String(args?.project_dir ?? '').trim();
|
|
1143
|
+
if (!dir)
|
|
1144
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'project_dir is required.' }) }], isError: true };
|
|
1145
|
+
const watch_id = startWatch(dir);
|
|
1146
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, watch_id, project_dir: dir, message: `Watching "${dir}". Call veto_watch_poll with watch_id to collect events.` }, null, 2) }] };
|
|
1147
|
+
}
|
|
1148
|
+
case 'veto_watch_poll': {
|
|
1149
|
+
const watch_id = String(args?.watch_id ?? '').trim();
|
|
1150
|
+
if (!watch_id)
|
|
1151
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'watch_id is required.' }) }], isError: true };
|
|
1152
|
+
const result = pollWatch(watch_id);
|
|
1153
|
+
if (!result.found)
|
|
1154
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: `No active watcher with id: ${watch_id}` }) }], isError: true };
|
|
1155
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, watch_id, project_dir: result.project_dir, event_count: result.events.length, events: result.events }, null, 2) }] };
|
|
1156
|
+
}
|
|
1157
|
+
case 'veto_watch_stop': {
|
|
1158
|
+
const watch_id = String(args?.watch_id ?? '').trim();
|
|
1159
|
+
if (!watch_id)
|
|
1160
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'watch_id is required.' }) }], isError: true };
|
|
1161
|
+
const stopped = stopWatch(watch_id);
|
|
1162
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: stopped, message: stopped ? `Watcher ${watch_id} stopped.` : `No watcher found with id: ${watch_id}` }, null, 2) }] };
|
|
1163
|
+
}
|
|
1164
|
+
case 'veto_workflow': {
|
|
1165
|
+
const rawSteps = Array.isArray(args?.steps) ? args.steps : [];
|
|
1166
|
+
if (rawSteps.length === 0)
|
|
1167
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'steps array is required and must not be empty.' }) }], isError: true };
|
|
1168
|
+
const steps = rawSteps.map((s) => ({
|
|
1169
|
+
id: String(s.id ?? ''),
|
|
1170
|
+
agent: String(s.agent ?? ''),
|
|
1171
|
+
task: String(s.task ?? ''),
|
|
1172
|
+
code: s.code ? String(s.code) : undefined,
|
|
1173
|
+
context: s.context ? String(s.context) : undefined,
|
|
1174
|
+
gate: typeof s.gate === 'number' ? s.gate : undefined,
|
|
1175
|
+
}));
|
|
1176
|
+
const result = await runPipeline(steps, args?.project_dir ? String(args.project_dir) : undefined);
|
|
1177
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1178
|
+
}
|
|
1179
|
+
case 'veto_explain': {
|
|
1180
|
+
const filePath = String(args?.file_path ?? '').trim();
|
|
1181
|
+
if (!filePath)
|
|
1182
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'file_path is required.' }) }], isError: true };
|
|
1183
|
+
let fileContent;
|
|
1184
|
+
try {
|
|
1185
|
+
fileContent = readFileSync(filePath, 'utf8');
|
|
1186
|
+
}
|
|
1187
|
+
catch {
|
|
1188
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: `Cannot read file: ${filePath}` }) }], isError: true };
|
|
1189
|
+
}
|
|
1190
|
+
const ext = extname(filePath).toLowerCase();
|
|
1191
|
+
const name_ = basename(filePath).toLowerCase();
|
|
1192
|
+
const depth = String(args?.depth ?? 'overview');
|
|
1193
|
+
// auto-detect best agent
|
|
1194
|
+
let agent = 'coder';
|
|
1195
|
+
if (['.tsx', '.jsx', '.vue', '.svelte'].includes(ext))
|
|
1196
|
+
agent = 'frontend';
|
|
1197
|
+
else if (['.sql', '.prisma'].includes(ext) || name_.includes('schema'))
|
|
1198
|
+
agent = 'database';
|
|
1199
|
+
else if (/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(name_))
|
|
1200
|
+
agent = 'tester';
|
|
1201
|
+
else if (['.yaml', '.yml', '.toml', '.dockerfile'].includes(ext) || name_ === 'dockerfile')
|
|
1202
|
+
agent = 'devops';
|
|
1203
|
+
else if (name_.includes('auth') || name_.includes('login') || name_.includes('jwt') || name_.includes('token'))
|
|
1204
|
+
agent = 'auth';
|
|
1205
|
+
else if (name_.includes('security') || name_.includes('crypt'))
|
|
1206
|
+
agent = 'security-scanner';
|
|
1207
|
+
else if (['.ts', '.js', '.mjs'].includes(ext))
|
|
1208
|
+
agent = 'coder';
|
|
1209
|
+
const userContext = args?.context ? String(args.context) : undefined;
|
|
1210
|
+
const task = `Explain this ${ext} file at ${depth} depth. File: ${basename(filePath)}${userContext ? `. Focus: ${userContext}` : ''}`;
|
|
1211
|
+
const result = await executeOne({ id: 'explain-1', agent, task, code: fileContent, project_dir: undefined });
|
|
1212
|
+
return {
|
|
1213
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
1214
|
+
file: filePath, agent_used: agent, depth,
|
|
1215
|
+
explanation: result.plan ?? result.analysis,
|
|
1216
|
+
output: result.output,
|
|
1217
|
+
}, null, 2) }],
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
case 'veto_plugins': {
|
|
1221
|
+
return { content: [{ type: 'text', text: JSON.stringify({ plugins: listPlugins(), plugin_dir: `${process.env.HOME ?? process.env.USERPROFILE}/.veto/agents/`, instructions: 'Drop a .js file exporting plan(task, context?) to register a custom agent.' }, null, 2) }] };
|
|
1222
|
+
}
|
|
934
1223
|
default:
|
|
935
1224
|
throw new Error(`Unknown tool: ${name}`);
|
|
936
1225
|
}
|
|
937
1226
|
});
|
|
1227
|
+
// ─── MCP Resources ─────────────────────────────────────────────────────────────
|
|
1228
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
1229
|
+
resources: [
|
|
1230
|
+
{
|
|
1231
|
+
uri: 'veto://sessions',
|
|
1232
|
+
name: 'Saved Sessions',
|
|
1233
|
+
description: 'List of all saved Veto sessions across AI platforms.',
|
|
1234
|
+
mimeType: 'application/json',
|
|
1235
|
+
},
|
|
1236
|
+
{
|
|
1237
|
+
uri: 'veto://project-map',
|
|
1238
|
+
name: 'Project Map',
|
|
1239
|
+
description: 'Stored project structure maps. Append ?dir=<absolute_path> to filter by project.',
|
|
1240
|
+
mimeType: 'application/json',
|
|
1241
|
+
},
|
|
1242
|
+
{
|
|
1243
|
+
uri: 'veto://memory',
|
|
1244
|
+
name: 'Knowledge Base',
|
|
1245
|
+
description: 'All stored knowledge entries. Append ?q=<query> to search.',
|
|
1246
|
+
mimeType: 'application/json',
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
uri: 'veto://patterns',
|
|
1250
|
+
name: 'Learned Patterns',
|
|
1251
|
+
description: 'Coding patterns Veto has learned from your sessions.',
|
|
1252
|
+
mimeType: 'application/json',
|
|
1253
|
+
},
|
|
1254
|
+
],
|
|
1255
|
+
}));
|
|
1256
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
1257
|
+
const uri = request.params.uri;
|
|
1258
|
+
const url = new URL(uri);
|
|
1259
|
+
if (url.host === 'sessions') {
|
|
1260
|
+
const sessions = listSessions(50);
|
|
1261
|
+
return {
|
|
1262
|
+
contents: [{
|
|
1263
|
+
uri,
|
|
1264
|
+
mimeType: 'application/json',
|
|
1265
|
+
text: JSON.stringify(sessions.map(s => ({
|
|
1266
|
+
id: s.id, platform: s.platform, summary: s.summary,
|
|
1267
|
+
project_dir: s.project_dir, started_at: s.started_at,
|
|
1268
|
+
})), null, 2),
|
|
1269
|
+
}],
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
if (url.host === 'project-map') {
|
|
1273
|
+
const dir = url.searchParams.get('dir') ?? '';
|
|
1274
|
+
if (dir) {
|
|
1275
|
+
const row = getProjectMap(dir);
|
|
1276
|
+
return {
|
|
1277
|
+
contents: [{
|
|
1278
|
+
uri,
|
|
1279
|
+
mimeType: 'application/json',
|
|
1280
|
+
text: JSON.stringify(row ?? { found: false }, null, 2),
|
|
1281
|
+
}],
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
return { contents: [{ uri, mimeType: 'application/json', text: '{"message":"Append ?dir=<absolute_path> to get a specific project map."}' }] };
|
|
1285
|
+
}
|
|
1286
|
+
if (url.host === 'memory') {
|
|
1287
|
+
const q = url.searchParams.get('q') ?? undefined;
|
|
1288
|
+
const results = searchKnowledge({ query: q, limit: 20 });
|
|
1289
|
+
return {
|
|
1290
|
+
contents: [{
|
|
1291
|
+
uri,
|
|
1292
|
+
mimeType: 'application/json',
|
|
1293
|
+
text: JSON.stringify(results.map(r => ({
|
|
1294
|
+
id: r.id, type: r.type, title: r.title,
|
|
1295
|
+
content: r.content, tags: r.tags ? JSON.parse(r.tags) : [],
|
|
1296
|
+
})), null, 2),
|
|
1297
|
+
}],
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
if (url.host === 'patterns') {
|
|
1301
|
+
const patterns = getPatterns(undefined, 50);
|
|
1302
|
+
return {
|
|
1303
|
+
contents: [{
|
|
1304
|
+
uri,
|
|
1305
|
+
mimeType: 'application/json',
|
|
1306
|
+
text: JSON.stringify(patterns.map(p => ({
|
|
1307
|
+
key: p.pattern_key, val: p.pattern_val,
|
|
1308
|
+
confidence: p.confidence, seen_count: p.seen_count,
|
|
1309
|
+
})), null, 2),
|
|
1310
|
+
}],
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
1314
|
+
});
|
|
1315
|
+
// ─── MCP Prompts ───────────────────────────────────────────────────────────────
|
|
1316
|
+
const PROMPTS = [
|
|
1317
|
+
{
|
|
1318
|
+
name: 'code-review',
|
|
1319
|
+
description: 'Full code review prompt — paste code, get scored findings with severity and fixes.',
|
|
1320
|
+
arguments: [
|
|
1321
|
+
{ name: 'code', description: 'The code to review.', required: true },
|
|
1322
|
+
{ name: 'focus', description: 'Optional focus area (e.g. security, performance, style).', required: false },
|
|
1323
|
+
],
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
name: 'security-audit',
|
|
1327
|
+
description: 'OWASP Top 10 security audit — scans code for vulnerabilities with CWE references.',
|
|
1328
|
+
arguments: [
|
|
1329
|
+
{ name: 'code', description: 'The code to audit.', required: true },
|
|
1330
|
+
{ name: 'language', description: 'Language or framework (e.g. TypeScript, Express).', required: false },
|
|
1331
|
+
],
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
name: 'deploy-checklist',
|
|
1335
|
+
description: 'Pre-deploy checklist debate — council reviews your deployment plan.',
|
|
1336
|
+
arguments: [
|
|
1337
|
+
{ name: 'plan', description: 'Your deployment plan or change description.', required: true },
|
|
1338
|
+
{ name: 'environment', description: 'Target environment (prod, staging, etc.).', required: false },
|
|
1339
|
+
],
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
name: 'explain-file',
|
|
1343
|
+
description: 'Expert explanation of a file — routes to the best-fit agent based on file type.',
|
|
1344
|
+
arguments: [
|
|
1345
|
+
{ name: 'file_path', description: 'Absolute path to the file to explain.', required: true },
|
|
1346
|
+
{ name: 'depth', description: 'Explanation depth: overview | detailed | line-by-line.', required: false },
|
|
1347
|
+
],
|
|
1348
|
+
},
|
|
1349
|
+
];
|
|
1350
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }));
|
|
1351
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
1352
|
+
const { name, arguments: pArgs } = request.params;
|
|
1353
|
+
if (name === 'code-review') {
|
|
1354
|
+
const code = pArgs?.code ?? '<paste code here>';
|
|
1355
|
+
const focus = pArgs?.focus ? ` Focus on: ${pArgs.focus}.` : '';
|
|
1356
|
+
return {
|
|
1357
|
+
description: PROMPTS[0].description,
|
|
1358
|
+
messages: [{
|
|
1359
|
+
role: 'user',
|
|
1360
|
+
content: { type: 'text', text: `Use veto_code_review to review this code.${focus}\n\n\`\`\`\n${code}\n\`\`\`` },
|
|
1361
|
+
}],
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
if (name === 'security-audit') {
|
|
1365
|
+
const code = pArgs?.code ?? '<paste code here>';
|
|
1366
|
+
const lang = pArgs?.language ? ` Language/framework: ${pArgs.language}.` : '';
|
|
1367
|
+
return {
|
|
1368
|
+
description: PROMPTS[1].description,
|
|
1369
|
+
messages: [{
|
|
1370
|
+
role: 'user',
|
|
1371
|
+
content: { type: 'text', text: `Use veto_security_scan to audit this code for OWASP Top 10 vulnerabilities.${lang}\n\n\`\`\`\n${code}\n\`\`\`` },
|
|
1372
|
+
}],
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
if (name === 'deploy-checklist') {
|
|
1376
|
+
const plan = pArgs?.plan ?? '<describe your deployment plan>';
|
|
1377
|
+
const env = pArgs?.environment ? ` Target environment: ${pArgs.environment}.` : '';
|
|
1378
|
+
return {
|
|
1379
|
+
description: PROMPTS[2].description,
|
|
1380
|
+
messages: [{
|
|
1381
|
+
role: 'user',
|
|
1382
|
+
content: { type: 'text', text: `Use veto_council_debate to review this deployment plan before we ship.${env}\n\nPlan: ${plan}` },
|
|
1383
|
+
}],
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
if (name === 'explain-file') {
|
|
1387
|
+
const filePath = pArgs?.file_path ?? '<absolute file path>';
|
|
1388
|
+
const depth = pArgs?.depth ?? 'overview';
|
|
1389
|
+
return {
|
|
1390
|
+
description: PROMPTS[3].description,
|
|
1391
|
+
messages: [{
|
|
1392
|
+
role: 'user',
|
|
1393
|
+
content: { type: 'text', text: `Read the file at "${filePath}" and use veto_agent_plan with the most appropriate agent (frontend for .tsx/.vue, database for .sql, backend for services, coder for general) to give a ${depth}-level explanation of what it does, how it works, and any concerns.` },
|
|
1394
|
+
}],
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
1398
|
+
});
|
|
938
1399
|
// ─── Start ─────────────────────────────────────────────────────────────────────
|
|
939
1400
|
async function main() {
|
|
1401
|
+
const loadedPlugins = await loadPlugins();
|
|
1402
|
+
if (loadedPlugins.length > 0) {
|
|
1403
|
+
process.stderr.write(`[veto] Loaded ${loadedPlugins.length} plugin(s): ${loadedPlugins.join(', ')}\n`);
|
|
1404
|
+
}
|
|
940
1405
|
const transport = new StdioServerTransport();
|
|
941
1406
|
await server.connect(transport);
|
|
942
|
-
// stderr so it doesn't pollute MCP stdio
|
|
943
1407
|
process.stderr.write(`Veto MCP server v${VERSION} running (stdio)\n`);
|
|
944
1408
|
}
|
|
945
1409
|
main().catch((err) => {
|