@jigyasudham/veto 1.4.4 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/AGENTS.md +134 -0
  2. package/README.md +28 -146
  3. package/dist/adapters/index.d.ts +3 -2
  4. package/dist/adapters/index.d.ts.map +1 -1
  5. package/dist/adapters/index.js +128 -5
  6. package/dist/adapters/index.js.map +1 -1
  7. package/dist/agents/executor.d.ts +2 -0
  8. package/dist/agents/executor.d.ts.map +1 -1
  9. package/dist/agents/executor.js +50 -3
  10. package/dist/agents/executor.js.map +1 -1
  11. package/dist/agents/llm-runner.d.ts +11 -0
  12. package/dist/agents/llm-runner.d.ts.map +1 -0
  13. package/dist/agents/llm-runner.js +252 -0
  14. package/dist/agents/llm-runner.js.map +1 -0
  15. package/dist/agents/local-llm.d.ts +14 -0
  16. package/dist/agents/local-llm.d.ts.map +1 -0
  17. package/dist/agents/local-llm.js +51 -0
  18. package/dist/agents/local-llm.js.map +1 -0
  19. package/dist/agents/manifest.d.ts +13 -0
  20. package/dist/agents/manifest.d.ts.map +1 -0
  21. package/dist/agents/manifest.js +55 -0
  22. package/dist/agents/manifest.js.map +1 -0
  23. package/dist/agents/quality/clone-detector.d.ts +16 -0
  24. package/dist/agents/quality/clone-detector.d.ts.map +1 -0
  25. package/dist/agents/quality/clone-detector.js +72 -0
  26. package/dist/agents/quality/clone-detector.js.map +1 -0
  27. package/dist/agents/research/cost-analyzer.js +1 -1
  28. package/dist/agents/research/cost-analyzer.js.map +1 -1
  29. package/dist/agents/types.d.ts +15 -0
  30. package/dist/agents/types.d.ts.map +1 -1
  31. package/dist/agents/validate.d.ts +11 -0
  32. package/dist/agents/validate.d.ts.map +1 -0
  33. package/dist/agents/validate.js +54 -0
  34. package/dist/agents/validate.js.map +1 -0
  35. package/dist/cli.js +339 -22
  36. package/dist/cli.js.map +1 -1
  37. package/dist/council/llm-council.d.ts.map +1 -1
  38. package/dist/council/llm-council.js +10 -9
  39. package/dist/council/llm-council.js.map +1 -1
  40. package/dist/council/types.d.ts +2 -0
  41. package/dist/council/types.d.ts.map +1 -1
  42. package/dist/memory/config.d.ts +1 -0
  43. package/dist/memory/config.d.ts.map +1 -1
  44. package/dist/memory/config.js +2 -0
  45. package/dist/memory/config.js.map +1 -1
  46. package/dist/memory/local.d.ts +12 -1
  47. package/dist/memory/local.d.ts.map +1 -1
  48. package/dist/memory/local.js +56 -2
  49. package/dist/memory/local.js.map +1 -1
  50. package/dist/memory/schema.d.ts +28 -43
  51. package/dist/memory/schema.d.ts.map +1 -1
  52. package/dist/memory/schema.js +31 -0
  53. package/dist/memory/schema.js.map +1 -1
  54. package/dist/memory/sync.d.ts +13 -0
  55. package/dist/memory/sync.d.ts.map +1 -1
  56. package/dist/memory/sync.js +113 -0
  57. package/dist/memory/sync.js.map +1 -1
  58. package/dist/repo-map/ignore.d.ts +8 -0
  59. package/dist/repo-map/ignore.d.ts.map +1 -0
  60. package/dist/repo-map/ignore.js +65 -0
  61. package/dist/repo-map/ignore.js.map +1 -0
  62. package/dist/repo-map/index.d.ts +29 -0
  63. package/dist/repo-map/index.d.ts.map +1 -0
  64. package/dist/repo-map/index.js +240 -0
  65. package/dist/repo-map/index.js.map +1 -0
  66. package/dist/router/context-compressor.d.ts +1 -1
  67. package/dist/router/context-compressor.d.ts.map +1 -1
  68. package/dist/router/context-compressor.js +9 -3
  69. package/dist/router/context-compressor.js.map +1 -1
  70. package/dist/router/index.d.ts +8 -3
  71. package/dist/router/index.d.ts.map +1 -1
  72. package/dist/router/index.js +20 -4
  73. package/dist/router/index.js.map +1 -1
  74. package/dist/router/learning-updater.d.ts +41 -0
  75. package/dist/router/learning-updater.d.ts.map +1 -1
  76. package/dist/router/learning-updater.js +62 -0
  77. package/dist/router/learning-updater.js.map +1 -1
  78. package/dist/router/model-selector.d.ts +4 -1
  79. package/dist/router/model-selector.d.ts.map +1 -1
  80. package/dist/router/model-selector.js +24 -28
  81. package/dist/router/model-selector.js.map +1 -1
  82. package/dist/router/rate-monitor.d.ts +2 -1
  83. package/dist/router/rate-monitor.d.ts.map +1 -1
  84. package/dist/router/rate-monitor.js +3 -0
  85. package/dist/router/rate-monitor.js.map +1 -1
  86. package/dist/server.js +3124 -1655
  87. package/dist/server.js.map +1 -1
  88. package/dist/tools/definitions.d.ts +1205 -171
  89. package/dist/tools/definitions.d.ts.map +1 -1
  90. package/dist/tools/definitions.js +678 -4
  91. package/dist/tools/definitions.js.map +1 -1
  92. package/dist/watcher/index.d.ts +1 -0
  93. package/dist/watcher/index.d.ts.map +1 -1
  94. package/dist/watcher/index.js +7 -6
  95. package/dist/watcher/index.js.map +1 -1
  96. package/dist/workflow/pipeline.d.ts +12 -2
  97. package/dist/workflow/pipeline.d.ts.map +1 -1
  98. package/dist/workflow/pipeline.js +395 -16
  99. package/dist/workflow/pipeline.js.map +1 -1
  100. package/package.json +9 -4
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import { homedir } from 'node:os';
9
9
  import { fileURLToPath } from 'node:url';
10
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
11
  const { version: VERSION } = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
12
- const TAGLINE = '50 agents. 49 tools. 3 AIs. Self-learning. Zero extra cost.';
12
+ const TAGLINE = '50 agents. 62 tools. 4 AIs. Self-learning. Zero extra cost.';
13
13
  const VETO_DIR = join(homedir(), '.veto');
14
14
  const HOME = homedir();
15
15
  const c = {
@@ -104,6 +104,12 @@ const PLATFORMS = [
104
104
  format: 'mcpServers',
105
105
  detectionDir: join(HOME, '.gemini'),
106
106
  },
107
+ {
108
+ name: 'Antigravity CLI',
109
+ path: join(HOME, '.gemini', 'antigravity-cli', 'mcp_config.json'),
110
+ format: 'mcpServers',
111
+ detectionDir: join(HOME, '.gemini', 'antigravity-cli'),
112
+ },
107
113
  {
108
114
  name: 'Cursor',
109
115
  path: join(HOME, '.cursor', 'mcp.json'),
@@ -162,8 +168,21 @@ async function initCommand() {
162
168
  console.error(c.red(` Error initializing database: ${msg}`));
163
169
  process.exit(1);
164
170
  }
165
- // 3. Auto-scan current project and store project map
171
+ // 3. Auto-import VETO_MEMORY.md if present in cwd
166
172
  const cwd = resolve(process.cwd());
173
+ const vetoMemoryPath = join(cwd, 'VETO_MEMORY.md');
174
+ if (existsSync(vetoMemoryPath)) {
175
+ process.stdout.write(' · Importing VETO_MEMORY.md...');
176
+ try {
177
+ const { importMemoryMarkdown } = await import('./memory/sync.js');
178
+ const importResult = importMemoryMarkdown(vetoMemoryPath);
179
+ console.log(c.green(' ✓') + ` ${importResult.imported} knowledge entries imported`);
180
+ }
181
+ catch {
182
+ console.log(c.dim(' skipped'));
183
+ }
184
+ }
185
+ // 4. Auto-scan current project and store project map
167
186
  const { updateProjectMap } = await import('./memory/local.js');
168
187
  const { discoverProject } = await import('./discover.js');
169
188
  try {
@@ -282,6 +301,171 @@ async function initCommand() {
282
301
  }
283
302
  }
284
303
  console.log('');
304
+ // 5. Write platform-specific context guidance files
305
+ // These are read at session start by each AI client — zero tool calls needed.
306
+ console.log(' Writing context guidance files...');
307
+ console.log('');
308
+ const VETO_GUIDE = `# Veto MCP Server
309
+
310
+ Veto is active. 49 tools across 5 categories:
311
+
312
+ **Session & Context** — veto_status · veto_session_save · veto_continue · veto_handoff
313
+ Save work at 60–70% context capacity. veto_status triggers auto-save above 70%.
314
+
315
+ **Code Intelligence** — veto_diff_review · veto_code_review · veto_security_scan · veto_secrets_scan · veto_ci_gate
316
+ Run veto_diff_review before any merge — it runs all three scans in parallel.
317
+
318
+ **Council & Routing** — veto_council_debate · veto_route_task · veto_execute_parallel
319
+ Council = 7 specialist agents (Lead Dev, PM, Architect, UX, Devil's Advocate, Legal, Security).
320
+ Verdicts: GREEN (proceed) · YELLOW (warnings) · RED (blocked) · DEADLOCK (human decision needed).
321
+ Two-phase LLM-backed flow: call with { task } → get debate_prompt → reason as all 7 agents → call again with { task, agent_responses }.
322
+
323
+ **Memory & Discovery** — veto_discover · veto_summarize · veto_memory_store · veto_memory_search
324
+ Run veto_discover on any unfamiliar repo before touching files.
325
+
326
+ **Observability** — veto_usage_status · veto_health · veto_audit_log · veto_learning_stats
327
+
328
+ Recommended start sequence:
329
+ 1. veto_status — confirm running
330
+ 2. veto_discover — map the project
331
+ 3. veto_route_task — pick the right agent
332
+ 4. veto_diff_review — validate before shipping
333
+ 5. veto_session_save — checkpoint before context fills
334
+ `;
335
+ let ctxWritten = 0;
336
+ // Gemini & Antigravity CLI: ~/.gemini/GEMINI.md
337
+ const geminiDir = join(HOME, '.gemini');
338
+ if (existsSync(geminiDir)) {
339
+ try {
340
+ writeFileSync(join(geminiDir, 'GEMINI.md'), VETO_GUIDE, 'utf8');
341
+ console.log(c.green(' ✓ ') + 'Gemini/Antigravity CLI — wrote ~/.gemini/GEMINI.md');
342
+ ctxWritten++;
343
+ }
344
+ catch {
345
+ console.log(c.yellow(' ⚠ ') + 'Gemini/Antigravity CLI — could not write GEMINI.md');
346
+ }
347
+ }
348
+ // Codex CLI: project AGENTS.md + global ~/.codex/AGENTS.override.md
349
+ const codexDir2 = join(HOME, '.codex');
350
+ if (existsSync(codexDir2)) {
351
+ // Project-level: only write if not already present (user may have customized it)
352
+ const projectAgents = join(cwd, 'AGENTS.md');
353
+ if (!existsSync(projectAgents)) {
354
+ try {
355
+ writeFileSync(projectAgents, VETO_GUIDE, 'utf8');
356
+ console.log(c.green(' ✓ ') + `Codex CLI — wrote AGENTS.md in ${cwd}`);
357
+ ctxWritten++;
358
+ }
359
+ catch {
360
+ console.log(c.yellow(' ⚠ ') + 'Codex CLI — could not write AGENTS.md');
361
+ }
362
+ }
363
+ else {
364
+ console.log(c.dim(' · ') + c.dim('Codex CLI — AGENTS.md already exists, skipping'));
365
+ }
366
+ // Global override
367
+ try {
368
+ writeFileSync(join(codexDir2, 'AGENTS.override.md'), VETO_GUIDE, 'utf8');
369
+ console.log(c.green(' ✓ ') + 'Codex CLI — wrote ~/.codex/AGENTS.override.md');
370
+ ctxWritten++;
371
+ }
372
+ catch {
373
+ console.log(c.yellow(' ⚠ ') + 'Codex CLI — could not write AGENTS.override.md');
374
+ }
375
+ }
376
+ // Windsurf: ~/.codeium/windsurf/rules/veto.md
377
+ const windsurfRulesDir = join(HOME, '.codeium', 'windsurf', 'rules');
378
+ if (existsSync(join(HOME, '.codeium', 'windsurf'))) {
379
+ try {
380
+ mkdirSync(windsurfRulesDir, { recursive: true });
381
+ writeFileSync(join(windsurfRulesDir, 'veto.md'), VETO_GUIDE, 'utf8');
382
+ console.log(c.green(' ✓ ') + 'Windsurf — wrote ~/.codeium/windsurf/rules/veto.md');
383
+ ctxWritten++;
384
+ }
385
+ catch {
386
+ console.log(c.yellow(' ⚠ ') + 'Windsurf — could not write rules/veto.md');
387
+ }
388
+ }
389
+ if (ctxWritten > 0)
390
+ console.log('');
391
+ // 6. Write Claude Code hook templates to .claude/hooks/ in current project
392
+ // Hooks enforce secrets scanning on every file write and auto-save before compaction.
393
+ if (existsSync(claudeDir)) {
394
+ const hooksDir = join(cwd, '.claude', 'hooks');
395
+ const settingsPath = join(cwd, '.claude', 'settings.json');
396
+ try {
397
+ mkdirSync(hooksDir, { recursive: true });
398
+ // Secrets scan — bash (Mac/Linux)
399
+ const shLines = [
400
+ '#!/usr/bin/env bash',
401
+ '# Veto hook: scan written files for exposed secrets (no API key needed).',
402
+ '# Triggered by Claude Code PostToolUse after Write/Edit tool calls.',
403
+ 'FILE="$1"',
404
+ '[ -z "$FILE" ] && exit 0',
405
+ '[ ! -f "$FILE" ] && exit 0',
406
+ "PATTERNS='(api[_-]?key|secret[_-]?key|password|passwd|token|access[_-]?key|private[_-]?key)\\s*[=:]\\s*[A-Za-z0-9+/]{20,}'",
407
+ 'if grep -qiE "$PATTERNS" "$FILE" 2>/dev/null; then',
408
+ ' echo "Veto: possible secret detected in $FILE — run veto_secrets_scan to confirm"',
409
+ ' exit 1',
410
+ 'fi',
411
+ 'exit 0',
412
+ '',
413
+ ];
414
+ writeFileSync(join(hooksDir, 'veto-secrets-scan.sh'), shLines.join('\n'), 'utf8');
415
+ // Secrets scan — PowerShell (Windows)
416
+ const ps1Lines = [
417
+ '# Veto hook: scan written files for exposed secrets (no API key needed).',
418
+ 'param([string]$File)',
419
+ 'if (-not $File -or -not (Test-Path $File)) { exit 0 }',
420
+ "$pattern = '(api[_-]?key|secret[_-]?key|password|passwd|token|access[_-]?key|private[_-]?key)\\s*[=:]\\s*[A-Za-z0-9+/]{20,}'",
421
+ 'if (Select-String -Path $File -Pattern $pattern -Quiet -CaseSensitive:$false) {',
422
+ ' Write-Host "Veto: possible secret detected in $File — run veto_secrets_scan to confirm"',
423
+ ' exit 1',
424
+ '}',
425
+ 'exit 0',
426
+ '',
427
+ ];
428
+ writeFileSync(join(hooksDir, 'veto-secrets-scan.ps1'), ps1Lines.join('\n'), 'utf8');
429
+ // Phase 1.4: Write standard hook files that Claude Code looks for
430
+ const postFileWrite = process.platform === 'win32'
431
+ ? `@powershell -NoProfile -ExecutionPolicy Bypass -File ".claude\\hooks\\veto-secrets-scan.ps1" "%1"`
432
+ : `#!/bin/sh\n./.claude/hooks/veto-secrets-scan.sh "$1"`;
433
+ const preCompact = process.platform === 'win32'
434
+ ? `@npx -y @jigyasudham/veto veto_session_save --auto_summarize=true`
435
+ : `#!/bin/sh\nnpx -y @jigyasudham/veto veto_session_save --auto_summarize=true`;
436
+ writeFileSync(join(hooksDir, 'post-file-write'), postFileWrite, { mode: 0o755 });
437
+ writeFileSync(join(hooksDir, 'pre-compact'), preCompact, { mode: 0o755 });
438
+ // Wire hooks into .claude/settings.json if it exists or create it
439
+ let projectSettings = {};
440
+ if (existsSync(settingsPath)) {
441
+ try {
442
+ projectSettings = JSON.parse(readFileSync(settingsPath, 'utf8'));
443
+ }
444
+ catch { /* leave empty */ }
445
+ }
446
+ else {
447
+ mkdirSync(dirname(settingsPath), { recursive: true });
448
+ }
449
+ const hooks = projectSettings.hooks ?? {};
450
+ // Only add if not already configured
451
+ if (!hooks['PostToolUse']) {
452
+ const scanCmd = process.platform === 'win32'
453
+ ? 'powershell -ExecutionPolicy Bypass -File .claude/hooks/veto-secrets-scan.ps1 "$CLAUDE_TOOL_INPUT_FILE_PATH"'
454
+ : 'bash .claude/hooks/veto-secrets-scan.sh "$CLAUDE_TOOL_INPUT_FILE_PATH"';
455
+ hooks['PostToolUse'] = [{ matcher: 'Write|Edit', hooks: [{ type: 'command', command: scanCmd }] }];
456
+ projectSettings.hooks = hooks;
457
+ writeFileSync(settingsPath, JSON.stringify(projectSettings, null, 2) + '\n', 'utf8');
458
+ console.log(c.green(' ✓ ') + 'Claude Code — wrote .claude/hooks/veto-secrets-scan + PostToolUse hook entry');
459
+ }
460
+ else {
461
+ console.log(c.dim(' · ') + c.dim('Claude Code — PostToolUse hook already configured, skipping'));
462
+ }
463
+ }
464
+ catch {
465
+ console.log(c.yellow(' ⚠ ') + 'Claude Code — could not write hook templates (permission denied?)');
466
+ }
467
+ console.log('');
468
+ }
285
469
  if (configured === 0 && skipped === 0) {
286
470
  console.log(c.yellow(' ⚠ No AI tools detected.'));
287
471
  console.log(' Install Claude Code, Gemini CLI, or Codex CLI and run veto init again.');
@@ -417,6 +601,7 @@ async function doctorCommand() {
417
601
  // Other platforms — check their config JSON files
418
602
  const platforms = [
419
603
  { name: 'Gemini CLI', configPath: join(HOME, '.gemini', 'settings.json'), detectionDir: join(HOME, '.gemini'), key: 'mcpServers' },
604
+ { name: 'Antigravity CLI', configPath: join(HOME, '.gemini', 'antigravity-cli', 'mcp_config.json'), detectionDir: join(HOME, '.gemini', 'antigravity-cli'), key: 'mcpServers' },
420
605
  { name: 'Cursor', configPath: join(HOME, '.cursor', 'mcp.json'), detectionDir: join(HOME, '.cursor'), key: 'mcpServers' },
421
606
  { name: 'Windsurf', configPath: join(HOME, '.codeium', 'windsurf', 'mcp_config.json'), detectionDir: join(HOME, '.codeium', 'windsurf'), key: 'mcpServers' },
422
607
  {
@@ -513,6 +698,57 @@ async function sessionsCommand() {
513
698
  }
514
699
  async function memoryCommand() {
515
700
  const args = process.argv.slice(3);
701
+ const subcommand = args[0];
702
+ // veto memory export [--format=markdown] [--output=path]
703
+ if (subcommand === 'export') {
704
+ const formatArg = args.find(a => a.startsWith('--format='));
705
+ const outputArg = args.find(a => a.startsWith('--output='));
706
+ const format = formatArg?.split('=')[1] ?? 'json';
707
+ const outputPath = outputArg?.split('=')[1];
708
+ const { exportMemory, exportMemoryMarkdown } = await import('./memory/sync.js');
709
+ if (format === 'markdown') {
710
+ const cwd = resolve(process.cwd());
711
+ const result = exportMemoryMarkdown(cwd, outputPath);
712
+ if (result.success) {
713
+ console.log(c.green(' ✓') + ` VETO_MEMORY.md written to ${result.output_path}`);
714
+ console.log(c.dim(` Sections: ${JSON.stringify(result.sections)}`));
715
+ }
716
+ else {
717
+ console.error(c.red(` ✗ Export failed: ${result.error}`));
718
+ }
719
+ }
720
+ else {
721
+ const result = exportMemory(outputPath);
722
+ if (result.success) {
723
+ console.log(c.green(' ✓') + ` Exported to ${result.export_path}`);
724
+ }
725
+ else {
726
+ console.error(c.red(` ✗ Export failed: ${result.error}`));
727
+ }
728
+ }
729
+ return;
730
+ }
731
+ // veto memory import [--format=markdown] <path>
732
+ if (subcommand === 'import') {
733
+ const formatArg = args.find(a => a.startsWith('--format='));
734
+ const format = formatArg?.split('=')[1] ?? 'json';
735
+ const inputPath = args.find(a => !a.startsWith('--')) ?? '';
736
+ const { importMemory, importMemoryMarkdown } = await import('./memory/sync.js');
737
+ if (format === 'markdown') {
738
+ if (!inputPath) {
739
+ console.error(c.red(' ✗ Provide a file path: veto memory import --format=markdown <path>'));
740
+ return;
741
+ }
742
+ const result = importMemoryMarkdown(inputPath);
743
+ console.log(result.success ? c.green(' ✓') + ` ${result.message}` : c.red(` ✗ ${result.message}`));
744
+ }
745
+ else {
746
+ const result = importMemory(inputPath || undefined);
747
+ console.log(result.success ? c.green(' ✓') + ` Import complete` : c.red(` ✗ Import failed: ${result.error}`));
748
+ }
749
+ return;
750
+ }
751
+ // veto memory [query]
516
752
  const query = args.join(' ') || undefined;
517
753
  const { searchKnowledge } = await import('./memory/local.js');
518
754
  const results = searchKnowledge({ query, limit: 20 });
@@ -554,7 +790,7 @@ async function patternsCommand() {
554
790
  }
555
791
  function shortHelpCommand() {
556
792
  console.log('');
557
- console.log(c.bold(c.cyan(' veto')) + c.dim(` v${VERSION}`) + c.dim(` — ${TAGLINE}`));
793
+ console.log(c.bold(c.cyan(' veto')) + c.dim(` v${VERSION}`) + c.dim(` — 62 agentic tools. 50+ specialists. Zero cost.`));
558
794
  console.log('');
559
795
  console.log(c.bold(' CLI Commands'));
560
796
  console.log(c.dim(' ─────────────────────────────────────────────────────'));
@@ -564,6 +800,8 @@ function shortHelpCommand() {
564
800
  console.log(` ${c.cyan('veto sessions')} List last 20 saved sessions`);
565
801
  console.log(` ${c.cyan('veto memory')} ${c.dim('[query]')} Search knowledge base`);
566
802
  console.log(` ${c.cyan('veto patterns')} ${c.dim('[prefix]')} List learned agent/routing patterns`);
803
+ console.log(` ${c.cyan('veto routing')} ${c.dim('[status|enable|disable|reset|log]')}`);
804
+ console.log(` Routing feedback loop (opt-in signal storage)`);
567
805
  console.log(` ${c.cyan('veto version')} Show version (alias for status)`);
568
806
  console.log(` ${c.cyan('veto hook install')} Install pre-commit secrets scan hook`);
569
807
  console.log(` ${c.cyan('veto hook remove')} Remove the veto pre-commit hook`);
@@ -571,26 +809,20 @@ function shortHelpCommand() {
571
809
  console.log(` ${c.cyan('veto help')} Show this help`);
572
810
  console.log(` ${c.cyan('veto help --troubleshoot')} Show troubleshooting guide`);
573
811
  console.log('');
574
- console.log(c.bold(' MCP Tools (49)'));
812
+ console.log(c.bold(' MCP Tools (62 Agentic Tools)'));
575
813
  console.log(c.dim(' ─────────────────────────────────────────────────────'));
576
- console.log(` ${c.dim('Session')} veto_status · veto_autosave_status · veto_session_save · veto_session_restore · veto_sessions_list`);
577
- console.log(` ${c.dim('Router')} veto_route_task · veto_rate_status`);
578
- console.log(` ${c.dim('Council')} veto_council_debate · veto_benchmark`);
579
- console.log(` ${c.dim('Agents')} veto_agent_plan · veto_execute_parallel · veto_explain`);
580
- console.log(` ${c.dim('Review')} veto_code_review · veto_security_scan · veto_secrets_scan · veto_diff_review`);
581
- console.log(` ${c.dim('Pipeline')} veto_workflow`);
582
- console.log(` ${c.dim('Watch')} veto_watch · veto_watch_poll · veto_watch_stop`);
583
- console.log(` ${c.dim('Memory')} veto_memory_store · veto_memory_search · veto_memory_delete`);
584
- console.log(` veto_project_map_update · veto_project_map_get`);
585
- console.log(` veto_pattern_store · veto_patterns_list`);
586
- console.log(` veto_memory_export · veto_memory_import`);
587
- console.log(` ${c.dim('Learning')} veto_record_outcome · veto_learning_stats · veto_learning_apply`);
588
- console.log(` ${c.dim('Handoff')} veto_handoff · veto_continue · veto_platform_setup`);
589
- console.log(` ${c.dim('Intelligence')} veto_docs_fetch · veto_context_status · veto_task_parse`);
590
- console.log(` ${c.dim('Observability')} veto_usage_status · veto_audit_log · veto_health · veto_metrics`);
591
- console.log(` ${c.dim('CI/CD')} veto_ci_gate · veto_pr_review`);
592
- console.log(` ${c.dim('Discover')} veto_discover · veto_summarize · veto_git_blame · veto_changelog`);
593
- console.log(` ${c.dim('Plugins')} veto_plugins`);
814
+ console.log(` ${c.dim('Session')} veto_status · veto_session_save · veto_session_restore · veto_sessions_list · veto_session_replay · veto_autosave_status`);
815
+ console.log(` ${c.dim('Council')} veto_council_debate · veto_benchmark · veto_adr`);
816
+ console.log(` ${c.dim('Intelligence')} veto_agent_plan · veto_execute_parallel · veto_explain · veto_delegate · veto_compose_agents`);
817
+ console.log(` ${c.dim('Scanning')} veto_code_review · veto_security_scan · veto_secrets_scan · veto_diff_review · veto_full_review · veto_pr_review`);
818
+ console.log(` ${c.dim('Pipelines')} veto_workflow · veto_task_parse · veto_new_feature · veto_pre_commit · veto_ci_gate`);
819
+ console.log(` ${c.dim('Watching')} veto_watch · veto_watch_poll · veto_watch_stop`);
820
+ console.log(` ${c.dim('Advanced')} veto_local_llm · veto_semantic_search · veto_sdd_agent · veto_playwright · veto_notify_ide · veto_translate · veto_a11y_advisor`);
821
+ console.log(` ${c.dim('Quality')} veto_type_coverage · veto_test_gaps · veto_clone_detector · veto_lint_rules · veto_api_contract`);
822
+ console.log(` ${c.dim('Discovery')} veto_discover · veto_summarize · veto_git_blame · veto_changelog · veto_onboard · veto_debt_register`);
823
+ console.log(` ${c.dim('DevTools')} veto_docs_fetch · veto_context_status · veto_openapi_gen · veto_flag_auditor · veto_env_setup · veto_diagram · veto_rca`);
824
+ console.log(` veto_commit_message · veto_pr_description · veto_pr_post · veto_prompt_optimizer · veto_sre_advisor · veto_merge_conflict`);
825
+ console.log(` ${c.dim('System')} veto_route_task · veto_rate_status · veto_audit_log · veto_health · veto_metrics · veto_learning_stats · veto_learning_apply · veto_handoff · veto_continue · veto_platform_setup · veto_plugins`);
594
826
  console.log('');
595
827
  console.log(c.bold(' MCP Resources'));
596
828
  console.log(c.dim(' ─────────────────────────────────────────────────────'));
@@ -602,6 +834,7 @@ function shortHelpCommand() {
602
834
  console.log(c.bold(' MCP Prompts'));
603
835
  console.log(c.dim(' ─────────────────────────────────────────────────────'));
604
836
  console.log(` ${c.cyan('code-review')} · ${c.cyan('security-audit')} · ${c.cyan('deploy-checklist')} · ${c.cyan('explain-file')}`);
837
+ console.log(` ${c.cyan('full-review')} · ${c.cyan('new-feature')} · ${c.cyan('debug-incident')} · ${c.cyan('onboard')}`);
605
838
  console.log('');
606
839
  console.log(c.bold(' Docs & Support'));
607
840
  console.log(c.dim(' ─────────────────────────────────────────────────────'));
@@ -679,6 +912,84 @@ function troubleshootCommand() {
679
912
  console.log(` ${c.dim('→')} Then restart the AI client on that machine`);
680
913
  console.log('');
681
914
  }
915
+ // ─── Routing Command ────────────────────────────────────────────────────────────
916
+ async function routingCommand() {
917
+ const { getRoutingFeedbackStats, resetRoutingFeedback, listRoutingFeedback, isFeedbackEnabled, setFeedbackEnabled } = await import('./router/learning-updater.js');
918
+ const sub = process.argv[3];
919
+ if (sub === 'enable') {
920
+ setFeedbackEnabled(true);
921
+ console.log('');
922
+ console.log(c.green(' ✓ Routing feedback enabled.'));
923
+ console.log(c.dim(' Every veto_route_task call now records a routing signal (30-day TTL).'));
924
+ console.log(c.dim(' Disable with: veto routing disable | Clear with: veto routing reset'));
925
+ console.log('');
926
+ return;
927
+ }
928
+ if (sub === 'disable') {
929
+ setFeedbackEnabled(false);
930
+ console.log('');
931
+ console.log(c.yellow(' ✓ Routing feedback disabled.'));
932
+ console.log(c.dim(' No new signals will be recorded. Existing data is retained.'));
933
+ console.log(c.dim(' Re-enable with: veto routing enable'));
934
+ console.log('');
935
+ return;
936
+ }
937
+ if (sub === 'reset') {
938
+ const result = resetRoutingFeedback();
939
+ console.log('');
940
+ console.log(c.green(' ✓ Routing feedback reset.'));
941
+ console.log(` ${c.dim('Deleted:')} ${c.cyan(String(result.deleted_feedback))} feedback signal${result.deleted_feedback !== 1 ? 's' : ''}`);
942
+ if (result.reset_thresholds) {
943
+ console.log(` ${c.dim('Thresholds:')} reset to defaults (30/70)`);
944
+ }
945
+ console.log('');
946
+ return;
947
+ }
948
+ if (sub === 'log') {
949
+ const limit = parseInt(process.argv[4] ?? '20', 10);
950
+ const entries = listRoutingFeedback(isNaN(limit) ? 20 : limit);
951
+ console.log('');
952
+ console.log(c.bold(' Routing Feedback Log') + c.dim(` (${entries.length})`));
953
+ console.log(c.dim(' ─────────────────────────────────────────────────────────────'));
954
+ if (entries.length === 0) {
955
+ console.log(c.dim(' No feedback signals yet. Enable feedback: veto routing enable'));
956
+ }
957
+ else {
958
+ for (const e of entries) {
959
+ const outcomeColor = e.outcome === 'accepted' ? c.green : e.outcome === 'overridden' ? c.yellow : c.dim;
960
+ const exp = new Date(e.expires_at).toLocaleDateString();
961
+ console.log(` ${c.dim(e.recorded_at.slice(0, 10))} T${e.model_tier} ${outcomeColor(e.outcome.padEnd(10))} ${c.dim(`q:${e.quality ?? '-'}`)} ${e.task_snippet.slice(0, 55)}`);
962
+ console.log(` ${c.dim(` expires: ${exp} agent: ${e.agent ?? 'dynamic'}`)}`);
963
+ console.log('');
964
+ }
965
+ }
966
+ return;
967
+ }
968
+ // Default: status
969
+ const stats = getRoutingFeedbackStats();
970
+ const enabled = isFeedbackEnabled();
971
+ const statusStr = enabled ? c.green('enabled') : c.dim('disabled');
972
+ console.log('');
973
+ console.log(c.bold(' Routing Feedback') + c.dim(' — loop status'));
974
+ console.log(c.dim(' ─────────────────────────────────────────────────────'));
975
+ console.log(` Status ${statusStr}`);
976
+ console.log(` TTL ${c.cyan(String(stats.ttl_days))} days`);
977
+ console.log(` Signals ${c.cyan(String(stats.active))} active · ${c.dim(String(stats.expired))} expired · ${String(stats.total)} total`);
978
+ if (Object.keys(stats.by_outcome).length > 0) {
979
+ const parts = Object.entries(stats.by_outcome).map(([k, v]) => `${k}: ${v}`).join(' · ');
980
+ console.log(` Outcomes ${c.dim(parts)}`);
981
+ }
982
+ if (Object.keys(stats.by_tier).length > 0) {
983
+ const parts = Object.entries(stats.by_tier).map(([tier, s]) => `T${tier}: ${s.count} (q${s.avg_quality ?? '-'})`).join(' · ');
984
+ console.log(` By tier ${c.dim(parts)}`);
985
+ }
986
+ if (stats.next_expiry) {
987
+ console.log(` Next expiry ${c.dim(new Date(stats.next_expiry).toLocaleDateString())}`);
988
+ }
989
+ console.log('');
990
+ console.log(c.dim(` Commands: veto routing enable · veto routing disable · veto routing reset · veto routing log`));
991
+ console.log('');
992
+ }
682
993
  // ─── Hook installer ────────────────────────────────────────────────────────────
683
994
  async function hookCommand() {
684
995
  const sub = process.argv[3];
@@ -831,6 +1142,12 @@ switch (command) {
831
1142
  process.exit(1);
832
1143
  });
833
1144
  break;
1145
+ case 'routing':
1146
+ routingCommand().catch((err) => {
1147
+ console.error(c.red(`Error: ${err.message}`));
1148
+ process.exit(1);
1149
+ });
1150
+ break;
834
1151
  case 'hook':
835
1152
  hookCommand().catch((err) => {
836
1153
  console.error(c.red(`Error: ${err.message}`));