@jigyasudham/veto 2.1.1 → 2.5.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.
Files changed (80) hide show
  1. package/.mcp-tools/LICENSE +216 -0
  2. package/.mcp-tools/README.md +153 -0
  3. package/.mcp-tools/mcp-publisher.exe +0 -0
  4. package/LAUNCH-KIT.md +211 -0
  5. package/MCP-REGISTRY.md +94 -0
  6. package/README.md +111 -20
  7. package/VETO-MCP-DIAGNOSTIC.md +73 -0
  8. package/dist/agents/security/dep-verify.d.ts +23 -0
  9. package/dist/agents/security/dep-verify.d.ts.map +1 -0
  10. package/dist/agents/security/dep-verify.js +229 -0
  11. package/dist/agents/security/dep-verify.js.map +1 -0
  12. package/dist/cli.js +4 -4
  13. package/dist/council/decision-engine.d.ts.map +1 -1
  14. package/dist/council/decision-engine.js +11 -2
  15. package/dist/council/decision-engine.js.map +1 -1
  16. package/dist/council/lead-developer.js +2 -2
  17. package/dist/council/lead-developer.js.map +1 -1
  18. package/dist/council/product-manager.js +2 -2
  19. package/dist/council/product-manager.js.map +1 -1
  20. package/dist/council/security.js +2 -2
  21. package/dist/council/security.js.map +1 -1
  22. package/dist/council/ux-designer.js +2 -2
  23. package/dist/council/ux-designer.js.map +1 -1
  24. package/dist/memory/config.d.ts +1 -0
  25. package/dist/memory/config.d.ts.map +1 -1
  26. package/dist/memory/config.js +6 -2
  27. package/dist/memory/config.js.map +1 -1
  28. package/dist/memory/decisions.d.ts +33 -0
  29. package/dist/memory/decisions.d.ts.map +1 -0
  30. package/dist/memory/decisions.js +125 -0
  31. package/dist/memory/decisions.js.map +1 -0
  32. package/dist/memory/local.d.ts.map +1 -1
  33. package/dist/memory/local.js +8 -0
  34. package/dist/memory/local.js.map +1 -1
  35. package/dist/memory/schema.d.ts +1 -1
  36. package/dist/memory/schema.d.ts.map +1 -1
  37. package/dist/memory/schema.js +14 -0
  38. package/dist/memory/schema.js.map +1 -1
  39. package/dist/router/implicit-outcomes.d.ts +7 -0
  40. package/dist/router/implicit-outcomes.d.ts.map +1 -0
  41. package/dist/router/implicit-outcomes.js +88 -0
  42. package/dist/router/implicit-outcomes.js.map +1 -0
  43. package/dist/router/learning-updater.d.ts.map +1 -1
  44. package/dist/router/learning-updater.js +15 -6
  45. package/dist/router/learning-updater.js.map +1 -1
  46. package/dist/router/model-selector.d.ts.map +1 -1
  47. package/dist/router/model-selector.js +33 -5
  48. package/dist/router/model-selector.js.map +1 -1
  49. package/dist/router/rate-monitor.d.ts +1 -0
  50. package/dist/router/rate-monitor.d.ts.map +1 -1
  51. package/dist/router/rate-monitor.js +1 -0
  52. package/dist/router/rate-monitor.js.map +1 -1
  53. package/dist/server/handlers/advisors.d.ts.map +1 -1
  54. package/dist/server/handlers/advisors.js +214 -0
  55. package/dist/server/handlers/advisors.js.map +1 -1
  56. package/dist/server/handlers/devtools.d.ts.map +1 -1
  57. package/dist/server/handlers/devtools.js +20 -3
  58. package/dist/server/handlers/devtools.js.map +1 -1
  59. package/dist/server/handlers/learning.d.ts.map +1 -1
  60. package/dist/server/handlers/learning.js +15 -0
  61. package/dist/server/handlers/learning.js.map +1 -1
  62. package/dist/server/handlers/memory.d.ts.map +1 -1
  63. package/dist/server/handlers/memory.js +47 -0
  64. package/dist/server/handlers/memory.js.map +1 -1
  65. package/dist/server/handlers/review.d.ts.map +1 -1
  66. package/dist/server/handlers/review.js +22 -4
  67. package/dist/server/handlers/review.js.map +1 -1
  68. package/dist/server.d.ts.map +1 -1
  69. package/dist/server.js +95 -2
  70. package/dist/server.js.map +1 -1
  71. package/dist/tools/compact.d.ts +12 -0
  72. package/dist/tools/compact.d.ts.map +1 -0
  73. package/dist/tools/compact.js +98 -0
  74. package/dist/tools/compact.js.map +1 -0
  75. package/dist/tools/definitions.d.ts +98 -2
  76. package/dist/tools/definitions.d.ts.map +1 -1
  77. package/dist/tools/definitions.js +48 -3
  78. package/dist/tools/definitions.js.map +1 -1
  79. package/package.json +3 -2
  80. package/server.json +23 -0
package/README.md CHANGED
@@ -1,36 +1,36 @@
1
1
  # veto
2
2
 
3
- > **89 agentic tools. 49 specialists. Every major AI CLI. Self-learning. Zero extra cost on subscriptions.**
3
+ > **92 agentic tools. 49 specialists. Every major AI CLI. Self-learning. Zero extra cost on subscriptions.**
4
4
 
5
5
  An MCP server that runs locally on your machine, plugs into Claude Code, Codex CLI, Gemini CLI, Antigravity CLI, Cursor, Windsurf, Zed, and JetBrains using your existing subscriptions — giving every AI a council of specialist agents, local LLM support, SDD agents, playwright automation, persistent cross-platform memory, a self-learning router that re-tunes its tier thresholds automatically every 20 recorded task outcomes (reviews record outcomes for you; configurable via `auto_apply_learning`), CI/CD gates, workspace discovery, and bidirectional IDE communication.
6
6
 
7
- > **Billing note:** "Zero cost" applies to subscription plans (Claude Max, Gemini Advanced, etc.). If you are on API/pay-per-token billing, MCP Sampling calls made by Veto agents will count toward your token usage. `veto init` detects API key environment variables and warns you automatically.
7
+ > **Billing note:** "Zero cost" applies to subscription plans (Claude Max, Gemini Advanced, etc.). If you are on API/pay-per-token billing, LLM reasoning done for Veto agents (via the agentic loop or MCP Sampling) counts toward your token usage like any other turn. `veto init` detects API key environment variables and warns you automatically.
8
8
 
9
9
  ---
10
10
 
11
11
  ## How the Agents Work
12
12
 
13
- **Every tool uses a 2-phase agentic loopno API keys required, zero extra cost.**
13
+ **No API keys, zero extra cost.** Every worker agent is a deterministic expert module at its core, with two optional layers of LLM reasoning on top all of it delegated to the AI you're already paying for.
14
14
 
15
- ### Phase 1 MCP Sampling
16
- The tool attempts real LLM reasoning via MCP Sampling (`server.createMessage`). If your client supports it, the agent reasons deeply and returns a structured plan or analysis.
15
+ ### DefaultDeterministic expert modules
16
+ Out of the box, each of the 42 worker agents runs as a hand-written expert module (`plan()` / `analyze()` in `src/agents/`) — **not** an LLM. They always run, work offline, and cost zero tokens. Their depth varies by design: analysis agents (security scanner, secrets, dependency audit, clone detector) apply real algorithms — regex/AST detection, OWASP/CWE rules, hash-based clone matching — while planning agents (coder, debugger, tester, …) are structured expert playbooks: curated steps, checklists, and pitfalls for the task category, built to be reasoned over by the AI you already pay for rather than to reason themselves.
17
17
 
18
- ### Phase 2 — Agentic Fallback
19
- If Sampling is unavailable, Veto returns an `llm_upgrade` prompt. The host AI reads the specialist's role, performs the reasoning itself, and passes the JSON response back to complete the operation.
18
+ ### Path A — Agentic loop (most clients: Claude Code, Cursor, Windsurf)
19
+ Veto returns the specialist's role, rubric, and an output contract as an `llm_upgrade` prompt. The host AI reasons as the specialist and passes structured JSON back to complete the operation. This is the primary path — it costs nothing beyond your existing subscription and works on every client.
20
20
 
21
- Every worker agent supports both modes. When multiple agents run, they execute in parallel. LLM calls delegate back to the AI you're already using — no extra billing.
21
+ ### Path B MCP Sampling (clients that support `server.createMessage`)
22
+ Where Sampling is available, the same upgrade happens server-side without the extra round-trip. Note: the July 2026 MCP spec revision deprecates Sampling protocol-wide (12-month sunset), so the agentic loop (Path A) is Veto's long-term default; Sampling remains a transparent optimization where it exists.
23
+
24
+ The 7-agent **Council** is LLM-first — its value is the multi-agent debate — but it too falls back to a deterministic verdict when no LLM path is available. When multiple agents run, they execute in parallel.
22
25
 
23
26
  ---
24
27
 
25
28
  ## Specialist Roles
26
29
 
27
- | Agent Group | Specialist Roles |
28
- |---|---|
29
- | **Council** | Lead Dev · PM · Architect · UX · Devil's Advocate · Legal · Security |
30
- | **Development** | Coder · Reviewer · Tester · Debugger · Refactor · Database · API · Frontend · Backend · DevOps · Performance · Migration |
31
- | **Advanced** | Local LLM (Ollama) · Semantic Search · SDD Agent · Playwright · i18n Translate · a11y Advisor |
32
- | **Intelligence** | Task Planner · Researcher · Tech Advisor · Risk Assessor · Cost Analyzer · Ethics/Bias |
33
- | **Workflow** | File Manager · Git Agent · Search Agent · Reporter · Automation |
30
+ **49 specialists: 42 deterministic worker agents across 6 domains + a 7-agent Council.** The Council debates trade-offs before you build; the worker agents do the hands-on analysis and planning. Each is a deterministic expert module that can upgrade to LLM reasoning — see [How the Agents Work](#how-the-agents-work). List them anytime with `veto agents`.
31
+
32
+ **Council (7)**
33
+ `Lead Dev` · `PM` · `Architect` · `UX` · `Devil's Advocate` · `Legal` · `Security`
34
34
 
35
35
  **Development (12)**
36
36
  `Coder` · `Code Reviewer` · `Tester` · `Debugger` · `Refactor` · `Database` · `API` · `Frontend` · `Backend` · `DevOps` · `Performance` · `Migration`
@@ -52,7 +52,7 @@ Every worker agent supports both modes. When multiple agents run, they execute i
52
52
 
53
53
  ---
54
54
 
55
- ## MCP Tools (89)
55
+ ## MCP Tools (92)
56
56
 
57
57
  | Category | Tools |
58
58
  |---|---|
@@ -61,18 +61,83 @@ Every worker agent supports both modes. When multiple agents run, they execute i
61
61
  | **Council** | `veto_council_debate` · `veto_benchmark` · `veto_adr` |
62
62
  | **Agents** | `veto_agent_plan` · `veto_execute_parallel` · `veto_explain` · `veto_compose_agents` · `veto_delegate` |
63
63
  | **Review** | `veto_code_review` · `veto_security_scan` · `veto_secrets_scan` · `veto_diff_review` · `veto_full_review` · `veto_pr_review` |
64
- | **Pipelines** | `veto_pre_commit` · `veto_new_feature` · `veto_workflow` · `veto_task_parse` |
64
+ | **Pipelines** | `veto_ci_gate` · `veto_pre_commit` · `veto_new_feature` · `veto_workflow` · `veto_task_parse` |
65
65
  | **Advanced** | `veto_local_llm` · `veto_semantic_search` · `veto_sdd_agent` · `veto_playwright` · `veto_notify_ide` |
66
66
  | **Quality** | `veto_clone_detector` · `veto_lint_rules` · `veto_api_contract` · `veto_a11y_advisor` · `veto_type_coverage` · `veto_test_gaps` |
67
+ | **Advisors** | `veto_dep_advisor` · `veto_dep_verify` · `veto_query_advisor` · `veto_bundle_advisor` · `veto_dead_code` · `veto_hitl_checkpoint` · `veto_drift_check` |
67
68
  | **Watching** | `veto_watch` · `veto_watch_poll` · `veto_watch_stop` |
68
69
  | **Memory** | `veto_memory_store` · `veto_memory_search` · `veto_memory_delete` · `veto_project_map_update` · `veto_project_map_get` · `veto_pattern_store` · `veto_patterns_list` · `veto_memory_export` · `veto_memory_import` |
69
70
  | **Learning** | `veto_record_outcome` · `veto_learning_stats` · `veto_learning_apply` |
70
71
  | **Handoff** | `veto_handoff` · `veto_continue` · `veto_platform_setup` |
71
72
  | **Observability** | `veto_usage_status` · `veto_audit_log` · `veto_health` · `veto_metrics` |
72
73
  | **Discover** | `veto_discover` · `veto_summarize` · `veto_git_blame` · `veto_changelog` · `veto_onboard` · `veto_debt_register` |
73
- | **DevTools** | `veto_docs_fetch` · `veto_context_status` · `veto_openapi_gen` · `veto_flag_auditor` · `veto_env_setup` · `veto_commit_message` · `veto_pr_description` · `veto_pr_post` · `veto_prompt_optimizer` · `veto_sre_advisor` · `veto_diagram` · `veto_rca` · `veto_translate` · `veto_merge_conflict` |
74
+ | **DevTools** | `veto_docs_fetch` · `veto_context_status` · `veto_openapi_gen` · `veto_flag_auditor` · `veto_env_setup` · `veto_commit_message` · `veto_pr_description` · `veto_pr_post` · `veto_prompt_optimizer` · `veto_sre_advisor` · `veto_diagram` · `veto_rca` · `veto_doc_gen` · `veto_postmortem` · `veto_release_notes` · `veto_translate` · `veto_merge_conflict` |
74
75
  | **Plugins** | `veto_plugins` |
75
76
 
77
+ ## Compact Mode — 92 tools without the context tax
78
+
79
+ 92 tool schemas cost a client ~16K context tokens before the user types a word. Compact mode advertises a surface that is **5–6× smaller**: seven core tools (`veto_status`, `veto_session_save`, `veto_session_restore`, `veto_route_task`, `veto_council_debate`, `veto_memory_search`, `veto_record_outcome`) plus two meta-tools — `veto_find_tools` searches the full catalog by keyword and returns matching schemas on demand; `veto_call` invokes any catalog tool by name. Every tool remains directly callable in both modes; compact only changes what is advertised up front.
80
+
81
+ Enable it with `VETO_COMPACT=1` in your MCP server config env, or `"compact_tools": true` in `~/.veto/config.json`:
82
+
83
+ ```jsonc
84
+ {
85
+ "mcpServers": {
86
+ "veto": {
87
+ "command": "npx",
88
+ "args": ["-y", "--package", "@jigyasudham/veto", "veto-server"],
89
+ "env": { "VETO_COMPACT": "1" }
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ## Dependency-Hallucination Guard
96
+
97
+ LLMs propose plausible-but-nonexistent package names, and adversaries register those names on public registries (slopsquatting) — a supply-chain attack class with no pre-install check in most AI workflows. `veto_dep_verify` checks every proposed package against the live registry **before** you install:
98
+
99
+ ```
100
+ veto_dep_verify { packages: ["axios", "axois", "left-padd"], ecosystem: "npm" }
101
+ → axios verified (14 years old, 40M downloads/month)
102
+ → axois HIGH_RISK (1 edit from "axios" — possible typosquat)
103
+ → left-padd NOT_FOUND (likely hallucinated — do NOT retry the install later:
104
+ nonexistent AI-suggested names are prime slopsquat targets)
105
+ ```
106
+
107
+ Signals per package: registry existence, age, monthly downloads, version history, deprecation, and typo-distance from popular packages. Supports npm, PyPI, and crates.io. Network failures return `unverifiable` — never silently safe.
108
+
109
+ ## Decision-Drift Enforcement
110
+
111
+ AI assistants forget architectural decisions and re-litigate them sessions later — the most common complaint about long-running AI projects. Veto's memory doesn't just store decisions; it **enforces** them. Record a decision once as a machine-checkable constraint:
112
+
113
+ ```
114
+ veto_decisions {
115
+ action: "add",
116
+ rule: "We use Postgres — no Mongo",
117
+ why: "Decided 2026-05: relational data, team expertise",
118
+ forbidden_patterns: ["mongoose", "mongodb"],
119
+ severity: "block"
120
+ }
121
+ ```
122
+
123
+ From then on, `veto_diff_review` and `veto_ci_gate` automatically fail any diff whose **added lines** match a forbidden pattern — when an AI quietly adds `mongoose` to the imports three sessions later, the review fails with the rule and the rationale attached. Patterns are case-insensitive regexes (with substring fallback), optionally scoped to a file glob (`src/**/*.ts`), per-project or global, severity `block` or `warn`. Manage with `action: list / check / disable / enable`.
124
+
125
+ ## Compounding-Error Circuit Breaker
126
+
127
+ Agents fail silently in loops — retrying the same broken call, re-hitting the same error, thrashing between two tools — and burn a whole session before anyone notices. `veto_drift_check` scans the recent tool-call trace for that pattern mid-flight and trips a breaker before the spiral compounds:
128
+
129
+ ```
130
+ veto_drift_check
131
+ → DRIFT DETECTED
132
+ • 4 consecutive failed calls (veto_diff_review)
133
+ • same error repeated 3× ("no diff provided")
134
+ • tool veto_route_task called 6× in a row
135
+ → remediation (debugger agent): stop retrying; the diff is empty —
136
+ point at a project_dir with uncommitted changes or pass `diff` explicitly.
137
+ ```
138
+
139
+ It looks for three drift signals — consecutive failures, duplicate error messages, and single-tool repetition — and when any trips, it runs the `debugger` agent over the trace for a concrete recovery step instead of letting the loop continue. Call it as a periodic checkpoint in long agentic runs.
140
+
76
141
  ## Which tool do I use?
77
142
 
78
143
  Several tools overlap by design (different granularity or entry point). Quick guide:
@@ -144,6 +209,9 @@ veto sessions # List last 20 saved sessions ([auto] badge on
144
209
  veto sessions --clean # Remove auto-saves older than 7 days
145
210
  veto memory [query] # Search knowledge base (blank = all entries)
146
211
  veto patterns [prefix] # List learned agent/routing patterns
212
+ veto tools [filter] # List all 92 MCP tools (--json for machine output)
213
+ veto agents [filter] # List all 49 specialists — workers + council (--json)
214
+ veto routing [status|log|reset] # Inspect the opt-in routing feedback loop
147
215
  veto hook install # Install pre-commit secrets scan hook
148
216
  veto hook remove # Remove the veto pre-commit hook
149
217
  veto check # Scan staged changes for secrets (used by hook)
@@ -297,6 +365,8 @@ veto_workflow {
297
365
 
298
366
  Every agent tool auto-records a quality signal when it completes. After any working session, `veto_learning_stats` shows live data and `veto_learning_apply` adjusts tier thresholds automatically after ~20 calls.
299
367
 
368
+ The loop also feeds itself **implicitly**: `veto_learning_stats` mines the tool-call trace for signals nobody recorded manually — an agent-backed tool that returned an error, or the same analysis tool re-run within minutes in one session (which usually means the first answer didn't satisfy) — and records them as low-quality outcomes automatically.
369
+
300
370
  ```bash
301
371
  veto_route_task { task: "debug auth issue", file_ext: ".ts" }
302
372
  → { ..., recommended_agent: "debugger" } # ← predicted from history
@@ -345,9 +415,30 @@ Platform switching is manual — Veto surfaces which platform has budget remaini
345
415
 
346
416
  ---
347
417
 
418
+ ## Release Notes
419
+
420
+ ### 2.5.0
421
+ - **`veto_drift_check` — compounding-error circuit breaker.** Scans the recent tool-call trace for consecutive failures, repeated error messages, and single-tool thrashing, then runs the `debugger` agent for a concrete recovery step. See [Compounding-Error Circuit Breaker](#compounding-error-circuit-breaker).
422
+ - **Council calibration fixes.** Tightened the deterministic council's pattern triggers so they no longer false-fire on generic words (e.g. "server", "transport") or semver strings like `v2.5.0`, and removed stale hardcoded tool counts from agent reasoning.
423
+
424
+ ### 2.4.0
425
+ - **`veto_decisions` — decision-drift enforcement.** Record an architectural decision once as a machine-checkable constraint; `veto_diff_review` and `veto_ci_gate` then auto-fail any diff whose added lines reintroduce a forbidden pattern. See [Decision-Drift Enforcement](#decision-drift-enforcement).
426
+
427
+ ### 2.3.0
428
+ - **`veto_dep_verify` — dependency-hallucination guard.** Verifies every proposed package against the live npm/PyPI/crates.io registry before install, flagging hallucinated names and typosquats (slopsquatting defense). See [Dependency-Hallucination Guard](#dependency-hallucination-guard).
429
+
430
+ ### 2.2.0
431
+ - **Compact mode** (`VETO_COMPACT=1`) — advertises 9 tools incl. `veto_find_tools` / `veto_call` meta-tools, ~5–6× schema reduction. See [Compact Mode](#compact-mode--92-tools-without-the-context-tax).
432
+ - **Implicit outcome mining** — `veto_learning_stats` now mines outcomes automatically from the tool-call trace (errored calls, rapid re-runs), no manual `veto_record_outcome` needed.
433
+ - **Experimental streamable HTTP transport** (`veto-server --http`). See [HTTP Transport](#http-transport-experimental).
434
+
435
+ ## HTTP Transport (experimental)
436
+
437
+ `veto-server --http [port]` serves MCP over streamable HTTP at `http://127.0.0.1:<port>/mcp` (default port 3939) instead of stdio — for gateways, remote clients, or anything that can't spawn a subprocess. It runs **stateless** (no session IDs — the direction of the July 2026 MCP spec) and binds loopback only; set `VETO_HTTP_HOST` to expose it deliberately.
438
+
348
439
  ## Project Structure
349
440
 
350
- Veto is a single MCP server (`src/server.ts`) that registers 89 tools, MCP Resources, and Prompts, then dispatches every tool call through a per-domain **handler registry** — there is no monolithic switch. Each domain owns a `HandlerMap` module under `src/server/handlers/`:
441
+ Veto is a single MCP server (`src/server.ts`) that registers 92 tools, MCP Resources, and Prompts, then dispatches every tool call through a per-domain **handler registry** — there is no monolithic switch. Each domain owns a `HandlerMap` module under `src/server/handlers/`:
351
442
 
352
443
  | Module | Tools | Domain |
353
444
  |---|---|---|
@@ -355,7 +446,7 @@ Veto is a single MCP server (`src/server.ts`) that registers 89 tools, MCP Resou
355
446
  | `generators.ts` | 11 | single-agent artifact generators (adr, diagram, rca, doc_gen, onboard, …) |
356
447
  | `memory.ts` | 9 | knowledge base, patterns, project map |
357
448
  | `observability.ts` | 7 | health, metrics, usage, audit, context/rate status |
358
- | `advisors.ts` | 7 | project scanners (dep, query, bundle, dead-code, flag, openapi, HITL) |
449
+ | `advisors.ts` | 8 | project scanners (dep, query, bundle, dead-code, flag, openapi, HITL, drift-check) |
359
450
  | `session.ts` | 6 | save · restore · list · handoff · continue · replay |
360
451
  | `review.ts` | 5 | diff · ci · pr · full review + pre-commit pipelines |
361
452
  | `git.ts` | 5 | blame · changelog · commit message · PR description/post |
@@ -0,0 +1,73 @@
1
+ # Veto MCP — connection failure diagnostic
2
+
3
+ Run these in the **failing VS Code window's project terminal** (the project where the
4
+ `veto_*` tools won't load). Paste all output back.
5
+
6
+ Context: Veto's server imports `node:sqlite` at startup, which needs **Node >= 22.5.0**.
7
+ If that window resolves an older Node, the server throws and exits immediately — the exact
8
+ "launches but never runs the stdio loop" symptom. These four checks pin down the cause.
9
+
10
+ ---
11
+
12
+ ## Run this block (PowerShell)
13
+
14
+ ```powershell
15
+ # 1. What command is this project actually configured to launch for veto?
16
+ claude mcp get veto
17
+
18
+ # (fallback if the above errors)
19
+ claude mcp list
20
+
21
+ # 2. Node version this window resolves — MUST be >= 22.5.0
22
+ node --version
23
+
24
+ # 3. Can a bare `veto-server` even resolve, and is it installed globally?
25
+ where.exe veto-server
26
+ npm ls -g @jigyasudham/veto
27
+
28
+ # 4. Decisive test — launch the server directly and watch what it prints:
29
+ npx -y --package @jigyasudham/veto veto-server
30
+ ```
31
+
32
+ For step 4:
33
+ - **GOOD** -> prints `Veto MCP server v2.1.2 running (stdio)` then hangs. Press **Ctrl+C** to exit.
34
+ - **BAD** -> an error/stack trace (copy the whole thing) OR it returns to the prompt instantly.
35
+
36
+ ---
37
+
38
+ ## What each result means
39
+
40
+ | Check | If this is the problem | Fix |
41
+ |---|---|---|
42
+ | **#2 Node < 22.5.0** | `node:sqlite` import throws -> server exits immediately | Point the MCP command at a Node >= 22.5.0, or use the `npx` form below (it inherits PATH — make sure that PATH's Node is >= 22.5.0). |
43
+ | **#3 `veto-server` not found** | config launches a bare `veto-server` that isn't on PATH | Use the `npx` command form below instead of a bare binary. |
44
+ | **#1 config points at `dist/server.js` of a different/uninstalled path** | wrong launcher | Repoint to the `npx` form below. |
45
+ | **#4 prints a stack trace** | the package itself errors in this environment | Copy the trace — that's the real bug; bring it back here. |
46
+ | **#4 GOOD but tools still fail in-CLI** | config command != the working `npx` one | Repoint config to the `npx` form, then fully restart the CLI. |
47
+
48
+ ---
49
+
50
+ ## The likely fix (repoint the config to the known-good launcher)
51
+
52
+ Update that project's veto MCP server entry to:
53
+
54
+ ```json
55
+ {
56
+ "command": "npx",
57
+ "args": ["-y", "--package", "@jigyasudham/veto", "veto-server"]
58
+ }
59
+ ```
60
+
61
+ Or via CLI from that project dir:
62
+
63
+ ```powershell
64
+ claude mcp remove veto
65
+ claude mcp add veto -- npx -y --package @jigyasudham/veto veto-server
66
+ ```
67
+
68
+ Then **fully restart Claude Code** — MCP config is read only at startup, so the current
69
+ in-memory session won't recover; the next launch will.
70
+
71
+ Note: `npx -y` now pulls **2.1.2** (latest), so a healthy run reports `v2.1.2`.
72
+ Verify the resolved Node behind `npx` is >= 22.5.0 (`node --version`), or the npx form
73
+ will fail the same way.
@@ -0,0 +1,23 @@
1
+ export type Ecosystem = 'npm' | 'pypi' | 'crates';
2
+ export type PackageSignals = {
3
+ exists: boolean;
4
+ age_days: number | null;
5
+ downloads_last_month: number | null;
6
+ version_count: number | null;
7
+ deprecated: boolean;
8
+ };
9
+ export type PackageVerdict = {
10
+ name: string;
11
+ ecosystem: Ecosystem;
12
+ verdict: 'verified' | 'caution' | 'high_risk' | 'not_found' | 'unverifiable';
13
+ risk_signals: string[];
14
+ signals: PackageSignals | null;
15
+ similar_popular_package: string | null;
16
+ error?: string;
17
+ };
18
+ export declare function editDistance(a: string, b: string, cap?: number): number;
19
+ export declare function findTyposquatTarget(name: string, ecosystem: Ecosystem): string | null;
20
+ export declare function assessPackage(name: string, ecosystem: Ecosystem, signals: PackageSignals | null, fetchError?: string): PackageVerdict;
21
+ export declare function verifyPackage(name: string, ecosystem: Ecosystem): Promise<PackageVerdict>;
22
+ export declare function verifyPackages(names: string[], ecosystem: Ecosystem): Promise<PackageVerdict[]>;
23
+ //# sourceMappingURL=dep-verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dep-verify.d.ts","sourceRoot":"","sources":["../../../src/agents/security/dep-verify.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;AAElD,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,EAAE,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;IAC7E,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAmCF,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,SAAI,GAAG,MAAM,CAelE;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAUrF;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,cAAc,CAiDrI;AAsFD,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC,CAO/F;AAED,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAErG"}
@@ -0,0 +1,229 @@
1
+ // Dependency-hallucination guard. LLMs propose plausible-but-nonexistent
2
+ // package names, and adversaries register those names on public registries
3
+ // (slopsquatting) — a supply-chain risk class with no pre-install check in
4
+ // most AI workflows. This module verifies a proposed package against the live
5
+ // registry: does it exist, how old is it, how used is it, and is its name a
6
+ // near-miss of a popular package it isn't?
7
+ //
8
+ // assessPackage() is pure (signals in, verdict out) so the risk logic is
9
+ // testable offline; the per-ecosystem fetchers do the network work.
10
+ // Top packages per ecosystem — the names adversaries typosquat. Curated, not
11
+ // exhaustive: the check is "1–2 edits from something everyone installs".
12
+ const POPULAR = {
13
+ npm: [
14
+ 'react', 'react-dom', 'lodash', 'express', 'axios', 'chalk', 'commander',
15
+ 'typescript', 'webpack', 'vite', 'next', 'vue', 'eslint', 'prettier',
16
+ 'jest', 'vitest', 'mocha', 'dotenv', 'cors', 'uuid', 'zod', 'moment',
17
+ 'dayjs', 'classnames', 'redux', 'rxjs', 'socket.io', 'mongoose', 'prisma',
18
+ 'sequelize', 'pg', 'mysql2', 'sqlite3', 'redis', 'ioredis', 'bcrypt',
19
+ 'jsonwebtoken', 'passport', 'multer', 'sharp', 'puppeteer', 'playwright',
20
+ 'cheerio', 'node-fetch', 'undici', 'got', 'inquirer', 'yargs', 'minimist',
21
+ 'glob', 'rimraf', 'fs-extra', 'semver', 'debug', 'winston', 'pino',
22
+ 'nodemon', 'tsx', 'ts-node', 'esbuild', 'rollup', 'tailwindcss', 'sass',
23
+ 'styled-components', 'svelte', 'electron', 'openai', 'langchain',
24
+ ],
25
+ pypi: [
26
+ 'requests', 'numpy', 'pandas', 'flask', 'django', 'boto3', 'urllib3',
27
+ 'pytest', 'scipy', 'matplotlib', 'pillow', 'sqlalchemy', 'pydantic',
28
+ 'fastapi', 'uvicorn', 'celery', 'redis', 'cryptography', 'httpx', 'aiohttp',
29
+ 'beautifulsoup4', 'lxml', 'openpyxl', 'python-dotenv', 'click', 'rich',
30
+ 'typer', 'scikit-learn', 'torch', 'tensorflow', 'transformers', 'openai',
31
+ 'anthropic', 'langchain', 'jinja2', 'pyyaml', 'toml', 'setuptools', 'wheel',
32
+ 'colorama', 'tqdm', 'selenium', 'playwright', 'psycopg2', 'pymongo',
33
+ ],
34
+ crates: [
35
+ 'serde', 'tokio', 'clap', 'rand', 'syn', 'quote', 'anyhow', 'thiserror',
36
+ 'log', 'tracing', 'regex', 'chrono', 'reqwest', 'hyper', 'axum', 'actix-web',
37
+ 'futures', 'itertools', 'lazy_static', 'once_cell', 'bytes', 'uuid',
38
+ 'serde_json', 'rayon', 'crossbeam', 'parking_lot', 'libc', 'bitflags',
39
+ ],
40
+ };
41
+ // Levenshtein distance with an early-exit cap — we only care about 0/1/2/more.
42
+ export function editDistance(a, b, cap = 3) {
43
+ if (a === b)
44
+ return 0;
45
+ if (Math.abs(a.length - b.length) >= cap)
46
+ return cap;
47
+ const prev = new Array(b.length + 1).fill(0).map((_, i) => i);
48
+ for (let i = 1; i <= a.length; i++) {
49
+ let cur = [i];
50
+ let rowMin = i;
51
+ for (let j = 1; j <= b.length; j++) {
52
+ cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
53
+ rowMin = Math.min(rowMin, cur[j]);
54
+ }
55
+ if (rowMin >= cap)
56
+ return cap;
57
+ prev.splice(0, prev.length, ...cur);
58
+ }
59
+ return Math.min(prev[b.length], cap);
60
+ }
61
+ // A popular package this name is suspiciously close to (1–2 edits) without
62
+ // being it. Scoped npm names are compared by their base name too.
63
+ export function findTyposquatTarget(name, ecosystem) {
64
+ const popular = POPULAR[ecosystem];
65
+ const lower = name.toLowerCase();
66
+ if (popular.includes(lower))
67
+ return null;
68
+ const base = lower.includes('/') ? lower.split('/').pop() : lower;
69
+ for (const candidate of popular) {
70
+ const d = Math.min(editDistance(lower, candidate), editDistance(base, candidate));
71
+ if (d >= 1 && d <= 2)
72
+ return candidate;
73
+ }
74
+ return null;
75
+ }
76
+ export function assessPackage(name, ecosystem, signals, fetchError) {
77
+ const similar = findTyposquatTarget(name, ecosystem);
78
+ if (fetchError || !signals) {
79
+ return {
80
+ name, ecosystem, verdict: 'unverifiable', signals: null,
81
+ similar_popular_package: similar,
82
+ risk_signals: ['Registry could not be reached — do not assume the package is safe.'],
83
+ error: fetchError ?? 'no signals',
84
+ };
85
+ }
86
+ if (!signals.exists) {
87
+ const risk_signals = [
88
+ 'Package does not exist on the registry — the name is likely hallucinated.',
89
+ 'Do NOT retry the install later: nonexistent AI-suggested names are prime slopsquatting targets.',
90
+ ];
91
+ if (similar)
92
+ risk_signals.push(`Did you mean "${similar}"?`);
93
+ return { name, ecosystem, verdict: 'not_found', signals, similar_popular_package: similar, risk_signals };
94
+ }
95
+ const risk_signals = [];
96
+ const young = signals.age_days !== null && signals.age_days < 90;
97
+ const brandNew = signals.age_days !== null && signals.age_days < 30;
98
+ const lowUsage = signals.downloads_last_month !== null && signals.downloads_last_month < 500;
99
+ const veryLowUsage = signals.downloads_last_month !== null && signals.downloads_last_month < 100;
100
+ const singleVersion = signals.version_count !== null && signals.version_count <= 1;
101
+ if (similar)
102
+ risk_signals.push(`Name is 1–2 edits from the popular package "${similar}" — possible typosquat.`);
103
+ if (brandNew)
104
+ risk_signals.push(`Published ${signals.age_days} days ago — too new to have a reputation.`);
105
+ else if (young)
106
+ risk_signals.push(`Only ${signals.age_days} days old.`);
107
+ if (veryLowUsage)
108
+ risk_signals.push(`Fewer than 100 downloads last month.`);
109
+ else if (lowUsage)
110
+ risk_signals.push(`Fewer than 500 downloads last month.`);
111
+ if (singleVersion)
112
+ risk_signals.push('Only one published version.');
113
+ if (signals.deprecated)
114
+ risk_signals.push('Package is marked deprecated.');
115
+ let verdict;
116
+ if (similar && (young || lowUsage || singleVersion)) {
117
+ // Near-miss name AND weak track record — the classic squat profile.
118
+ verdict = 'high_risk';
119
+ }
120
+ else if (brandNew && (veryLowUsage || singleVersion)) {
121
+ verdict = 'high_risk';
122
+ }
123
+ else if (risk_signals.length > 0) {
124
+ verdict = 'caution';
125
+ }
126
+ else {
127
+ verdict = 'verified';
128
+ }
129
+ return { name, ecosystem, verdict, signals, similar_popular_package: similar, risk_signals };
130
+ }
131
+ // ─── Registry fetchers ────────────────────────────────────────────────────────
132
+ const FETCH_TIMEOUT_MS = 6000;
133
+ const UA = { 'User-Agent': 'veto-dep-verify (https://github.com/jigyasudham/veto)' };
134
+ async function getJson(url) {
135
+ const res = await fetch(url, { headers: UA, signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
136
+ if (res.status === 404)
137
+ return { status: 404, body: null };
138
+ if (!res.ok)
139
+ throw new Error(`${url} -> HTTP ${res.status}`);
140
+ return { status: res.status, body: await res.json() };
141
+ }
142
+ function daysSince(iso) {
143
+ if (!iso)
144
+ return null;
145
+ const t = new Date(iso).getTime();
146
+ if (Number.isNaN(t))
147
+ return null;
148
+ return Math.max(0, Math.round((Date.now() - t) / 86_400_000));
149
+ }
150
+ async function fetchNpmSignals(name) {
151
+ const meta = await getJson(`https://registry.npmjs.org/${encodeURIComponent(name)}`);
152
+ if (meta.status === 404)
153
+ return { exists: false, age_days: null, downloads_last_month: null, version_count: null, deprecated: false };
154
+ const versions = meta.body?.versions ? Object.keys(meta.body.versions) : [];
155
+ const latestTag = meta.body?.['dist-tags']?.latest;
156
+ const deprecated = !!(latestTag && meta.body?.versions?.[latestTag]?.deprecated);
157
+ let downloads = null;
158
+ try {
159
+ const dl = await getJson(`https://api.npmjs.org/downloads/point/last-month/${encodeURIComponent(name)}`);
160
+ downloads = typeof dl.body?.downloads === 'number' ? dl.body.downloads : null;
161
+ }
162
+ catch { /* downloads API down — verdict degrades gracefully */ }
163
+ return {
164
+ exists: true,
165
+ age_days: daysSince(meta.body?.time?.created),
166
+ downloads_last_month: downloads,
167
+ version_count: versions.length || null,
168
+ deprecated,
169
+ };
170
+ }
171
+ async function fetchPypiSignals(name) {
172
+ const meta = await getJson(`https://pypi.org/pypi/${encodeURIComponent(name)}/json`);
173
+ if (meta.status === 404)
174
+ return { exists: false, age_days: null, downloads_last_month: null, version_count: null, deprecated: false };
175
+ const releases = meta.body?.releases ?? {};
176
+ const versionKeys = Object.keys(releases);
177
+ let earliest;
178
+ for (const files of Object.values(releases)) {
179
+ for (const f of files) {
180
+ if (f.upload_time_iso_8601 && (!earliest || f.upload_time_iso_8601 < earliest))
181
+ earliest = f.upload_time_iso_8601;
182
+ }
183
+ }
184
+ let downloads = null;
185
+ try {
186
+ const dl = await getJson(`https://pypistats.org/api/packages/${encodeURIComponent(name.toLowerCase())}/recent`);
187
+ downloads = typeof dl.body?.data?.last_month === 'number' ? dl.body.data.last_month : null;
188
+ }
189
+ catch { /* stats API is best-effort */ }
190
+ return {
191
+ exists: true,
192
+ age_days: daysSince(earliest),
193
+ downloads_last_month: downloads,
194
+ version_count: versionKeys.length || null,
195
+ deprecated: meta.body?.info?.yanked === true,
196
+ };
197
+ }
198
+ async function fetchCratesSignals(name) {
199
+ const meta = await getJson(`https://crates.io/api/v1/crates/${encodeURIComponent(name)}`);
200
+ if (meta.status === 404)
201
+ return { exists: false, age_days: null, downloads_last_month: null, version_count: null, deprecated: false };
202
+ const crate = meta.body?.crate ?? {};
203
+ return {
204
+ exists: true,
205
+ age_days: daysSince(crate.created_at),
206
+ // recent_downloads is the 90-day figure; closest available proxy.
207
+ downloads_last_month: typeof crate.recent_downloads === 'number' ? Math.round(crate.recent_downloads / 3) : null,
208
+ version_count: Array.isArray(meta.body?.versions) ? meta.body.versions.length : null,
209
+ deprecated: false,
210
+ };
211
+ }
212
+ const FETCHERS = {
213
+ npm: fetchNpmSignals,
214
+ pypi: fetchPypiSignals,
215
+ crates: fetchCratesSignals,
216
+ };
217
+ export async function verifyPackage(name, ecosystem) {
218
+ try {
219
+ const signals = await FETCHERS[ecosystem](name);
220
+ return assessPackage(name, ecosystem, signals);
221
+ }
222
+ catch (err) {
223
+ return assessPackage(name, ecosystem, null, err instanceof Error ? err.message : String(err));
224
+ }
225
+ }
226
+ export async function verifyPackages(names, ecosystem) {
227
+ return Promise.all(names.map(n => verifyPackage(n.trim(), ecosystem)));
228
+ }
229
+ //# sourceMappingURL=dep-verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dep-verify.js","sourceRoot":"","sources":["../../../src/agents/security/dep-verify.ts"],"names":[],"mappings":"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"}
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 = '89 agentic tools. 49 specialists. Every major AI CLI. Self-learning. Zero extra cost on subscriptions.';
12
+ const TAGLINE = '92 agentic tools. 49 specialists. Every major AI CLI. Self-learning. Zero extra cost on subscriptions.';
13
13
  const VETO_DIR = join(homedir(), '.veto');
14
14
  const HOME = homedir();
15
15
  const c = {
@@ -307,7 +307,7 @@ async function initCommand() {
307
307
  console.log('');
308
308
  const VETO_GUIDE = `# Veto MCP Server
309
309
 
310
- Veto is active. 89 tools across 6 categories:
310
+ Veto is active. 92 tools across 6 categories:
311
311
 
312
312
  **Session & Context** — veto_status · veto_session_save · veto_continue · veto_handoff
313
313
  Save work at 60–70% context capacity. veto_status triggers auto-save above 70%.
@@ -865,7 +865,7 @@ async function patternsCommand() {
865
865
  }
866
866
  function shortHelpCommand() {
867
867
  console.log('');
868
- console.log(c.bold(c.cyan(' veto')) + c.dim(` v${VERSION}`) + c.dim(` — 89 agentic tools. 49 specialists. Every major AI CLI. Zero extra cost on subscriptions.`));
868
+ console.log(c.bold(c.cyan(' veto')) + c.dim(` v${VERSION}`) + c.dim(` — 92 agentic tools. 49 specialists. Every major AI CLI. Zero extra cost on subscriptions.`));
869
869
  console.log('');
870
870
  console.log(c.bold(' CLI Commands'));
871
871
  console.log(c.dim(' ─────────────────────────────────────────────────────'));
@@ -886,7 +886,7 @@ function shortHelpCommand() {
886
886
  console.log(` ${c.cyan('veto help')} Show this help`);
887
887
  console.log(` ${c.cyan('veto help --troubleshoot')} Show troubleshooting guide`);
888
888
  console.log('');
889
- console.log(c.bold(' MCP Tools (89 Agentic Tools)'));
889
+ console.log(c.bold(' MCP Tools (92 Agentic Tools)'));
890
890
  console.log(c.dim(' ─────────────────────────────────────────────────────'));
891
891
  console.log(` ${c.dim('Session')} veto_status · veto_session_save · veto_session_restore · veto_sessions_list · veto_session_replay · veto_autosave_status`);
892
892
  console.log(` ${c.dim('Council')} veto_council_debate · veto_benchmark · veto_adr`);
@@ -1 +1 @@
1
- {"version":3,"file":"decision-engine.d.ts","sourceRoot":"","sources":["../../src/council/decision-engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1E,KAAK,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;AAEnC,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG;IACpC,aAAa,EAAE,cAAc,CAAC;IAC9B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAsDA;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,MAAM,EAAE,EACvB,QAAQ,EAAE,MAAM,EAAE,EAClB,WAAW,EAAE,MAAM,GAClB,MAAM,CAmHR"}
1
+ {"version":3,"file":"decision-engine.d.ts","sourceRoot":"","sources":["../../src/council/decision-engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1E,KAAK,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;AAEnC,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG;IACpC,aAAa,EAAE,cAAc,CAAC;IAC9B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAkEA;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,MAAM,EAAE,EACvB,QAAQ,EAAE,MAAM,EAAE,EAClB,WAAW,EAAE,MAAM,GAClB,MAAM,CAmHR"}
@@ -17,6 +17,10 @@ export function decide(votes) {
17
17
  const criticalBlockers = blockers.filter(([name]) => CRITICAL.has(name));
18
18
  const secondaryBlockers = blockers.filter(([name]) => !CRITICAL.has(name));
19
19
  const criticalApprovers = approvers.filter(([name]) => CRITICAL.has(name));
20
+ // A warn only counts toward escalation when it names a specific risk.
21
+ // Hedge-warns with no concerns are recorded as warnings but can't flip the verdict.
22
+ const meaningfulWarners = warners.filter(([, v]) => v.concerns.some(c => typeof c === 'string' && c.length > 0));
23
+ const criticalMeaningfulWarners = meaningfulWarners.filter(([name]) => CRITICAL.has(name));
20
24
  const block_reasons = blockers.map(([, v]) => v.reason);
21
25
  const warnings = [
22
26
  ...blockers.flatMap(([, v]) => v.concerns),
@@ -32,8 +36,13 @@ export function decide(votes) {
32
36
  // Business/UX objections vs technical approval — genuine split
33
37
  final_verdict = 'DEADLOCK';
34
38
  }
35
- else if (secondaryBlockers.length >= 1 || warners.length >= 2) {
36
- // At least one concern but no hard expert block
39
+ else if (secondaryBlockers.length >= 1 ||
40
+ meaningfulWarners.length >= 3 ||
41
+ criticalMeaningfulWarners.length >= 2) {
42
+ // Escalate only on substantive dissent: a block, three agents naming real
43
+ // risks, or two expert-domain agents naming real risks. The Devil's
44
+ // Advocate warns on almost everything by design — one routine warn plus a
45
+ // hedge must not be enough to fence-sit, or YELLOW carries no signal.
37
46
  final_verdict = 'YELLOW';
38
47
  }
39
48
  else {