распознавание: большие раздачи размечаются целиком, файлы вне плана видны

- модель адресует файл номером строки нашего списка вместо копии пути: ответ
  на 180 файлов вместо ~15k токенов стоит ~2.5k, усечение сотней снято,
  max_files и max_tokens ушли в [recognition], correction-ретрай больше не
  переприсылает список
- негодный элемент ответа отбрасывается поимённой причиной, обрыв генерации и
  отказ по размеру запроса названы своими причинами, покрытие плана блокирует
  авто только при непокрытом видеофайле
- раскладка показывает все файлы раздачи со строками «не в плане» и полным
  порядком сортировки; снимок списка файлов лёг рядом с планом (миграция 0012)
This commit is contained in:
av
2026-09-02 08:54:58 +03:00
parent 01ac0430a9
commit fc9a3b4066
26 changed files with 1978 additions and 201 deletions
+24 -4
View File
@@ -96,16 +96,36 @@ func TestМиграцииБезAutoincrementИСерверногоВремени
// docs/conventions/errors.md: сравнение ошибок — errors.Is/errors.As, никогда
// по тексту. errorlint ловит `err == ErrX` и приведение типа, но не матчинг
// подстрокой — его ловим здесь.
//
// Прямой формы мало: `msg := strings.ToLower(err.Error())` и следом
// `strings.Contains(msg, …)` — то же сравнение по тексту, просто через
// промежуточную переменную (так оно и просочилось в recognize). Поэтому вторым
// проходом собираем имена, которым присвоен `.Error()`, и ищем их в тех же
// сравнениях.
func TestОшибкиНеМатчатсяПоТексту(t *testing.T) {
re := regexp.MustCompile(`(strings\.(Contains|HasPrefix|HasSuffix|EqualFold)\([^)]*\.Error\(\)|\.Error\(\)\s*==)`)
direct := regexp.MustCompile(`(strings\.(Contains|HasPrefix|HasSuffix|EqualFold)\([^)]*\.Error\(\)|\.Error\(\)\s*==)`)
// Присваивание текста ошибки переменной, в т.ч. завёрнутое в вызовы
// (strings.ToLower, strings.TrimSpace и подобные).
assigned := regexp.MustCompile(`(?m)^\s*(\w+)\s*:?=\s*(?:[\w.]+\()*\s*[\w.]+\.Error\(\)`)
const why = "ошибку матчим через errors.Is/errors.As, а не по тексту сообщения"
for _, path := range goFiles(t) {
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("читаю %s: %v", path, err)
}
if loc := re.FindIndex(body); loc != nil {
rel, _ := filepath.Rel(repoRoot, path)
t.Errorf("%s:%d — ошибку матчим через errors.Is/errors.As, а не по тексту сообщения", rel, lineOf(body, loc[0]))
rel, _ := filepath.Rel(repoRoot, path)
if loc := direct.FindIndex(body); loc != nil {
t.Errorf("%s:%d — %s", rel, lineOf(body, loc[0]), why)
}
for _, m := range assigned.FindAllSubmatch(body, -1) {
name := regexp.QuoteMeta(string(m[1]))
used := regexp.MustCompile(
`strings\.(Contains|HasPrefix|HasSuffix|EqualFold)\(\s*` + name + `\b` +
`|\b` + name + `\s*==\s*"` + `|"\s*==\s*` + name + `\b`)
if loc := used.FindIndex(body); loc != nil {
t.Errorf("%s:%d — %s (текст ошибки положен в %s)",
rel, lineOf(body, loc[0]), why, m[1])
}
}
}
}