NanoJev Code Review Decisions

A decision model that reviews JavaScript and TypeScript code. Give it a snippet and the task the snippet implements, and it answers 13 typed questions with probabilities: which of 10 code review flaws the code has, whether it has any flaw, which flaw matters most, and how mergeable it is. Every answer option is encoded as its own path with the snippet and question (27 paths per snippet), and the choice head scores the paths.

It is C-Tianyu/NanoJev fine-tuned on JS/TS Code Review Decisions. On that dataset's test split it scores macro F1 0.452 and accuracy 0.831 over 15,600 decisions.

Status: research baseline. This checkpoint trained for half an epoch. It calls every clean snippet flawed and never flags over_abstracted or duplicated_logic. It is published for comparison; for code review use d1-3B Code Review Decisions.

Base model C-Tianyu/NanoJev, Qwen3-0.6B with a choice head
Parameters 596M (fp32, 2.4 GB)
Input A state with task, language and code, and named questions
Output P(yes) for yes/no questions; a distribution over options for choice and score questions
Training data JS/TS Code Review Decisions, decisions config, train split (9,600 snippets)
Test results Macro F1 0.452 (cutoff 0.5), accuracy 0.831, ECE 0.038
License Apache 2.0

Questions

The model answers the 13 questions of the dataset's decisions config. They are the same for every snippet and are stored in the dataset's manifest.json. Each question's criteria carry the flaw definition the labels follow.

Question Type Asks Answer
unclear_naming noul Does this code have unclear naming? P(yes)
noise_comments noul Does this code have noise comments? P(yes)
dead_code noul Does this code have dead code? P(yes)
inconsistent_style noul Does this code have inconsistent style? P(yes)
over_abstracted noul Does this code have over-abstraction? P(yes)
too_clever noul Does this code have overly clever code? P(yes)
duplicated_logic noul Does this code have duplicated logic? P(yes)
error_handling_problem noul Does this code have an error handling problem? P(yes)
type_safety_hole noul Does this code have a type safety hole? P(yes)
unsafe_input noul Does this code have unsafe input handling? P(yes)
has_flaw noul Does this code have at least one code review flaw? P(yes)
primary_flaw choice Which code review problem matters most in this code? Choose none if the code is clean. Distribution over none and the 10 flaws
quality score How mergeable is this code as written? Distribution over 5 levels, 0 "Should not merge." to 4 "Exemplary. Merge as is."

Quality level i is quality i + 1 in the dataset's default config.

Usage

pip install torch==2.14.1 transformers==5.19.0 huggingface_hub safetensors pyarrow
import json
import os
import sys
from pathlib import Path

import torch
from huggingface_hub import hf_hub_download, snapshot_download

path = Path(snapshot_download("yannelli/nanojev-code-review-decisions"))
sys.path.insert(0, str(path / "training"))
os.environ.setdefault("NANOJEV_REPO", os.path.expanduser("~/.cache/nanojev"))  # NanoJev's source goes here
from nanojev import STATE_CAP, answers_of, autocast, capped_state, examples_for, load_model

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model, tokenizer, limit = load_model(path, device)

# The 13 questions the model was trained on, from the dataset's manifest.json
manifest = hf_hub_download("yannelli/js-ts-code-review-decisions", "manifest.json", repo_type="dataset")
questions = json.load(open(manifest))["questions"]

state = {
    "task": "Define payroll bank account fields and decode a JSON account record, "
            "accepting only a nine-digit routing number and a four-digit account suffix.",
    "language": "TypeScript",
    "code": """import { z } from 'zod';

const bankAccountSchema = z.object({
    routingNumber: z.string().regex(/^\\d{9}$/),
    accountSuffix: z.string().regex(/^\\d{4}$/)
});
export type PayrollBankAccount = z.infer<typeof bankAccountSchema>;
export function decodeBankAccount(serialized: string): PayrollBankAccount {
    const account_payload = JSON.parse(serialized);
    return account_payload;
}""",
}

case = {"id": "example", "state": state, "questions": questions}
text, _ = capped_state(case, tokenizer, STATE_CAP)
examples = examples_for([case], {"example": text}, tokenizer, limit, gold=False)
with torch.inference_mode(), autocast(device):
    logits, _ = model(examples, tokenizer.pad_token_id)
answers = answers_of(case, examples, logits)
print(json.dumps(answers, indent=1))

The example is a test-split snippet the model did not see in training. Its gold labels are inconsistent_style (account_payload next to camelCase names) and unsafe_input (the JSON.parse result is returned without the schema defined above it). This model's answers, from the code above on CPU:

Question Gold This model
unclear_naming no P(yes) 0.01
noise_comments no P(yes) 0.00
dead_code no P(yes) 0.16
inconsistent_style yes P(yes) 0.11
over_abstracted no P(yes) 0.09
too_clever no P(yes) 0.09
duplicated_logic no P(yes) 0.13
error_handling_problem no P(yes) 0.01
type_safety_hole no P(yes) 0.05
unsafe_input yes P(yes) 0.13
has_flaw yes P(yes) 0.58
primary_flaw unsafe_input none (0.34)
quality level 0 level 4 (0.40); expected level 2.69

Answers use the dataset's answers format: a noul answer is P(yes), and choice and score answers carry probabilities over every option. One {"id": ..., "answers": ...} line per snippet scores directly with the dataset's score_decisions.py. The quality answer above, rounded:

{
 "type": "score",
 "probabilities": {
  "0": 0.04,
  "1": 0.146,
  "2": 0.3,
  "3": 0.109,
  "4": 0.404
 }
}

The bundle loads with NanoJev's own code. training/nanojev.py fetches NanoJev's source from C-Tianyu/NanoJev (revision unified-games-v1) into NANOJEV_REPO and refuses to run it unless the files match pinned SHA-256 hashes.

The model accepts other questions in the same schema, but it was trained and evaluated only on these 13.

Results

Results cover the test split of the decisions config: 300 tasks, 1,200 snippets, and 15,600 decisions, scored with the dataset's score_decisions.py. Each snippet was one request with all 13 questions. The four fine-tuned models used the same data and scorer.

Model Params Macro F1 @ 0.5 Accuracy Log loss Brier ECE Clean judged clean
d1-3B 3.1B 0.917 0.940 0.172 0.072 0.017 0.950
d1-omni-600M 587M 0.771 0.882 0.321 0.127 0.028 0.827
GLiNER2.5-Decide 486M 0.687 0.863 0.398 0.149 0.039 0.683
NanoJev (this model) 596M 0.452 0.831 0.459 0.173 0.038 0.000
NanoJev base model, zero-shot 596M 0.237 0.192 1.413 0.559 0.510 0.000
TF-IDF + logistic regression (dataset baseline) - 0.502 0.830 0.449 0.172 0.030 0.307

Higher is better for macro F1, accuracy, and clean judged clean; lower is better for log loss, Brier, and ECE. Clean judged clean is the share of the 300 clean test snippets with P(has_flaw) below 0.5. The dataset card's Metrics section defines the other metrics.

95% intervals for this model, from 500 bootstrap resamples of test tasks: macro F1 0.431 to 0.472, accuracy 0.825 to 0.835, log loss 0.447 to 0.472, Brier 0.169 to 0.177. By question type its accuracy is 0.897 on the 11 nouls, 0.491 on primary_flaw, and 0.446 on quality.

Per question

Question Type Test positives Accuracy F1 @ 0.5 Log loss ECE
unclear_naming noul 139 0.985 0.933 0.060 0.009
noise_comments noul 178 0.998 0.992 0.012 0.001
dead_code noul 223 0.858 0.414 0.403 0.030
inconsistent_style noul 147 0.911 0.428 0.285 0.020
over_abstracted noul 145 0.879 0.000 0.360 0.021
too_clever noul 164 0.875 0.185 0.375 0.031
duplicated_logic noul 171 0.858 0.000 0.325 0.147
error_handling_problem noul 140 0.957 0.792 0.151 0.037
type_safety_hole noul 148 0.921 0.527 0.281 0.029
unsafe_input noul 165 0.872 0.252 0.371 0.039
has_flaw noul 900 0.750 0.857 0.421 0.047
primary_flaw choice 0.491 1.658 0.044
quality score 0.446 1.265 0.039

Calibration

Answers are the model's raw softmax (test ECE 0.038). temperatures.json holds one temperature per question, fitted on the validation split by log loss. Replacing each answer's distribution p with softmax(log p / T) moves test log loss from 0.459 to 0.456, Brier from 0.173 to 0.172 and ECE from 0.038 to 0.031. It never changes the top answer or the order of P(yes) values, so F1 and accuracy stay the same.

Speed

Median wall-clock time for one snippet with all 13 questions, batch size 1. The CPU row covers 52 test snippets.

Hardware Runtime p50 per snippet
One H100 SXM PyTorch, bf16 autocast 115.4 ms
16 Neoverse V3 CPU cores PyTorch, fp32 18.2 s

Intended use

Research and comparison: a small decision model trained briefly on this data. It is not suited to flagging real code, because it calls every clean snippet flawed.

Training

  • Data: the train split of the decisions config, 2,400 tasks and 9,600 snippets (124,800 decisions). The state is the task description, language and code. The training files match the SHA-256 hashes in the dataset's published manifest.json.
  • The dataset keeps the 4 variants of a task in one split, so no test task was seen in training.
Setting Value
Method Full fine-tune of the Qwen3-0.6B backbone (all 28 layers) and the choice head
Objective Cross entropy over each question's candidate paths
Schedule 1,200 steps of 4 snippets: half an epoch (4,800 of the 9,600 training snippets)
Optimizer AdamW, learning rate 2e-5 (backbone) and 2e-4 (head), 100 warmup steps
Precision bf16 autocast over fp32 weights
Hardware One H100 SXM, 40 minutes
Validation loss 0.453 at the end of training (300 validation snippets)

training/ holds the scripts that trained and scored this model (nanojev.py, common.py, calibrate.py). To rerun it:

pip install pyarrow numpy
hf download yannelli/js-ts-code-review-decisions --repo-type dataset --local-dir js-ts-code-review-decisions
export DECISIONS_DATA=js-ts-code-review-decisions
python training/nanojev.py train --ckpt ckpt --epochs 1 --max-steps 1200
python training/nanojev.py predict --ckpt ckpt --split test --out test-answers.jsonl
python js-ts-code-review-decisions/score_decisions.py test-answers.jsonl --split test

Evaluation files

eval/test-answers.jsonl holds this model's answers to all 1,200 test snippets in the scorer's format, and eval/score.txt is the scorer's output for it. To rescore:

python js-ts-code-review-decisions/score_decisions.py eval/test-answers.jsonl --split test

Limitations

  • Gold labels come from blind model review and a model audit that accepted 98.3% of findings, not from human reviewers. Scores measure agreement with those labels.
  • The snippets are synthetic, 5 to 80 lines long, and 25% of them are clean, so false positive rates on real repositories will differ.
  • Training covered only JavaScript, JSX, TypeScript and TSX snippets, each with a one-sentence English task; pass one with every snippet.
  • Weakest flags by test F1: over_abstracted (0.00), duplicated_logic (0.00), too_clever (0.18). quality is the hardest question (accuracy 0.446); 46% of its wrong answers are one level off.
  • unsafe_input follows the dataset's definition (external data used unchecked); it is not a vulnerability scanner.
  • has_flaw P(yes) stays above 0.54 on every test snippet, so all 300 clean snippets are called flawed.
  • over_abstracted and duplicated_logic have F1 0: their highest test P(yes) is 0.47 and 0.43.

Other models trained on this dataset

Model Base Params Macro F1 @ 0.5 Accuracy
d1-3B LFM2.5-VL-3B with Liquid AI's d1 decision readout 3.1B 0.917 0.940
d1-omni-600M LFM2.5 encoder with a decision head 587M 0.771 0.882
GLiNER2.5-Decide GLiNER2 DeBERTa-v3 encoder with a classification head 486M 0.687 0.863
NanoJev (this model) Qwen3-0.6B with a choice head 596M 0.452 0.831

yannelli/code-review-decisions-GGUF has GGUF builds of the two Liquid models for llama.cpp and Ollama.

License

Apache 2.0, the license of the Qwen/Qwen3-0.6B base model.

The training data, JS/TS Code Review Decisions, is CC BY 4.0.

Citation

@misc{yannelli2026nanojevcodereview,
  title        = {NanoJev Code Review Decisions},
  author       = {Ryan Yannelli},
  year         = {2026},
  publisher    = {Hugging Face},
  howpublished = {\url{https://huggingface.co/yannelli/nanojev-code-review-decisions}}
}

@misc{yannelli2026jstscodereviewdecisions,
  title        = {JS/TS Code Review Decisions},
  author       = {Ryan Yannelli},
  year         = {2026},
  publisher    = {Hugging Face},
  howpublished = {\url{https://huggingface.co/datasets/yannelli/js-ts-code-review-decisions}}
}

Maintainer

Ryan Yannelli | 10/12/2026 | GitHub: @yannelli | Hugging Face: yannelli

Downloads last month
3
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for yannelli/nanojev-code-review-decisions

Finetuned
Qwen/Qwen3-0.6B
Finetuned
C-Tianyu/NanoJev
Finetuned
(5)
this model

Dataset used to train yannelli/nanojev-code-review-decisions

Collection including yannelli/nanojev-code-review-decisions

Evaluation results