---
docgen:
  source: npm/scripts/coverage-classify/verdict-schema.mjs
  crc: 8a2c0df1
---

# verdict-schema.mjs

## Огляд

Модуль `verdict-schema.mjs` визначає контракт даних для відповіді LLM-класифікатора в межах інструменту `coverage-classify`. Класифікатор приймає рішення про те, як трактувати вцілілого мутанта (surviving mutant) після прогону мутаційного тестування: чи варто на нього писати тест, чи він поведінково еквівалентний, чи це захисна гілка для неможливого стану, чи це склейка/обгортка, що покривається інтеграційними тестами.

Файл розв'язує дві задачі:

1. Декларує **Zod-схему** `VerdictSchema`, якій має відповідати JSON-об'єкт-вердикт від LLM.
2. Надає утиліту `parseVerdict`, яка приймає сирий текст відповіді LLM, витягує з нього перший знайдений JSON-блок, парсить його і валідовує за схемою.

Призначення схеми — стабілізувати взаємодію з LLM: навіть якщо LLM повертає вердикт у вигляді тексту з преамбулою чи поясненнями, модуль робить «liberal-in, strict-out» — толерантно витягує JSON-острівець, а далі застосовує жорстку валідацію (категорія, межі впевненості, мінімальна осмисленість пояснення).

Категорії вердикту (`verdict`):

- `worth-testing` — pure logic / реальні гілки, варто писати юніт-тест.
- `equivalent` — мутант поведінково еквівалентний оригіналу, він не killable.
- `defensive` — гілка для impossible state (захисний код), не killable.
- `glue` — CLI entry / обгортка `runStandardRule` тощо; покривається інтеграційними тестами.
- `wrapper` — тонкий `spawn`/`fetch`-обгортка; покривається інтеграційними тестами.

## Експорти / API

Модуль експортує два символи (обидва — іменовані експорти ES modules):

| Символ          | Тип                         | Призначення                                          |
| --------------- | --------------------------- | ---------------------------------------------------- |
| `VerdictSchema` | `ZodObject`                 | Схема валідації для verdict-об'єкта LLM.             |
| `parseVerdict`  | `function(rawText: string)` | Парсер raw-text відповіді LLM у валідований verdict. |

### `VerdictSchema`

Z-об'єкт зі строго визначеними полями:

- `verdict` — `z.enum(['worth-testing', 'equivalent', 'defensive', 'glue', 'wrapper'])`. Обов'язкове. Будь-яке інше значення відхиляється.
- `confidence` — `z.number().min(0).max(1)`. Обов'язкове. Дробове число в діапазоні `[0, 1]`, де `0` — повна невпевненість, `1` — повна впевненість LLM у вердикті.
- `reason` — `z.string().min(20).max(500)`. Обов'язкове. Текстове пояснення довжиною від 20 до 500 символів — мінімум змушує LLM аргументувати, максимум — обмежує балакучість.
- `suggestedTest` — `z.string().max(300).optional()`. Опціональне. Текстова підказка/начерк тесту довжиною до 300 символів; за домовленістю заповнюється переважно для категорії `worth-testing`.

Поля поза цим переліком не описано як заборонені на рівні схеми; за замовчуванням Zod пропускає невідомі ключі без помилки, але вони не з'являться в результаті типізації, що використовує цей модуль. Якщо в майбутньому потрібен strict-режим, його можна додати через `.strict()`.

### `parseVerdict(rawText)`

Високорівнева функція-фасад: «витягни JSON з тексту LLM і провалідуй».

## Функції

### `parseVerdict(rawText)`

Сигнатура:

```js
function parseVerdict(rawText: string): {
  verdict: 'worth-testing' | 'equivalent' | 'defensive' | 'glue' | 'wrapper',
  confidence: number,
  reason: string,
  suggestedTest?: string
}
```

Параметри:

- `rawText` — `string`. Сирий текст, повернений LLM. Може містити довільний preamble/postamble навколо JSON-блоку (пояснення, markdown-загортки тощо). Очікується, що в тексті є рівно один корисний JSON-об'єкт-вердикт.

Повертає:

- Обʼєкт verdict, який пройшов валідацію `VerdictSchema`. Тип повернення відповідає схемі (див. вище).

Side effects:

- Жодних. Функція чиста: не пише в файлову систему, не звертається до мережі, не модифікує параметри.

Алгоритм:

1. Знайти позицію першого символу `{` через `rawText.indexOf('{')` → `jsonStart`.
2. Знайти позицію останнього `}` через `rawText.lastIndexOf('}')` → `jsonEnd`.
3. Якщо хоча б один з індексів дорівнює `-1` — кинути `Error('No JSON object found in LLM response')`.
4. Інакше — вирізати підрядок `rawText.slice(jsonStart, jsonEnd + 1)` (включно з обома фігурними дужками) та віддати у `JSON.parse`.
5. Результат парсингу передати у `VerdictSchema.parse(...)` і повернути.

Виключення / помилки:

- `Error('No JSON object found in LLM response')` — якщо в тексті немає жодної з фігурних дужок (`{` або `}`).
- `SyntaxError` (від `JSON.parse`) — якщо вирізаний фрагмент не є валідним JSON. Наприклад, через зайвий текст усередині `{...}`, який LLM могла додати між дужками.
- `ZodError` (від `VerdictSchema.parse`) — якщо JSON валідний, але не відповідає схемі: невідома категорія `verdict`, `confidence` поза `[0, 1]`, `reason` коротший за 20 символів або довший за 500, `suggestedTest` довший за 300 символів, відсутнє обов'язкове поле тощо.

Поведінкові нюанси, важливі для викликача:

- Стратегія «перший `{` … останній `}`» свідомо толерантна: дозволяє LLM писати щось ось так: `Here's the verdict: { ... } Hope it helps.` Однак якщо LLM повертає **кілька** JSON-обʼєктів, функція ризикує склеїти їх у невалідний фрагмент — це очікувано і впирається у валідацію JSON.
- Функція не намагається «латати» некоректний JSON (немає trailing-comma-cleanup, немає markdown-fence-strip). Помилки прокидаються догори викликача — там і місце для retry / fallback-логіки.
- У разі `ZodError` повідомлення зазвичай містить читабельний шлях до невалідного поля, що зручно для логування при debug LLM-промпта.

## Залежності

Зовнішні:

- [`zod`](https://www.npmjs.com/package/zod) — рантайм-валідатор схем. Імпортується як `import { z } from 'zod'`. Версія керується кореневим `package.json` робочого простору `npm/`.

Внутрішні:

- Немає. Модуль не імпортує нічого з власного проєкту і не залежить від інших файлів `coverage-classify`.

Стандартна бібліотека / середовище:

- `JSON.parse` — глобальний API JavaScript runtime.
- `String.prototype.indexOf` / `lastIndexOf` / `slice` — стандартні методи рядків.

Цільове середовище: Node-сумісний рантайм (Node.js / Bun) із підтримкою ES Modules — файл написано як `.mjs`.

## Потік виконання / Використання

### Як модуль вписується в `coverage-classify`

Інструмент `coverage-classify` працює за такою послідовністю (типова):

1. Зчитати список вцілілих мутантів із результатів мутаційного тестування.
2. Для кожного мутанта зібрати контекст (фрагмент коду, місце змін) та згенерувати промпт для LLM.
3. Виконати запит до LLM і отримати raw-text відповідь.
4. **Викликати `parseVerdict(rawText)`** із цього модуля, щоб отримати типізований verdict.
5. Зберегти verdict у кеш / звіт (`apply.mjs`, `cache.mjs`) і застосувати дії: для `worth-testing` згенерувати TODO/тести, для решти — позначити мутант відповідною категорією.

Таким чином, `verdict-schema.mjs` — це межа між «світом LLM» (нечіткий текст) і «світом інструменту» (структуровані дані).

### Приклад використання

```js
import { parseVerdict, VerdictSchema } from './verdict-schema.mjs'

// 1) Парсимо raw-відповідь LLM
const rawLLM = `
The mutant is in a defensive branch. Here is the verdict:
{
  "verdict": "defensive",
  "confidence": 0.92,
  "reason": "The condition guards an impossible state when the input list is empty.",
  "suggestedTest": ""
}
Thanks!
`

try {
  const verdict = parseVerdict(rawLLM)
  // verdict.verdict === 'defensive'
  // verdict.confidence === 0.92
  console.log(verdict)
} catch (err) {
  // 'No JSON object found in LLM response' | SyntaxError | ZodError
  console.error('LLM verdict invalid:', err.message)
}

// 2) Якщо в нас уже є об'єкт (наприклад, з кешу) — валідуємо безпосередньо через схему
const cached = { verdict: 'glue', confidence: 1, reason: 'CLI entry point only, integration covers.' }
const ok = VerdictSchema.parse(cached) // кине ZodError, якщо не відповідає
```

### Граничні випадки

- Порожній рядок → `indexOf('{') === -1` → кидаємо `'No JSON object found in LLM response'`.
- Рядок без `}` (LLM обірвалась) → `lastIndexOf('}') === -1` → та сама помилка.
- LLM повернула markdown-fence (` ```json ... ``` `) навколо JSON → `parseVerdict` витягне саме JSON-острів між першим `{` і останнім `}`, fence ігнорується.
- LLM повернула `confidence: 1.5` → `ZodError` (порушено `max(1)`).
- LLM повернула `verdict: 'unknown'` → `ZodError` (не входить у `z.enum([...])`).
- LLM повернула `reason` коротшим за 20 символів → `ZodError` (`min(20)`). Це навмисний захист від відповідей-однословів типу `"ok"`.

### Рекомендації для викликача

- Обгортайте виклик `parseVerdict` у `try/catch` і за помилки робіть retry з посиленим промптом (наприклад, з нагадуванням про схему) або падайте у fallback-категорію.
- Не зберігайте у кеш `rawText` як вердикт — зберігайте лише провалідований об'єкт (результат `parseVerdict`); це підтримує інваріант «у кеші тільки валідні дані».
- Якщо в майбутньому з'являться нові категорії — оновлюйте `z.enum` тут, інакше LLM-вердикти з новими значеннями будуть відхилятися.
