Implement TASK
Scanned 9/5/2026
Install to Claude Code
npx -y skills add cryndoc/polisade-orchestrator --skill implement --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Implement?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/cryndoc-implement)More formats (shields.io, HTML) on the badges page.
---
name: implement
description: Implement TASK
argument-hint: "[TASK-XXX]"
cli_requires: "task_tool, codex_cli"
fallback: self
---
# /polisade:implement [TASK-XXX] — Реализация через субагент
Автономная реализация задачи через изолированный субагент с чистым контекстом.
**ВАЖНО:** `/polisade:implement` принимает ТОЛЬКО `TASK-XXX`. Для BUG/DEBT/CHORE автоматически создаётся TASK.
<!-- polisade:claude-only BEGIN -->
⛔ **`ARCHRUN-NNN` (corpus-run, #187) НЕ реализуется** — даже если он попал в
`readyToWork` после `/polisade:unblock`. Это не work-item: `ARCHRUN.ready`
означает «resume required». Если PM передал `ARCHRUN-XXX` или он оказался
ready-кандидатом — **пропусти его** и сообщи: «ARCHRUN-XXX — corpus-run;
продолжи через `/polisade:design-corpus --resume=<runId>`, не через implement».
<!-- polisade:claude-only END -->
---
## ⛔ КРИТИЧЕСКИ ВАЖНО: Merge выполняет ТОЛЬКО PM!
```
┌─────────────────────────────────────────────────────────────┐
│ ⛔ /polisade:implement НИКОГДА не мержит PR автоматически! │
│ │
│ После написания кода статус: in_progress │
│ После создания PR статус: review │
│ После успешного review → статус остаётся: review │
│ Merge и статус done → ответственность PM │
└─────────────────────────────────────────────────────────────┘
```
**Полный цикл /polisade:implement:**
```
LOCALIZE → КОД → ТЕСТЫ → PR → REVIEW → STOP
↑ ↑ ↑
│ └── статус in_progress └── PR готов, ждём PM для merge
└── детерминированный grep-протокол (термины → символы → ссылки),
артефакт целей до правок
```
---
## ⛔ ЗАПРЕЩЁННЫЕ git-команды в /polisade:implement
`/polisade:implement` РАБОТАЕТ ТОЛЬКО в рамках feature-ветки. Следующие
действия ЗАПРЕЩЕНЫ в любой момент жизненного цикла команды —
до и после успешного self-review, при первом и при повторном вызове,
в основном агенте и в субагенте:
- `git checkout main` / `git checkout master` / `git switch main`
- `git push origin main` / `git push origin master` / `git push --force` в main
- `git merge <feature>` / `git rebase <feature>` onto main
- `git branch -D <feature>` / `git branch --delete <feature>`
- `git push origin --delete <feature>` / `git push origin :<feature>`
- автоматический merge PR через любой VCS CLI/API (`polisade_vcs.py pr-merge`, `gh`, curl к Bitbucket)
- `git commit` / `git add` / `git push` с `current_branch ≠ compute_expected_branch(TASK)`
(main/master/develop — частный случай: если видишь `On branch main` и собираешься
коммитить, это ЯВНЫЙ БАГ OPS-001 — не продолжай, останавливайся, верни blocked)
- ⛔ NEVER `git add -f <path>` / `git add --force <path>` на gitignored
путях (`.gigacode/`, `.qwen/`, `.codex/`, `.worktrees/` и любые
другие). Разрешено только при явной просьбе PM «добавить
принудительно». Фраза «закоммить всё кроме X» — это ИСКЛЮЧЕНИЕ
пути X, а НЕ команда его форсить.<!-- polisade:claude-only BEGIN --> Исключение по `.claude/` — только
**файл** `.claude/settings.json` (коммитится), директория `.claude/`
целиком — НЕТ.<!-- polisade:claude-only END -->
Если алгоритм видит «main ahead by N commits» ИЛИ `git status`
на main перед коммитом — это СИГНАЛ БАГА (OPS-001), а не задача на
merge/commit. ОСТАНОВИСЬ и сообщи PM.
**Agent must NEVER push to main/master directly.** Merge выполняет только
PM (вручную) либо `/polisade:continue` (в рамках автономного цикла).
Feature-ветка СОХРАНЯЕТСЯ после завершения `/polisade:implement` — её удаление
произойдёт автоматически при merge PR с флагом `--delete-branch`.
---
## Использование
```
/polisade:implement TASK-001 # Реализовать задачу
/polisade:implement # Выбрать из доступных ready TASK
```
## Deprecated (с предупреждением)
```
/polisade:implement BUG-001 # DEPRECATED: используй созданную TASK
/polisade:implement DEBT-001 # DEPRECATED: используй созданную TASK
```
При попытке `/polisade:implement BUG-XXX` или `/polisade:implement DEBT-XXX`:
1. Показать предупреждение о deprecated
2. Найти связанную TASK (в поле `task` артефакта)
3. Если TASK нет — создать автоматически. Это явная opt-in ветка: PM уже
выбрал реализовать артефакт, поэтому создание TASK допустимо даже при
`settings.debt.autoCreateTask: false` (opt-in контракт `/polisade:debt`
касается только регистрации, не команды implement).
4. Выполнить `/polisade:implement TASK-XXX`
## Архитектура с субагентом
```
┌─────────────────────────────────────────────────────┐
│ PM: /polisade:implement TASK-001 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ ОСНОВНОЙ АГЕНТ │
│ 1. Валидация TASK │
│ 2. Оценка размера задачи │
│ ├─ S-задача → реализовать напрямую (без субагента)
│ └─ M/L-задача → подготовить контекст → субагент │
└─────────────────────────────────────────────────────┘
│
┌─────────┴─────────┐
▼ ▼
┌──────────────────────┐ ┌────────────────────────────┐
│ S-задача (напрямую) │ │ M/L-задача (субагент) │
│ • Read/Edit файлов │ │ • Формирование prompt │
│ • Self-review │ │ • Task tool: general-purpose
│ • Коммит │ │ • Реализация кода │
└──────────────────────┘ │ • Self-review + коммит │
│ │ • Возврат результатов │
│ └────────────────────────────┘
│ │
└─────────┬─────────┘
▼
┌─────────────────────────────────────────────────────┐
│ ОСНОВНОЙ АГЕНТ │
│ 1. Обновление PROJECT_STATE.json │
│ 2. Обновление knowledge.json (если есть learnings) │
└─────────────────────────────────────────────────────┘
```
## Алгоритм работы основного агента
### 0. Pre-check: активная TASK уже в работе (re-invocation guard)
ПЕРЕД любой валидацией и ЛЮБОЙ git-операцией.
Source of truth — **frontmatter `tasks/TASK-*.md`** (как объявлено в шаге 5
этого же алгоритма: markdown frontmatter и PROJECT_STATE.json должны
синхронизироваться, но при рассинхроне авторитетен frontmatter).
PROJECT_STATE.json используется как быстрый индекс и cross-check.
1. Прочитай frontmatter всех `tasks/TASK-*.md` (`status:` поле).
2. Прочитай `.state/PROJECT_STATE.json` (`inProgress`, `inReview`, `waitingForPM`,
`blocked`).
3. Собери объединённое множество «активных» TASK по любому из источников:
`status ∈ {in_progress, review, waiting_pm}` в frontmatter **ИЛИ**
TASK-ID в `inProgress / inReview / waitingForPM` в PROJECT_STATE.
(OR намеренно: guard должен сработать даже при рассинхроне — false
positive допустим, false negative — нет.)
**`blocked` НЕ входит в guard-множество.** Контракт `/polisade:continue`
(см. `skills/continue/SKILL.md`) явно предписывает пропускать blocked
и продолжать работу с другими TASK — то есть одна технически
заблокированная задача не должна запрещать запуск implement для
ready-TASK. `blocked` снимается PM вручную (устраняется техническая
причина — окружение, зависимость, падающий тест) + смена `status:
blocked → ready` в frontmatter TASK и ре-индексация через
`/polisade:sync --apply`. `/polisade:unblock` для blocked НЕ применим — он
обрабатывает только `waitingForPM`.
4. Если объединённое множество НЕ пусто — НЕ переходи к валидации,
НЕ трогай git, **независимо от того, указан ли TASK-XXX аргументом**.
Это соответствует контракту Polisade Orchestrator «не начинай новую TASK, пока есть
незавершённые в работе» (см. `/polisade:continue`). Выведи:
```
⛔ /polisade:implement: найдены незавершённые задачи
В работе (in_progress — PR ещё не создан):
{перечень из frontmatter/inProgress, с пометкой расхождения между
источниками, если есть}
В review (merge — ответственность PM):
{перечень из frontmatter/inReview с PR-ссылками или пометкой «PR не создан»}
Ждут PM (waiting_pm):
{перечень из frontmatter/waitingForPM с вопросами}
Если frontmatter и PROJECT_STATE.json расходятся — запусти
`/polisade:sync --apply` (или `python3 scripts/polisade_sync.py . --apply --yes`)
ДО любых git-действий. `/polisade:sync` без `--apply` работает в dry-run
и ничего не пишет.
Доступные действия — в зависимости от статусов найденных TASK:
→ in_progress / review:
→ /polisade:continue — продолжить автономно (НЕ /polisade:implement!)
→ merge PR вручную — действие PM (если review зелёный)
→ waiting_pm:
→ /polisade:unblock — интерактивная сессия по всему
waitingForPM (аргумент не нужен,
скилл сам пройдёт список)
(⚠️ /polisade:continue при waitingForPM ≠ [] сразу остановится
и потребует именно /polisade:unblock — см. skills/continue/SKILL.md)
→ в любом случае:
→ /polisade:state — обзор
⛔ В re-invocation report режиме ЗАПРЕЩЕНО: git merge, git push origin main,
git branch -D <feature>, любой pr-merge (VCS CLI/API). Feature-ветки остаются как есть.
Даже если активная TASK в review без PR — НЕ «докидывай» merge;
resume через /polisade:continue (он сам создаст PR и запустит review).
НИКАКОЙ новый /polisade:implement (ни с аргументом, ни без) не продолжает
работу, пока есть хоть одна TASK в in_progress / review / waiting_pm.
(blocked-задачи это ограничение НЕ создают — их пропускает и сам
/polisade:continue.)
```
5. STOP. Никаких `git checkout`, `git pull`, `git push`, `git branch -D`,
`git worktree add`, `git checkout -b`.
**Охват:** `in_progress`, `review` (inReview), `waiting_pm` (waitingForPM).
`blocked` намеренно исключён — соответствует контракту `/polisade:continue`
(пропускает blocked). Чтение двух источников с OR-семантикой — страховка
против рассинхрона. **Блокирует любой повторный `/polisade:implement`**
(с аргументом или без) — намеренное соответствие контракту «не начинай
новую TASK пока есть незавершённые в работе». Исключений нет: если нужно
продолжить уже активную TASK — правильный инструмент `/polisade:continue`
(у него есть resume-логика) либо `/polisade:unblock` для waiting_pm
(интерактивный проход по всему waitingForPM, аргумент не требуется),
а не повторный запуск implement.
### 0.5. State machine: диспетчер для `/polisade:implement`
ЭТА СЕКЦИЯ ВЫПОЛНЯЕТСЯ ТОЛЬКО ЕСЛИ guard §0 прошёл (нет активных TASK
в {in_progress, review, waiting_pm}). Задача §0.5 — выбрать путь по
статусу конкретной TASK (если есть аргумент) или по множеству ready-TASK
(без аргумента). НЕ трогай git до завершения диспетчеризации.
| Статус TASK (frontmatter) | С аргументом `TASK-XXX` | Без аргумента |
|---------------------------|------------------------------------|---------------------------------|
| `ready` | full cycle (шаги §1–§5) | pick next ready → full cycle |
| `in_progress` | unreachable (guard §0 остановит) | unreachable (guard §0) |
| `review` + pr_url | unreachable (guard §0) | unreachable (guard §0) |
| `review` + pr_url пуст | unreachable (guard §0) — resume через /polisade:continue | unreachable (guard §0) — resume через /polisade:continue |
| `done` | «уже done», STOP, без git-операций | skip → pick next ready |
| `blocked` | показать blocker, STOP | skip → pick next ready (§continue) |
| `waiting_pm` | unreachable (guard §0) → /polisade:unblock | unreachable (guard §0) |
Для всех `unreachable` cells — guard §0 блокирует re-invocation и
маршрутизирует на `/polisade:continue` (resume review/in_progress) или
`/polisade:unblock` (waiting_pm). Эта таблица НЕ даёт лицензии обойти
guard — если сюда попала TASK в активном статусе, это баг диспетчера,
STOP с `blocked: OPS-008 dispatcher invariant`.
```python
def dispatch_implement(task_arg, state):
# Called only after §0 guard passed.
assert not state.has_active_tasks(), \
"OPS-008: dispatcher reached despite active TASK — STOP"
if task_arg:
task = resolve(task_arg) # frontmatter + PROJECT_STATE cross-check
if task.status == "done":
return stop("TASK уже done — ничего не делаем, git не трогаем")
if task.status == "blocked":
return stop(f"TASK blocked: {task.reason}. Снятие блокировки — PM.")
if task.status == "ready":
return full_cycle(task) # переход к §1 Валидация
# in_progress/review/waiting_pm — guard §0 должен был остановить
return stop(f"OPS-008: unreachable status {task.status}, guard bypassed")
# Без аргумента
candidates = state.ready_tasks() # blocked/done уже отфильтрованы
if not candidates:
return stop("Нет ready TASK. /polisade:tasks или /polisade:state для обзора.")
return full_cycle(pick_by_priority(candidates))
```
⛔ После возврата из диспетчера (любой `stop(...)` arm):
- НЕ `git merge`, НЕ `git push origin main`, НЕ `git branch -D <feature>`,
НЕ любой `pr-merge` (VCS CLI/API), НЕ `git reset`, НЕ `git rebase main`.
- Feature-ветки, worktree'ы, PR'ы — как есть. Ответственность PM
(merge) или `/polisade:continue` (resume).
### 1. Валидация
1. Прочитай `.state/PROJECT_STATE.json`
2. Найди TASK со статусом `ready`
3. Диспетчер §0.5 уже выбрал arm. Здесь — только ready-arm:
- Если указан ID: проверь что это TASK (не BUG/DEBT напрямую),
статус `ready`, все `depends_on` имеют статус `done`
- Если не указан: next ready без невыполненных зависимостей,
приоритет P0 > P1 > P2 > P3
```
Нет готовых задач для реализации.
Возможные причины:
• Все задачи ждут зависимости
• Нет созданных задач
Доступные действия:
→ /polisade:tasks для создания задач из FEAT/SPEC/PLAN
→ /polisade:defect для добавления бага (создаст TASK)
→ /polisade:chore для простой задачи (создаст TASK)
→ /polisade:state для обзора проекта
```
### 1.5. Определение размера задачи
Перед запуском субагента оцени размер задачи по TASK файлу:
**S-задача (реализовать напрямую, без субагента):**
- Acceptance criteria ≤ 3 пунктов
- Затрагивает ≤ 2 файлов
- Изменения < 50 строк (оценка)
- Примеры: замена строки, добавление конфига, мелкий fix
**M/L-задача (через субагент):**
- Всё остальное
```python
def estimate_size(task):
ac_count = len(task.acceptance_criteria)
files_mentioned = count_files_in_task(task)
if ac_count <= 3 and files_mentioned <= 2:
return "S" # direct implementation
return "M+" # subagent
```
Если S-задача:
- Пропустить шаги 3-4 (формирование prompt, запуск субагента)
- **Шаг 1.8 LOCALIZE обязателен и здесь** — выполни его ДО чтения/правок
(детерминированный grep-протокол: термины → символы → ссылки, артефакт
локализации).
Если TASK несёт `kind: coordinate-task` — вместо свободного поиска действует
режим верификации координат §1.8-C (тот же honest halt и границы правок)
- Реализовать напрямую: читать файлы, писать код, тесты, коммит
- Self-review checklist остаётся ОБЯЗАТЕЛЬНЫМ
- Те же требования по чтению parent chain (SPEC через PLAN, DESIGN package,
FR/NFR по `TASK.requirements`, контракты по `TASK.design_refs`) применяются
и к S-задачам — см. шаг 2 «Связанные документы (resolve full chain)»
- Далее полный цикл (regression → PR → review → STOP) без изменений
### 1.7. [OPS-001 GUARD] Branch/worktree setup (MANDATORY — expected-branch invariant)
⛔ Этот шаг ОБЯЗАТЕЛЕН для S, M, L задач при `gitBranching: true`.
Пропуск = OPS-001 (коммит не в ту ветку, в частности в main).
На выходе шага должен выполняться **expected-branch invariant**:
```
cd "$WORK_DIR" && git rev-parse --abbrev-ref HEAD == compute_expected_branch(TASK)
```
где `compute_expected_branch(TASK)` — детерминированная функция по `parent` из
TASK frontmatter; правила — в секции "Git Branching" ниже (source of truth).
Коротко: `parent: PLAN-*` → `plan/PLAN-XXX-TASK-YYY-<slug>`; `parent: FEAT-*`
→ `feat/FEAT-XXX-<slug>`; аналогично для `BUG-`/`DEBT-`/`CHORE-`.
**ВАЖНО для worktree mode.** `$WORK_DIR` = `worktree_path` (в корне репо ветка
остаётся на `main` — это нормальное поведение `git worktree`). Guard и все
последующие git-инспекции ВСЕГДА выполняются внутри `$WORK_DIR`.
**Алгоритм шага:**
```
1. expected = compute_expected_branch(TASK)
2. Если workspaceMode == "worktree" И gitBranching: true:
— git worktree add .worktrees/<dir> -b <expected> (если ветки нет)
— git worktree add .worktrees/<dir> <expected> (если ветка уже есть)
— WORK_DIR = .worktrees/<dir>
Иначе если gitBranching: true (inplace):
— git checkout <expected> (если ветка уже есть)
— git checkout -b <expected> (если новая)
— WORK_DIR = project_root
Иначе (gitBranching: false, legacy):
— Инвариант отключён. Пропустить шаг, коммит в текущую ветку.
— Экспортировать ТОЛЬКО явный мод-флаг: export POLISADE_GIT_BRANCHING=false
— POLISADE_EXPECTED_BRANCH и POLISADE_WORK_DIR НЕ выставляются.
3. Assertion ВНУТРИ WORK_DIR (для worktree — критично!):
current = run(f'cd "{WORK_DIR}" && git rev-parse --abbrev-ref HEAD').stdout.strip()
assert current == expected, \
f"OPS-001: cwd={WORK_DIR} current={current}, expected={expected}"
Если assertion упал → STOP с диагностикой, НЕ продолжать к Шагу 2.
4. Экспортировать для всех последующих bash-вызовов (основной агент и субагент).
Fail-closed модель: bash-guard всегда требует ЯВНЫЙ signal, никогда не
"fall-through по умолчанию" (это ловит truncation/dropout в prompt для слабых моделей).
Если gitBranching: true:
export POLISADE_GIT_BRANCHING="true"
export POLISADE_EXPECTED_BRANCH="<expected>"
export POLISADE_WORK_DIR="<WORK_DIR>"
Если gitBranching: false:
export POLISADE_GIT_BRANCHING="false"
(остальные НЕ выставляются)
Guard-сниппет перед каждым commit/push/add читает POLISADE_GIT_BRANCHING:
— "true" → проверить CURRENT == EXPECTED, fail иначе
— "false" → pass-through (инвариант отключён по дизайну)
— unset/другое → ⛔ fail (bug: основной агент не экспортировал mode)
```
⛔ **Не использовать** `git.current_branch()` или `git rev-parse …` без явного
`cd "$WORK_DIR"` — в worktree mode корневой репо возвращает `main`, это даёт
ложный fail.
---
**Детали реализации ниже (переиспользуемые: setup_worktree и fallback).**
Проверь `settings.workspaceMode` и `settings.gitBranching` в PROJECT_STATE.json.
**Если `workspaceMode == "worktree"` И `gitBranching: true`:**
```python
def setup_worktree(project_root, branch_name):
if workspace_mode != "worktree" or not git_branching:
run(f"git checkout -b {branch_name}") # fallback
return project_root
worktrees_root = f"{project_root}/.worktrees"
dir_name = branch_name.replace("/", "__")
worktree_path = os.path.join(worktrees_root, dir_name)
# Проверить существующий worktree для ветки
existing = parse_git_worktree_list()
if branch_name in existing:
return existing[branch_name] # переиспользовать
try:
mkdir -p {worktrees_root}
git worktree add {worktree_path} -b {branch_name}
except:
# Graceful fallback
warn("git worktree add failed, falling back to git checkout -b")
run(f"git checkout -b {branch_name}")
return project_root
# Копировать .state/ (КРОМЕ counters.json!)
# ⚠️ ВАЖНО: каждую команду выполняй ОТДЕЛЬНЫМ Bash-вызовом!
# НЕ объединяй в одну цепочку через && с переменными —
# это ломает матчинг permissions в settings.json.
mkdir -p {worktree_path}/.state
cp .state/PROJECT_STATE.json {worktree_path}/.state/
cp .state/knowledge.json {worktree_path}/.state/
cp .state/session-log.md {worktree_path}/.state/ 2>/dev/null || true
# ⚠️ counters.json НЕ копируется — глобальный ресурс
# .claude/ уже в worktree через git (tracked directory) — НЕ нужен симлинк!
# Симлинк dependency-каталогов (если есть) — чтобы инструменты были доступны из worktree
# .venv — Python (ruff/pytest/mypy), node_modules — JS/TS, vendor — Go/PHP/Ruby
for dep_dir in [".venv", "node_modules", "vendor"]:
if os.path.isdir(f"{project_root}/{dep_dir}"):
ln -s {project_root}/{dep_dir} {worktree_path}/{dep_dir}
return worktree_path
```
**Шаги выполнения:**
1. Определи имя ветки по правилам из секции "Git Branching"
2. Нормализуй имя папки: `/` → `__` (например `feat/FEAT-001-auth` → `feat__FEAT-001-auth`)
3. Worktree path: `.worktrees/{dir_name}/` (внутри проекта, добавлена в `.gitignore`)
4. Проверь `git worktree list --porcelain` — если worktree для ветки уже существует, переиспользуй
5. Если ветка существует без worktree: `git worktree add {path} {branch}` (без `-b`)
6. Если ветка новая: `git worktree add {path} -b {branch}`
7. Скопируй `.state/` файлы (кроме `counters.json`!)
8. `.claude/` уже в worktree (tracked в git) — **НЕ создавай симлинк и НЕ копируй!**
9. Симлинк dependency-каталогов: для каждого из `.venv`, `node_modules`, `vendor` — если есть в project_root → `ln -s {project_root}/{dep_dir} {worktree_path}/{dep_dir}`
10. Все последующие операции выполняй в `{worktree_path}`
11. **[OPS-001 GUARD] Post-setup assertion** ВНУТРИ `{worktree_path}`:
```
current = run(f'cd "{worktree_path}" && git rev-parse --abbrev-ref HEAD').stdout.strip()
assert current == branch_name, \
f"OPS-001: cwd={worktree_path} current={current}, expected={branch_name}"
```
НЕ использовать `git.current_branch()` без явного `cd "{worktree_path}"` —
в worktree mode корень репо возвращает `main`, это даст ложный fail.
12. Экспортировать для последующих bash-вызовов и для инжекции в prompt субагента:
```
POLISADE_GIT_BRANCHING=true
POLISADE_EXPECTED_BRANCH=<branch_name>
POLISADE_WORK_DIR=<worktree_path>
```
**Если `workspaceMode != "worktree"` или `gitBranching: false`:**
- `gitBranching: true, workspaceMode: inplace` → `git checkout -b {branch_name}`
+ post-setup assertion: `git rev-parse --abbrev-ref HEAD == branch_name`
+ export `POLISADE_GIT_BRANCHING=true`, `POLISADE_WORK_DIR=project_root`, `POLISADE_EXPECTED_BRANCH=branch_name`
- `gitBranching: false` → инвариант отключён, но **ОБЯЗАТЕЛЬНО** export
`POLISADE_GIT_BRANCHING=false` (явный positive signal для guard'а).
POLISADE_EXPECTED_BRANCH и POLISADE_WORK_DIR НЕ выставляются. Guard видит
"false" → pass-through. Если флаг не выставлен вообще → guard fail-closed
(защита от truncation/dropout в prompt).
### 1.8. ⛔ LOCALIZE — детерминированная локализация целей (ОБЯЗАТЕЛЬНО до любых правок)
⛔ **Пока не выполнен LOCALIZE — НЕ читай файлы подряд, НЕ правь код, НЕ пиши
тесты.** Это ПЕРВЫЙ исполнительный шаг реализации — и в прямой S-реализации, и
в субагенте. Навигация детерминирована: она задаётся этим протоколом, а не
привычкой начинать с произвольного grep/read по всему проекту.
Шаг рассчитан на слабую модель — выполняй его буквально и по порядку.
⚙️ **Развилка по `kind` (kind-gating).** Если исполняемый TASK несёт
`kind: coordinate-task` в frontmatter — свободного поиска ниже **НЕТ**: вместо
него действует режим верификации координат (см. **§1.8-C Coordinate-task
mode** сразу после этой секции). Координаты уже в таске — их надо подтвердить,
а не искать. Legacy-таски (frontmatter **без** `kind`) — протокол §1.8 ниже
без единого изменения.
<!-- polisade:nav-canon POINTER — канон навигации клиента: капсула ниже
(grep-протокол LOCALIZE). ДОМ канона — первая копия капсулы в этом файле
(§1.8); вторая копия (промпт субагента) линтуется на байт-паритет с ней
(check_nav_canon_parity). НЕ правь одну копию отдельно — синхронизируй
обе. MCP-нав-протокол платного движка вырезан в V3-P2 (ADR-0004). -->
<!-- polisade:nav-canon LOCALIZE-CAPSULE BEGIN -->
**Вход:** 2–5 ключевых терминов из TASK (имена классов/функций/полей,
endpoint'ы, тексты ошибок, доменные сущности) — из заголовка, `## Summary`,
Acceptance criteria.
**Протокол (ровно в этом порядке; навигация — детерминированный grep, а не
свободное чтение файлов подряд):**
1. Выпиши ключевые термины из TASK.
2. Для каждого термина — `grep -rn "<термин>"` по исходникам проекта:
файлы-кандидаты и точные имена символов.
3. Для 1–3 самых релевантных символов — `grep -rn "<symbol>"` (точки
использования). Правка затрагивает контракт/сигнатуру → пройди по ВСЕМ
попаданиям символа и перечисли задетые файлы (что сломается).
4. (Опционально, при неоднозначности) прицельно прочитай целевой файл —
карта его символов; `git log --oneline -5 -- "<file>"` — что обычно
меняется вместе с ним.
**Триггерная эвристика (какой шаг когда).** Конкретный символ/кейворд из
TASK → grep по термину (шаг 2, силён на keyword-findable задачах). Правка
контракта/сигнатуры или оценка регрессии со скрытыми зависимостями → полный
обход попаданий символа (шаг 3 — ПО ТРИГГЕРУ, не always-on: на простых
локальных правках полный обход добавляет шум).
**Выход — артефакт локализации фиксированного формата. Выведи его ДО первой
правки:**
```
─────────── LOCALIZATION ({TASK-ID}) ───────────
tool: grep
targets:
- file: <path> symbols: <Class.method, ...> why: <обоснование из TASK/ссылок>
- ...
refs_checked: grep(<термин>), grep(<symbol>)
out_of_scope: <файлы, которые намеренно НЕ трогаем>
─────────────────────────────────────────────────
```
**Правила после LOCALIZE (жёсткие):**
- Правь **только** файлы/символы из `targets`. Файл не из списка — не трогать.
- Появилась новая цель — **повтори LOCALIZE** (ещё grep по термину/символу) и
допиши строку в `targets` с пометкой `(добавлено повторным LOCALIZE)`. Молча
расширять скоуп нельзя.
- Пустой результат grep = сигнал неверного термина, а НЕ разрешение править
наугад: уточни термины и повтори протокол.
- «Инструмент недоступен» ≠ «находок нет»: если поиск не выполнялся — так и
скажи, не подменяй отсутствие проверки пустым результатом (класс F1).
<!-- polisade:nav-canon LOCALIZE-CAPSULE END -->
**Совместимость (§ карты):** grep — штатный и единственный навигационный
механизм клиента (не флаг и не деградация чего-то большего); шаг работоспособен
в любом проекте, регрессионные `/polisade:*`-флоу не ломаются.
⛔ **Честность провенанса (F1):** grep ищет строки, а не граф символов. В
`provenance` артефактов ниже по циклу пиши `grep-fallback` (закрытый словарь
формата), `refs_checked` не выдавай за обход графа зависимостей. Отсутствие
проверки ≠ отсутствие находок; подменять первое вторым запрещено.
### 1.8-C. ⛔ Coordinate-task mode (kind: coordinate-task) — исполнение без свободного поиска
**Гейт (kind-gating).** Режим включён ТОЛЬКО когда исполняемый TASK несёт
`kind: coordinate-task` в frontmatter. Такой таск породил `/polisade:tasks` из
change-spec (Pipeline V2, WP2.4 / #211): он уже несёт `coordinates:` (файл +
символ из §3 «Локализация»), `requirements:` (FR/NFR-id) и Gherkin-AC.
Legacy-таски (frontmatter **без** `kind`) — прежний флоу §1.8 (свободный
LOCALIZE) **без единого изменения**; ничего из этой секции к ним не
применяется.
**Почему `kind`, а не `settings.experimental.changeSpec` (выбор
задокументирован — требование WP3.1 шаг 1).** Флаг `experimental.changeSpec`
гейтит только *генерацию* coordinate-таск'ов (скилл `/polisade:tasks`).
*Исполнение* (`/polisade:implement`) ключуется на собственном `kind` таска:
`kind` путешествует вместе с таском и самодостаточен, а coordinate-task
физически не мог возникнуть при выключенном флаге. Читать проектный флаг в
implement не нужно — иначе таск с координатами мог бы молча исполниться
свободным поиском при перевыключенном флаге. Разделение чистое: флаг гейтит
генерацию, `kind` — исполнение.
Режим меняет: **LOCALIZE** (C1), **границы правок** (C2), **выход при
недожатии** (C4 honest halt — правил, но красно; C5 no-op-защита — не правил
вовсе). Всё остальное — branch-guard §1.7, TDD-first (C3), self-review,
regression, PR — как в обычном флоу.
Формулировки ниже рассчитаны на слабую модель: короткие, императивные,
нумерованные, с негативными примерами. Выполняй буквально.
#### C1. LOCALIZE = ВЕРИФИКАЦИЯ координат (НЕ свободный поиск)
Координаты уже в таске. Твоя работа — **подтвердить** их, а не искать. Ровно по
порядку:
1. Прочитай `coordinates:` из frontmatter таска — это твой готовый список целей.
2. Для каждого координатного файла проверь **existence** (файл есть на диске).
3. Для каждого символа — **один** точечный `grep -n "<symbol>" <file>`:
подтверди, что символ существует в указанном файле.
4. Только при правке контракта/сигнатуры — один `grep -rn "<symbol>"` для
контекста вызовов.
⛔ **Бюджет: 1–3 nav-вызова на таск. Не больше.**
- ✅ ПРАВИЛЬНО: 1× точечный `grep -n "<symbol>" <file>` на символ из
координаты (подтверждение).
- ⛔ НЕПРАВИЛЬНО: `grep -rn` по всему проекту доменными словами.
- ⛔ НЕПРАВИЛЬНО: десятки grep-запросов по терминам, которых нет в
`coordinates:`.
- ⛔ НЕПРАВИЛЬНО: «на всякий случай прочитаю соседние файлы / весь каталог».
Координаты — источник истины. Свободный поиск ЗАПРЕЩЁН.
Выведи артефакт верификации ДО первой правки:
```
──────── COORDINATE-VERIFY (TASK-XXX) ────────
mode: coordinate-task
tool: grep
verified:
- file: <path> symbol: <Class.method> exists: yes/no confirmed: yes/no
- ...
nav_calls: <N> (в бюджете 1–3)
──────────────────────────────────────────────
```
Если координатный файл/символ НЕ подтвердился (`exists: no` / `confirmed: no`)
— это дефект координат таска, НЕ разрешение искать свободно: точечный
re-LOCALIZE (C2) или honest halt (C4).
#### C2. Правки — ТОЛЬКО внутри координат; выход = ЯВНЫЙ re-LOCALIZE
- Правь **только** файлы/символы из `coordinates:`. Файл не из списка — не трогать.
- Если правку нельзя завершить без файла/символа вне координат — НЕ расширяй
скоуп молча. Сделай **явный re-LOCALIZE**:
1. один точечный grep по недостающей цели (в бюджет nav-вызовов);
2. допиши строку в артефакт с пометкой
`(re-LOCALIZE: координаты таска неполны — <что и почему>)`;
3. добавь цель в `verified`.
Каждый re-LOCALIZE — **сигнал качества координат таска**. Он ОБЯЗАН быть виден
в выводе (для отчёта), а не спрятан в молчаливом дрейфе скоупа.
- ⛔ НЕПРАВИЛЬНО: «координаты кажутся неполными → грепну весь проект и поправлю
где надо». Это откат к свободному поиску. Только точечный re-LOCALIZE — либо
honest halt (C4).
#### C3. TDD-first по Gherkin-AC (обязателен)
Gherkin-AC таска (подсекция `### Gherkin AC`) → **красный тест ДО правки кода**
(ЭТАП 1 RED протокола TDD-first ниже). Каждый Scenario → ≥1 тест. Реализация
(ЭТАП 2 GREEN) — только внутри координат (C2).
#### C4. Honest halt — лимит 3 итерации edit→test, потом waiting_pm
Цикл `edit → запусти тесты AC` имеет **жёсткий лимит: 3 итерации**.
- Тесты AC зелёные → выходишь из цикла (дальше regression → PR как обычно).
- Красные после **3-й** итерации → **STOP, honest halt**. Верни `waiting_pm` с
диагностикой:
```
──────── HONEST HALT (TASK-XXX) ────────
iterations: 3/3 (лимит достигнут)
red_ac:
- <Scenario AC-FR-NNN-MM>: <какой assert красный, фактический результат>
tried:
- итер.1: <что менял> → <почему не прошло>
- итер.2: <...>
- итер.3: <...>
coordinates_suspect: yes/no (не хватило координат? какой символ/файл)
next: PM — координаты неполны? AC противоречив? нужен re-scope?
────────────────────────────────────────
```
⛔ Три «недо-зелёных» итерации → `waiting_pm`, а НЕ:
- молчаливое ослабление теста (`assert True`, удаление проверки, `xfail`);
- пометка `done`/`review` при красных AC;
- бесконечный цикл правок (лимит ровно 3).
Честная остановка с диагнозом ценнее ложного успеха (принцип Ф3: «недожатие —
honest halt → эскалация, а не имитация успеха»).
#### C5. No-op-защита — «0 правок» ≠ успех (ОБЯЗАТЕЛЬНО перед PR)
Coordinate-таск существует, чтобы **изменить код**. Если после исполнения ни
один координатный файл не изменён — это **no-op**, а НЕ «готово». Это закрывает
оговорку крит.5 вердикта Ф3 (11 no-op тасков WP3.4 прошли как тихий успех, ни
одного honest-halt) и переносит diff-гейт исполнительного контура в skills-путь.
> **Skills-режим — деградированный путь** (ADR-0002): гейт воспроизводит
> diff-гейт исполнительного контура, но без durable-resume, эскалации по данным
> validate и per-узловых трейсов. Путь рабочий и самодостаточный, но промптовый
> гейт — не эквивалент движкового.
**Перед выходом в regression/PR — проверь фактические правки:**
```bash
git status --porcelain # и modified, и НОВЫЕ (untracked) файлы рабочей ветки
```
⚠️ **Не `git diff --name-only`** — он слеп к untracked (только что созданным)
файлам, а create-file таск (frontmatter `creates_files:`) производит именно их;
голый `git diff` дал бы ложно-пустой результат → ложный no-op halt (issue #228).
`git status --porcelain` показывает и `M` (изменён), и `??` (новый) — этого
достаточно, чтобы увидеть эффект create-file таска.
- Изменён/создан **≥1 файл из `coordinates:`** (или из `creates_files:`) → ок,
продолжай (regression → PR).
- **0 затронутых координатных файлов** → **STOP, no-op halt**: верни
`waiting_pm` с артефактом NO-OP HALT ниже.
⛔ Категорически запрещено при 0 правок:
- ставить `done` / `review` без единой правки координатного файла;
- «AC уже зелёные на base, делать нечего» как **тихий** успех — если правки
действительно не нужны, это дефект постановки (таск избыточен / координаты
не те / AC уже покрыт), решение за PM, а не молчаливое закрытие;
- имитация правки (косметика, комментарий, whitespace) ради непустого диффа.
```
──────── NO-OP HALT (TASK-XXX) ────────
changed_coordinate_files: 0
coordinates: <список файлов из таска>
ac_state_on_base: green/red (были ли AC-тесты зелёными ДО правок?)
reason: <почему нет правок — AC уже выполнен на base? координаты неверны?
таск дублирует уже сделанное?>
next: PM — таск избыточен / координаты неверны / AC требует пересмотра?
────────────────────────────────────────
```
Отличие от C4: honest halt C4 — «правил, но после 3 итераций красно»; no-op C5
— «не правил вовсе». Оба → `waiting_pm`, оба ⛔ **никогда** не `done`. Принцип:
узел, обязанный произвести эффект и не произведший его, — halt, не done.
### 2. Подготовка контекста (M/L-задачи)
#### 2.0. Pre-check: локация TASK-файла (FAIL-FAST)
⛔ **TASK-файлы ВСЕГДА лежат в корневой `tasks/TASK-XXX-*.md` — НИКОГДА в `docs/tasks/`, `docs/TASK-*.md` или где-то ещё.**
Это единственное допустимое расположение, зафиксированное в структуре проекта (`CLAUDE.md → Project Structure`). Все скиллы-создатели (`/polisade:tasks`, `/polisade:defect`, `/polisade:debt`, `/polisade:chore`) обязаны создавать файлы ИМЕННО там.
**Перед чтением TASK выполни проверку:**
```python
import os, glob
task_id = "TASK-XXX" # из аргумента команды или выбранной ready-задачи
canonical = glob.glob(f"tasks/{task_id}-*.md")
if not canonical:
# Проверить распространённые «неправильные» места
misplaced = (
glob.glob(f"docs/tasks/{task_id}-*.md") +
glob.glob(f"docs/{task_id}-*.md") +
glob.glob(f"backlog/tasks/{task_id}-*.md") +
glob.glob(f"{task_id}-*.md") # в корне
)
if misplaced:
STOP_WITH_ERROR(f"""
⛔ НАЙДЕН TASK-файл НЕ В КОРНЕВОЙ `tasks/`:
{misplaced}
По конвенции Polisade Orchestrator все TASK-файлы ДОЛЖНЫ быть в `tasks/TASK-XXX-*.md`.
`/polisade:implement` НЕ ищет таски в других местах.
Действие:
mkdir -p tasks
mv {misplaced[0]} tasks/
Затем пересобери индексы:
python3 scripts/polisade_sync.py .
После этого перезапусти /polisade:implement {task_id}.
""")
else:
STOP_WITH_ERROR(f"TASK-файл {task_id} не найден. Создай через /polisade:tasks, /polisade:defect, /polisade:debt или /polisade:chore.")
```
#### 2.1. Сбор данных
<!-- polisade:silo-legacy CAPSULE BEGIN -->
> **Силос → корпус.** Живой корпус `docs/architecture/` — **источник правды**
> по архитектуре. Пакет
> `docs/architecture/DESIGN-NNN-<slug>/` — **legacy-силос**: сначала ищи факт в
> корпусе (`model/`, `c4/`, `glossary/`, `quality/`, `flows/`, `contracts/`,
> `decisions/`), и только если там его нет — читай силос. **Прочитал силос —
> скажи вслух**, отдельной строкой в выводе:
> `⚠️ переходное чтение силоса: <путь> (источник правды — корпус docs/architecture/)`.
> Молчаливое чтение силоса — дефект, а не экономия: PM не видит, что решение
> принято по устаревшему укладу. Если в пакете есть `MIGRATED.md`, его карта
> домов обязательна к прочтению, и читается она так: **из силоса перестают
> читать ТОЛЬКО файлы таблицы «Перенесено 1:1»** — они уже лежат в корпусе
> целиком. Файлы таблицы «Ещё НЕ в корпусе» **не переносились**: там назван
> целевой дом, которого ещё нет, и **единственная копия факта — в силосе**.
> Такой файл читают отсюда (громко), пока модель не свернула его в корпус;
> считать его устаревшим — потерять факт. Перевод силоса на корпус —
> `python3 scripts/polisade_migrate_silo.py <пакет>` (dry-run по умолчанию,
> запись — явным `--apply`, конфликты — выбор человека, не скрипта).
<!-- polisade:silo-legacy CAPSULE END -->
Прочитай и собери:
1. **Файл TASK** (`tasks/TASK-XXX-*.md`) — полное содержимое. Путь ОБЯЗАТЕЛЬНО начинается с `tasks/` (см. 2.0).
2. **Knowledge base** (`.state/knowledge.json`):
- `patterns` — используемые паттерны
- `antiPatterns` — что избегать
- `decisions` — принятые решения (ссылки на ADR)
- `glossary` — ubiquitous language project-wide (federated из DESIGN packages). Передавай в субагент как source-of-truth для именования сущностей в коде, тестах, комментариях.
- `keyFiles` — ключевые файлы проекта
- `testing.testCommand` — команда запуска тестов (если задана)
- `testing.typeCheckCommand` — проверка типов (если задана)
- `testing.lintCommand` — линтер (если задан)
- `testing.strategy` — стратегия тест-авторинга: `"tdd-first"` или `"test-along"` (см. `references/test-authoring-protocol.md`)
3. **Связанные документы (resolve full chain)**:
a. Прочитай прямого parent (PLAN/SPEC/FEAT/BUG)
b. Если parent — PLAN или roadmap-item → resolve до ближайшего SPEC через
`PROJECT_STATE.artifacts[parent_id].parent` (рекурсивно по chain)
c. Если найден SPEC и `TASK.requirements` не пусто:
- Извлеки из SPEC только секции FR/NFR с указанными в `requirements` ID
- Передай ИМЕННО эти секции (не весь SPEC) в субагент — экономит контекст
- Если `requirements: []` — передай весь SPEC (legacy/безопасный fallback)
d. Если у SPEC есть child DESIGN-PKG (через `PROJECT_STATE.artifacts`):
- Прочитай `DESIGN-NNN-{slug}/README.md`
- Если `TASK.design_refs` указывает конкретные файлы — прочитай ИХ
- Если `design_refs: []` но TASK явно про API → прочитай `api.md`
- Если TASK явно про данные → прочитай `data-model.md`
e. Если в SPEC.constraints или в DESIGN упоминаются ADR — прочитай эти ADR
f. Извлеки **Assumptions** (A-N) из SPEC §4 (если SPEC найден) — передай
в субагент для awareness: если assumption можно проверить программно
(например, A-1: "API возвращает user_id в JWT"), субагент должен добавить
assert/validation в код
g. Извлеки `system_boundary` и `external_systems` из SPEC frontmatter
(если SPEC найден) — передай в субагент для ограничения скоупа
### 3. Формирование prompt для субагента
Используй следующий шаблон:
```
Реализуй задачу {TASK-ID}: {task_title}
═══════════════════════════════════════════
КОНТЕКСТ ПРОЕКТА
═══════════════════════════════════════════
Patterns (следуй этим паттернам):
{patterns из knowledge.json или "Не определены"}
Anti-patterns (избегай):
{antiPatterns из knowledge.json или "Не определены"}
Decisions (учитывай):
{decisions из knowledge.json или "Нет зафиксированных решений"}
Glossary (ubiquitous language — source of truth для именования):
{knowledge.glossary как список "term — definition (source)" или "Glossary пуст"}
TERMINOLOGY (ОБЯЗАТЕЛЬНО):
- Используй ТОЧНО эти термины в названиях классов, функций, переменных, полей,
тестов и комментариях. Один концепт — одно имя project-wide.
- Если в glossary есть "Session" — НЕ изобретай "UserSession", "SessionRecord",
"AuthState". Не вводи синонимы существующих терминов.
- `synonyms_to_avoid` в записи glossary — буквальный blacklist имён.
- Если для нужного концепта нет термина — придерживайся convention проекта;
при сомнении flag в waiting_pm, не плоди дубликаты.
Key files:
{keyFiles из knowledge.json или "Изучи структуру проекта"}
═══════════════════════════════════════════
ТРЕБОВАНИЯ ЗАДАЧИ
═══════════════════════════════════════════
{полное содержимое TASK файла}
═══════════════════════════════════════════
⛔ ТОЧНОЕ СЛЕДОВАНИЕ ИНСТРУКЦИЯМ ЗАДАЧИ
═══════════════════════════════════════════
CRITICAL: Реализуй задачу СТРОГО по инструкциям в TASK файле.
- Если таск говорит "используй X" — используй X, НЕ подставляй альтернативу Y
- Если таск говорит "удали/замени X на Y" — удали X и используй Y
- Если таск описывает порядок операций — соблюдай ИМЕННО этот порядок
- НЕ "оптимизируй" подход, даже если видишь "лучший" вариант в существующем коде
Если ты считаешь что инструкция таска ошибочна или есть лучший путь —
верни waiting_pm с объяснением, а НЕ реализуй свою версию молча.
═══════════════════════════════════════════
⛔ ШАГ 0: LOCALIZE — ДО ЛЮБЫХ ПРАВОК И ТЕСТОВ
═══════════════════════════════════════════
Первое, что ты делаешь — детерминированная локализация целей. НЕ читай файлы
подряд и НЕ грепай весь проект по привычке. Выполни протокол по порядку:
<!-- polisade:nav-canon POINTER — канон навигации клиента: капсула ниже
(grep-протокол LOCALIZE). ДОМ канона — первая копия капсулы в этом файле
(§1.8); вторая копия (промпт субагента) линтуется на байт-паритет с ней
(check_nav_canon_parity). НЕ правь одну копию отдельно — синхронизируй
обе. MCP-нав-протокол платного движка вырезан в V3-P2 (ADR-0004). -->
<!-- polisade:nav-canon LOCALIZE-CAPSULE BEGIN -->
**Вход:** 2–5 ключевых терминов из TASK (имена классов/функций/полей,
endpoint'ы, тексты ошибок, доменные сущности) — из заголовка, `## Summary`,
Acceptance criteria.
**Протокол (ровно в этом порядке; навигация — детерминированный grep, а не
свободное чтение файлов подряд):**
1. Выпиши ключевые термины из TASK.
2. Для каждого термина — `grep -rn "<термин>"` по исходникам проекта:
файлы-кандидаты и точные имена символов.
3. Для 1–3 самых релевантных символов — `grep -rn "<symbol>"` (точки
использования). Правка затрагивает контракт/сигнатуру → пройди по ВСЕМ
попаданиям символа и перечисли задетые файлы (что сломается).
4. (Опционально, при неоднозначности) прицельно прочитай целевой файл —
карта его символов; `git log --oneline -5 -- "<file>"` — что обычно
меняется вместе с ним.
**Триггерная эвристика (какой шаг когда).** Конкретный символ/кейворд из
TASK → grep по термину (шаг 2, силён на keyword-findable задачах). Правка
контракта/сигнатуры или оценка регрессии со скрытыми зависимостями → полный
обход попаданий символа (шаг 3 — ПО ТРИГГЕРУ, не always-on: на простых
локальных правках полный обход добавляет шум).
**Выход — артефакт локализации фиксированного формата. Выведи его ДО первой
правки:**
```
─────────── LOCALIZATION ({TASK-ID}) ───────────
tool: grep
targets:
- file: <path> symbols: <Class.method, ...> why: <обоснование из TASK/ссылок>
- ...
refs_checked: grep(<термин>), grep(<symbol>)
out_of_scope: <файлы, которые намеренно НЕ трогаем>
─────────────────────────────────────────────────
```
**Правила после LOCALIZE (жёсткие):**
- Правь **только** файлы/символы из `targets`. Файл не из списка — не трогать.
- Появилась новая цель — **повтори LOCALIZE** (ещё grep по термину/символу) и
допиши строку в `targets` с пометкой `(добавлено повторным LOCALIZE)`. Молча
расширять скоуп нельзя.
- Пустой результат grep = сигнал неверного термина, а НЕ разрешение править
наугад: уточни термины и повтори протокол.
- «Инструмент недоступен» ≠ «находок нет»: если поиск не выполнялся — так и
скажи, не подменяй отсутствие проверки пустым результатом (класс F1).
<!-- polisade:nav-canon LOCALIZE-CAPSULE END -->
{Если TASK.kind == coordinate-task — основной агент ВКЛЮЧАЕТ блок ниже ВМЕСТО
свободного ШАГ 0 выше (свободный поиск в этом режиме запрещён). Если у TASK нет
kind (legacy) — блок НЕ включать, работает ШАГ 0 выше. Source-of-truth: §1.8-C.}
═══════════════════════════════════════════
⛔ COORDINATE-TASK MODE — верификация координат вместо поиска
═══════════════════════════════════════════
Этот TASK несёт `kind: coordinate-task`: координаты кода (`coordinates:` —
файл + символ), `requirements:` и Gherkin-AC УЖЕ в задаче. Ты НЕ ищешь цели —
ты их ПОДТВЕРЖДАЕШЬ. Свободный поиск (grep по проекту, поиск по доменным
словам) ЗАПРЕЩЁН.
ШАГ 0 (coordinate): ВЕРИФИКАЦИЯ координат — по порядку:
1. Прочитай `coordinates:` из frontmatter — это готовый список целей.
2. Проверь existence каждого координатного файла (файл есть на диске).
3. Один точечный `grep -n "<symbol>" <file>` на символ — подтверди, что
символ есть в указанном файле.
4. Только при правке контракта/сигнатуры — один `grep -rn "<symbol>"`
(контекст вызовов).
⛔ БЮДЖЕТ: 1–3 nav-вызова на таск. Не больше.
✅ 1× точечный grep -n "<symbol>" <file> на символ из координаты.
⛔ grep -rn по всему проекту доменными словами.
⛔ десятки grep-запросов по словам, которых нет в coordinates.
⛔ «на всякий случай прочитаю соседние файлы/каталог».
Выведи артефакт ДО первой правки:
```
──────── COORDINATE-VERIFY ({TASK-ID}) ────────
mode: coordinate-task
tool: grep
verified:
- file: <path> symbol: <Class.method> exists: yes/no confirmed: yes/no
nav_calls: <N> (в бюджете 1–3)
──────────────────────────────────────────────
```
ПРАВКИ — ТОЛЬКО внутри координат. Файл не из `coordinates:` — не трогать.
Нужна цель вне координат → ЯВНЫЙ re-LOCALIZE: один точечный grep по
недостающей цели, строка в артефакт с пометкой
`(re-LOCALIZE: координаты таска неполны — <что>)`.
⛔ НЕ грепай весь проект «раз координат не хватило» — это откат к поиску.
TDD-first: Gherkin-AC → красный тест ДО кода (см. TDD-FIRST ниже). Реализация —
только внутри координат.
HONEST HALT: цикл edit→тесты AC имеет лимит 3 итерации. Красные после 3-й →
верни status `waiting_pm` с диагностикой (какой AC красный, что пробовал каждая
итерация, подозрение на неполные координаты). ⛔ НЕ имитируй успех: не ослабляй
тест (assert True / xfail / удаление проверки), не ставь done/review при красных
AC, не крути цикл дольше 3 итераций. Честный halt ценнее ложного успеха.
NO-OP HALT: перед PR проверь `git status --porcelain` (НЕ `git diff --name-only`:
он слеп к untracked, а create-file таск с `creates_files:` создаёт новые файлы —
issue #228). Если НИ ОДИН файл из `coordinates:`/`creates_files:` не затронут
(нет ни `M`, ни `??`) — это НЕ «готово», а no-op. Верни status `waiting_pm`
с артефактом NO-OP HALT (changed_coordinate_files: 0, coordinates, причина).
⛔ 0 правок → НИКОГДА не done/review; «AC уже зелёные, делать нечего» — не тихий
успех, а дефект постановки для PM; не имитируй правку косметикой ради диффа.
═══════════════════════════════════════════
СВЯЗАННЫЕ ДОКУМЕНТЫ
═══════════════════════════════════════════
{содержимое родительского FEAT/SPEC/BUG если есть}
═══════════════════════════════════════════
ТРЕБОВАНИЯ ИЗ SPEC (resolved через parent chain)
═══════════════════════════════════════════
Эта TASK реализует следующие требования parent SPEC:
{для каждого composite FR/NFR из TASK.requirements (формат `{DOC}.FR-NNN`):}
### {DOC_ID}.{FR-NNN}: {title}
**EARS Statement:** {statement}
**Acceptance criteria:**
{Gherkin scenarios — Given/When/Then}
(Если TASK.requirements: [] — этот блок: "N/A — TASK не привязан к SPEC requirements")
⛔ **НЕ делай `grep -r 'FR-NNN' .` по проекту** — parent chain уже резолвит
scope однозначно. `FR-007` в разных top-level документах (PRD vs FEAT vs SPEC)
— это **разные требования**. При сомнении — спроси PM, в каком именно
документе работаем.
═══════════════════════════════════════════
ARCHITECTURE CONTRACTS (из DESIGN package)
═══════════════════════════════════════════
{релевантные секции из api.md / data-model.md / sequences.md по TASK.design_refs}
(Если design_refs: [] — этот блок: "N/A — у parent SPEC нет DESIGN package")
═══════════════════════════════════════════
ASSUMPTIONS AND CONSTRAINTS (из SPEC §4)
═══════════════════════════════════════════
Assumptions (A-N):
{assumptions из SPEC §4.1 или "N/A"}
Constraints (C-N):
{constraints из SPEC §4.2 или "N/A"}
ИНСТРУКЦИИ:
- Constraints — нерушимые. Код обязан быть совместим со всеми constraints.
- Assumptions — если assumption можно проверить программно (например,
"API возвращает user_id в JWT"), добавь defensive validation/assert в код.
Если нельзя — пропусти, но не нарушай assumption молча.
═══════════════════════════════════════════
SYSTEM BOUNDARY (из SPEC frontmatter)
═══════════════════════════════════════════
system_boundary: {system_boundary из SPEC frontmatter или "N/A"}
external_systems: {список external_systems из SPEC frontmatter или "N/A"}
ИНСТРУКЦИИ (если system_boundary не N/A):
- Ты работаешь ВНУТРИ {system_boundary}. Внешние системы = клиенты/адаптеры.
- НЕ реализуй код внешних систем. Реализуй НАШУ сторону интеграции:
адаптеры, клиенты, маппинг протоколов.
- Для тестов: mock/stub внешних систем, НЕ реальные вызовы.
- Если TASK требует работу с external system — реализуй клиент/адаптер
на нашей стороне, не сервер/логику внешней системы.
═══════════════════════════════════════════
РАБОЧАЯ ДИРЕКТОРИЯ (WORKTREE)
═══════════════════════════════════════════
⚠️ Ты работаешь в git worktree!
WORKTREE_PATH: {worktree_path}
EXPECTED_BRANCH: {expected_branch} ← для OPS-001 PRE-COMMIT GUARD
POLISADE_GIT_BRANCHING: true ← обязательный mode-signal для guard
ПРАВИЛА:
1. ВСЕ операции с кодом — в WORKTREE_PATH
2. Команды: cd "{worktree_path}" && <команда>
3. .state/ файлы: {worktree_path}/.state/ (локальная копия)
4. НЕ переключай ветки! Worktree привязан к одной ветке.
5. git commit/push — только после PRE-COMMIT GUARD (см. секцию ниже).
6. НЕ создавай новые артефакты (TASK/FEAT/ADR) — counters.json недоступен.
Если нужен новый артефакт → верни waiting_pm.
7. Бери команды тестирования/линтинга из knowledge.json (testing.*).
НЕ изобретай команды — используй ТОЛЬКО то, что задано в проекте.
Примеры вызова в worktree для разных стеков:
# Python (если .venv/ есть в worktree через симлинк)
cd "{worktree_path}" && .venv/bin/pytest tests/ -x -q
cd "{worktree_path}" && .venv/bin/ruff check src/
# Java/Scala (Gradle)
cd "{worktree_path}" && ./gradlew test
cd "{worktree_path}" && ./gradlew check
# Node.js/TypeScript
cd "{worktree_path}" && npm test
cd "{worktree_path}" && npx eslint .
cd "{worktree_path}" && npx tsc --noEmit
# Go
cd "{worktree_path}" && go test ./...
cd "{worktree_path}" && golangci-lint run
# Rust
cd "{worktree_path}" && cargo test
cd "{worktree_path}" && cargo clippy
⛔ ЗАПРЕЩЕНО:
⛔ Абсолютные пути: /Users/.../Projects/.../.venv/bin/python
⛔ Изобретать команды — бери из knowledge.json (testing.*)
⛔ Присвоение в начале: WT="/path" && cd "$WT" && ...
{Если .venv/ присутствует в worktree — дополнительные Python-ограничения:}
⛔ python -m <tool>: .venv/bin/python -m pytest (вызывай инструмент напрямую)
⛔ python -c "...": .venv/bin/python -c "import ..."
⛔ Голый pytest/ruff/mypy без .venv/bin/ (без активации venv — не на PATH!)
(Блок добавляется в prompt ТОЛЬКО при workspaceMode: "worktree".
Если worktree не используется — блок не включать.)
═══════════════════════════════════════════
КОМАНДЫ ДЛЯ ТЕСТИРОВАНИЯ И ПРОВЕРОК
═══════════════════════════════════════════
{Блок добавляется ТОЛЬКО если хотя бы одно поле testing.* заполнено в knowledge.json}
Используй ИМЕННО эти команды (из knowledge.json), НЕ изобретай свои:
Тесты: {testing.testCommand или "НЕ ЗАДАНО — регрессионные тесты будут пропущены"}
Type check: {testing.typeCheckCommand или "не задано"}
Lint: {testing.lintCommand или "не задано"}
Для worktree всегда добавляй cd "{worktree_path}" && перед командой.
⛔ ЗАПРЕЩЕНО (для worktree):
ПРАВИЛЬНО: cd "{worktree_path}" && {testing.testCommand}
ПРАВИЛЬНО: cd "{worktree_path}" && ./gradlew test
ПРАВИЛЬНО: cd "{worktree_path}" && npm test
НЕПРАВИЛЬНО: cd "{worktree_path}" && /абсолютный/путь/к/инструменту (абсолютные пути!)
НЕПРАВИЛЬНО: cd "{worktree_path}" && выдуманная-команда (только из knowledge.json!)
НЕПРАВИЛЬНО: WT="/path" && cd "$WT" && ... (присвоение в начале запрещено!)
{Если testing.strategy == "tdd-first" И testCommand задан И task-scoped run разрешим — инлайнить блок ниже.
Если testing.strategy == "test-along", отсутствует, testCommand не задан, или task-scoped run невозможен — НЕ включать этот блок.
Source-of-truth: references/test-authoring-protocol.md}
═══════════════════════════════════════════
⛔ TDD-FIRST ПРОТОКОЛ (testing.strategy: "tdd-first")
═══════════════════════════════════════════
Ты ОБЯЗАН реализовать задачу в ДВА ЭТАПА:
### ЭТАП 1: RED — ТЕСТЫ (до написания кода реализации)
Источники тестов (по приоритету):
1. Gherkin scenarios из SPEC (FR-NNN → Given/When/Then) — каждый Scenario → 1 тест
2. Acceptance criteria checklist из TASK — каждый AC → минимум 1 тест
3. Design contracts из design_refs (api.md, data-model.md) → контрактные тесты
4. Assumptions/constraints из SPEC §4 → defensive/negative тесты
Действия:
1. Сгенерируй тесты, покрывающие ВСЕ источники выше
2. Запусти ТОЛЬКО новые тесты (task-scoped run):
- Команда из секции ## Verification в TASK (первая тестовая команда)
- Или derive file-scoped: pytest → `pytest tests/test_<module>.py`, jest → `jest <file>`, etc.
3. Классифицируй падения:
- Syntax/import/compilation error → ИСПРАВЬ harness, перезапусти
- Assertion failures → ОК, это ожидаемый red
- Все тесты прошли (vacuous pass) → ⚠️ Проверь что тесты реально тестируют новое поведение
4. Перед коммитом выведи RED CHECKLIST:
```
───────────────────────────────────────────
RED CHECKLIST (test-authoring)
───────────────────────────────────────────
[✓/✗] Добавлены/обновлены только тесты и минимальный harness (stubs)
[✓/✗] Новые тесты компилируются/парсятся без ошибок
[✓/✗] Новые тесты падают по ожидаемой причине (assertion failures, NOT import/syntax error)
[✓/✗] Production code НЕ реализован на этом этапе
[✓/✗] Источники тестов: покрыты все AC и Gherkin из TASK/SPEC
───────────────────────────────────────────
```
5. Коммит: `[{TASK-ID}] Add failing tests for {TASK-ID}`
⛔ НЕ ПИШИ КОД РЕАЛИЗАЦИИ НА ЭТОМ ЭТАПЕ!
Только тестовые файлы + минимальные stubs (пустые функции/классы) чтобы тесты компилировались.
### ЭТАП 2: GREEN — РЕАЛИЗАЦИЯ (чтобы тесты прошли)
1. Напиши код, который делает тесты из этапа 1 зелёными
2. Можно добавить дополнительные edge-case тесты
3. Все тесты (из этапа 1 + новые) должны проходить
4. Выполни полный SELF-REVIEW CHECKLIST (см. ниже)
5. Коммит: `[{TASK-ID}] Implement {TASK-ID}`
⛔ ПРАВИЛО ФИЛЬТРАЦИИ:
- Red phase: допустима фильтрация (file/test target) — ТОЛЬКО новые тесты
- Regression (шаг 2 полного цикла): фильтрация ЗАПРЕЩЕНА — без изменений
═══════════════════════════════════════════
═══════════════════════════════════════════
SELF-REVIEW (ОБЯЗАТЕЛЬНО ВЫВЕСТИ перед коммитом!)
═══════════════════════════════════════════
⛔ ПЕРЕД КОММИТОМ ты ОБЯЗАН:
1. Перечитать ВСЕ изменённые файлы (используй Read tool)
2. ВЫВЕСТИ этот чеклист с результатами проверки:
```
───────────────────────────────────────────
SELF-REVIEW CHECKLIST
───────────────────────────────────────────
[✓/✗] Hardcoded values: нет паролей/ключей/URL
[✓/✗] Error handling: async обёрнут в try/catch
[✓/✗] Patterns: код соответствует patterns
[✓/✗] Anti-patterns: нет нарушений antiPatterns
[✓/✗] Terminology: имена классов/функций/полей соответствуют knowledge.glossary
(нет синонимов для канонических терминов; нет имён из synonyms_to_avoid)
[✓/✗] Tests: тесты добавлены/обновлены
[✓/✗] TDD: тесты написаны ДО реализации (если testing.strategy: "tdd-first")
RED CHECKLIST пройден | Коммит 1: failing tests | Коммит 2: implementation
(N/A если strategy: "test-along")
[✓/✗] Каждое composite FR/NFR из требований реализовано в коде (поштучно):
✓/✗ SPEC-001.FR-001: <EARS statement> → <file:function>
✓/✗ SPEC-001.FR-002: <EARS statement> → <file:function>
... (по списку TASK.requirements, composite IDs из parent SPEC/PRD/FEAT)
[✓/✗] DESIGN CONFORMANCE (если design_refs non-empty):
⛔ Агентского обхода НЕТ: флаг design_waiver этой проверкой не
читается (issue #205). Waiver дрейфа — только ревьюируемый
артефакт docs/waivers/DRIFT-WAIVER-NNN.md, его создаёт PM, а
читает scripts/polisade_drift_gate.py — не ты.
Для каждого файла из design_refs:
✓/✗ <artifact>: реализация совпадает с контрактом
Если есть расхождение (DESIGN-DEVIATION):
⛔ ОБЯЗАТЕЛЬНО:
1. Обнови затронутый design-артефакт в ТОМ ЖЕ коммите/PR
(design docs — source of truth, drift недопустим)
2. Добавь в PR description секцию "Design Updates":
## Design Updates
- DESIGN-NNN/api.md: <что изменилось>
- DESIGN-NNN/data-model.md: <что изменилось>
3. DESIGN-DEVIATION комментарий в коде — audit trail, НЕ удалять
(N/A только если design_refs пуст)
[✓/✗] Acceptance criteria (ПОШТУЧНО):
✓/✗ AC1: <описание> → <file:line>
✓/✗ AC2: <описание> → <file:line>
... (каждый критерий отдельно!)
───────────────────────────────────────────
```
3. Если хотя бы один [✗] — ИСПРАВЬ перед коммитом
4. После исправления — повтори self-review
⚠️ КОММИТ БЕЗ ВЫВОДА CHECKLIST = НАРУШЕНИЕ ПРОТОКОЛА!
═══════════════════════════════════════════
ФОРМАТ КОММИТА
═══════════════════════════════════════════
test-along: [{TASK-ID}] краткое описание
tdd-first коммит 1: [{TASK-ID}] Add failing tests for {TASK-ID}
tdd-first коммит 2: [{TASK-ID}] Implement {TASK-ID}
⚠️ Перед КАЖДЫМ коммитом — обязательный PRE-COMMIT GUARD (OPS-001), см. ниже.
═══════════════════════════════════════════
⛔ ЗАПРЕЩЁННЫЕ git-команды (HARD BOUNDARIES)
═══════════════════════════════════════════
В рамках реализации TASK ты работаешь ТОЛЬКО в своей feature-ветке
(или worktree, привязанном к ней). ЗАПРЕЩЕНО:
- git checkout main / master / switch main
- git push origin main / origin master / --force в main
- git merge / git rebase onto main
- git branch -D / git push origin --delete
- git commit / git add / git push с current_branch ≠ EXPECTED_BRANCH
(см. PRE-COMMIT GUARD ниже)
- ⛔ NEVER git add -f / git add --force на gitignored путях (.gigacode,
.qwen, .codex, .worktrees и т. д.). «Кроме X» = исключение, не фокус.<!-- polisade:claude-only BEGIN -->
NB: исключение по `.claude/` — только файл `.claude/settings.json`,
не директория целиком.<!-- polisade:claude-only END -->
После self-review ты ВОЗВРАЩАЕШЬ JSON-результат и БОЛЬШЕ НИЧЕГО:
— НЕ ищешь следующую TASK
— НЕ «готовишь main к следующей задаче»
— НЕ запускаешь новый цикл
— НЕ пытаешься сделать merge/push/delete
Твоя задача ОДНА. Возврат управления — это конец.
Если ты запущен для TASK, которая уже в review (PR создан или нет) —
это bug диспетчера основного агента. Верни JSON
{"status":"blocked","reason":"OPS-008: subagent spawned for review-stage TASK"}
и больше ничего не делай. НЕ пытайся «докидать», НЕ пытайся мержить.
Merge — ответственность PM. Если в процессе ты обнаружишь, что main
опередила feature-ветку — НЕ мёржи, верни `waiting_pm` с описанием.
═══════════════════════════════════════════
⛔ PRE-COMMIT GUARD (OPS-001 — ОБЯЗАТЕЛЬНО перед КАЖДЫМ git commit/push/add)
═══════════════════════════════════════════
MODE (POLISADE_GIT_BRANCHING): {git_branching_mode} ← "true" | "false", инжектируется основным агентом
EXPECTED_BRANCH: {expected_branch_or_NA} ← инжектируется ТОЛЬКО при MODE=true
WORK_DIR: {worktree_path_or_NA} ← инжектируется ТОЛЬКО при MODE=true
ПЕРВЫЕ bash-команды в твоей работе (до любого git). Экспортируй ВСЁ, что
дал основной агент — даже если одна из переменных кажется «необязательной»:
```bash
# При MODE=true (gitBranching: true):
export POLISADE_GIT_BRANCHING="true"
export POLISADE_EXPECTED_BRANCH="{expected_branch}"
export POLISADE_WORK_DIR="{worktree_path_or_dot}"
# При MODE=false (gitBranching: false, legacy):
export POLISADE_GIT_BRANCHING="false"
# (POLISADE_EXPECTED_BRANCH и POLISADE_WORK_DIR НЕ устанавливаются)
```
ПЕРЕД каждым `git commit`, `git push`, `git add` ты ОБЯЗАН выполнить:
```bash
MODE="${POLISADE_GIT_BRANCHING:-}"
EXPECTED="${POLISADE_EXPECTED_BRANCH:-}"
WORK="${POLISADE_WORK_DIR:-.}"
case "$MODE" in
true)
if [ -z "$EXPECTED" ]; then
echo "⛔ OPS-001: POLISADE_GIT_BRANCHING=true, но POLISADE_EXPECTED_BRANCH пуст — bug"
exit 1
fi
CURRENT=$(cd "$WORK" && git rev-parse --abbrev-ref HEAD)
if [ "$CURRENT" != "$EXPECTED" ]; then
echo "⛔ OPS-001: cwd=$WORK current=$CURRENT, expected=$EXPECTED — коммит запрещён"
exit 1
fi
echo "✓ pre-commit guard OK: cwd=$WORK branch=$CURRENT"
;;
false)
echo "ℹ️ pre-commit guard skipped: POLISADE_GIT_BRANCHING=false (legacy)"
;;
*)
# fail-closed: отсутствие явного mode-signal = баг (truncation/dropout/bug)
echo "⛔ OPS-001: POLISADE_GIT_BRANCHING не выставлен (ожидалось 'true'|'false'). Commit запрещён."
exit 1
;;
esac
```
⚠️ Критично: `cd "$WORK"` ОБЯЗАТЕЛЕН. В режиме git worktree корень
репозитория остаётся на main/master — это нормальное поведение. Ветка
задачи видна ТОЛЬКО внутри `{worktree_path}`. Без `cd` guard даст ложный
fail.
⚠️ **Fail-closed модель.** Отсутствие `POLISADE_GIT_BRANCHING` НЕ трактуется как
"безопасно". Для legacy-режима основной агент ОБЯЗАН явно выставить
`POLISADE_GIT_BRANCHING=false`; пустое/неизвестное значение mode = баг (truncation
prompt-а, dropout инструкций, забытый export) → guard fail-closed, коммит
запрещён. Это защита ровно от того класса ошибок, которые вызвали OPS-001.
Если guard упал — НЕ ретрай, НЕ `git checkout`, НЕ создавай ветку сам,
НЕ пытайся "починить" через `export POLISADE_GIT_BRANCHING=false` — это
реинтродукция OPS-001. Верни JSON:
```json
{"status": "blocked", "reason": "OPS-001: mode=<mode> expected=<expected> current=<current> cwd=<work>"}
```
═══════════════════════════════════════════
⛔ POST-PUSH VERIFICATION (OPS-028 — после КАЖДОГО git push)
═══════════════════════════════════════════
После `git push` ОБЯЗАТЕЛЬНО использовать:
```bash
python3 {plugin_root}/scripts/polisade_vcs.py git-push \
--branch "$POLISADE_EXPECTED_BRANCH" \
--project-root "$POLISADE_WORK_DIR"
# (в Phase C при первом пуше новой ветки добавь --set-upstream)
```
**Никогда** не ограничивайся bare `git push` — Bitbucket Server (и иногда
GitHub) возвращают `exit 0` даже когда pre-receive/post-receive hook
или DB-constraint отказали в приёме коммита через `remote: fatal` /
`remote: ERROR` / `pre-receive hook declined` / `value too long for type` /
`duplicate key value`. Хелпер сверяет локальный branch SHA
(`refs/heads/<branch>`, НЕ `HEAD`) с remote SHA и сканирует stdout+stderr
на известные failure-паттерны.
- `exit=0` → push verified, можно продолжать.
- `exit=2` → push verification failed. НЕ ставь `done`/`review` — ставь
**`waiting_pm`**, в `waitingForPM` процитируй `remote_lines` и `reason`
из JSON-вывода. STOP.
Контракт: OPS-028 / issue #75.
═══════════════════════════════════════════
ВЕРНИ В КОНЦЕ
═══════════════════════════════════════════
После завершения верни структурированный ответ:
РЕЗУЛЬТАТ (верни СТРОГО в JSON формате):
```json
{
"status": "code_complete | blocked | waiting_pm",
"files_changed": ["path/to/file1.ts", "path/to/file2.ts"],
"commit_hash": "abc1234",
"commits": [
{"phase": "tests_red", "hash": "abc1234"},
{"phase": "implementation", "hash": "def5678"}
],
"learnings": ["новый паттерн или особенность проекта"],
"questions": ["вопрос к PM, если статус waiting_pm"]
}
```
- `commit_hash` = финальный implementation commit (backward-compatible)
- `commits` = optional массив с фазами (при test-along: один элемент `{"phase": "implementation", "hash": "..."}`)
⚠️ НЕ ВОЗВРАЩАЙ status: "done"! Только code_complete.
done ставится ТОЛЬКО PM-ом после merge PR!
```
### 4. Запуск (субагент или напрямую)
**Если M/L-задача** — используй Task tool (как раньше):
```
Task tool:
subagent_type: "general-purpose"
description: "Implement TASK-XXX"
prompt: [сформированный prompt]
```
**Если S-задача** — реализуй напрямую:
⚠️ **[OPS-001 GUARD]** В S-task direct path основной агент работает напрямую,
без субагента — НО те же правила: все `Read`/`Edit`/`Bash` выполняются внутри
`$POLISADE_WORK_DIR` (в worktree mode = `worktree_path`), и ПЕРЕД каждым коммитом
обязателен **pre-commit guard**:
```bash
MODE="${POLISADE_GIT_BRANCHING:-}"
EXPECTED="${POLISADE_EXPECTED_BRANCH:-}"
WORK="${POLISADE_WORK_DIR:-.}"
case "$MODE" in
true)
[ -n "$EXPECTED" ] || { echo "⛔ OPS-001: MODE=true но EXPECTED пуст"; exit 1; }
CURRENT=$(cd "$WORK" && git rev-parse --abbrev-ref HEAD)
[ "$CURRENT" = "$EXPECTED" ] \
|| { echo "⛔ OPS-001: cwd=$WORK current=$CURRENT, expected=$EXPECTED"; exit 1; }
;;
false)
echo "ℹ️ pre-commit guard skipped: POLISADE_GIT_BRANCHING=false (legacy)"
;;
*)
# Fail-closed: отсутствие явного POLISADE_GIT_BRANCHING = bug (Шаг 1.7 не
# экспортировал). НЕ интерпретируй это как "безопасно".
echo "⛔ OPS-001: POLISADE_GIT_BRANCHING не выставлен — commit запрещён"
exit 1
;;
esac
```
Если guard упал — STOP, не коммитить. Вернуть статус `blocked` с причиной.
Только явный `POLISADE_GIT_BRANCHING=false` пропускает guard; пустой/неизвестный
mode — сигнал бага Шага 1.7, fail-closed по дизайну.
**При testing.strategy == "tdd-first" (и testCommand задан, и task-scoped run возможен):**
0. **LOCALIZE (шаг 1.8)** — детерминированный grep-протокол (термины →
символы → ссылки), выведи артефакт
локализации ДО чтения файлов и правок
1. Прочитай затрагиваемые файлы (Read tool) — только цели из LOCALIZE
2. Напиши тесты по источникам (Gherkin/AC/contracts/assumptions)
3. Запусти task-scoped тесты — убедись что падают на assertions (не на import/syntax)
4. Выведи RED CHECKLIST
5. **Pre-commit guard (OPS-001)** → Коммит: `[{TASK-ID}] Add failing tests for {TASK-ID}`
6. Внеси изменения в код (Edit tool) — тесты должны стать зелёными
7. Выполни полный SELF-REVIEW CHECKLIST (ОБЯЗАТЕЛЬНО!)
8. **Pre-commit guard (OPS-001)** → Коммит: `[{TASK-ID}] Implement {TASK-ID}`
**При testing.strategy == "test-along" (или не задан, или fallback):**
0. **LOCALIZE (шаг 1.8)** — детерминированный grep-протокол (термины →
символы → ссылки), выведи артефакт
локализации ДО чтения файлов и правок
1. Прочитай затрагиваемые файлы (Read tool) — только цели из LOCALIZE
2. Внеси изменения (Edit tool)
3. Напиши/обнови тесты
4. Выполни self-review checklist (ОБЯЗАТЕЛЬНО!)
5. **Pre-commit guard (OPS-001)** → Коммит: `[{TASK-ID}] краткое описание`
Self-review checklist и pre-commit guard обязательны для ОБОИХ стратегий.
═══════════════════════════════════════════════════════════════════
═══ OPS-010: КОНТРАКТ ВИДОВ КОММИТОВ (issue #58) ═══
═══════════════════════════════════════════════════════════════════
За один прогон `/polisade:implement` на TASK разрешены ТОЛЬКО следующие виды
коммитов (подсчёт ведётся по именам — агенты надёжнее считают имена, чем
общие итоги):
| Вид | Шаблон сообщения | Что внутри | Когда |
|---|---|---|---|
| `implementation` | `[{TASK-ID}] {desc}` (варианты для TDD/регрессии: `Add failing tests for …`, `Implement …`, `Fix regression: …`) | Source + tests + **все** правки frontmatter TASK.md + движения задачи в PROJECT_STATE.json — **staged вместе**. Переход `ready → in_progress` бандлится сюда. | Коммит(ы) субагента на шаге реализации. |
| `improvement` | `[{TASK-ID}] Address review feedback: {summary}` | Исправления кода + любые отложенные правки status. | IMPROVE-ветка review-loop (`commit_and_push()`). Бандлит любую ожидающую правку status. |
| `finalize` | Две допустимые формы: `[{TASK-ID}] Finalize status: {new-status} (PR #{N})` — когда PR был создан в этом прогоне; ИЛИ `[{TASK-ID}] Finalize status: {new-status}` (без PR-суффикса) — когда терминальный путь срабатывает до появления PR. Форма regex: `^\[{TASK-ID}\] Finalize status: \S+( \(PR #\d+\))?$`. | ТОЛЬКО frontmatter TASK.md (`status:` + любые PR-метаданные — `pr_url`, `prId`, `prNumber` — добавленные в том же терминальном шаге) + движение задачи в PROJECT_STATE.json. **НЕ** код, **НЕ** скрипты, **НЕ** `lastUpdated`, **НЕ** посторонние поля. | Только при терминальном выходе skill, когда у финального status-перехода НЕТ семантического коммита, в который можно было бы его забандлить. Покрывает оба случая: (а) post-PR terminal (PR создан → `review_mode=off/blocked`, max-iteration `waiting_pm`) — форма с `(PR #{N})`; (б) pre-PR terminal (`pr-create` failure → `waiting_pm`; любой другой `blocked`/`waiting_pm` до создания PR) — форма без суффикса. **Максимум один `finalize`-коммит на один прогон skill.** |
**ЗАПРЕЩЕНО:**
- Любой промежуточный (не-терминальный) status-only коммит. Если агент
собирается сделать коммит, в diff которого только строки `status:`
и/или movement в PROJECT_STATE.json, А в этом же прогоне skill
будет следующий `commit_and_push()` / improvement / PR-шаг — **бандли с
ним, не разделяй**.
- Любой коммит, пишущий `lastUpdated` в PROJECT_STATE.json. Шаблон
`finalize` запрещает это по форме diff, и отдельный guard (ниже)
запрещает саму запись поля.
- Буквальные шаблоны сообщений `Update status to …`,
`Update PROJECT_STATE.json lastUpdated …` — отпечатки бага из bug-report
issue #58, забанены на уровне линтера.
- Больше одного `finalize`-коммита на прогон `/polisade:implement`.
**⛔ НЕ пиши `lastUpdated` в PROJECT_STATE.json — поле зарезервировано,
всегда `null` (OPS-010 / issue #58). Для времени последнего изменения
используй `git log -1 --format=%cI .state/PROJECT_STATE.json`.**
═══════════════════════════════════════════════════════════════════
### 5. Обработка результата субагента
После завершения субагента:
0. **Валидация ответа**: парси JSON из ответа субагента. Проверь:
- `status` — одно из: `code_complete`, `blocked`, `waiting_pm`
- `files_changed` — непустой массив (для code_complete)
- `commit_hash` — непустая строка (для code_complete)
- `commits` — optional массив `[{"phase": "...", "hash": "..."}]` (при tdd-first: 2 элемента)
- Если JSON не парсится — извлеки данные из текста как fallback
1. **Обнови PROJECT_STATE.json И frontmatter в .md файле**:
- Если `code_complete` → TASK статус `in_progress`, добавить в `inProgress`
- Если `blocked` → TASK в `blocked`, добавить причину
- Если `waiting_pm` → TASK в `waitingForPM`, добавить вопрос
**⚠️ При КАЖДОМ изменении статуса TASK — обновляй ОБА источника:**
```
# После code_complete
Edit task .md: status: ready → status: in_progress
Update PROJECT_STATE.json: task → inProgress
# OPS-010: frontmatter + PROJECT_STATE правки бандлятся в `implementation`
# commit субагента (тот же коммит, что несёт код/тесты) — НЕ отдельный
# status-only commit. Перехода `ready → in_progress` это обязательное
# место бандлинга. НЕ пиши lastUpdated.
# После создания PR
Edit task .md: status: in_progress → status: review
Update PROJECT_STATE.json: task → inReview
# OPS-010: эта правка идёт либо в следующий `commit_and_push()` (если
# будет review-loop IMPROVE), либо — при терминальном выходе без
# следующего коммита — в единственный `finalize` commit
# `[TASK-ID] Finalize status: review (PR #N)`. НЕ пиши lastUpdated.
# Merge выполняет PM вручную
# После merge PM ставит: status: done
```
Это критично для `/polisade:sync` — source of truth = .md frontmatter.
```
⛔ /polisade:implement НЕ ставит done и НЕ мержит!
Последовательность статусов в /polisade:implement:
ready → in_progress → review → STOP
↑ ↑
│ └── после создания PR и прохождения review
└── после написания кода (code_complete)
done ставит PM после merge
```
2. **ПРОДОЛЖИ ПОЛНЫЙ ЦИКЛ** (см. секцию "Полный автономный цикл"):
- Прогони regression tests
- Создай PR
- Дождись review
- STOP — merge выполняет PM
3. **Обнови knowledge.json** (если субагент вернул learnings):
```json
{
"learnings": [
{
"task": "TASK-001",
"date": "2026-01-31",
"learning": "В этом проекте используется custom error class"
}
]
}
```
3. **Продолжи полный цикл** (см. следующую секцию)
## Полный автономный цикл (после реализации)
После успешной реализации кода автоматически выполняй полный цикл:
```
┌─────────────────────────────────────────────────────────────┐
│ ПОЛНЫЙ ЦИКЛ TASK │
│ │
│ 1. IMPLEMENT ─────────────────────────────────────────────│
│ • [OPS-001 GUARD] Branch/worktree setup (Шаг 1.7) — │
│ ОБЯЗАТЕЛЬНО ДО редактирования файлов. Invariant: │
│ current_branch(WORK_DIR) == compute_expected_branch(TASK)│
│ • Test authoring (см. references/test-authoring-protocol.md) │
│ tdd-first: 1a RED (failing тесты, RED CHECKLIST, │
│ [pre-commit guard], коммит) → │
│ 1b GREEN (код, SELF-REVIEW, │
│ [pre-commit guard], коммит) — 2 коммита│
│ test-along: код + тесты, [pre-commit guard], 1 коммит │
│ ↓ │
│ 2. REGRESSION TEST (см. «Протокол регрессионного │
│ тестирования» ниже) │
│ • Запустить ВСЕ тесты (без -k, без фильтрации!) │
│ • Сравнить падения с testing.knownFlakyTests │
│ — Известные (в knownFlakyTests) → игнорировать │
│ — Новые → исправить, [pre-commit guard], коммит, │
│ повторить │
│ • Type check если testing.typeCheckCommand задан │
│ • Lint (ruff/eslint) если настроен │
│ • Drift-gate: python3 scripts/polisade_drift_gate.py │
│ (если файл есть) — exit≠0 = дрейф arch↔code, │
│ устранить ДО PR (правило 8 протокола) │
│ • Если всё ОК → продолжить │
│ ↓ │
│ 3. PR ────────────────────────────────────────────────────│
│ • [pre-commit guard] Push ветки на remote │
│ • Создать Pull Request │
│ • Статус TASK → review │
│ ↓ │
│ 3.5. PRE-CHECK: REVIEWER CLI ───────────────────────────│
│ • python3 scripts/polisade_cli_caps.py detect │
│ → reviewer.mode = codex | self | blocked │
│ • mode=blocked → STOP с диагностикой │
│ ↓ │
│ 4. QUALITY REVIEW (Independent) ─────────────────────────│
│ • /polisade:review-pr [self] для независимого ревью │
│ • Ревьюер оценивает PR vs TASK │
│ • Если score >= 8 (PASS): │
│ - Статус TASK → review (PR готов к merge) │
│ - STOP — merge выполняет PM │
│ • Если score < 8 (IMPROVE): │
│ - Improvement субагент исправляет код │
│ - [pre-commit guard] commit_and_push │
│ - Re-review (макс. 2 итерации) │
│ ↓ │
│ 5. STOP (hard boundary) ─────────────────────────────────│
│ • /polisade:implement завершает работу после ОДНОЙ задачи │
│ • Feature-ветка СОХРАНЯЕТСЯ (её удалит merge PR) │
│ • ⛔ ЗАПРЕЩЕНО после этой точки: │
│ — искать следующую TASK / запускать новый цикл │
│ — повторно вызывать /polisade:implement в этой сессии │
│ — git checkout main / git push origin main │
│ — git merge / git branch -D / git push --delete │
│ • Merge выполняет только PM или /polisade:continue │
│ • Легитимные next-steps для PM (одно из): │
│ — merge PR → TASK выйдет из review/активных │
│ — /polisade:continue (PM явно запускает, уже знает про │
│ активные TASK и resume-логику) │
│ • ⛔ НЕ «в новой сессии /polisade:implement TASK-YYY»: │
│ re-invocation guard читает frontmatter + state, а НЕ │
│ сессию — всё равно заблокирует │
└─────────────────────────────────────────────────────────────┘
```
### Протокол регрессионного тестирования
**⛔ Этот протокол ОБЯЗАТЕЛЕН на шаге 2 (REGRESSION TEST) полного цикла.**
#### Правила
1. **Таймаут**: Используй `timeout: 600000` (10 мин) для Bash-вызовов тестов. Для pytest добавляй `--timeout=120` если `pytest-timeout` доступен в проекте.
2. **ЗАПРЕЩЕНО `-k` и любая фильтрация**: Запускай ВСЕ тесты. Никаких `-k "not ..."`, `--ignore`, `--deselect` для обхода падающих тестов. Цель — увидеть полную картину.
3. **Сравнение с known failures**: Прочитай `testing.knownFlakyTests` из `.state/knowledge.json`. Классифицируй каждое падение:
- **Известное** (тест есть в `knownFlakyTests`) → игнорировать, продолжить
- **Новое** (теста нет в `knownFlakyTests`) → это регрессия, ИСПРАВИТЬ до PR
4. **Проверка типов**: Если `testing.typeCheckCommand` задан в knowledge.json — запустить его. Иначе — пропустить с предупреждением.
5. **Линтинг**: Если `testing.lintCommand` задан в knowledge.json — запустить его.
6. **Обработка таймаута**: Если тесты зависли (Bash timeout) — зафиксировать факт зависания в выводе и продолжить к PR. **НЕ перезапускать** ту же команду. Не блокировать весь цикл из-за зависших тестов.
7. **Обновление knownFlakyTests**: Если обнаружены pre-existing падения, которых НЕТ в `knownFlakyTests` — добавить их в `.state/knowledge.json` **основного репо** (не worktree-копии):
```json
{
"test": "test_module::test_name",
"reason": "Краткое описание причины",
"date": "2026-02-16"
}
```
8. **Drift-gate (детерминированный, issue #205)**: Если в проекте есть
`scripts/polisade_drift_gate.py` — запусти `python3 scripts/polisade_drift_gate.py`
из корня проекта (в worktree mode — из worktree). Exit≠0 = дрейф arch↔code =
регрессия, устранить **до PR** одним из двух способов:
- привести код в соответствие design-артефактам, ИЛИ
- обновить design-артефакт в том же PR (DESIGN-DEVIATION протокол из
SELF-REVIEW + секция "Design Updates" в PR description).
⛔ Флаг `design_waiver` гейт НЕ читает — агентского обхода не существует.
Временный пропуск дрейфа — только ревьюируемый артефакт
`docs/waivers/DRIFT-WAIVER-NNN.md` (обоснование + срок `expires`), его
создаёт и утверждает PM. Тебе создавать waiver ЗАПРЕЩЕНО: если дрейф
нельзя устранить в рамках TASK — статус `waiting_pm` с отчётом гейта
(`--json`) в комментарии.
9. **Приёмка (best-effort, если заведена)**: если в корне проекта есть
`acceptance/ACCEPTANCE.md` — после зелёной регрессии предложи прогон
`/polisade:acceptance run` (по красным — `/polisade:acceptance repair`).
Регрессия отвечает на вопрос «не сломали ли соседнее», приёмка — «получил
ли заказчик то, что просил»; одно другое не заменяет. Файла нет — скажи об
этом ОДНОЙ строкой («приёмка не заведена — `/polisade:acceptance author`»)
и не блокируй цикл.
⛔ Сам файл приёмки в рамках `/polisade:implement` **не правь**: это образ
результата, его пишет человек. Правка проверок ради зелени — ровно тот
путь, которым промптовая приёмка и обесценивается.
### Алгоритм автономного цикла
```python
def compute_expected_branch(task):
"""Правила — в секции 'Git Branching' ниже (source of truth).
parent:PLAN-* → plan/PLAN-XXX-TASK-YYY-<slug>
parent:FEAT-* → feat/FEAT-XXX-<slug>
parent:BUG-* → fix/BUG-XXX-<slug>
parent:DEBT-* → debt/DEBT-XXX-<slug>
parent:CHORE-*→ chore/CHORE-XXX-<slug>"""
...
def assert_expected_branch(expected, worktree_path):
"""[OPS-001 GUARD] Pre-commit guard. Проверяет ветку ВНУТРИ worktree_path,
а не в project_root — в worktree mode корень остаётся на main, это ок."""
if expected is None:
return # gitBranching: false — инвариант отключён
cwd = worktree_path or "."
current = run(f'cd "{cwd}" && git rev-parse --abbrev-ref HEAD').stdout.strip()
if current != expected:
raise RuntimeError(f"OPS-001: cwd={cwd} current={current}, expected={expected}")
def full_task_cycle(task_id):
# 0. Read strategy (см. references/test-authoring-protocol.md)
knowledge = read_json(".state/knowledge.json")
raw_strategy = knowledge.get("testing", {}).get("strategy")
test_cmd = knowledge.get("testing", {}).get("testCommand")
# Нормализация strategy
if raw_strategy is None:
log("⚠️ testing.strategy не задан в knowledge.json. Используем test-along.")
strategy = "test-along"
elif raw_strategy not in ("tdd-first", "test-along"):
log(f"⚠️ Неизвестное значение testing.strategy: '{raw_strategy}'. Fallback на test-along.")
strategy = "test-along"
else:
strategy = raw_strategy
# Guard: tdd-first requires testCommand + task-scoped run
if strategy == "tdd-first" and not test_cmd:
log("⚠️ testing.strategy=tdd-first но testCommand не задан. Fallback на test-along.")
strategy = "test-along"
if strategy == "tdd-first":
task_verification = read_task_verification_section(task_id) # ## Verification из TASK
# 1) парсит ## Verification (первая тестовая команда)
# 2) если нет — derive file-scoped из test_cmd
task_scoped_cmd = resolve_task_scoped_run(task_id, task_verification, test_cmd)
if not task_scoped_cmd:
log("⚠️ Невозможно derive task-scoped run. Fallback на test-along.")
strategy = "test-along"
# 1. Implement (Шаг 1.7 [OPS-001 GUARD])
task = read_task(task_id)
worktree_path = setup_worktree_or_branch(task_id) # worktree or checkout -b
# [OPS-001] Expected-branch invariant: post-setup assertion + env export.
# POLISADE_GIT_BRANCHING — positive mode-signal, ВСЕГДА экспортируется ("true"|"false").
# Bash-guard читает его первым: отсутствие = fail-closed (защита от prompt truncation).
expected_branch = compute_expected_branch(task) if settings.gitBranching else None
assert_expected_branch(expected_branch, worktree_path)
if expected_branch is not None:
export_env("POLISADE_GIT_BRANCHING", "true")
export_env("POLISADE_EXPECTED_BRANCH", expected_branch)
export_env("POLISADE_WORK_DIR", worktree_path or project_root)
else:
export_env("POLISADE_GIT_BRANCHING", "false")
# POLISADE_EXPECTED_BRANCH и POLISADE_WORK_DIR НЕ выставляются — guard видит
# mode=false и pass-through по дизайну.
if strategy == "tdd-first":
# 1a. Red phase
write_tests_from_sources(task_id) # Gherkin → AC → contracts → assumptions
result = run(task_scoped_cmd) # targeted run, NOT full suite
# Классификация причин падения
if result.errors: # syntax error, import error, compilation failure
log("⚠️ Тесты не компилируются/не парсятся. Исправь harness.")
fix_compilation_errors()
result = run(task_scoped_cmd)
if result.all_passed:
log("⚠️ Все тесты прошли сразу (vacuous pass). Проверь что тесты тестируют новое поведение.")
assert result.test_failures > 0, "Tests should fail on assertions (red phase)"
assert result.errors == 0, "No syntax/import/compilation errors in red phase"
# RED CHECKLIST → commit
assert_expected_branch(expected_branch, worktree_path) # [OPS-001]
commit(f"[{task_id}] Add failing tests for {task_id}")
# 1b. Green phase
implement_code(task_id)
result = run(task_scoped_cmd)
assert result.failures == 0, "Tests should pass (green phase)"
# SELF-REVIEW CHECKLIST → commit
assert_expected_branch(expected_branch, worktree_path) # [OPS-001]
commit(f"[{task_id}] Implement {task_id}")
else:
# test-along: текущее поведение
implement_code(task_id) # all ops in worktree_path
run_unit_tests_for_task(task_id)
assert_expected_branch(expected_branch, worktree_path) # [OPS-001]
commit_changes(task_id)
# 2. Regression (см. «Протокол регрессионного тестирования»)
# knowledge и test_cmd уже определены в шаге 0
known_flaky = {t["test"] for t in knowledge.get("testing", {}).get("knownFlakyTests", [])}
if not test_cmd:
log("⚠️ testing.testCommand не задан в knowledge.json — регрессионные тесты пропущены.")
log(" Запусти /polisade:init или /polisade:spec чтобы настроить тестовую команду.")
skip_regression = True
else:
skip_regression = False
if not skip_regression:
# В worktree — всегда cd перед командой
if worktree_path and worktree_path != project_root:
full_cmd = f'cd "{worktree_path}" && {test_cmd}'
else:
full_cmd = test_cmd
result = run(full_cmd, timeout=600_000) # 10 мин таймаут
if not skip_regression:
if result.timed_out:
log("⚠️ Тесты зависли (timeout 10 мин). Продолжаем к PR.")
elif result.failures:
new_failures = [f for f in result.failures if f.test_id not in known_flaky]
known_failures = [f for f in result.failures if f.test_id in known_flaky]
if new_failures:
# Исправить ТОЛЬКО новые падения
while new_failures:
fix_failures(new_failures)
assert_expected_branch(expected_branch, worktree_path) # [OPS-001]
commit_fixes()
result = run(test_cmd, timeout=600_000)
if result.timed_out:
log("⚠️ Тесты зависли при повторном запуске. Продолжаем.")
break
new_failures = [f for f in result.failures if f.test_id not in known_flaky]
# Обнаружены pre-existing падения не в knownFlakyTests — добавить
if known_failures:
update_known_flaky_tests(knowledge, known_failures)
# 2b. Type check
type_cmd = knowledge.get("testing", {}).get("typeCheckCommand")
if type_cmd:
if worktree_path and worktree_path != project_root:
type_cmd = f'cd "{worktree_path}" && {type_cmd}'
run(type_cmd, timeout=600_000)
else:
log("ℹ️ Type check пропущен: typeCheckCommand не задан в knowledge.json. Задайте testing.typeCheckCommand для вашего стека (tsc --noEmit, mypy, pyright, и т.д.).")
# 2c. Lint
lint_cmd = knowledge.get("testing", {}).get("lintCommand")
if lint_cmd:
if worktree_path and worktree_path != project_root:
lint_cmd = f'cd "{worktree_path}" && {lint_cmd}'
run(lint_cmd, timeout=600_000)
# 2d. Drift-gate (issue #205, правило 8 протокола) — детерминированная
# сверка arch↔code ДО PR. Флаг design_waiver гейт НЕ читает; waiver —
# только PM-артефакт docs/waivers/DRIFT-WAIVER-NNN.md (агент не создаёт).
work_dir = worktree_path or project_root
if exists(f"{work_dir}/scripts/polisade_drift_gate.py"):
result = run(f'cd "{work_dir}" && python3 scripts/polisade_drift_gate.py',
timeout=600_000)
if result.exit_code != 0:
# Дрейф = регрессия: чинить код ИЛИ обновить design-артефакт
# в том же PR (DESIGN-DEVIATION). Не устраняется в рамках TASK →
# статус waiting_pm с отчётом гейта (--json), НЕ идти на PR.
resolve_drift_or_stop_waiting_pm(result)
# 3. PR (OPS-015: буквальный вызов polisade_vcs.py pr-create — не импровизируй)
assert_expected_branch(expected_branch, worktree_path) # [OPS-001]
push_branch()
# 3a. Собрать тело PR в project-local temp-файл, чтобы не попасть в
# quoting-ад с многострочным --body "...". Путь — относительно pwd
# (worktree root при workspaceMode=worktree, project root при inplace),
# папка .polisade/tmp/ gitignored. /tmp НЕ используется: GigaCode CLI
# sandboxes /tmp через виртуальную FS (~/.gigacode/tmp/<hash>/) и файл,
# записанный одним tool-call'ом, не виден последующему Read/ReadFile
# (issue #57 / legacy OPS-009; см. docs/gigacode-cli-notes.md §4).
run("mkdir -p .polisade/tmp")
PR_BODY_FILE = f".polisade/tmp/pr-body-{TASK_ID}.md"
write(PR_BODY_FILE, f"""\
## Summary
{TASK_TITLE}
{TASK_DESCRIPTION}
## Acceptance
{format_bullets(TASK_ACCEPTANCE)}
## Tests
{TESTS_RUN_SUMMARY}
Ref: tasks/{TASK_ID}-{slug}.md
""")
# 3b. Создать PR. Команда ИДЕНТИЧНА `/polisade:pr create ...` — ровно то,
# что PM запустил бы вручную. Никаких `gh`, `bbs`, `npx codex`,
# `curl` к Bitbucket REST или самостоятельных путей к polisade_vcs.py.
# Собираем bash-команду конкатенацией — `{plugin_root}` лежит в
# plain-string сегменте (без f-строк), чтобы конвертер Qwen/GigaCode
# мог подменить его без конфликтов с Python quoting.
cmd = (
'python3 {plugin_root}/scripts/polisade_vcs.py pr-create '
f'--title "[{TASK_ID}] {TASK_TITLE}" '
f'--body-file "{PR_BODY_FILE}" '
f'--head "{BRANCH}" '
f'--base "{BASE or "main"}" '
'--project-root "${POLISADE_WORK_DIR:-$(pwd)}" '
'--format json'
)
PR_JSON_RC = run(cmd)
# 3c. Failure path: waiting_pm, НЕ blocked (иначе OPS-008 guard §0
# зацикливает при `/polisade:implement <task>` повторно). Сообщение
# обязано содержать "Создайте PR вручную" / "pr_url_request" —
# этот текст ловит early-exit в skills/unblock/SKILL.md.
if PR_JSON_RC.exit_code != 0:
set_status(task_id, "waiting_pm")
update_project_state(task_id, "waitingForPM", reason=(
f"TASK-{task_id}: pr_url_request. Автоматическое создание PR "
f"не удалось (exit={PR_JSON_RC.exit_code}). Ветка '{BRANCH}' "
f"запушена в origin. Создайте PR вручную через web UI и "
f"запустите `/polisade:unblock`, чтобы указать URL. "
f"Для диагностики VCS: /polisade:doctor --vcs"
))
# OPS-010: pre-PR терминальный waiting_pm — PR ещё НЕ создан, суффикса
# `(PR #N)` нет. Бандли set_status + update_project_state в единственный
# `finalize` commit БЕЗ суффикса:
# `[TASK-ID] Finalize status: waiting_pm` (форма без PR-номера).
# diff: только TASK.md frontmatter + PROJECT_STATE.json.
# НЕ пиши lastUpdated. НЕ добавляй код.
return # STOP — никаких git checkout main / branch -D / push --delete
# 3d. Разобрать JSON ответ и зафиксировать pr_url в TASK frontmatter
# (source of truth — та же семантика, что в /polisade:continue Phase C.3).
pr = json.loads(PR_JSON_RC.stdout) # {"url": ..., "number": ..., ...}
write_pr_url_to_task_frontmatter(task_id, pr["url"])
set_status(task_id, "review")
# OPS-010: `set_status(review)` + write_pr_url_to_task_frontmatter —
# НЕ отдельный commit. Если ниже (§3.5) выход через review_mode="blocked"
# или "off" — эта правка идёт в единственный `finalize` commit
# `[TASK-ID] Finalize status: review (PR #{pr.number})` (diff: только
# TASK.md frontmatter + PROJECT_STATE.json; НЕ lastUpdated, НЕ код).
# Если ниже идёт review-loop — бандли в следующий `commit_and_push()`.
# > **Контракт**: эта команда идентична `/polisade:pr create …`. Если
# > автоматический цикл упал — TASK переходит в `waiting_pm` с
# > сообщением "pr_url_request / Создайте PR вручную …";
# > `/polisade:unblock` (без флагов) поймает этот текст в
# > skills/unblock/SKILL.md, попросит PM ввести URL и пропишет
# > `pr_url` в frontmatter TASK. Никогда `gh pr create` / `bbs` /
# > `curl` / `npx @openai/codex` — провайдер определяется из
# > `.state/PROJECT_STATE.json → settings.vcsProvider`, единственная
# > точка вызова — `scripts/polisade_vcs.py pr-create`.
# 3.5. Pre-check: reviewer CLI via OPS-011 helper (single source of truth)
caps = json.loads(run("python3 {plugin_root}/scripts/polisade_cli_caps.py detect").stdout)
review_mode = caps["reviewer"]["mode"] # "codex" | "self" | "blocked" | "off" (OPS-017)
reason = caps["reviewer"].get("reason")
# OPS-007 / issue #55: warn when the helper ignored a codex binary that
# failed identity verification, so the impersonator is visible in logs
# rather than resulting in a silent fallback.
warning = caps["reviewer"].get("warning")
if warning:
print(f"⚠ {warning}")
if review_mode == "blocked":
# OPS-017: reason может указывать на settings-конфликт или отсутствие CLI;
# печатаем его дословно и разветвляем подсказки.
print("═══════════════════════════════════════════")
print("REVIEWER BLOCKED")
print("═══════════════════════════════════════════")
print(f"Reason: {reason or 'no reviewer CLI available'}")
print("")
if reason and "settings" in reason:
print("Проверьте settings.reviewer.mode и settings.reviewer.cli")
print("в .state/PROJECT_STATE.json — текущее значение конфликтует")
print("с доступными CLI в окружении.")
else:
print("Quality review требует CLI ревьюера.")
print("")
print("Варианты:")
print(" • Codex CLI: npm install -g @openai/codex")
print(" • Claude Code: https://docs.anthropic.com/claude-code")
print(" • Qwen CLI: документация Qwen")
print("")
print("TASK остаётся в статусе: review")
print("PR создан, но НЕ замержен.")
print("═══════════════════════════════════════════")
return f"BLOCKED: {reason or 'No reviewer CLI found'}"
if review_mode == "off":
# OPS-017: reviewer отключён в settings. TASK уже в review с PR_URL;
# STOP — PM делает ревью руками и выполняет merge (/polisade:pr merge <id>).
print(f"Reviewer disabled in settings.reviewer.mode. "
f"TASK status=review, PR={pr.url}. "
f"PM manually reviews and merges via /polisade:pr merge <id>.")
return "OFF: reviewer disabled, handed off to PM"
# 4. Quality review (Independent)
# review_mode == "codex" → /polisade:review-pr {PR}
# review_mode == "self" → /polisade:review-pr {PR} self
iterations = 0
while iterations < 2:
review = run_review(pr, task_id, review_mode)
iterations += 1
if review.score >= 8: # PASS
# НЕ мержим автоматически! Merge — ответственность PM
set_status(task_id, "review") # PR готов к merge
# OPS-010: PASS-путь идёт в терминальный STOP без следующего
# коммита. Эту правку (и любую совпадающую движуху в
# PROJECT_STATE.json) бандли в единственный `finalize` commit
# `[TASK-ID] Finalize status: review (PR #{pr.number})`.
# diff ТОЛЬКО frontmatter TASK.md + PROJECT_STATE.json task-bucket.
# НЕ пиши lastUpdated. НЕ добавляй код/скрипты в этот коммит.
break
else: # IMPROVE
run_improvement(pr, review.recommendations)
run_all_tests()
assert_expected_branch(expected_branch, worktree_path) # [OPS-001]
# OPS-028: commit_and_push() =
# git commit ... && python3 {plugin_root}/scripts/polisade_vcs.py git-push \
# --branch <expected_branch> --project-root "$POLISADE_WORK_DIR"
# На exit=2 (push verification failed, remote: fatal/ERROR/rejected) →
# set_status(task_id, "waiting_pm")
# update_project_state(task_id, "waitingForPM",
# reason=f"Push failed: {json['reason']}",
# remote_lines=json['remote_lines'])
# break # НЕ продолжаем итерацию, НЕ ставим done/review
commit_and_push()
else:
# Max iterations — STOP, ждём PM
set_status(task_id, "waiting_pm")
update_project_state(task_id, "waitingForPM",
reason=f"Review ({review_mode}): score {review.score}/10 after 2 iterations")
# OPS-010: терминальный waiting_pm после max-iterations — без следующего
# коммита. Бандли set_status + update_project_state в единственный
# `finalize` commit `[TASK-ID] Finalize status: waiting_pm (PR #{pr.number})`.
# diff: только TASK.md frontmatter + PROJECT_STATE.json (task-bucket +
# waitingForPM reason). НЕ пиши lastUpdated. НЕ добавляй код/скрипты.
# 5. STOP - /polisade:implement завершает работу после одной задачи
# ⛔ ПОСЛЕ ЭТОЙ ТОЧКИ АГЕНТ НЕ ДЕЛАЕТ НИЧЕГО САМ:
# — НЕ ищет следующую TASK
# — НЕ запускает новый цикл full_task_cycle
# — НЕ вызывает /polisade:implement повторно
# — НЕ выполняет git checkout main / push main / merge / branch -D / push --delete
# Управление возвращается PM. Точка.
# См. секцию "⛔ ЗАПРЕЩЁННЫЕ git-команды в /polisade:implement" выше.
print(f"""
═══════════════════════════════════════════
/polisade:implement ЗАВЕРШЁН
═══════════════════════════════════════════
TASK: {task_id} → status=review
PR: {pr_url_or_manual_instruction}
Feature branch: {branch_name} (СОХРАНЕНА — НЕ удалять!)
Дальнейшие действия — ответственность PM (одно из):
• Manual review PR → merge (с флагом --delete-branch).
После merge TASK выйдет из активных → разблокируется /polisade:implement.
• /polisade:continue — PM явно запускает; команда умеет работать
с активными TASK (resume-логика).
НЕ агент сам, НЕ повторный /polisade:implement — guard заблокирует в любой сессии.
⛔ АГЕНТ БОЛЬШЕ НЕ ДЕЙСТВУЕТ в этой сессии:
— не переходит к следующей TASK «самостоятельно»
— не «готовит main к следующей задаче»
— не делает никаких git-операций
═══════════════════════════════════════════
""")
STOP # вернуть управление PM
return "TASK reviewed. PR ready for merge by PM."
```
### Когда прерывать цикл
**⛔ ВАЖНО: /polisade:implement ВСЕГДА останавливается после завершения ОДНОЙ задачи!**
Завершение цикла:
- После успешного review (score >= 8) → STOP, PR готов к merge PM-ом
- `waiting_pm` → STOP, вывести вопрос
- `blocked` → STOP, вывести причину
**НЕ прерывайся** внутри цикла для:
- Падающих тестов — исправь и повтори
- Review замечаний — исправь и повтори
- Merge конфликтов — разреши и продолжи
**Для автономной работы над несколькими задачами используй `/polisade:continue`**
## Git Branching (если включён)
Проверь `settings.gitBranching` и `settings.workspaceMode` в PROJECT_STATE.json.
⚠️ **Эта секция — source of truth для `compute_expected_branch(TASK)`**, которую
использует Шаг 1.7 `[OPS-001 GUARD]` и pre-commit guard во всех commit paths
(prompt субагента, S-task direct path, псевдокод `full_task_cycle`). Изменение
правил branch naming здесь должно сопровождаться обновлением assertion логики
в Шаге 1.7.
### Если gitBranching: true
#### Branch naming (source of truth для compute_expected_branch)
**Для TASK от FEAT/BUG/DEBT/CHORE (стандартный режим):**
- Несколько TASK одного родителя → одна ветка
- `feat/FEAT-XXX-slug`, `fix/BUG-XXX-slug`, `debt/DEBT-XXX-slug`, `chore/CHORE-XXX-slug`
**Для TASK от PLAN (режим плана):**
- Каждая TASK = отдельная ветка
- `plan/PLAN-XXX-TASK-YYY-slug`
**Логика определения режима:**
1. Прочитай `parent` из TASK файла
2. Если parent начинается с `PLAN-` → режим плана (ветка per TASK)
3. Иначе → стандартный режим (ветка per parent)
#### Создание ветки: worktree vs checkout
⚠️ **Алгоритм создания ветки и worktree вынесен в Шаг 1.7 `[OPS-001 GUARD]`**
(строки ~270–395). Эта секция оставлена как reference для branch naming rules
выше — не дублировать здесь алгоритм создания.
Краткая сводка (полный алгоритм с assertion'ами — в Шаге 1.7):
- `workspaceMode: "worktree"` (по умолчанию) → `git worktree add .worktrees/<dir> -b <branch>`,
симлинк `.venv`/`node_modules`/`vendor`, все операции внутри `worktree_path`.
- `workspaceMode: "inplace"` (legacy) → `git checkout -b <branch>` в project_root.
- **Post-setup ОБЯЗАТЕЛЬНО:** assertion `cd "$WORK_DIR" && git rev-parse --abbrev-ref HEAD == <branch>`.
- **Graceful fallback:** если `git worktree add` не проходит → откат на `git checkout -b` с предупреждением (в обоих случаях assertion и export `POLISADE_EXPECTED_BRANCH`/`POLISADE_WORK_DIR` обязательны).
### Если gitBranching: false (legacy)
Инвариант expected-branch **отключён**, но основной агент ОБЯЗАТЕЛЬНО
экспортирует явный positive signal:
```
export POLISADE_GIT_BRANCHING="false"
```
`POLISADE_EXPECTED_BRANCH` и `POLISADE_WORK_DIR` не экспортируются. Pre-commit guard
видит `MODE=false` → pass-through с info-сообщением. Добавь в prompt субагента:
"Коммить прямо в текущую ветку; POLISADE_GIT_BRANCHING=false экспортируй первой
bash-командой."
⛔ **Важно:** отсутствие `POLISADE_GIT_BRANCHING` (вообще не выставлен) bash-guard
трактует как bug — fail-closed. Это защита от truncation/dropout в prompt
(OPS-001 amplification сценарий на слабых моделях). Только явный
`POLISADE_GIT_BRANCHING=false` отключает guard.
## Формат вывода
### Начало работы (M/L-задача)
```
═══════════════════════════════════════════
РЕАЛИЗАЦИЯ: TASK-001
═══════════════════════════════════════════
Задача: Create user API endpoint
Родитель: FEAT-001
Статус: in_progress
Ветка: feat/FEAT-001-user-auth
Worktree: .worktrees/feat__FEAT-001-user-auth/ # если workspaceMode: "worktree"
Контекст из knowledge.json:
• Patterns: Repository pattern, Error as value
• Anti-patterns: no any in TS
• Decisions: PostgreSQL (ADR-001)
Запускаю субагент...
```
### Начало работы (S-задача)
```
═══════════════════════════════════════════
РЕАЛИЗАЦИЯ: TASK-042 (S-задача, напрямую)
═══════════════════════════════════════════
Задача: Update prompt template wording
Родитель: FEAT-005
Размер: S (2 AC, 1 файл)
Реализую напрямую (без субагента)...
```
### При завершении реализации (переход к тестированию)
```
═══════════════════════════════════════════
РЕАЛИЗАЦИЯ ЗАВЕРШЕНА
═══════════════════════════════════════════
ID: TASK-001
Родитель: FEAT-001
Ветка: feat/FEAT-001-user-auth
Изменения:
• src/api/users.ts — создан endpoint
• tests/api/users.test.ts — добавлены тесты
Коммит: abc123
Сообщение: "[TASK-001] Add user API endpoint"
───────────────────────────────────────────
ЗАПУСК REGRESSION TESTS...
───────────────────────────────────────────
```
### При прохождении regression (создание PR)
```
───────────────────────────────────────────
✓ REGRESSION TESTS PASSED
───────────────────────────────────────────
Всего: 142 тестов
Прошло: 140 | Известные падения: 2 | Новые падения: 0
Время: 8.5s
Известные падения (из knownFlakyTests):
• test_external_api_timeout — flaky network mock (2026-01-15)
• test_race_condition — timing-dependent (2026-02-01)
Type check: ✓ mypy src/ --strict (0 ошибок)
Lint: ✓ ruff check src/ (0 замечаний)
───────────────────────────────────────────
СОЗДАНИЕ PR...
───────────────────────────────────────────
PR #45: [TASK-001] Add user API endpoint
URL: https://github.com/org/repo/pull/45
Статус TASK: review
Ожидание code review...
```
### При успешном review (score >= 8)
```
═══════════════════════════════════════════
✓ REVIEW ПРОЙДЕН — PR ГОТОВ К MERGE
═══════════════════════════════════════════
ID: TASK-001
Тип: Feature task
Родитель: FEAT-001
Статус: review
Review score: 9/10
PR #45: ready to merge
URL: https://github.com/org/repo/pull/45
Learnings добавлены в knowledge.json:
• "Используется custom ApiError class"
Worktree: .worktrees/feat__FEAT-001-user-auth/ (сохранён для правок по ревью)
═══════════════════════════════════════════
/polisade:implement завершён
Следующие действия:
→ PM мержит PR: /polisade:pr merge N --squash --delete-branch
→ /polisade:continue — продолжить автономную работу
→ /polisade:state — посмотреть статус проекта
═══════════════════════════════════════════
```
(Блок "Worktree" и "Cleanup" — только при workspaceMode: "worktree")
### При падении тестов (автоисправление)
```
───────────────────────────────────────────
✗ REGRESSION TESTS: НОВЫЕ ПАДЕНИЯ
───────────────────────────────────────────
Всего упало: 4 теста
Известные (knownFlakyTests) — ИГНОРИРУЕМ:
• test_external_api_timeout — flaky network mock
• test_race_condition — timing-dependent
⚠️ Новые падения — ИСПРАВЛЯЕМ:
1. test_user_validation — AssertionError
2. test_auth_middleware — TypeError
Анализирую и исправляю новые падения...
[...исправление...]
Коммит: def456 "[TASK-001] Fix regression test failures"
───────────────────────────────────────────
ПОВТОРНЫЙ ЗАПУСК TESTS...
───────────────────────────────────────────
```
### При review замечаниях (автоисправление)
```
───────────────────────────────────────────
⚠️ CHANGES REQUESTED
───────────────────────────────────────────
PR #45 требует исправлений:
• src/api/users.ts:45 — добавить валидацию email
• tests/api/users.test.ts — покрыть edge case
Исправляю...
[...исправление...]
Коммит: ghi789 "[TASK-001] Address review comments"
Тесты: ✓ passed
Push и обновление PR...
Ожидание повторного review...
```
### При блокировке (waiting_pm)
```
═══════════════════════════════════════════
ЖДЁТ РЕШЕНИЯ PM
═══════════════════════════════════════════
ID: TASK-001
Статус: waiting_pm
Вопрос: Какой формат ответа API использовать?
• Вариант 1: JSON API спецификация
• Вариант 2: Простой JSON
→ /polisade:unblock для ответа
═══════════════════════════════════════════
```
### При технической блокировке (blocked)
```
═══════════════════════════════════════════
ЗАБЛОКИРОВАНО
═══════════════════════════════════════════
ID: TASK-001
Статус: blocked
Причина: Не установлена зависимость xyz
Попытки решения:
• npm install xyz — ошибка версии
• Альтернативная библиотека — не подходит
→ Требуется ручное вмешательство
═══════════════════════════════════════════
```
## Self-review checklist (для субагента)
**⛔ ОБЯЗАТЕЛЬНО ВЫВЕСТИ CHECKLIST перед коммитом!**
Субагент ОБЯЗАН перед коммитом:
1. **Использовать Read tool** — перечитать ВСЕ изменённые файлы, не полагаться на память
2. **ВЫВЕСТИ checklist** в формате:
```
───────────────────────────────────────────
SELF-REVIEW CHECKLIST
───────────────────────────────────────────
[✓] Hardcoded values: проверено, нет паролей/ключей
[✓] Error handling: async обёрнут в try/catch в X, Y, Z
[✓] Patterns: следует Repository pattern
[✓] Anti-patterns: нет any, нет magic numbers
[✓] Tests: добавлено 5 тестов в test_xxx.py
[✓] Acceptance criteria (ПОШТУЧНО):
✓ AC1: API endpoint returns 200 → src/api/handler.py:45
✓ AC2: Error logged on failure → src/api/handler.py:52
✓ AC3: Test covers happy path → tests/test_handler.py:12
───────────────────────────────────────────
Готов к коммиту: ДА
```
3. Если хотя бы один [✗] — **ИСПРАВИТЬ и повторить checklist**
4. Только после всех [✓] — делать коммит
**⚠️ КОММИТ БЕЗ ЯВНОГО ВЫВОДА CHECKLIST = НАРУШЕНИЕ ПРОТОКОЛА!**
Проверки:
- **Hardcoded values**: нет паролей, API ключей, hardcoded URL (кроме localhost)
- **Error handling**: async операции в try/catch, ошибки логируются/пробрасываются
- **Patterns**: код следует паттернам из knowledge.json
- **Anti-patterns**: нет нарушений antiPatterns из knowledge.json
- **Tests**: новый код покрыт, существующие тесты не сломаны
- **Acceptance criteria**: каждый критерий ОТДЕЛЬНО с указанием file:line где реализован. Общее "все выполнены" — НЕ принимается.
## Важно
- `/polisade:implement` работает ТОЛЬКО с TASK
- **`/polisade:implement` останавливается после ОДНОЙ задачи** — это ключевое отличие от `/polisade:continue`
- Субагент получает чистый контекст с релевантной информацией
- Knowledge.json — "память" между сессиями и субагентами
- **Self-review с выводом checklist ОБЯЗАТЕЛЕН перед каждым коммитом**
- При сомнениях — субагент должен вернуть `waiting_pm`
- Обновляй PROJECT_STATE.json после каждого изменения статуса
## Различие /polisade:implement vs /polisade:continue
| Аспект | /polisade:implement | /polisade:continue |
|--------|-----------------|----------------|
| Количество задач | **ОДНА** | Все ready |
| После review | **STOP** (merge через PM) | Следующая задача |
| Когда использовать | Контролируемое выполнение | Автономная работа |
| PM контроль | После каждой задачи | Только при блокировке |
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!