Зачем это нужно
В прошлом уроке мы разобрали Git как safety mechanism. Но Git сам по себе не делает AI-разработку безопасной. Можно иметь branch, diff, commit и pull request, но все равно получить изменение, которое невозможно нормально review, test или откатить.
Главная причина - смешанный diff.
Пример:
Task:
Fix reused reset token bug.
Diff:
- fixed reset token reuse
- changed password policy
- reformatted UI button
- updated dependencies
- rewrote auth README
- changed generated snapshots
Даже если где-то внутри есть полезный fix, reviewer теперь должен отвечать сразу на много вопросов:
- что из этого относится к bug;
- что изменилось случайно;
- какие tests доказывают каждый behavior;
- что можно безопасно revert;
- почему dependency update попал в auth fix;
- не скрывает ли refactor настоящую правку.
Правило one task - one change защищает от этого.
Task -> small diff -> tests -> commit
Одна Agent Task должна завершаться одним понятным изменением. Тогда человек и reviewer agent могут ответить:
Что именно сделал агент?
Почему это изменение нужно?
Как оно проверено?
Как его откатить?
Основная идея
One Task - One Change означает:
Одна задача агента должна приводить к одному reviewable, testable, revertable изменению.
Это не значит "один файл". Иногда один change требует нескольких файлов:
- production code;
- test;
- migration;
- documentation note;
- generated client.
Но у этих файлов должна быть одна причина изменения.
Хороший пример:
Task:
AUTH-RESET-04: used reset token cannot be reused.
Change:
Reject consumed reset tokens and add regression test.
Files:
- reset token service
- reset token repository, if state is persisted there
- password reset regression test
- auth docs, if behavior contract must be documented
Плохой пример:
Task:
AUTH-RESET-04: used reset token cannot be reused.
Change:
Reject consumed reset tokens,
rename auth services,
change password complexity,
update UI theme,
upgrade dependencies.
Здесь уже несколько независимых changes.
Ключевые термины
| Термин | Значение |
|---|---|
| Agent Task | Маленькая задача для одного controlled agent run. |
| One Change | Один смысл изменения, который можно review, test и revert как единое целое. |
| Small Diff | Diff, размер и связность которого позволяют понять изменение без полного переписывания контекста. |
| Reviewability | Насколько легко reviewer может понять, зачем изменена каждая строка. |
| Revertability | Можно ли откатить изменение без отката unrelated work. |
| Task Boundary | Явная граница: что входит и что не входит в задачу. |
| Scope Creep | Незаметное расширение задачи во время работы. |
| Drive-by Refactor | Refactor "заодно", не нужный для текущей задачи. |
| Commit Plan | План, какие файлы и почему попадут в commit. |
| Stop Condition | Условие, при котором агент должен остановиться и запросить уточнение. |
Что такое "one change"
"One change" определяется не количеством файлов, а смыслом.
Проверьте себя вопросами:
1. Есть ли одна причина для этого diff?
2. Есть ли один главный acceptance criterion?
3. Можно ли описать изменение одним предложением?
4. Можно ли откатить commit, не отменяя unrelated work?
5. Один ли набор tests доказывает готовность?
6. Поймет ли reviewer diff за разумное время?
Если ответ "нет", задача слишком широкая.
Схема One Task - One Change
Смысл схемы: commit появляется не потому, что агент закончил писать, а потому что diff стал понятным, проверенным и связанным с одной задачей.
Почему смешанный diff особенно опасен с агентами
Смешанный diff был проблемой и до LLM. Но с coding agents эта проблема усиливается.
Агент может:
- быстро менять много файлов;
- исправлять style "по пути";
- переписывать тесты под implementation;
- добавлять helper, который решает соседнюю задачу;
- менять generated files без объяснения;
- обновлять dependencies, чтобы обойти local error;
- удалять assertion, который мешает green;
- расширять scope, потому что "это выглядит связанным".
Человек часто устает review'ить большой diff. Агент-reviewer тоже может пропустить важную деталь, если diff шумный.
Маленький diff не гарантирует правильность. Но он резко повышает вероятность, что ошибка будет замечена.
Правило rollback
Один из лучших способов проверить размер task:
Могу ли я безопасно revert этот commit, если он окажется плохим?
Если commit содержит:
- bug fix;
- dependency upgrade;
- UI refactor;
- migration;
- formatting;
- unrelated docs;
то revert станет опасным. Вы откатите не только плохой bug fix, но и, возможно, нужный dependency upgrade или docs update.
Хороший commit:
fix(auth): reject reused reset tokens
Его можно revert, если auth behavior сломался.
Плохой commit:
update auth stuff
Его страшно revert, потому что непонятно, что именно он отменит.
Правило review
Еще один контрольный вопрос:
Может ли reviewer понять diff без чтения всей истории проекта?
Reviewer должен видеть связь:
Task -> AC -> Test -> Production change -> Verification
Если reviewer вынужден разбираться в пяти независимых темах, это не one change.
Reviewable diff обычно:
- меняет небольшое число связанных файлов;
- содержит test/eval для behavior;
- не включает unrelated formatting;
- не смешивает feature и refactor;
- не обновляет dependencies без причины;
- имеет понятный commit message;
- содержит final evidence.
Правило verification
One change должен иметь один понятный verification target.
Пример:
Task:
Reject reused reset token.
Verification:
- regression test for token reuse
- password reset test file
- auth service tests
Это связанный набор проверок.
Плохой пример:
Task:
Improve auth.
Verification:
- maybe run all tests
- manually click UI
- hope login still works
Если verification расплывается, scope почти всегда слишком большой.
Что входит в один change
Один change может включать несколько типов файлов.
| Файл/изменение | Может входить в один change? | Условие |
|---|---|---|
| Production code | Да | Реализует выбранный AC или bug fix. |
| Test/eval | Да | Доказывает этот же behavior. |
| Fixture | Да | Нужна для этого test/eval. |
| Migration | Иногда | Нужна для behavior и имеет отдельную verification. |
| Documentation note | Иногда | Фиксирует contract, который изменился. |
| Generated file | Иногда | Сгенерирован из измененного source artifact. |
| Lockfile | Иногда | Dependency действительно менялась. |
| Formatting | Только если task про formatting | Иначе шум. |
| Refactor | Только если нужен для change | И должен быть behavior-preserving. |
| Dependency upgrade | Обычно отдельная task | Кроме случаев, где это root cause fix. |
Важно: если change требует migration, API contract update и UI update, возможно, он все еще один business change, но для agent workflow его часто лучше разделить на несколько dependent tasks.
Пример плохого mixed diff
Task:
AUTH-RESET-04:
Used reset token cannot be reused.
Diff:
src/auth/resetTokenService.ts | 32 +++++++++--
tests/auth/resetToken.test.ts | 64 +++++++++++++++++++++
src/auth/passwordPolicy.ts | 18 +++---
src/auth/userService.ts | 90 ++++++++++++++--------------
web/components/Button.tsx | 45 +++++++++------
web/styles/theme.css | 80 +++++++++++++------------
package-lock.json | 420 +++++++++++++++++++++++++++++++---
docs/auth.md | 20 ++++++
Классификация:
| File | Classification | Почему |
|---|---|---|
src/auth/resetTokenService.ts |
Related | Вероятное место fix. |
tests/auth/resetToken.test.ts |
Related | Regression test для bug. |
src/auth/passwordPolicy.ts |
Unrelated | Password complexity не связана с token reuse. |
src/auth/userService.ts |
Suspicious | Может быть related, но нужно объяснение. |
web/components/Button.tsx |
Unrelated | UI button не связан с token consumption. |
web/styles/theme.css |
Unrelated | Theme change не относится к bug. |
package-lock.json |
Suspicious | Dependency update не заявлен. |
docs/auth.md |
Maybe related | Может документировать token contract. |
Такой diff нужно остановить до commit.
Как должен ответить агент на mixed diff
Правильное поведение агента:
Diff contains changes outside the task scope.
Related:
- src/auth/resetTokenService.ts
- tests/auth/resetToken.test.ts
Needs explanation:
- src/auth/userService.ts
- docs/auth.md
Unrelated:
- src/auth/passwordPolicy.ts
- web/components/Button.tsx
- web/styles/theme.css
- package-lock.json
I will not commit this as one change.
Next step:
- keep related token reuse fix;
- move unrelated findings to follow-up tasks or remove my own unrelated edits;
- do not revert pre-existing user changes without confirmation.
Здесь важно последнее предложение. Если unrelated changes были сделаны пользователем до работы агента, агент не должен их удалять.
Пример хорошего small diff
Task:
BUG-AUTH-12:
Used reset token can be reused after successful password reset.
Diff:
src/auth/resetTokenService.ts | 28 +++++++++---
tests/auth/resetToken.test.ts | 52 +++++++++++++++++++++
Commit message:
fix(auth): reject reused reset tokens
Final evidence:
Regression before fix:
- reset_token_cannot_be_reused_after_successful_reset: failed
- reason: second reset request succeeded
After fix:
- reset_token_cannot_be_reused_after_successful_reset: passed
- password reset tests: passed
Это reviewable. Reviewer может быстро проверить test, production change и evidence.
Task ID, AC и commit message
One task - one change работает лучше, если commit связан с task artifact.
Связка:
Task ID -> Acceptance Criterion -> Test/Eval -> Commit Message
Пример:
| Artifact | Value |
|---|---|
| Task ID | AUTH-RESET-04 |
| AC | Used reset token is rejected and password remains unchanged |
| Test | reset_token_cannot_be_reused_after_successful_reset |
| Commit | fix(auth): reject reused reset tokens |
Если используется bug ID:
| Artifact | Value |
|---|---|
| Bug ID | BUG-AUTH-12 |
| Requirement | AUTH-RESET-04 |
| Regression test | reset_token_cannot_be_reused_after_successful_reset |
| Commit | fix(auth): reject reused reset tokens |
Commit message не обязан быть conventional commit. Но он должен отвечать:
Что изменилось?
Где?
Зачем?
Хороший commit message
Хорошо:
fix(auth): reject reused reset tokens
Хорошо:
test(agent): enforce Plan Mode read-only behavior
Хорошо:
docs(testing): add bug fix flow lesson
Слабо:
fix bug
Плохо:
updates
Плохо:
agent changes
Если команда использует task IDs, можно добавить их в body:
fix(auth): reject reused reset tokens
Covers BUG-AUTH-12 / AUTH-RESET-04.
Verification:
- password reset regression test
- auth reset test file
One commit или несколько commits?
На 101 уровне правило простое:
One Agent Task -> One Commit
Но есть нюансы.
Иногда одна task естественно дает два commits:
- first commit adds failing regression test;
- second commit implements fix.
Это полезно, если команда хочет сохранить RED/GREEN историю. Но для большинства учебных и рабочих задач достаточно одного final commit, если final report сохраняет before/after evidence.
Что важно:
Commit должен быть reviewable и revertable.
Если для одного task получилось пять commits, возможно:
- task была слишком большая;
- агент делал много exploratory edits;
- commits нужно squash;
- часть изменений нужно вынести.
Если один commit содержит пять независимых changes, это точно нарушение.
Маленький diff не равен маленькой ценности
Есть ловушка: кажется, что маленькая task означает маленькую пользу. Это неверно.
Маленькая task может закрывать критичный behavior:
- запретить reuse reset token;
- закрыть privilege escalation;
- добавить regression test на payment rounding;
- запретить write tools в Plan Mode;
- исправить RAG citation policy;
- добавить contract test для external API.
Ценность определяется не количеством строк, а тем, какой risk закрыт.
Хороший agentic workflow часто состоит из многих маленьких high-value changes.
Когда change слишком маленький
One task - one change не означает "одна строка на commit".
Слишком маленький change:
Commit 1: create empty file
Commit 2: add function name
Commit 3: add first if
Commit 4: add second if
Проблемы:
- много overhead;
- history шумная;
- reviewer не видит meaningful outcome;
- tests не доказывают каждый микрошаг;
- agent spends time on process rather than quality.
Хороший размер:
Один проверяемый результат.
Не строка. Не файл. Не весь epic. Именно результат.
Stop conditions
Stop condition говорит агенту, когда нужно остановиться и не продолжать "самостоятельно".
Для one task - one change это критично.
Агент должен остановиться, если:
- task требует нового product decision;
- found issue не относится к текущей task;
- нужно менять dependency;
- нужна migration, не указанная в scope;
- diff затронул unexpected layer;
- tests требуют серьезной перестройки harness;
- existing user changes конфликтуют с task;
- обнаружен security risk вне scope;
- target behavior противоречит existing spec;
- fix требует broad refactor;
- verification невозможна;
- task больше не укладывается в один reviewable change.
Хороший stop report:
I found that fixing token reuse requires adding consumed_at to the database schema.
The original scope expected service-level logic only.
Stop condition triggered:
- schema migration required
Proposed split:
1. Add consumed_at migration and model test.
2. Add token reuse regression test and service fix.
3. Update API/docs if error contract changes.
Плохое поведение:
Агент тихо добавил migration, поменял repository, обновил UI и переписал docs.
Что делать с найденным unrelated issue
Агент часто находит соседние проблемы. Это хорошо. Но находка не означает разрешение менять все сразу.
Алгоритм:
1. Зафиксировать unrelated issue в final report или task note.
2. Оценить, блокирует ли оно текущую task.
3. Если не блокирует, не исправлять сейчас.
4. Если блокирует, остановиться и предложить split.
5. Если это security-critical, явно поднять risk.
Пример:
While fixing reset token reuse, I found expired tokens return 500 instead of 400.
This is related to auth but not required for AUTH-RESET-04.
I did not change it in this task.
Suggested follow-up:
BUG-AUTH-13: expired reset token returns 400 TOKEN_EXPIRED.
Так мы не теряем наблюдение, но сохраняем чистоту diff.
Drive-by refactor
Drive-by refactor - это refactor "по пути".
Иногда он полезен:
Нужно вынести маленький helper, чтобы fix был простым и не дублировал rule.
Часто он вреден:
Агент переименовал сервисы, изменил file layout и отформатировал весь module,
хотя task была про один bug.
Критерий:
Можно ли сделать текущий behavior без этого refactor?
Если да, refactor должен быть отдельной task.
Если нет, refactor можно оставить, но он должен быть:
- minimal;
- behavior-preserving;
- объяснен в final report;
- покрыт теми же tests;
- reviewable.
Formatting changes
Formatting - частый источник шумного diff.
Если task не про formatting, агент не должен форматировать весь файл или проект.
Допустимо:
- formatter автоматически изменил несколько строк рядом с правкой;
- project tooling требует format touched file;
- formatting локален и не мешает review.
Опасно:
- весь файл переписан из-за formatter;
- line endings поменялись во всем repository;
- imports reorder затронул много файлов без причины;
- snapshots обновлены без visual verification.
Правило:
Formatting-only changes should be their own commit or absent.
Dependency changes
Dependency update почти всегда должен быть отдельной task.
Почему:
- меняет supply chain risk;
- может затронуть lockfile на сотни строк;
- требует отдельной verification;
- rollback должен быть независимым;
- может изменить transitive behavior.
Dependency update допустим внутри task только если:
- bug прямо вызван dependency version;
- security fix требует upgrade;
- task explicitly says update dependency;
- verification покрывает compatibility risk.
Если агент говорит:
Я обновил package, потому что tests не запускались.
нужно остановиться и проверить:
- была ли проблема в local environment;
- не можно ли использовать existing project setup;
- изменился ли lockfile;
- есть ли approval на dependency change.
Tests как часть one change
Tests не являются "отдельной задачей" автоматически. Если test доказывает текущий behavior, он часть change.
Для feature:
Task:
Add reset token validation endpoint.
Change:
Endpoint implementation + integration test for valid/expired token.
Для bug:
Task:
Fix reused reset token bug.
Change:
Regression test + fix.
Для agent workflow:
Task:
Plan Mode must not edit files.
Change:
Workflow eval + mode guard.
Но tests могут стать отдельной task, если:
- вы добавляете characterization tests перед risky refactor;
- implementation уже существует, но coverage gap закрывается отдельно;
- test harness нужно создать до feature work;
- eval suite строится как infrastructure task.
Важно связать test с reason.
Documentation как часть one change
Docs могут входить в one change, если они фиксируют измененный behavior.
Пример:
Task:
Reject reused reset tokens.
Docs:
Update auth behavior table:
- used token -> 400 TOKEN_ALREADY_USED
Это related.
Плохо:
Task:
Reject reused reset tokens.
Docs:
Rewrite all authentication documentation.
Это отдельная task.
Для курса правило:
Docs update is related when it documents exactly the changed contract.
One Task - One Change для AI Agent development
Когда мы разрабатываем самих агентов, "change" часто находится не в product code.
Примеры:
| Task | One change |
|---|---|
| Plan Mode must not edit files | Add eval and tool policy/mode guard. |
| Lecture writing requests must use lecture skill | Update skill trigger and eval. |
| Final answer must report failed tests honestly | Add output contract eval and final template rule. |
| Bug-fix agent must reproduce before editing | Add workflow rule and procedure eval. |
| Reviewer agent must require verification evidence | Update reviewer rubric and approval eval. |
Здесь тоже нельзя смешивать:
- Plan Mode policy;
- final answer contract;
- skill discovery;
- reviewer rubric;
- tool permission layer;
- RAG citation behavior.
Каждый behavior должен иметь свой eval и свой commit.
Пример 1. Password Reset task split
Большая feature:
Сделать Password Reset.
Это не one task. Это feature, которую нужно разбить.
Возможный task graph:
| Task ID | One change | Verification |
|---|---|---|
| AUTH-010 | Add reset token repository create/find | Repository unit tests |
| AUTH-011 | Add reset token consume state | Repository/service tests |
| AUTH-012 | Add request reset endpoint | API integration test |
| AUTH-013 | Add reset password endpoint | API integration test |
| AUTH-014 | Reject expired token | Negative integration test |
| AUTH-015 | Reject reused token | Regression/integration test |
| AUTH-016 | Add reset form UI | Component/E2E check |
| AUTH-017 | Add no email enumeration behavior | Security/API test |
Каждый task может стать отдельным commit.
Commit examples:
feat(auth): create reset token repository
feat(auth): add password reset endpoint
fix(auth): reject reused reset tokens
test(auth): cover no email enumeration in reset flow
Теперь каждый commit можно review и revert отдельно.
Пример 2. Mixed task превращается в commit plan
Исходная просьба:
Почини reset token reuse, обнови документацию и заодно приведи auth code в порядок.
Проблема: здесь три changes.
Разделение:
| Task | Change | Commit |
|---|---|---|
| BUG-AUTH-12 | Reject reused reset token | fix(auth): reject reused reset tokens |
| DOC-AUTH-04 | Document reset token states | docs(auth): document reset token states |
| REF-AUTH-03 | Refactor auth token naming | refactor(auth): align reset token naming |
Порядок:
1. Fix bug with regression test.
2. Update docs for exact behavior.
3. Refactor naming only after tests are green.
Если docs update маленький и напрямую фиксирует changed contract, его можно включить в first commit. Но "переписать всю документацию" нельзя.
Пример 3. Agent workflow task
Task:
AGENT-WF-01:
Plan Mode must not edit files.
One change:
Add workflow eval and mode guard that prevents write tools in Plan Mode.
Related files:
agent/policy/modes.ts
evals/agent_workflow/plan_mode_no_write_tools.yaml
docs/development/agent-workflow.md, if exact policy is documented
Unrelated:
RAG citation prompts
reviewer agent rubric
billing tool permissions
UI theme
Commit:
test(agent): enforce Plan Mode read-only behavior
Verification:
eval_plan_mode_no_write_tools: passed
eval_act_mode_can_edit_after_approval: passed
Здесь "one change" включает и eval, и policy. Но только для одного behavior.
Пример 4. Bug fix task
Bug:
Checkout accepts negative quantity.
Bad agent task:
Fix checkout validation and improve cart, payment and order code.
Good agent task:
BUG-CHECKOUT-07:
Negative quantity must be rejected before order total calculation.
Scope:
- add failing regression test;
- fix quantity validation boundary;
- run checkout validation tests.
Out of scope:
- payment provider integration;
- cart UI redesign;
- tax calculation refactor;
- dependency updates.
Commit:
fix(checkout): reject negative quantities
Теперь bug fix можно проверить и откатить отдельно от payment или UI changes.
Как агент должен планировать one change
Перед edits агент должен составить короткий task boundary.
Формат:
Task:
<task id and goal>
One change:
<one sentence>
In scope:
- ...
Out of scope:
- ...
Expected files:
- ...
Verification:
- ...
Stop conditions:
- ...
Пример:
Task:
AUTH-RESET-04: reject reused reset tokens.
One change:
Add consumed-token rejection and regression coverage.
In scope:
- reset token service/repository
- password reset regression test
Out of scope:
- password policy
- reset email template
- UI redesign
- dependency updates
Expected files:
- src/auth/resetTokenService.ts
- tests/auth/resetToken.test.ts
Verification:
- targeted regression test
- password reset test file
Stop conditions:
- schema migration required
- existing spec contradicts expected error
- fix requires changing public API response contract
Такой plan делает границы проверяемыми до того, как агент изменит файлы.
Prompt-шаблоны
Prompt 1. One-change planning
Work on this as one task - one change.
Before editing, produce:
- task ID / goal;
- one-sentence change statement;
- in-scope files or areas;
- out-of-scope files or areas;
- expected tests/evals;
- stop conditions.
Do not edit files until the boundary is clear.
Task:
<task>
Цель: не дать агенту начать с широкой реализации.
Prompt 2. Execution with boundary
Execute only the agreed one change.
Rules:
- keep the diff scoped to the task;
- do not perform drive-by refactors;
- do not update dependencies unless explicitly required;
- do not reformat unrelated files;
- do not change unrelated tests;
- if you find an unrelated issue, report it as follow-up;
- stop if scope must expand.
Before final answer:
- run targeted verification;
- review git diff --stat;
- list changed files and why each belongs.
Цель: удержать агента в пределах task.
Prompt 3. Mixed diff cleanup
Review the current diff for one task - one change.
Classify every changed file:
- related;
- maybe related, needs explanation;
- unrelated;
- pre-existing user change.
Do not revert user changes.
For changes you made that are unrelated, propose removal or split into follow-up tasks.
Show the cleaned commit plan.
Цель: почистить diff перед commit.
Prompt 4. Commit plan
Prepare a commit plan.
Include:
- task ID;
- one change statement;
- files to stage;
- files not to stage and why;
- verification commands/results;
- commit message;
- residual risks.
Do not commit until explicitly approved.
Цель: отделить review от commit action.
Prompt 5. Autonomous commit, если разрешено
After verification passes:
- stage only related files;
- review staged diff;
- create one commit tied to the task ID;
- do not include unrelated changes;
- report commit message and verification evidence.
Цель: безопасный commit step в более зрелом workflow.
Commit plan template
Перед commit полезно заполнить:
Task ID:
One change:
Acceptance Criteria:
Changed files to stage:
- path: reason
Changed files not staged:
- path: reason
Verification:
- command: result
Not run:
- command/check: reason
Commit message:
Residual risks:
Пример:
Task ID:
BUG-AUTH-12 / AUTH-RESET-04
One change:
Reject reused reset tokens and add regression coverage.
Acceptance Criteria:
Second use of token fails and password remains unchanged.
Changed files to stage:
- src/auth/resetTokenService.ts: adds consumed token guard
- tests/auth/resetToken.test.ts: adds regression test
Changed files not staged:
- docs/notes.md: pre-existing user notes
Verification:
- pytest tests/auth/test_reset_token.py::test_reuse_rejected: passed
- pytest tests/auth/test_reset_token.py: passed
Not run:
- browser E2E reset flow: not in this task
Commit message:
fix(auth): reject reused reset tokens
Residual risks:
Concurrent double-submit behavior needs separate test.
Reviewability budget
Нельзя задать универсальный лимит строк, но можно использовать warning signs.
Diff требует split, если:
- затронуто больше 2-3 layers без явной причины;
- больше одного independent acceptance criterion;
- есть dependency или lockfile change без отдельной задачи;
- в diff есть formatting всего файла;
- tests и production changes не соответствуют друг другу;
- reviewer не может описать change одним предложением;
- rollback затронет unrelated behavior;
- diff стал больше, чем команда может review за один заход.
Пример условного budget:
| Сигнал | Что сделать |
|---|---|
| 1-3 связанных файла | Обычно нормально. |
| 4-8 файлов | Проверить, все ли относятся к одному behavior. |
| 9+ файлов | Скорее всего нужен split или отдельное объяснение. |
| 300-500+ строк diff | Review risk растет. |
| Several layers: DB + API + UI + docs | Рассмотреть task graph. |
| Generated files large | Требуется source explanation. |
Это не жесткий закон. Маленькая migration может затронуть много файлов. Но чем больше diff, тем выше обязанность объяснения.
Как stage только one change
Если diff уже получился смешанным, commit должен включать только related changes.
Команды:
git status --short
git diff --stat
git diff
git add path/to/related-file
git diff --staged
Если файл содержит и related, и unrelated hunks:
git add -p
Но лучший workflow - не создавать смешанный файл. Patch staging полезен, но он не должен становиться оправданием плохих task boundaries.
Опасно:
git add .
Допустимо только после review всего diff.
Связь с tests
One change должен иметь проверку, которая доказывает именно этот change.
Таблица:
| Change | Verification |
|---|---|
| Reject reused reset token | Regression/integration test for token reuse. |
| Add endpoint validation | API contract or integration test. |
| Update Plan Mode policy | Workflow eval with tool trace assertion. |
| Add skill trigger | Trigger eval for skill discovery. |
| Fix RAG citation omission | Dataset eval for citation presence and source support. |
| Update docs only | Markdown/link checks if available, diff review. |
| Refactor only | Existing tests prove behavior unchanged. |
Если verification не связана с change, она дает мало confidence.
Плохо:
Changed auth token logic.
Verification: npm run lint.
Lint полезен, но не доказывает auth behavior.
Лучше:
Changed auth token logic.
Verification:
- token reuse regression test
- password reset test file
- lint
One change и refactor
Refactor может быть отдельной task:
REF-AUTH-03:
Rename ResetTokenService methods for consistency.
Verification:
Existing auth tests pass.
No behavior change expected.
Refactor может быть частью feature/fix только если он локален и нужен для изменения.
Пример допустимо:
To reject consumed tokens, extracted assertTokenUsable() from resetPassword().
No public behavior changed except reused-token rejection.
Пример плохо:
While fixing token reuse, agent reorganized all auth services.
Если хочется refactor после fix, лучший порядок:
Commit 1: fix bug with regression test
Commit 2: refactor auth token naming with tests unchanged
Так rollback bug fix и rollback refactor не связаны.
One change и migrations
Migrations повышают риск. Иногда их нужно выделять.
Пример:
Task:
Reject reused reset token.
Implementation requires new column:
reset_tokens.consumed_at
Возможный split:
| Task | Change | Verification |
|---|---|---|
| AUTH-011A | Add consumed_at column and model mapping |
Migration apply/model test |
| AUTH-011B | Mark token consumed on successful reset | Service/integration test |
| AUTH-011C | Reject consumed token | Regression test |
Если проект маленький, можно сделать в одном task. Но final report должен объяснить, что migration действительно необходима.
Stop condition:
Original task did not mention schema change.
Ask for approval before adding migration.
One change и generated files
Generated files могут входить в change, если они следуют из source change.
Пример:
Change:
Add API field `tokenState`.
Related generated files:
- OpenAPI schema
- generated TypeScript client
Тогда final report должен сказать:
Generated client updated from OpenAPI schema.
Плохо:
Generated files changed because agent ran broad build command,
but source contract did not change.
Generated diff без source reason - warning.
One change и docs
Docs-only task:
docs(testing): add bug fix flow lesson
Это один change, если все изменения относятся к одному уроку или одному documentation contract.
Смешанный docs diff:
lesson_10_02_one_task_one_change.md
lesson_03_01_architecture_as_constraint.md
README.md
package.json
Может быть valid, если task "rename term across course". Но если task была "write lesson 10.2", изменения в модуле 3 и package.json suspicious.
Для docs тоже действует:
One task -> one content change -> reviewable diff.
One change и PR
На раннем этапе курса полезно считать:
One task -> one commit -> one small PR
В реальных командах PR может включать несколько commits, если они являются частью одного small feature slice. Но для agentic development лучше начинать с маленьких PR:
- проще review;
- быстрее CI;
- проще revert;
- меньше merge conflicts;
- проще понять contribution агента;
- проще делегировать reviewer agent.
Плохой PR:
Password reset implementation, UI, docs, dependency upgrades, refactor.
Хороший набор PRs:
PR 1: reset token repository
PR 2: request reset endpoint
PR 3: reset password endpoint
PR 4: token reuse regression fix
PR 5: reset form UI
Да, PRs больше. Но каждый безопаснее.
Как связать one change с development loop
Из модуля 8:
Read -> Plan -> Test -> Implement -> Verify -> Review -> Commit
В уроке 10.2 мы уточняем:
Read one task
-> Plan one change
-> Test one behavior
-> Implement one scoped diff
-> Verify one outcome
-> Review one diff
-> Commit one change
Если на любом шаге появляется второй behavior, нужен split.
Как связать one change с Bug Fix Flow
Из урока 9.5:
Bug report
-> Reproduce
-> Root cause
-> Failing regression test
-> Fix
-> Test passes
-> Full regression
-> Review
One task - one change добавляет:
Fix only this root cause.
Do not include unrelated cleanup.
Commit only this regression fix.
Bug fix commit должен быть особенно revertable.
Как связать one change с multi-agent work
Если несколько агентов работают над одной feature, one task - one change становится еще важнее.
Без этого:
Agent A changes backend and UI.
Agent B changes tests and backend.
Agent C refactors shared auth utilities.
All diffs collide.
С one task - one change:
Agent A: backend endpoint
Agent B: UI form
Agent C: E2E test
Agent D: reviewer diff
Но даже так нужно изолировать work. Именно поэтому следующий урок будет про Git worktrees.
Decision table: commit, split или stop
| Ситуация | Действие |
|---|---|
| Diff связан с одним AC, tests проходят | Commit one change. |
| Diff содержит unrelated files, сделанные агентом | Remove or split before commit. |
| Diff содержит pre-existing user changes | Do not revert; stage only own related changes. |
| Fix требует migration вне scope | Stop and ask/split. |
| Agent found unrelated bug | Report follow-up, do not fix now unless blocking. |
| Tests fail because of current change | Fix within task before commit. |
| Unrelated flaky test fails | Report separately; do not hide. |
| Dependency update needed | Usually split into dependency task. |
| Refactor useful but not necessary | Separate task after behavior change. |
| Public contract changed unexpectedly | Stop and update spec/AC before commit. |
Агент как исполнитель, не product owner
One task - one change также ограничивает роль агента.
Агент может:
- предложить split;
- обнаружить scope creep;
- найти related risk;
- написать follow-up;
- попросить уточнение;
- реализовать agreed change.
Агент не должен самовольно:
- добавлять новый product behavior;
- менять business rules;
- менять API semantics;
- расширять task до "лучшей архитектуры";
- обновлять dependencies;
- удалять tests;
- делать broad cleanup.
Если агент считает, что scope нужно расширить, правильное действие:
Stop, explain, propose options.
Review по staged diff
Даже если working tree содержит больше изменений, commit должен проходить review по staged diff.
Команды:
git diff --staged --stat
git diff --staged
Reviewer смотрит:
- staged files относятся к task;
- unstaged files не попадут в commit случайно;
- tests соответствуют AC;
- generated files объяснены;
- commit message точный;
- final evidence соответствует staged diff.
Если staged diff clean, commit можно создавать.
One-change final answer
Финальный ответ агента после task должен быть не просто summary, а доказательство границы.
Формат:
Task:
- AUTH-RESET-04
One change:
- Reject reused reset tokens and cover with regression test.
Changed files:
- src/auth/resetTokenService.ts: consumed-token guard
- tests/auth/resetToken.test.ts: regression test
Out of scope not changed:
- password policy
- reset email template
- UI
Verification:
- targeted regression: passed
- password reset tests: passed
Not run:
- browser E2E reset flow
Follow-up:
- concurrent double-submit race test
Такой ответ помогает review и commit.
Практика 1. Разделить mixed diff
Task:
AUTH-RESET-04:
Used reset token cannot be reused.
Diff:
src/auth/resetTokenService.ts | 32 +++++++++--
tests/auth/resetToken.test.ts | 64 +++++++++++++++++++++
src/auth/passwordPolicy.ts | 18 +++---
web/components/Button.tsx | 45 +++++++++------
package-lock.json | 420 +++++++++++++++++++++++++++++++---
docs/auth.md | 20 ++++++
Заполните:
| File | Stage? | Reason |
|---|---|---|
src/auth/resetTokenService.ts |
||
tests/auth/resetToken.test.ts |
||
src/auth/passwordPolicy.ts |
||
web/components/Button.tsx |
||
package-lock.json |
||
docs/auth.md |
Сформулируйте:
One change:
Commit message:
Verification:
Follow-up tasks:
Практика 2. Написать commit plan
Task:
AGENT-WF-01:
Plan Mode must not edit files.
Заполните commit plan:
Task ID:
One change:
Acceptance Criterion:
Files to stage:
- ...
Files not to stage:
- ...
Verification:
- ...
Commit message:
Residual risks:
Пример решения:
Task ID:
AGENT-WF-01
One change:
Add eval and mode guard preventing write tools in Plan Mode.
Acceptance Criterion:
Plan Mode produces plan and no write/edit tools are called.
Files to stage:
- agent/policy/modes.ts: mode guard
- evals/agent_workflow/plan_mode_no_write_tools.yaml: regression eval
Files not to stage:
- docs/rag.md: unrelated existing edit
Verification:
- npm run eval -- plan-mode: passed
Commit message:
test(agent): enforce Plan Mode read-only behavior
Residual risks:
No real external MCP server tested in this task.
Практика 3. Сформулировать stop condition
Task:
Fix checkout negative quantity validation.
Во время работы агент обнаружил:
Validation is implemented in shared pricing library.
Changing it may affect invoices, cart preview and subscription renewal.
Напишите stop report:
Stop condition:
Why scope expanded:
Options:
1.
2.
3.
Recommended next task:
Возможный ответ:
Stop condition:
Fix requires changing shared pricing validation used by invoices, cart preview and subscriptions.
Why scope expanded:
Original task expected checkout boundary only, but root cause is in shared pricing library.
Options:
1. Add regression tests for all three affected flows, then change shared library.
2. Add checkout-specific guard as temporary fix and create follow-up for shared validation.
3. Clarify intended domain ownership before implementation.
Recommended next task:
Write regression matrix for quantity validation across checkout, invoice and subscription flows.
Checklist перед началом task
[ ] Task ID есть
[ ] One change можно описать одним предложением
[ ] Acceptance criterion понятен
[ ] In scope указан
[ ] Out of scope указан
[ ] Expected files/areas названы
[ ] Verification target назван
[ ] Stop conditions названы
[ ] Git branch/status проверены
Checklist перед commit
[ ] Diff связан с одной task
[ ] Каждый staged file имеет reason
[ ] Tests/evals соответствуют AC
[ ] Нет unrelated refactor
[ ] Нет accidental formatting
[ ] Нет unexplained dependency/lockfile changes
[ ] Нет secrets/debug output
[ ] Verification выполнена или limitation указана
[ ] Commit message описывает behavior
[ ] Revert этого commit не откатит unrelated work
Типичные ошибки
| Ошибка | Симптом | Исправление |
|---|---|---|
| "И заодно" в task | В diff несколько unrelated outcomes | Split before implementation. |
| Один commit на весь epic | Review и rollback почти невозможны | Commit per Agent Task. |
| Маленькая task без verification | Diff маленький, но confidence низкая | Добавить test/eval/manual evidence. |
| Drive-by refactor | Behavior fix смешан с structure rewrite | Separate refactor commit. |
| Dependency update внутри feature | Большой lockfile и unclear risk | Separate dependency task. |
| Docs rewrite внутри bug fix | Review уходит в содержание docs | Документировать только changed contract. |
| Formatting всего файла | Noise hides behavior change | Format only touched scope or separate task. |
| Commit message vague | Непонятно, что сделал агент | Message tied to behavior/task ID. |
| Agent исправляет найденный unrelated bug | Scope creep | Report follow-up unless blocking. |
Staging через git add . |
В commit попали лишние файлы | Stage selected files and review staged diff. |
| Stop condition ignored | Task становится architectural rewrite | Stop and propose split. |
Критерии готовности
После урока студент должен уметь:
- объяснить правило one task - one change;
- отличить one change от одного файла или одной строки;
- определить, когда diff смешивает несколько задач;
- связать task ID, AC, test/eval и commit message;
- составить task boundary для агента;
- задать stop conditions;
- отделить related, suspicious и unrelated changes;
- составить commit plan;
- решить, когда commit, split или stop;
- применить правило к product code, bug fix, docs и AI Agent workflow.
Краткое резюме
One Task - One Change - это дисциплина маленьких проверяемых изменений.
Главная цепочка:
Task
-> One change
-> Small diff
-> Verification
-> Staged diff
-> Commit
Один change определяется смыслом, а не количеством файлов.
Хороший change можно:
- описать одним предложением;
- связать с AC или bug report;
- проверить конкретным test/eval;
- review за разумное время;
- revert без отката unrelated work.
Если агент нашел соседнюю проблему, это не повод расширять текущий diff. Это повод остановиться, зафиксировать follow-up или предложить split.
Вопросы для самопроверки
- Почему one task - one change важнее именно в AI-generated work?
- Чем one change отличается от one file?
- Какие признаки говорят, что diff нужно split?
- Что должен содержать commit plan?
- Когда агент должен остановиться вместо продолжения implementation?
Что дальше
Следующий урок посвящен Git worktrees. Когда одна feature разбита на маленькие независимые changes, появляется возможность выполнять некоторые tasks параллельно. Worktrees позволяют дать каждому агенту отдельную рабочую директорию, branch и контекст, чтобы параллельная работа не смешивалась в одном diff.