Глава 6

Стройте план, который можно отклонить до patch

План полезен, если называет файлы, символы, риски, команды и ожидаемое доказательство. Перечень "написать код, добавить тесты" не помогает проверить scope и не выявляет скрытых действий. В companion-проекте шаг плана - это типизированная структура, а не свободный текст.

TypeScript
export interface PlanStep {
  readonly id: string;
  readonly title: string;
  readonly reads: readonly string[];
  readonly writes: readonly string[];
  readonly commands: readonly CommandSpec[];
  readonly evidence: string;
}

export interface ChangePlan {
  readonly contract: TaskContract;
  readonly steps: readonly PlanStep[];
}

И раз план - это данные, его можно проверить кодом до исполнения: что каждый write входит в разрешённый scope, а каждая команда - в allowlist.

TypeScript
function validateStep(root: string, contract: TaskContract, step: PlanStep, policy: CommandPolicy): void {
  if (step.id.trim() === "" || step.title.trim() === "" || step.evidence.trim() === "") {
    throw new Error("Every plan step needs an id, title, and evidence target");
  }
  for (const path of [...step.reads, ...step.writes]) {
    resolveContained(root, path);
  }
  for (const path of step.writes) {
    const normalized = relative(root, resolveContained(root, path)).split("\\").join("/");
    if (!pathAllowed(normalized, contract.allowedWritePaths)) {
      throw new Error(`Plan writes outside allowed scope: ${path}`);
    }
  }
  for (const command of step.commands) {
    if (!policy.allowedCommandKeys.has(commandKey(command))) {
      throw new Error(`Plan contains a command outside the allowlist: ${command.label}`);
    }
  }
}

export function validatePlan(root: string, plan: ChangePlan, policy: CommandPolicy): ChangePlan {
  if (plan.steps.length === 0) {
    throw new Error("Plan must have at least one step");
  }
  const ids = new Set<string>();
  for (const step of plan.steps) {
    if (ids.has(step.id)) {
      throw new Error(`Duplicate plan step id: ${step.id}`);
    }
    ids.add(step.id);
    validateStep(root, plan.contract, step, policy);
  }
  return plan;

Смысл валидатора виден по вопросам review к каждому полю шага.

Поле шага · Вопрос review

  • reads - Достаточно ли фактов, чтобы менять этот слой?
  • writes - Все ли пути входят в разрешенный scope?
  • commands - Команда точная, безопасная и доступна в среде?
  • evidence - Какой наблюдаемый результат докажет шаг?
  • risk - Что может сломаться за пределами happy path?
  • stop - Какое открытие требует нового решения владельца?

Прогнать план через проверку полноты и увидеть, где он молчит о рисках или доказательстве, помогает лаборатория.

Интерактивная лаборатория 2

Проверьте полноту плана

Заполнено 0 из 5. Не разрешайте edit, пока открытые пункты меняют scope или проверку.

Главное - запретить скрытое расширение. Если во время реализации выяснилось, что нужно менять schema, dependency или публичный API, старый план больше не действует.

План - это точка отклонения, а не формальность. Агент должен вернуться с новым фактом и предложением, а не молча дописать patch за пределами согласованного. Отклонить план дёшево; откатить незапланированное изменение в проде - дорого.

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

Ссылки