@jigyasudham/veto 0.8.3 → 1.1.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 +217 -54
- package/dist/adapters/index.js +4 -3
- package/dist/agents/executor.js +36 -3
- package/dist/cli.js +246 -7
- package/dist/context/reader.js +113 -0
- package/dist/council/index.js +3 -1
- package/dist/memory/local.js +18 -1
- package/dist/memory/schema.js +12 -10
- 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 +507 -21
- 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 -204
- 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,29 @@
|
|
|
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.1.0';
|
|
22
|
+
// Tracks the project_dir of the most recently active session in this process.
|
|
23
|
+
// Used as a fallback when memory_store/memory_search are called without an explicit project_dir,
|
|
24
|
+
// so memories are automatically scoped to the current project.
|
|
25
|
+
let activeProjectDir = null;
|
|
16
26
|
const server = new Server({ name: 'veto', version: VERSION }, {
|
|
17
27
|
capabilities: {
|
|
18
28
|
tools: {},
|
|
29
|
+
resources: {},
|
|
30
|
+
prompts: {},
|
|
19
31
|
},
|
|
20
32
|
});
|
|
21
33
|
// ─── Tool Definitions ─────────────────────────────────────────────────────────
|
|
@@ -75,6 +87,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
75
87
|
type: 'string',
|
|
76
88
|
description: 'UUID of the session to restore.',
|
|
77
89
|
},
|
|
90
|
+
resuming_as: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
description: 'The AI client resuming this session (e.g. "claude", "gemini", "codex"). Recorded as active_client.',
|
|
93
|
+
enum: ['claude', 'gemini', 'codex'],
|
|
94
|
+
},
|
|
78
95
|
},
|
|
79
96
|
required: ['session_id'],
|
|
80
97
|
},
|
|
@@ -158,6 +175,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
158
175
|
type: 'string',
|
|
159
176
|
description: 'Optional: additional context such as codebase state, prior decisions, or constraints.',
|
|
160
177
|
},
|
|
178
|
+
project_dir: {
|
|
179
|
+
type: 'string',
|
|
180
|
+
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.',
|
|
181
|
+
},
|
|
161
182
|
session_id: {
|
|
162
183
|
type: 'string',
|
|
163
184
|
description: 'Optional: session ID to associate this council outcome with an active session.',
|
|
@@ -179,6 +200,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
179
200
|
},
|
|
180
201
|
task: { type: 'string', description: 'The task for the agent to plan.' },
|
|
181
202
|
context: { type: 'string', description: 'Optional additional context.' },
|
|
203
|
+
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
204
|
},
|
|
183
205
|
required: ['agent', 'task'],
|
|
184
206
|
},
|
|
@@ -195,6 +217,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
195
217
|
required: ['code'],
|
|
196
218
|
},
|
|
197
219
|
},
|
|
220
|
+
{
|
|
221
|
+
name: 'veto_diff_review',
|
|
222
|
+
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.',
|
|
223
|
+
inputSchema: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
properties: {
|
|
226
|
+
diff: { type: 'string', description: 'The git diff to review. If omitted, Veto runs git diff HEAD in project_dir.' },
|
|
227
|
+
project_dir: { type: 'string', description: 'Absolute project path. Used to auto-read git diff if diff is not provided, and to inject codebase context.' },
|
|
228
|
+
context: { type: 'string', description: 'Optional: PR description, ticket number, or focus area.' },
|
|
229
|
+
},
|
|
230
|
+
required: [],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
198
233
|
{
|
|
199
234
|
name: 'veto_security_scan',
|
|
200
235
|
description: 'Runs the Security Scanner (OWASP Top 10) on provided code. Returns vulnerabilities with severity, CWE/OWASP category, and remediation steps.',
|
|
@@ -235,10 +270,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
235
270
|
task: { type: 'string', description: 'Task description for this agent.' },
|
|
236
271
|
code: { type: 'string', description: 'Optional code to analyze (triggers analyze() instead of plan()).' },
|
|
237
272
|
context: { type: 'string', description: 'Optional additional context.' },
|
|
273
|
+
project_dir: { type: 'string', description: 'Optional: per-task project dir override.' },
|
|
238
274
|
},
|
|
239
275
|
required: ['id', 'agent', 'task'],
|
|
240
276
|
},
|
|
241
277
|
},
|
|
278
|
+
project_dir: { type: 'string', description: 'Optional: project directory applied to all tasks (per-task project_dir overrides this). Auto-injects codebase context.' },
|
|
242
279
|
},
|
|
243
280
|
required: ['tasks'],
|
|
244
281
|
},
|
|
@@ -391,6 +428,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
391
428
|
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
429
|
agent: { type: 'string', description: 'The worker agent type used (optional but useful for agent performance tracking).' },
|
|
393
430
|
tokens_used: { type: 'number', description: 'Approximate tokens used (optional).' },
|
|
431
|
+
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
432
|
},
|
|
395
433
|
required: ['task_type', 'complexity', 'model_tier', 'output_quality'],
|
|
396
434
|
},
|
|
@@ -441,6 +479,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
441
479
|
type: 'object',
|
|
442
480
|
properties: {
|
|
443
481
|
session_id: { type: 'string', description: 'Optional. Session ID from veto_handoff. If omitted, the most recent saved session is restored.' },
|
|
482
|
+
resuming_as: { type: 'string', description: 'The AI client resuming this session (e.g. "gemini"). Recorded as active_client so you can track which tool is currently working on it.', enum: ['claude', 'gemini', 'codex'] },
|
|
444
483
|
},
|
|
445
484
|
required: [],
|
|
446
485
|
},
|
|
@@ -457,6 +496,88 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
457
496
|
required: ['platform', 'veto_server_path'],
|
|
458
497
|
},
|
|
459
498
|
},
|
|
499
|
+
{
|
|
500
|
+
name: 'veto_watch',
|
|
501
|
+
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.',
|
|
502
|
+
inputSchema: {
|
|
503
|
+
type: 'object',
|
|
504
|
+
properties: {
|
|
505
|
+
project_dir: { type: 'string', description: 'Absolute path to the project directory to watch.' },
|
|
506
|
+
},
|
|
507
|
+
required: ['project_dir'],
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: 'veto_watch_poll',
|
|
512
|
+
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.',
|
|
513
|
+
inputSchema: {
|
|
514
|
+
type: 'object',
|
|
515
|
+
properties: {
|
|
516
|
+
watch_id: { type: 'string', description: 'The watch_id returned by veto_watch.' },
|
|
517
|
+
},
|
|
518
|
+
required: ['watch_id'],
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
name: 'veto_watch_stop',
|
|
523
|
+
description: 'Stops an active file watcher.',
|
|
524
|
+
inputSchema: {
|
|
525
|
+
type: 'object',
|
|
526
|
+
properties: {
|
|
527
|
+
watch_id: { type: 'string', description: 'The watch_id returned by veto_watch.' },
|
|
528
|
+
},
|
|
529
|
+
required: ['watch_id'],
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
name: 'veto_workflow',
|
|
534
|
+
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).',
|
|
535
|
+
inputSchema: {
|
|
536
|
+
type: 'object',
|
|
537
|
+
properties: {
|
|
538
|
+
steps: {
|
|
539
|
+
type: 'array',
|
|
540
|
+
description: 'Ordered pipeline steps.',
|
|
541
|
+
items: {
|
|
542
|
+
type: 'object',
|
|
543
|
+
properties: {
|
|
544
|
+
id: { type: 'string', description: 'Step identifier.' },
|
|
545
|
+
agent: { type: 'string', description: 'Worker agent type.' },
|
|
546
|
+
task: { type: 'string', description: 'Task description for this step.' },
|
|
547
|
+
code: { type: 'string', description: 'Optional code to analyze.' },
|
|
548
|
+
context: { type: 'string', description: 'Optional context.' },
|
|
549
|
+
gate: { type: 'number', description: 'Optional minimum confidence % (0–100) required to proceed to the next step.' },
|
|
550
|
+
},
|
|
551
|
+
required: ['id', 'agent', 'task'],
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
project_dir: { type: 'string', description: 'Optional project directory — auto-injects codebase context into all steps.' },
|
|
555
|
+
},
|
|
556
|
+
required: ['steps'],
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
name: 'veto_explain',
|
|
561
|
+
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.',
|
|
562
|
+
inputSchema: {
|
|
563
|
+
type: 'object',
|
|
564
|
+
properties: {
|
|
565
|
+
file_path: { type: 'string', description: 'Absolute path to the file to explain.' },
|
|
566
|
+
depth: { type: 'string', enum: ['overview', 'detailed', 'line-by-line'], description: 'Explanation depth. Default: overview.' },
|
|
567
|
+
context: { type: 'string', description: 'Optional additional context about what you want explained.' },
|
|
568
|
+
},
|
|
569
|
+
required: ['file_path'],
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
name: 'veto_plugins',
|
|
574
|
+
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.',
|
|
575
|
+
inputSchema: {
|
|
576
|
+
type: 'object',
|
|
577
|
+
properties: {},
|
|
578
|
+
required: [],
|
|
579
|
+
},
|
|
580
|
+
},
|
|
460
581
|
],
|
|
461
582
|
}));
|
|
462
583
|
// ─── Tool Handlers ────────────────────────────────────────────────────────────
|
|
@@ -483,12 +604,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
483
604
|
};
|
|
484
605
|
}
|
|
485
606
|
case 'veto_session_save': {
|
|
607
|
+
const sessionProjectDir = args?.project_dir ? String(args.project_dir) : undefined;
|
|
608
|
+
if (sessionProjectDir)
|
|
609
|
+
activeProjectDir = sessionProjectDir;
|
|
486
610
|
const result = saveSession({
|
|
487
611
|
summary: String(args?.summary ?? ''),
|
|
488
612
|
context: String(args?.context ?? ''),
|
|
489
613
|
task_state: args?.task_state ? String(args.task_state) : undefined,
|
|
490
614
|
platform: args?.platform ? String(args.platform) : 'claude',
|
|
491
|
-
project_dir:
|
|
615
|
+
project_dir: sessionProjectDir,
|
|
492
616
|
token_count: typeof args?.token_count === 'number' ? args.token_count : 0,
|
|
493
617
|
});
|
|
494
618
|
return {
|
|
@@ -506,7 +630,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
506
630
|
}
|
|
507
631
|
case 'veto_session_restore': {
|
|
508
632
|
const session_id = String(args?.session_id ?? '');
|
|
509
|
-
const
|
|
633
|
+
const resuming_as = args?.resuming_as ? String(args.resuming_as) : undefined;
|
|
634
|
+
const result = restoreSession(session_id, resuming_as);
|
|
510
635
|
if (!result.found) {
|
|
511
636
|
return {
|
|
512
637
|
content: [
|
|
@@ -519,6 +644,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
519
644
|
};
|
|
520
645
|
}
|
|
521
646
|
const s = result.session;
|
|
647
|
+
if (s.project_dir)
|
|
648
|
+
activeProjectDir = s.project_dir;
|
|
522
649
|
return {
|
|
523
650
|
content: [
|
|
524
651
|
{
|
|
@@ -526,7 +653,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
526
653
|
text: JSON.stringify({
|
|
527
654
|
success: true,
|
|
528
655
|
session_id: s.id,
|
|
529
|
-
|
|
656
|
+
created_by: s.platform,
|
|
657
|
+
active_client: s.active_client ?? s.platform,
|
|
658
|
+
last_resumed_at: s.last_resumed_at,
|
|
530
659
|
started_at: s.started_at,
|
|
531
660
|
ended_at: s.ended_at,
|
|
532
661
|
project_dir: s.project_dir,
|
|
@@ -563,20 +692,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
563
692
|
};
|
|
564
693
|
}
|
|
565
694
|
case 'veto_route_task': {
|
|
566
|
-
const
|
|
695
|
+
const routeTaskStr = String(args?.task ?? '');
|
|
696
|
+
const fileExt = args?.file_ext ? String(args.file_ext) : undefined;
|
|
697
|
+
const result = routeTask(routeTaskStr, {
|
|
567
698
|
agentType: args?.agent_type ? String(args.agent_type) : undefined,
|
|
568
699
|
filesAffected: typeof args?.files_affected === 'number' ? args.files_affected : undefined,
|
|
569
700
|
forceCouncil: args?.force_council === true,
|
|
570
701
|
context: args?.context ? String(args.context) : undefined,
|
|
571
702
|
preferredPlatform: args?.preferred_platform ? String(args.preferred_platform) : 'claude',
|
|
572
703
|
});
|
|
704
|
+
const recommended_agent = getRecommendedAgent(routeTaskStr, fileExt);
|
|
573
705
|
return {
|
|
574
|
-
content: [
|
|
575
|
-
{
|
|
706
|
+
content: [{
|
|
576
707
|
type: 'text',
|
|
577
|
-
text: JSON.stringify(result, null, 2),
|
|
578
|
-
},
|
|
579
|
-
],
|
|
708
|
+
text: JSON.stringify({ ...result, recommended_agent }, null, 2),
|
|
709
|
+
}],
|
|
580
710
|
};
|
|
581
711
|
}
|
|
582
712
|
case 'veto_rate_status': {
|
|
@@ -600,6 +730,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
600
730
|
const result = await runDebate({
|
|
601
731
|
task,
|
|
602
732
|
context: args?.context ? String(args.context) : undefined,
|
|
733
|
+
project_dir: args?.project_dir ? String(args.project_dir) : undefined,
|
|
603
734
|
});
|
|
604
735
|
const outcomeId = saveCouncilOutcome({
|
|
605
736
|
session_id: args?.session_id ? String(args.session_id) : undefined,
|
|
@@ -645,11 +776,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
645
776
|
if (!task) {
|
|
646
777
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'task is required.' }) }], isError: true };
|
|
647
778
|
}
|
|
648
|
-
const result = await executeOne({
|
|
779
|
+
const result = await executeOne({
|
|
780
|
+
id: 'plan-1',
|
|
781
|
+
agent: agentType,
|
|
782
|
+
task,
|
|
783
|
+
context: args?.context ? String(args.context) : undefined,
|
|
784
|
+
project_dir: args?.project_dir ? String(args.project_dir) : undefined,
|
|
785
|
+
});
|
|
649
786
|
if (result.error) {
|
|
650
787
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: result.error }) }], isError: true };
|
|
651
788
|
}
|
|
652
|
-
return { content: [{ type: 'text', text: JSON.stringify(result.plan ?? result.analysis, null, 2) }] };
|
|
789
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ...(result.plan ?? result.analysis), output: result.output }, null, 2) }] };
|
|
653
790
|
}
|
|
654
791
|
case 'veto_code_review': {
|
|
655
792
|
const code = String(args?.code ?? '').trim();
|
|
@@ -662,6 +799,92 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
662
799
|
}
|
|
663
800
|
return { content: [{ type: 'text', text: JSON.stringify(result.analysis, null, 2) }] };
|
|
664
801
|
}
|
|
802
|
+
case 'veto_diff_review': {
|
|
803
|
+
const projectDir = args?.project_dir ? String(args.project_dir) : undefined;
|
|
804
|
+
const userContext = args?.context ? String(args.context) : undefined;
|
|
805
|
+
// Resolve diff — use provided or read from git
|
|
806
|
+
let diff = args?.diff ? String(args.diff).trim() : '';
|
|
807
|
+
if (!diff && projectDir) {
|
|
808
|
+
const { execSync: execSyncDiff } = await import('node:child_process');
|
|
809
|
+
try {
|
|
810
|
+
diff = execSyncDiff('git diff HEAD --no-color', {
|
|
811
|
+
cwd: projectDir, timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
812
|
+
}).toString().trim();
|
|
813
|
+
if (!diff) {
|
|
814
|
+
diff = execSyncDiff('git diff --cached --no-color', {
|
|
815
|
+
cwd: projectDir, timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
816
|
+
}).toString().trim();
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
catch { /* not a git repo or no changes */ }
|
|
820
|
+
}
|
|
821
|
+
if (!diff) {
|
|
822
|
+
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 };
|
|
823
|
+
}
|
|
824
|
+
// Parse changed files from diff header lines
|
|
825
|
+
const changedFiles = [...diff.matchAll(/^diff --git a\/.+ b\/(.+)$/gm)].map(m => m[1]);
|
|
826
|
+
const diffChunks = diff.split(/^diff --git /m).filter(Boolean);
|
|
827
|
+
// Run code-review, security-scan, secrets-scan in parallel across the full diff
|
|
828
|
+
const context = buildContextString(projectDir, userContext);
|
|
829
|
+
const [reviewResult, secResult, secretsResult] = await Promise.all([
|
|
830
|
+
executeOne({ id: 'diff-review', agent: 'reviewer', task: 'Review this git diff for code quality issues', code: diff, context }),
|
|
831
|
+
executeOne({ id: 'diff-sec', agent: 'security-scanner', task: 'Scan this git diff for security vulnerabilities', code: diff, context }),
|
|
832
|
+
executeOne({ id: 'diff-secrets', agent: 'secrets', task: 'Scan this git diff for exposed secrets or credentials', code: diff }),
|
|
833
|
+
]);
|
|
834
|
+
// Derive overall verdict
|
|
835
|
+
const hasBlocking = (reviewResult.analysis?.critical_count ?? 0) > 0
|
|
836
|
+
|| (secResult.analysis?.critical_count ?? 0) > 0
|
|
837
|
+
|| (secretsResult.analysis?.critical_count ?? 0) > 0;
|
|
838
|
+
const hasWarnings = (reviewResult.analysis?.high_count ?? 0) > 0
|
|
839
|
+
|| (secResult.analysis?.high_count ?? 0) > 0;
|
|
840
|
+
const verdict = hasBlocking ? 'fail' : hasWarnings ? 'warn' : 'pass';
|
|
841
|
+
const verdictEmoji = verdict === 'pass' ? '✅ PASS' : verdict === 'warn' ? '⚠️ WARN' : '❌ FAIL';
|
|
842
|
+
// Per-file finding counts (approximate from line refs)
|
|
843
|
+
const fileFindings = {};
|
|
844
|
+
for (const f of changedFiles)
|
|
845
|
+
fileFindings[f] = 0;
|
|
846
|
+
for (const finding of [...(reviewResult.analysis?.findings ?? []), ...(secResult.analysis?.findings ?? [])]) {
|
|
847
|
+
const match = changedFiles.find(f => finding.location?.includes(f));
|
|
848
|
+
if (match)
|
|
849
|
+
fileFindings[match]++;
|
|
850
|
+
}
|
|
851
|
+
return {
|
|
852
|
+
content: [{
|
|
853
|
+
type: 'text',
|
|
854
|
+
text: JSON.stringify({
|
|
855
|
+
verdict,
|
|
856
|
+
verdict_label: verdictEmoji,
|
|
857
|
+
files_changed: changedFiles.length,
|
|
858
|
+
files: changedFiles,
|
|
859
|
+
file_findings: fileFindings,
|
|
860
|
+
code_review: {
|
|
861
|
+
score: reviewResult.analysis?.score ?? null,
|
|
862
|
+
verdict: reviewResult.analysis?.verdict ?? null,
|
|
863
|
+
critical: reviewResult.analysis?.critical_count ?? 0,
|
|
864
|
+
high: reviewResult.analysis?.high_count ?? 0,
|
|
865
|
+
findings: reviewResult.analysis?.findings ?? [],
|
|
866
|
+
},
|
|
867
|
+
security: {
|
|
868
|
+
score: secResult.analysis?.score ?? null,
|
|
869
|
+
verdict: secResult.analysis?.verdict ?? null,
|
|
870
|
+
critical: secResult.analysis?.critical_count ?? 0,
|
|
871
|
+
high: secResult.analysis?.high_count ?? 0,
|
|
872
|
+
findings: secResult.analysis?.findings ?? [],
|
|
873
|
+
},
|
|
874
|
+
secrets: {
|
|
875
|
+
verdict: secretsResult.analysis?.verdict ?? null,
|
|
876
|
+
findings: secretsResult.analysis?.findings ?? [],
|
|
877
|
+
},
|
|
878
|
+
summary: [
|
|
879
|
+
`${verdictEmoji} — ${changedFiles.length} file(s) changed`,
|
|
880
|
+
`Code: ${reviewResult.analysis?.verdict ?? 'n/a'} (score ${reviewResult.analysis?.score ?? '?'}/100)`,
|
|
881
|
+
`Security: ${secResult.analysis?.verdict ?? 'n/a'} — ${secResult.analysis?.critical_count ?? 0} critical, ${secResult.analysis?.high_count ?? 0} high`,
|
|
882
|
+
`Secrets: ${(secretsResult.analysis?.findings?.length ?? 0) > 0 ? '🔴 Exposed credentials detected' : '✅ Clean'}`,
|
|
883
|
+
].join('\n'),
|
|
884
|
+
}, null, 2),
|
|
885
|
+
}],
|
|
886
|
+
};
|
|
887
|
+
}
|
|
665
888
|
case 'veto_security_scan': {
|
|
666
889
|
const code = String(args?.code ?? '').trim();
|
|
667
890
|
if (!code) {
|
|
@@ -689,12 +912,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
689
912
|
if (rawTasks.length === 0) {
|
|
690
913
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'tasks array is required and must not be empty.' }) }], isError: true };
|
|
691
914
|
}
|
|
915
|
+
const parallelProjectDir = args?.project_dir ? String(args.project_dir) : undefined;
|
|
692
916
|
const tasks = rawTasks.map((t) => ({
|
|
693
917
|
id: String(t.id ?? ''),
|
|
694
918
|
agent: String(t.agent ?? ''),
|
|
695
919
|
task: String(t.task ?? ''),
|
|
696
920
|
code: t.code ? String(t.code) : undefined,
|
|
697
921
|
context: t.context ? String(t.context) : undefined,
|
|
922
|
+
project_dir: t.project_dir ? String(t.project_dir) : parallelProjectDir,
|
|
698
923
|
}));
|
|
699
924
|
const results = await executeParallel(tasks);
|
|
700
925
|
return {
|
|
@@ -708,7 +933,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
708
933
|
agent: r.agent,
|
|
709
934
|
duration_ms: r.duration_ms,
|
|
710
935
|
error: r.error,
|
|
711
|
-
output: r.plan ?? r.analysis,
|
|
936
|
+
output: { ...(r.plan ?? r.analysis), structured: r.output },
|
|
712
937
|
})),
|
|
713
938
|
}, null, 2),
|
|
714
939
|
}],
|
|
@@ -725,7 +950,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
725
950
|
content,
|
|
726
951
|
type: args?.type ? String(args.type) : 'solution',
|
|
727
952
|
tags: Array.isArray(args?.tags) ? args.tags.map(String) : undefined,
|
|
728
|
-
project_dir: args?.project_dir ? String(args.project_dir) : undefined,
|
|
953
|
+
project_dir: args?.project_dir ? String(args.project_dir) : (activeProjectDir ?? undefined),
|
|
729
954
|
session_id: args?.session_id ? String(args.session_id) : undefined,
|
|
730
955
|
relevance: typeof args?.relevance === 'number' ? args.relevance : 1.0,
|
|
731
956
|
});
|
|
@@ -735,7 +960,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
735
960
|
const results = searchKnowledge({
|
|
736
961
|
query: args?.query ? String(args.query) : undefined,
|
|
737
962
|
type: args?.type ? String(args.type) : undefined,
|
|
738
|
-
project_dir: args?.project_dir ? String(args.project_dir) : undefined,
|
|
963
|
+
project_dir: args?.project_dir ? String(args.project_dir) : (activeProjectDir ?? undefined),
|
|
739
964
|
limit: typeof args?.limit === 'number' ? args.limit : 10,
|
|
740
965
|
});
|
|
741
966
|
return {
|
|
@@ -854,16 +1079,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
854
1079
|
return { content: [{ type: 'text', text: result.instructions + '\n\n' + JSON.stringify({ session_id: result.session_id, to_platform: result.to_platform, saved_at: result.saved_at, reason: result.reason }, null, 2) }] };
|
|
855
1080
|
}
|
|
856
1081
|
case 'veto_continue': {
|
|
857
|
-
const
|
|
1082
|
+
const resuming_as = args?.resuming_as ? String(args.resuming_as) : undefined;
|
|
1083
|
+
const result = continueSession(args?.session_id ? String(args.session_id) : undefined, resuming_as);
|
|
858
1084
|
if (!result.found) {
|
|
859
1085
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: result.message }, null, 2) }], isError: true };
|
|
860
1086
|
}
|
|
1087
|
+
if (result.project_dir)
|
|
1088
|
+
activeProjectDir = result.project_dir;
|
|
861
1089
|
return {
|
|
862
1090
|
content: [{
|
|
863
1091
|
type: 'text',
|
|
864
1092
|
text: result.message + '\n\n' + JSON.stringify({
|
|
865
1093
|
session_id: result.session_id,
|
|
866
|
-
|
|
1094
|
+
created_by: result.platform,
|
|
1095
|
+
active_client: result.active_client ?? result.platform,
|
|
867
1096
|
summary: result.summary,
|
|
868
1097
|
context: result.context,
|
|
869
1098
|
task_state: result.task_state,
|
|
@@ -892,7 +1121,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
892
1121
|
if (!task_type) {
|
|
893
1122
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'task_type is required.' }) }], isError: true };
|
|
894
1123
|
}
|
|
895
|
-
recordOutcome(task_type, complexity, model_tier, args?.agent ? String(args.agent) : 'dynamic', output_quality, typeof args?.tokens_used === 'number' ? args.tokens_used : 0);
|
|
1124
|
+
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
1125
|
const stats = getLearningStats();
|
|
897
1126
|
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
1127
|
}
|
|
@@ -931,15 +1160,272 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
931
1160
|
const result = importMemory(args?.input_path ? String(args.input_path) : undefined);
|
|
932
1161
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
933
1162
|
}
|
|
1163
|
+
case 'veto_watch': {
|
|
1164
|
+
const dir = String(args?.project_dir ?? '').trim();
|
|
1165
|
+
if (!dir)
|
|
1166
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'project_dir is required.' }) }], isError: true };
|
|
1167
|
+
const watch_id = startWatch(dir);
|
|
1168
|
+
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) }] };
|
|
1169
|
+
}
|
|
1170
|
+
case 'veto_watch_poll': {
|
|
1171
|
+
const watch_id = String(args?.watch_id ?? '').trim();
|
|
1172
|
+
if (!watch_id)
|
|
1173
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'watch_id is required.' }) }], isError: true };
|
|
1174
|
+
const result = pollWatch(watch_id);
|
|
1175
|
+
if (!result.found)
|
|
1176
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: `No active watcher with id: ${watch_id}` }) }], isError: true };
|
|
1177
|
+
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) }] };
|
|
1178
|
+
}
|
|
1179
|
+
case 'veto_watch_stop': {
|
|
1180
|
+
const watch_id = String(args?.watch_id ?? '').trim();
|
|
1181
|
+
if (!watch_id)
|
|
1182
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'watch_id is required.' }) }], isError: true };
|
|
1183
|
+
const stopped = stopWatch(watch_id);
|
|
1184
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: stopped, message: stopped ? `Watcher ${watch_id} stopped.` : `No watcher found with id: ${watch_id}` }, null, 2) }] };
|
|
1185
|
+
}
|
|
1186
|
+
case 'veto_workflow': {
|
|
1187
|
+
const rawSteps = Array.isArray(args?.steps) ? args.steps : [];
|
|
1188
|
+
if (rawSteps.length === 0)
|
|
1189
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'steps array is required and must not be empty.' }) }], isError: true };
|
|
1190
|
+
const steps = rawSteps.map((s) => ({
|
|
1191
|
+
id: String(s.id ?? ''),
|
|
1192
|
+
agent: String(s.agent ?? ''),
|
|
1193
|
+
task: String(s.task ?? ''),
|
|
1194
|
+
code: s.code ? String(s.code) : undefined,
|
|
1195
|
+
context: s.context ? String(s.context) : undefined,
|
|
1196
|
+
gate: typeof s.gate === 'number' ? s.gate : undefined,
|
|
1197
|
+
}));
|
|
1198
|
+
const result = await runPipeline(steps, args?.project_dir ? String(args.project_dir) : undefined);
|
|
1199
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1200
|
+
}
|
|
1201
|
+
case 'veto_explain': {
|
|
1202
|
+
const filePath = String(args?.file_path ?? '').trim();
|
|
1203
|
+
if (!filePath)
|
|
1204
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: 'file_path is required.' }) }], isError: true };
|
|
1205
|
+
let fileContent;
|
|
1206
|
+
try {
|
|
1207
|
+
fileContent = readFileSync(filePath, 'utf8');
|
|
1208
|
+
}
|
|
1209
|
+
catch {
|
|
1210
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: `Cannot read file: ${filePath}` }) }], isError: true };
|
|
1211
|
+
}
|
|
1212
|
+
const ext = extname(filePath).toLowerCase();
|
|
1213
|
+
const name_ = basename(filePath).toLowerCase();
|
|
1214
|
+
const depth = String(args?.depth ?? 'overview');
|
|
1215
|
+
// auto-detect best agent
|
|
1216
|
+
let agent = 'coder';
|
|
1217
|
+
if (['.tsx', '.jsx', '.vue', '.svelte'].includes(ext))
|
|
1218
|
+
agent = 'frontend';
|
|
1219
|
+
else if (['.sql', '.prisma'].includes(ext) || name_.includes('schema'))
|
|
1220
|
+
agent = 'database';
|
|
1221
|
+
else if (/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(name_))
|
|
1222
|
+
agent = 'tester';
|
|
1223
|
+
else if (['.yaml', '.yml', '.toml', '.dockerfile'].includes(ext) || name_ === 'dockerfile')
|
|
1224
|
+
agent = 'devops';
|
|
1225
|
+
else if (name_.includes('auth') || name_.includes('login') || name_.includes('jwt') || name_.includes('token'))
|
|
1226
|
+
agent = 'auth';
|
|
1227
|
+
else if (name_.includes('security') || name_.includes('crypt'))
|
|
1228
|
+
agent = 'security-scanner';
|
|
1229
|
+
else if (['.ts', '.js', '.mjs'].includes(ext))
|
|
1230
|
+
agent = 'coder';
|
|
1231
|
+
const userContext = args?.context ? String(args.context) : undefined;
|
|
1232
|
+
const task = `Explain this ${ext} file at ${depth} depth. File: ${basename(filePath)}${userContext ? `. Focus: ${userContext}` : ''}`;
|
|
1233
|
+
const result = await executeOne({ id: 'explain-1', agent, task, code: fileContent, project_dir: undefined });
|
|
1234
|
+
return {
|
|
1235
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
1236
|
+
file: filePath, agent_used: agent, depth,
|
|
1237
|
+
explanation: result.plan ?? result.analysis,
|
|
1238
|
+
output: result.output,
|
|
1239
|
+
}, null, 2) }],
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
case 'veto_plugins': {
|
|
1243
|
+
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) }] };
|
|
1244
|
+
}
|
|
934
1245
|
default:
|
|
935
1246
|
throw new Error(`Unknown tool: ${name}`);
|
|
936
1247
|
}
|
|
937
1248
|
});
|
|
1249
|
+
// ─── MCP Resources ─────────────────────────────────────────────────────────────
|
|
1250
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
1251
|
+
resources: [
|
|
1252
|
+
{
|
|
1253
|
+
uri: 'veto://sessions',
|
|
1254
|
+
name: 'Saved Sessions',
|
|
1255
|
+
description: 'List of all saved Veto sessions across AI platforms.',
|
|
1256
|
+
mimeType: 'application/json',
|
|
1257
|
+
},
|
|
1258
|
+
{
|
|
1259
|
+
uri: 'veto://project-map',
|
|
1260
|
+
name: 'Project Map',
|
|
1261
|
+
description: 'Stored project structure maps. Append ?dir=<absolute_path> to filter by project.',
|
|
1262
|
+
mimeType: 'application/json',
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
uri: 'veto://memory',
|
|
1266
|
+
name: 'Knowledge Base',
|
|
1267
|
+
description: 'All stored knowledge entries. Append ?q=<query> to search.',
|
|
1268
|
+
mimeType: 'application/json',
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
uri: 'veto://patterns',
|
|
1272
|
+
name: 'Learned Patterns',
|
|
1273
|
+
description: 'Coding patterns Veto has learned from your sessions.',
|
|
1274
|
+
mimeType: 'application/json',
|
|
1275
|
+
},
|
|
1276
|
+
],
|
|
1277
|
+
}));
|
|
1278
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
1279
|
+
const uri = request.params.uri;
|
|
1280
|
+
const url = new URL(uri);
|
|
1281
|
+
if (url.host === 'sessions') {
|
|
1282
|
+
const sessions = listSessions(50);
|
|
1283
|
+
return {
|
|
1284
|
+
contents: [{
|
|
1285
|
+
uri,
|
|
1286
|
+
mimeType: 'application/json',
|
|
1287
|
+
text: JSON.stringify(sessions.map(s => ({
|
|
1288
|
+
id: s.id, platform: s.platform, summary: s.summary,
|
|
1289
|
+
project_dir: s.project_dir, started_at: s.started_at,
|
|
1290
|
+
})), null, 2),
|
|
1291
|
+
}],
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
if (url.host === 'project-map') {
|
|
1295
|
+
const dir = url.searchParams.get('dir') ?? '';
|
|
1296
|
+
if (dir) {
|
|
1297
|
+
const row = getProjectMap(dir);
|
|
1298
|
+
return {
|
|
1299
|
+
contents: [{
|
|
1300
|
+
uri,
|
|
1301
|
+
mimeType: 'application/json',
|
|
1302
|
+
text: JSON.stringify(row ?? { found: false }, null, 2),
|
|
1303
|
+
}],
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
return { contents: [{ uri, mimeType: 'application/json', text: '{"message":"Append ?dir=<absolute_path> to get a specific project map."}' }] };
|
|
1307
|
+
}
|
|
1308
|
+
if (url.host === 'memory') {
|
|
1309
|
+
const q = url.searchParams.get('q') ?? undefined;
|
|
1310
|
+
const results = searchKnowledge({ query: q, limit: 20 });
|
|
1311
|
+
return {
|
|
1312
|
+
contents: [{
|
|
1313
|
+
uri,
|
|
1314
|
+
mimeType: 'application/json',
|
|
1315
|
+
text: JSON.stringify(results.map(r => ({
|
|
1316
|
+
id: r.id, type: r.type, title: r.title,
|
|
1317
|
+
content: r.content, tags: r.tags ? JSON.parse(r.tags) : [],
|
|
1318
|
+
})), null, 2),
|
|
1319
|
+
}],
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
if (url.host === 'patterns') {
|
|
1323
|
+
const patterns = getPatterns(undefined, 50);
|
|
1324
|
+
return {
|
|
1325
|
+
contents: [{
|
|
1326
|
+
uri,
|
|
1327
|
+
mimeType: 'application/json',
|
|
1328
|
+
text: JSON.stringify(patterns.map(p => ({
|
|
1329
|
+
key: p.pattern_key, val: p.pattern_val,
|
|
1330
|
+
confidence: p.confidence, seen_count: p.seen_count,
|
|
1331
|
+
})), null, 2),
|
|
1332
|
+
}],
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
1336
|
+
});
|
|
1337
|
+
// ─── MCP Prompts ───────────────────────────────────────────────────────────────
|
|
1338
|
+
const PROMPTS = [
|
|
1339
|
+
{
|
|
1340
|
+
name: 'code-review',
|
|
1341
|
+
description: 'Full code review prompt — paste code, get scored findings with severity and fixes.',
|
|
1342
|
+
arguments: [
|
|
1343
|
+
{ name: 'code', description: 'The code to review.', required: true },
|
|
1344
|
+
{ name: 'focus', description: 'Optional focus area (e.g. security, performance, style).', required: false },
|
|
1345
|
+
],
|
|
1346
|
+
},
|
|
1347
|
+
{
|
|
1348
|
+
name: 'security-audit',
|
|
1349
|
+
description: 'OWASP Top 10 security audit — scans code for vulnerabilities with CWE references.',
|
|
1350
|
+
arguments: [
|
|
1351
|
+
{ name: 'code', description: 'The code to audit.', required: true },
|
|
1352
|
+
{ name: 'language', description: 'Language or framework (e.g. TypeScript, Express).', required: false },
|
|
1353
|
+
],
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
name: 'deploy-checklist',
|
|
1357
|
+
description: 'Pre-deploy checklist debate — council reviews your deployment plan.',
|
|
1358
|
+
arguments: [
|
|
1359
|
+
{ name: 'plan', description: 'Your deployment plan or change description.', required: true },
|
|
1360
|
+
{ name: 'environment', description: 'Target environment (prod, staging, etc.).', required: false },
|
|
1361
|
+
],
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
name: 'explain-file',
|
|
1365
|
+
description: 'Expert explanation of a file — routes to the best-fit agent based on file type.',
|
|
1366
|
+
arguments: [
|
|
1367
|
+
{ name: 'file_path', description: 'Absolute path to the file to explain.', required: true },
|
|
1368
|
+
{ name: 'depth', description: 'Explanation depth: overview | detailed | line-by-line.', required: false },
|
|
1369
|
+
],
|
|
1370
|
+
},
|
|
1371
|
+
];
|
|
1372
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }));
|
|
1373
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
1374
|
+
const { name, arguments: pArgs } = request.params;
|
|
1375
|
+
if (name === 'code-review') {
|
|
1376
|
+
const code = pArgs?.code ?? '<paste code here>';
|
|
1377
|
+
const focus = pArgs?.focus ? ` Focus on: ${pArgs.focus}.` : '';
|
|
1378
|
+
return {
|
|
1379
|
+
description: PROMPTS[0].description,
|
|
1380
|
+
messages: [{
|
|
1381
|
+
role: 'user',
|
|
1382
|
+
content: { type: 'text', text: `Use veto_code_review to review this code.${focus}\n\n\`\`\`\n${code}\n\`\`\`` },
|
|
1383
|
+
}],
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
if (name === 'security-audit') {
|
|
1387
|
+
const code = pArgs?.code ?? '<paste code here>';
|
|
1388
|
+
const lang = pArgs?.language ? ` Language/framework: ${pArgs.language}.` : '';
|
|
1389
|
+
return {
|
|
1390
|
+
description: PROMPTS[1].description,
|
|
1391
|
+
messages: [{
|
|
1392
|
+
role: 'user',
|
|
1393
|
+
content: { type: 'text', text: `Use veto_security_scan to audit this code for OWASP Top 10 vulnerabilities.${lang}\n\n\`\`\`\n${code}\n\`\`\`` },
|
|
1394
|
+
}],
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
if (name === 'deploy-checklist') {
|
|
1398
|
+
const plan = pArgs?.plan ?? '<describe your deployment plan>';
|
|
1399
|
+
const env = pArgs?.environment ? ` Target environment: ${pArgs.environment}.` : '';
|
|
1400
|
+
return {
|
|
1401
|
+
description: PROMPTS[2].description,
|
|
1402
|
+
messages: [{
|
|
1403
|
+
role: 'user',
|
|
1404
|
+
content: { type: 'text', text: `Use veto_council_debate to review this deployment plan before we ship.${env}\n\nPlan: ${plan}` },
|
|
1405
|
+
}],
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
if (name === 'explain-file') {
|
|
1409
|
+
const filePath = pArgs?.file_path ?? '<absolute file path>';
|
|
1410
|
+
const depth = pArgs?.depth ?? 'overview';
|
|
1411
|
+
return {
|
|
1412
|
+
description: PROMPTS[3].description,
|
|
1413
|
+
messages: [{
|
|
1414
|
+
role: 'user',
|
|
1415
|
+
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.` },
|
|
1416
|
+
}],
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
1420
|
+
});
|
|
938
1421
|
// ─── Start ─────────────────────────────────────────────────────────────────────
|
|
939
1422
|
async function main() {
|
|
1423
|
+
const loadedPlugins = await loadPlugins();
|
|
1424
|
+
if (loadedPlugins.length > 0) {
|
|
1425
|
+
process.stderr.write(`[veto] Loaded ${loadedPlugins.length} plugin(s): ${loadedPlugins.join(', ')}\n`);
|
|
1426
|
+
}
|
|
940
1427
|
const transport = new StdioServerTransport();
|
|
941
1428
|
await server.connect(transport);
|
|
942
|
-
// stderr so it doesn't pollute MCP stdio
|
|
943
1429
|
process.stderr.write(`Veto MCP server v${VERSION} running (stdio)\n`);
|
|
944
1430
|
}
|
|
945
1431
|
main().catch((err) => {
|