--- license: apache-2.0 base_model: C-Tianyu/NanoJev base_model_relation: finetune datasets: - yannelli/js-ts-code-review-decisions language: - en - code library_name: pytorch pipeline_tag: text-classification tags: - decision-model - typed-decisions - system-one - code-review - code-quality - javascript - typescript - nanojev - qwen3 model-index: - name: nanojev-code-review-decisions results: - task: type: text-classification name: Code review decisions dataset: name: JS/TS Code Review Decisions type: yannelli/js-ts-code-review-decisions config: decisions split: test metrics: - type: f1 name: Macro F1 @ 0.5 value: 0.452 - type: accuracy name: Accuracy value: 0.831 - type: log_loss name: Log loss value: 0.459 - type: brier_score name: Brier score value: 0.173 - type: ece name: Expected calibration error value: 0.038 source: name: score_decisions.py url: https://huggingface.co/datasets/yannelli/js-ts-code-review-decisions --- ![NanoJev Code Review Decisions: C-Tianyu’s NanoJev, fine-tuned for half an epoch to score each answer option as its own path.](https://huggingface.co/yannelli/nanojev-code-review-decisions/resolve/main/assets/nanojev-code-review-decisions-banner.png) # 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](https://huggingface.co/C-Tianyu/NanoJev) fine-tuned on [JS/TS Code Review Decisions](https://huggingface.co/datasets/yannelli/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](https://huggingface.co/yannelli/d1-3b-code-review-decisions). | | | |---|---| | Base model | [C-Tianyu/NanoJev](https://huggingface.co/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](https://huggingface.co/datasets/yannelli/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](https://huggingface.co/datasets/yannelli/js-ts-code-review-decisions#flaw-definitions) 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 ```sh pip install torch==2.14.1 transformers==5.19.0 huggingface_hub safetensors pyarrow ``` ```python 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; 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: ```json { "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](https://huggingface.co/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](https://huggingface.co/datasets/yannelli/js-ts-code-review-decisions#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: ```sh 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: ```sh 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](https://huggingface.co/yannelli/d1-3b-code-review-decisions) | LFM2.5-VL-3B with Liquid AI's d1 decision readout | 3.1B | 0.917 | 0.940 | | [d1-omni-600M](https://huggingface.co/yannelli/d1-omni-600m-code-review-decisions) | LFM2.5 encoder with a decision head | 587M | 0.771 | 0.882 | | [GLiNER2.5-Decide](https://huggingface.co/yannelli/gliner2.5-decide-code-review-decisions) | GLiNER2 DeBERTa-v3 encoder with a classification head | 486M | 0.687 | 0.863 | | [NanoJev](https://huggingface.co/yannelli/nanojev-code-review-decisions) (this model) | Qwen3-0.6B with a choice head | 596M | 0.452 | 0.831 | [yannelli/code-review-decisions-GGUF](https://huggingface.co/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](https://huggingface.co/Qwen/Qwen3-0.6B) base model. The training data, [JS/TS Code Review Decisions](https://huggingface.co/datasets/yannelli/js-ts-code-review-decisions), is CC BY 4.0. ## Citation ```bibtex @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](https://ryanyannelli.com) | 10/12/2026 | [GitHub: @yannelli](https://github.com/yannelli) | [Hugging Face: yannelli](https://huggingface.co/yannelli)