npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

jev-agent-mcp

v0.1.0

Published

MCP server that gives an agent typed, calibrated judgments from TypeSafe's Jev model

Readme

jev-agent-mcp

An MCP server that lets a coding or reasoning agent ask TypeSafe's Jev model for a typed judgment at a decision point. The agent keeps control of the workflow. The server returns answers, probabilities, and confidence as structured data, and it never returns generated prose.

It wraps POST https://api.typesafe.ai/v1/systemone through the official @typesafe-ai/sdk. See the TypeSafe docs for the model itself.

Tools

| Tool | Use it to | Built on | | --- | --- | --- | | judge | Ask any mix of Choice, Noul, and Score questions about one state, in a single request | the three primitives | | rank_candidates | Rank 2 to 30 candidates on several weighted dimensions | one Score per dimension, one request per candidate, weights applied in code | | verify_claim | Check claims against supplied evidence | a string match for quotes, then one Choice per claim (supports / contradicts / says_nothing) | | review_changes | Review a diff the agent supplies, one unified patch per changed file, in any language | the staged jev-review pipeline: five Noul screens per file, then Choice and Score follow-ups on the strongest signals | | review_files | Review source files the agent supplies, as they stand, for issues that already exist | the same pipeline, asking about the source as it stands and not about a patch |

Each tool description tells the calling agent when to use the tool, what state to pass, and how to read the numbers. Every tool declares an outputSchema and returns structuredContent, with the same JSON repeated as text for clients that ignore structured results.

Setup

Requires Node.js 20 or newer. The server is published on npm as jev-agent-mcp, and an MCP client can start it with npx -y jev-agent-mcp without a separate install step.

To run it from a checkout instead, build it once and point the client at dist/index.js:

npm install
npm run build

API key

The server reads the key from the TYPESAFE_API_KEY environment variable and from nowhere else. Create a key in the TypeSafe console. The server does not log the key or write it to disk. Without a key the server still starts and lists its tools, and each tool call returns an error saying the key is missing.

The SDK also honors TYPESAFE_DEFAULT_MODEL (default jev-latest), TYPESAFE_BASE_URL, and TYPESAFE_LOG_LEVEL. All SDK logging goes to stderr because stdout carries the MCP protocol. TYPESAFE_LOG_LEVEL=debug logs request bodies, which include the state you send.

Register it in Claude Code

Export TYPESAFE_API_KEY in the shell that launches Claude Code, then add the server to a .mcp.json file in the project root. Claude Code expands ${VAR} in .mcp.json when it starts the server, so the file holds the variable name and the key itself stays out of it:

{
  "mcpServers": {
    "jev": {
      "command": "npx",
      "args": ["-y", "jev-agent-mcp"],
      "env": { "TYPESAFE_API_KEY": "${TYPESAFE_API_KEY}" }
    }
  }
}

The CLI writes the same file when you use project scope. The single quotes stop the shell from expanding the variable, so .mcp.json receives the name and not the value:

claude mcp add --scope project --transport stdio jev -e 'TYPESAFE_API_KEY=${TYPESAFE_API_KEY}' -- npx -y jev-agent-mcp

Claude Code asks you to approve a project-scoped server the first time you start it in that directory.

To make the server available in every project, use user scope. Claude Code 2.1.270 expands ${VAR} there as well (checked with a probe server that wrote its environment to a file), so ~/.claude.json stores the variable name and not the key:

claude mcp add --scope user --transport stdio jev -e 'TYPESAFE_API_KEY=${TYPESAFE_API_KEY}' -- npx -y jev-agent-mcp

To run a checkout, replace npx -y jev-agent-mcp with node /absolute/path/to/jev-mcp/dist/index.js in any of the commands above, or set "command": "node" and "args": ["/absolute/path/to/jev-mcp/dist/index.js"] in .mcp.json.

The variable has to exist in the environment Claude Code starts from. An export typed into one terminal does not reach the desktop app, which reads your shell profile.

Run /mcp inside Claude Code to confirm that jev is connected and shows five tools.

Agent skill

skills/jev-mcp/SKILL.md tells the agent which tool fits which situation, how to chain them (review a diff, confirm the findings, verify the completion claims), and what to do with a flagged answer. The tool descriptions cover each tool's arguments and output on their own, so the server works without the skill. The npm package includes it under skills/, but the simplest copy comes from GitHub. Put it in .claude/skills/ to use it in one project, or in ~/.claude/skills/ to use it everywhere:

mkdir -p .claude/skills/jev-mcp
curl -fsSL -o .claude/skills/jev-mcp/SKILL.md https://raw.githubusercontent.com/thedv91/jev-mcp/main/skills/jev-mcp/SKILL.md

Reading the results

| Field | Meaning | | --- | --- | | choice, probabilities | The top option and the distribution over every option you supplied | | noul | The probability that the answer is yes. Near 0.5 means the model cannot tell. It does not mean "medium" or "partly". Ask a Score question if you want a degree. | | score, max_level | The probability-weighted mean level, from 0 to max_level. It can land between levels, and different distributions can give the same score. | | confidence | How concentrated a Choice or Score distribution is, from 0 to 1. It describes the model's answer. It does not guarantee the answer is right. Noul answers have no confidence. | | certainty | high, medium, or low, from applying the thresholds to confidence | | verdict (Noul) | yes, no, or uncertain, from applying the Noul band | | needs_escalation | The ids of every answer that came back low or uncertain | | top_margin, top_tied (rank_candidates) | The composite gap between first and second place, and whether it is at or below tie_margin |

Thresholds

| Threshold | Default | Effect | | --- | --- | --- | | confidence_high | 0.8 | Choice or Score confidence at or above this is high | | confidence_low | 0.5 | Confidence below this is low and is flagged | | noul_band | [0.35, 0.65] | A Noul inside the band is uncertain and is flagged |

The defaults come from the examples in TypeSafe's confidence page and its citation-check cookbook. They have not been calibrated on your workload. Every tool accepts a thresholds object to override them for one call, so a risky action can demand more, for example { "confidence_high": 0.9, "noul_band": [0.1, 0.9] }.

The server flags uncertain answers and leaves the response to the agent. What escalation means depends on the agent: it can gather the missing evidence and ask again, reason the point through itself, or ask the user. medium certainty is not flagged. Treat it as a reason to verify first when the action is hard to undo.

Example calls

The arguments below are what an MCP client sends. scripts/smoke.ts holds the same cases plus ambiguous ones for each tool.

judge

Guard a consequential tool call with three independent questions in one request:

{
  "state": {
    "proposed_tool_call": { "tool": "issue_customer_refund", "order_id": "ord_7429", "amount_usd": 680 },
    "side_effects": ["Moves funds", "Changes the order payment state"],
    "safeguards": ["Customer identity and duplicate charge verified"],
    "policy": "Refunds above USD 500 require human approval."
  },
  "questions": {
    "action": {
      "type": "choice",
      "instructions": "What should happen to `proposed_tool_call` under `policy`?",
      "criteria": {
        "allow": "Execute the call now without asking anyone",
        "confirm": "A human must approve before the call executes",
        "deny": "The call must not execute at all"
      }
    },
    "policy_requires_approval": {
      "type": "noul",
      "instructions": "Does `policy` require human approval for `proposed_tool_call`?"
    },
    "risk": {
      "type": "score",
      "instructions": "How much damage would `proposed_tool_call` do if it turned out to be wrong?",
      "criteria": [
        "Nothing changes outside the agent's own workspace",
        "A change that is easy to undo completely",
        "Money or customer-visible state changes and can only partly be undone",
        "Irreversible loss of money or data"
      ]
    }
  }
}

Result from jev-1.13.0 (the Score legend repeats the four levels and is omitted here):

{
  "answers": {
    "action": {
      "type": "choice",
      "choice": "confirm",
      "probabilities": { "confirm": 1, "deny": 0, "allow": 0 },
      "confidence": 1,
      "certainty": "high"
    },
    "policy_requires_approval": { "type": "noul", "noul": 0.98, "verdict": "yes" },
    "risk": {
      "type": "score",
      "score": 2.09,
      "max_level": 3,
      "probabilities": { "0": 0, "1": 0.01, "2": 0.89, "3": 0.1 },
      "confidence": 0.9,
      "certainty": "high"
    }
  },
  "needs_escalation": [],
  "model": "jev-1.13.0",
  "usage": { "input_tokens": 558, "output_tokens": 72 }
}

The stop-or-continue case in scripts/smoke.ts ("Make the dashboard faster", an index added, nothing measured) came back decisive: task_complete was 0.05 (no) and next_step was measure with confidence 1. Thin evidence gave a confident "not done", not an uncertain answer.

A request with unclear scope did get flagged. The state held the message "Can you clean up the old branches?" and the facts that 4 of 12 branches are merged and 3 of the 8 unmerged ones are stale:

{
  "answers": {
    "wants_unmerged_deleted": { "type": "noul", "noul": 0.55, "verdict": "uncertain" },
    "scope": {
      "type": "choice",
      "choice": "merged_and_stale",
      "probabilities": { "merged_only": 0.32, "all_but_main": 0.07, "merged_and_stale": 0.61 },
      "confidence": 0.41,
      "certainty": "low"
    }
  },
  "needs_escalation": ["wants_unmerged_deleted", "scope"],
  "model": "jev-1.13.0",
  "usage": { "input_tokens": 442, "output_tokens": 69 }
}

The Noul of 0.55 says the model cannot tell whether the user wants unmerged branches deleted. It does not say the user wants about half of them deleted. Deleting branches is hard to undo, so the right move for an agent here is to ask the user.

rank_candidates

Route a task to a model. Each candidate is graded in its own request and sees only context and its own content, which the questions refer to as candidate. Order the levels so that a higher level is always better, including for cost.

{
  "context": {
    "task": "Review a complex customer dispute: 100k tokens of history, needs tool use, a wrong answer is costly."
  },
  "candidates": [
    { "id": "fast-model", "content": "Fast general model. 32k context. No tool use. Very low cost." },
    { "id": "reasoning-model", "content": "Strong multi-step reasoning. 200k context. Tool use. High cost." },
    { "id": "mid-model", "content": "Solid general model. 128k context. Tool use. Moderate cost." }
  ],
  "dimensions": [
    {
      "id": "capability_fit",
      "instructions": "Can `candidate` handle `context.task`, given its context size, tool use, and reasoning needs?",
      "levels": [
        "Cannot do the task: a hard requirement such as context size or tool use is missing",
        "Meets the hard requirements but reasoning quality is a concern for this task",
        "Meets every requirement comfortably"
      ],
      "weight": 3
    },
    {
      "id": "cost",
      "instructions": "How expensive is `candidate` to run?",
      "levels": ["High cost", "Moderate cost", "Low or very low cost"],
      "weight": 1
    }
  ]
}

composite is the weighted mean of each dimension's score / max_level. The raw scores come back too, so the agent can change the weights without another call. top_margin is the gap between first and second place. top_tied is true when that gap is at or below tie_margin (default 0.05, a starting value that does not come from the TypeSafe docs), and the order of the top two then means nothing.

Result from jev-1.13.0:

{
  "ranking": [
    {
      "id": "reasoning-model",
      "composite": 0.74,
      "dimensions": {
        "capability_fit": { "score": 1.97, "normalized": 0.985, "confidence": 0.96, "certainty": "high" },
        "cost": { "score": 0.01, "normalized": 0.005, "confidence": 0.98, "certainty": "high" }
      }
    },
    {
      "id": "mid-model",
      "composite": 0.675,
      "dimensions": {
        "capability_fit": { "score": 1.47, "normalized": 0.735, "confidence": 0.25, "certainty": "low" },
        "cost": { "score": 0.99, "normalized": 0.495, "confidence": 0.98, "certainty": "high" }
      }
    },
    {
      "id": "fast-model",
      "composite": 0.2488,
      "dimensions": {
        "capability_fit": { "score": 0, "normalized": 0, "confidence": 1, "certainty": "high" },
        "cost": { "score": 1.99, "normalized": 0.995, "confidence": 0.99, "certainty": "high" }
      }
    }
  ],
  "top_margin": 0.065,
  "top_tied": false,
  "needs_escalation": [{ "candidate": "mid-model", "dimension": "capability_fit" }],
  "model": "jev-1.13.0",
  "usage": { "input_tokens": 1358, "output_tokens": 99 }
}

The model could not place mid-model between "reasoning quality is a concern" and "meets every requirement" (confidence 0.25), so that pair is flagged. The runner-up's composite rests on that uncertain grade, and the 0.065 margin is close to the tie threshold, so an agent should check mid-model's capabilities before treating the order as settled.

In the ambiguous case, two vague documents both landed on "Same topic but does not answer the query" with confidence 0.97 and 0.99. Their composites were 0.34 and 0.33, so top_margin was 0.01 and top_tied was true. needs_escalation stayed empty, because the model was sure that both documents are weak. The signals to read there are top_tied and the low composite of the winner.

verify_claim

Check completion claims against real test output before reporting success:

{
  "evidence": "$ npm test\n PASS  src/cart.test.ts (12 tests)\n PASS  src/checkout.test.ts (8 tests)\n FAIL  src/refund.test.ts\n   x refunds above the limit require approval (expected \"pending_approval\", received \"refunded\")\nTests: 1 failed, 27 passed, 28 total",
  "claims": [
    { "id": "cart_passes", "claim": "The cart tests pass." },
    { "id": "all_green", "claim": "All tests pass." },
    { "id": "quoted", "claim": "The refund suite passes.", "quote": "PASS  src/refund.test.ts" }
  ]
}

Result from jev-1.13.0:

{
  "verdicts": [
    {
      "id": "cart_passes",
      "verdict": "verified",
      "checked_by": "model",
      "probabilities": { "contradicts": 0, "says_nothing": 0, "supports": 1 },
      "confidence": 1,
      "certainty": "high"
    },
    {
      "id": "all_green",
      "verdict": "contradicted",
      "checked_by": "model",
      "probabilities": { "contradicts": 1, "says_nothing": 0, "supports": 0 },
      "confidence": 1,
      "certainty": "high"
    },
    {
      "id": "quoted",
      "verdict": "fabricated",
      "checked_by": "string_match",
      "probabilities": null,
      "confidence": null,
      "certainty": null
    }
  ],
  "needs_escalation": [],
  "model": "jev-1.13.0",
  "usage": { "input_tokens": 584, "output_tokens": 90 }
}

The ambiguous case shows why a verdict has to be read together with its certainty. Against the same test output, the claim "The refund failure is caused by a missing approval-limit check in the refund service" came back verified, but with probabilities of 0.62 supports, 0.36 says_nothing, and 0.02 contradicts. Confidence was 0.42, so certainty was low and the claim appeared in needs_escalation. "The change did not slow down checkout" came back unsupported with confidence 0.96.

A claim with a quote that does not appear in the evidence comes back fabricated from an exact string match, without a model call, so its probabilities, confidence, and certainty are null. The match normalizes whitespace and curly quotes and nothing else, so a reworded or truncated quote also counts as fabricated. A verdict covers only the evidence passed in.

Using it for code review

The server reviews code in two ways. review_changes and review_files run the whole staged review and return one report. With judge, rank_candidates, and verify_claim the agent runs each stage itself. The packaged tools give a ranked list of places to look. The separate tools are for reviews that need questions other than the fixed ones.

The packaged review

review_changes reads nothing from disk and runs no git. The agent collects the diff and passes it in, so the agent and the tool always judge the same change:

{
  "files": [
    { "path": "src/routes/invoices.ts", "patch": "diff --git a/src/routes/invoices.ts b/src/routes/invoices.ts\n--- a/src/routes/invoices.ts\n+++ b/src/routes/invoices.ts\n@@ -12,11 +12,8 @@ ..." }
  ],
  "test_files": []
}

Each patch is the unified diff of one file with its @@ hunk headers, exactly as git printed it, because findings take their line numbers from those headers. The agent decides the scope first: which base to diff against, and whether uncommitted and untracked work is included. It leaves out generated files, lock files, and anything that must not leave the machine, since every patch goes to the TypeSafe API. test_files are not reviewed. The model receives them as evidence for the test-gap screen, so leaving them out makes that screen fire on every behavioral change.

review_files works the same way on whole files. It takes files as { "path", "content" } entries holding the complete, unmodified text, plus optional test_files, and asks whether an issue exists in the code as it stands. Neither tool touches the disk or runs a command, and neither is tied to one language. The agent picks the files at the center of the flow it cares about. A whole repository does not fit, because every file passes through the agent's context first.

Each call runs these stages, with at most three requests in flight:

| Stage | Question | Limit | | --- | --- | --- | | Screen | Five Nouls per file: correctness, security, reliability, compatibility, test gap | every file; a patch over 40,000 characters is screened in slices and the highest probability per dimension is kept | | Profile | A Choice for the kind of change or file role, and a Score for review priority | the 5 files with the strongest signals | | Locate | A Choice over diff hunks or 80-line source regions, with a noMatch option | the 8 strongest signals at or above 0.7; a selection below 0.55 confidence is dropped | | Classify | A Choice over the mechanisms of that dimension, with a noIssue option | noIssue drops the finding | | Score | Severity on a 0 to 3 rubric | none | | Route | A Choice over reviewer roles | only at severity 1.5 or above |

Those limits are constants in src/review-core/domain/config.ts. Both tools accept the same thresholds object as the other tools, which sets how the report is flagged.

Reading the report

src/review-report.ts reshapes the pipeline's report for an agent:

| Field | Meaning | | --- | --- | | findings | Sorted by severity. Each carries the three confidences it rests on (location_confidence, mechanism_confidence, severity_confidence), a certainty taken from the lowest of them, and weakest_judgment, which names it | | needs_escalation | The findings with low certainty | | unresolved_signals | Every screen at or above 0.7 that produced no finding. no_evidence_located means it was followed and then dropped at the Locate or Classify stage. not_followed means it fell past the cap of 8. request_failed means the follow-up request itself failed | | screening, quiet_files | Per file, the probability of each dimension that is not a clear no (at or above the lower bound of noul_band). Files where every dimension was a clear no are only counted | | profiles | Category and review priority for the 5 strongest files, with a certainty | | failed_requests | Every request that still failed after the SDK's retries, with its file, stage (screen, profile, locate), and error message. A file that failed at screen was not judged at all and is absent from screening and quiet_files | | workflow, config | Stage counts and the pipeline constants |

The pipeline's action field (request_changes at severity 2 or above) is left out. That is jev-review's merge policy, and here the agent decides what a finding warrants.

A review_changes call on three patches (a removed ownership check, a dropped Math.min in a pagination helper, and a typo fix in a string constant) took 4.0 seconds on jev-1.13.0. All five signals at or above 0.7 became findings. The top one:

{
  "file": "src/routes/invoices.ts",
  "line": 12,
  "dimension": "security",
  "probability": 0.97,
  "location_confidence": 1,
  "mechanism": "authorization",
  "mechanism_confidence": 1,
  "severity": 2.84,
  "severity_confidence": 0.84,
  "owner": "security",
  "owner_confidence": 1,
  "certainty": "high",
  "weakest_judgment": "severity"
}

The typo fix screened as a clear no on every dimension, so it appears only in quiet_files: 1. Its profile came back routine at low certainty (category confidence 0.5).

A review_files call with two of this repository's own files, src/review-core/domain/patch.ts and src/verify-claim.ts, took about 3 seconds. Neither has a test, and the test-gap screen fired on both (0.94 and 0.96). One became a finding at medium certainty, with location as its weakest judgment (confidence 0.61). The other was followed and dropped, and the report says so:

{
  "unresolved_signals": [
    { "file": "src/verify-claim.ts", "dimension": "testGap", "probability": 0.96, "reason": "no_evidence_located" }
  ],
  "needs_escalation": [],
  "failed_requests": []
}

A 0.96 screen with no finding is a place the agent still has to read for itself.

line is the first line of the selected hunk or region, which is usually not the line of the defect. A finding tells the agent where to read, and the agent still has to open the file and confirm it. Progress lines go to stderr because stdout carries the MCP protocol.

Where the review code comes from

src/review-core/ is a copy of the domain and review layers of jev-review. jev-review runs its TypeScript directly on Node.js 24 and this server compiles with tsc, so the code is copied and not imported. The copy differs from the original in four places. Relative imports end in .js. The judgment modules call the shared typesafeClient(), which is created on first use, so the server still starts without a key. isReviewReport is left out because only the jev-review dashboard uses it. runReview in review/workflow.ts no longer aborts on the first failed request: it records the failure in the report's failures list and carries on, and it raises an error only when every file fails to screen, which points at a bad key or an unreachable API and not at a partial result. Both review tools run the copied stages over what the agent passes in, so the parts of jev-review that find files are not copied: the adapters layer, review/changes.ts, review/codebase.ts, and the SOURCE_FILE, TEST_FILE, and patchForNewFile helpers that only they used. The dashboard and the report store were not copied, so these tools do not write reviews/latest.json. When jev-review changes a prompt or a threshold, copy the file again and reapply those four edits.

Running the stages yourself

With judge the agent also owns the questions: it picks the files, cuts a large patch into pieces that fit one request, and writes each screen. The server only judges what it is sent. The stages are the ones review_changes runs.

| Review step | Tool | | --- | --- | | Screen a file's patch: one Noul per concern (correctness, security, reliability, compatibility, test gap), all in one call | judge | | Pick the hunk that best supports a concern: a Choice over hunk ids plus a no_match option | judge | | Classify the mechanism, then score severity | judge, as follow-up calls, because each needs the previous answer | | Order files by how closely they need review | rank_candidates | | Check a finding the agent wrote against the actual diff before reporting it | verify_claim |

instructions, Choice option descriptions, Noul true/false criteria, and Score levels accept a string, an object, or an array, as the TypeSafe API does. An object helps when a review question needs scoping:

{
  "type": "noul",
  "instructions": {
    "question": "Does `file.patch` introduce or weaken a security boundary?",
    "focus": "Authorization, injection, secret exposure, unsafe defaults"
  },
  "criteria": {
    "true": { "what": "The patch opens a concrete path around a security control", "examples": ["An ownership check is removed"] },
    "false": { "what": "No security boundary is weakened", "not_for": "Code that merely uses security-related names" }
  }
}

When the agent quotes a line in a finding, pass it as quote to verify_claim. A line that is not in the patch character for character, including its +, -, or space prefix, comes back fabricated.

Results from jev-1.13.0 on the three review cases in scripts/smoke.ts, which ask three Noul screens and one severity Score per patch:

| Patch | correctness | security | compatibility | severity | Flagged | | --- | --- | --- | --- | --- | --- | | Removes the account ownership check from GET /invoices/:id | 0.84 yes | 0.97 yes | 0.90 yes | 2.98 of 3, confidence 0.98 | none | | Drops Math.min(start + size, items.length) from a pagination helper | 0.28 no | 0.05 no | 0.50 uncertain | 0.67 of 3, confidence 0.33 (low) | compatibility, severity |

The pagination patch is harmless only because Array.prototype.slice clamps its end index, which the patch does not show. The model leaned toward "no bug" and flagged the two answers it could not settle, so the agent knows to open the file before it writes a finding. On the first patch, the 0.90 for compatibility is debatable: no caller should depend on receiving a 403. Read each screen as a prompt to look.

verify_claim then checked three findings against the first patch. "Any authenticated user can read another account's invoice", quoted with the removed line, came back verified at confidence 0.99. "This patch introduces a SQL injection through req.params.id" came back unsupported (0.91 says_nothing, 0.09 contradicts, confidence 0.86). A finding that quoted the 404 line with a - prefix came back fabricated, because the patch carries that line as unchanged context.

Jev judges the patch text it is given. It does not compile the code, run tests, or follow calls into files it was not sent, so treat a fired screen as a place to look and not as proof of a defect.

Smoke test

Run it from a checkout after npm run build:

npm run smoke

The script starts dist/index.js over stdio, lists the tools, and calls each one against the real API with a representative case and at least one ambiguous case. It needs TYPESAFE_API_KEY in the environment and Node.js 22.18 or newer, because it runs the TypeScript file directly.

Limits

Answers are not bit-for-bit repeatable. Two runs of the same smoke cases on jev-1.13.0 gave the same choices, verdicts, and flags, while individual numbers moved by a few hundredths (a risk score of 2.11 then 2.09, a confidence of 0.33 then 0.25). Leave room for that when you set a threshold close to a value you have observed.

One request carries the state and all its questions in a budget of roughly 32,000 tokens. rank_candidates sends one request per candidate in parallel, so 30 candidates means 30 requests. The SDK retries 408, 429, and 5xx responses with backoff, and each attempt times out after 10 seconds.

review_changes and review_files send at least one request per file, three at a time. Three files took about 4 seconds from call to report. Both accept at most 100 files per call, and a call that large can outlast an MCP client's tool timeout, so review in batches.

License

MIT