Глава 17

Превратите тесты в обратную связь, а не финальный ритуал

Цикл надёжен, когда агент видит красный baseline, меняет код, получает зелёный focused test и затем проверяет более широкий набор. Это отличает исправление причины от случайного зелёного результата. В companion-проекте весь цикл проходит сквозным демо: fixture обязан воспроизвести дефект до patch, иначе демонстрация падает.

TypeScript
    validatePlan(repositoryBefore.root, plan, policy);
    const baseline = await runCommand(temporaryRoot, testCommand, policy);
    if (baseline.code === 0) {
      throw new Error("The demonstration fixture must reproduce the bug before the patch");
    }
    const operation: TextReplacement = {
      path: "src/discount.js",
      expected: "  return subtotal * (1 - percent);",
      replacement: "  return subtotal * (1 - percent / 100);"
    };
    await applyTextReplacement(repositoryBefore.root, operation);
    const verification = await runCommand(temporaryRoot, testCommand, policy);
    const repositoryAfter = await indexRepository(temporaryRoot, { maxFiles: 100, maxBytesPerFile: 128_000 });
    const snapshotAfter = await snapshotRepository(repositoryAfter);
    const changed = changedFiles(snapshotBefore, snapshotAfter);
    const review = reviewChange(contract, changed, [verification]);
    if (!review.passed) {
      throw new Error(`Review gate failed: ${review.findings.join("; ")}`);
    }
    const fixedSource = await readFile(join(temporaryRoot, "src", "discount.js"), "utf8");
    const output = {
      repositoryFiles: repositoryBefore.entries.length,
      promptSections: ["Goal", "Context", "Allowed write scope", "Constraints", "Procedure", "Done when", "Final report"],
      planSteps: plan.steps.map((step) => step.id),
      baselineExitCode: baseline.code,
      finalExitCode: verification.code,
      changedFiles: review.changedFiles,
      reviewPassed: review.passed,
      selectedSurfaceForRepeatableWorkflow: chooseGuidanceSurface({
        repeats: true,
        isWorkflow: true,
        mustBeDeterministic: false,
        isObjectiveMergeGate: false
      }),
      fixedLinePresent: fixedSource.includes("percent / 100")
    };
    process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
    if (!prompt.includes("# Done when")) {
      throw new Error("Prompt contract is incomplete");

За кодом стоят пять сигналов, и пропуск любого делает "зелёный" ненадёжным: reproduction (тест падает до patch по ожидаемой причине), focused verification (тот же тест проходит после), regression envelope (соседние тесты, typecheck и build не поймали побочный эффект), diff review (изменены только ожидаемые файлы) и evidence report (сохранены команды, exit codes и значимые строки вывода).

Как реагировать на срыв проверки - и как не реагировать, - лучше держать перед глазами.

Сбой проверки · Неправильная реакция · Правильный следующий шаг

  • Test не воспроизводит дефект - Все равно менять код; Уточнить fixture, вход и ожидаемое поведение
  • Unrelated tests уже красные - Объявить их результатом patch; Зафиксировать baseline и отделить новые failures
  • Test завис - Ждать без ограничения; Timeout, лог и анализ ресурса или deadlock
  • Output обрезан - Считать code 0 достаточным для diagnosis; Сохранить artifact или сузить команду
  • UI test зеленый - Не смотреть результат; Проверить screenshot и browser console

Доказательство задаётся в самом prompt: Claude Code советует дать агенту проверку, которую он может выполнить, Codex рекомендует формулировать Done when, Cursor включает verification steps в план. Это общий фундамент трёх продуктов.

Не ослабляйте oracle. Удаление assertion, широкий snapshot update и увеличение timeout могут сделать pipeline зелёным, не исправив поведение. Любое изменение теста должно объяснять, почему прежнее ожидание неверно, - иначе вы чините индикатор, а не дефект.

Тесты дают сигнал самому исполнителю. Но окончательную готовность должен проверять кто-то другой - в свежем контексте.

Ссылки