Skip to content
Back to skills

Asana Blockers

ASecurity

Проставить связи между задачами Asana текстом в описании - чего ждет и что держит: "проставь блокировки", "видно, что мы ждем клиента". Нативные зависимости недоступны - тариф без них (402).

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 29, 2026
developmentpythonbashgitapi

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned October 3, 2026

npx -y skills add dewil/claude-toolkit --skill asana-blockers --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Asana Blockers?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Asana Blockers
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/dewil-asana-blockers/badge)](https://www.skillsdirectory.com/skills/dewil-asana-blockers)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: asana-blockers
description: Проставить связи между задачами Asana текстом в описании - чего ждет и что держит: "проставь блокировки", "видно, что мы ждем клиента". Нативные зависимости недоступны - тариф без них (402).
---

# asana-blockers

Цепочки блокировок пишутся в описание задачи первым блоком - упоминаниями связанных задач и их исполнителей. Движок - `scripts/asana-blockers.py` (top-level папка `scripts/` в корне проекта), цепочки - в `.asana-blockers.json` там же. Только stdlib, из внешнего - опционально `certifi`.

## Зачем

Общее обоснование - `rules/tasks-tracking.md`, раздел "Зависимость выражается связью, а не сдвигом срока": там про то, почему ожидание фиксируется связью и почему обе стороны важнее одной.

Здесь - почему связь ставится **текстом**: тариф бесплатный, `POST /tasks/{gid}/addDependencies` отдает `402 Payment Required`, и единственный доступный носитель связи - описание задачи. Побочная выгода: связь видна прямо в карточке и в предпросмотре, без открывания отдельной вкладки.

## Как выглядит блок

```
СВЯЗИ ЗАДАЧИ
ЭТА ЗАДАЧА ЖДЕТ - ее нельзя сделать, пока не закрыты:
  - <упоминание задачи> - <упоминание исполнителя>, открыта
ЭТА ЗАДАЧА ДЕРЖИТ - они не сдвинутся, пока не закрыта эта:
  - <упоминание задачи> - <упоминание исполнителя>
- - -
```

Пример показывает **секции этого скрипта**. В том же блоке живет третья секция - `СВЯЗАННЫЕ ЗАДАЧИ - откуда выросла эта работа:`, ее пишет `asana-project` (поле `related`), и пункты в ней короче: только упоминание задачи, без исполнителя и статуса. Полный разбор - ниже, в "Блок один на все скрипты".

Три решения в этом формате не косметические, каждое куплено разбором на живых людях:

- **направление связи - целым предложением, а не термином.** Первая редакция писала `ЗАБЛОКИРОВАНА:` и `БЛОКИРУЕТ:`, и заказчик сказал прямо: непонятно, кто кого блокирует - та задача эту или эта ту. Термин читается в обе стороны одинаково правдоподобно, а ошибка не видна: строка выглядит осмысленной при любом прочтении;
- **исполнитель - рядом с каждой связанной задачей.** Увидев блокировку, человек первым делом хочет знать, ждет он своего шага или чужого. У задачи без исполнителя пишется "исполнитель не назначен": разница между "ничей" и "мой" важнее аккуратности строки;
- **статус (открыта/закрыта) - только у тех, кого ждем.** У задач, которые держим мы, он избыточен и мешает читать.

**Ни название задачи, ни имя исполнителя не пишутся текстом** - в описании лежат упоминания (`<a data-asana-gid="..."/>`), а подставляет их сама Asana. Отсюда две выгоды: задачу переименовали - в блоке сразу новое имя, без нашего прогона; и отдельная строка с голым URL не нужна, упоминание уже ссылка.

### Блок один на все скрипты, секции у каждого свои

Технический блок - **общая конвенция, а не деталь этого скрипта**. Каркас один: маркеры `СВЯЗИ ЗАДАЧИ` / `- - -`, блок первым абзацем описания, внутри - секции, у каждой заголовок целым предложением капсом и под ним пункты с упоминаниями. Секции сейчас три:

| Заголовок | Кто пишет |
|---|---|
| `ЭТА ЗАДАЧА ЖДЕТ - ...` | `asana-blockers` |
| `ЭТА ЗАДАЧА ДЕРЖИТ - ...` | `asana-blockers` |
| `СВЯЗАННЫЕ ЗАДАЧИ - откуда выросла эта работа:` | `asana-project` (поле `related`) |

Правило одно: **каждый скрипт перезаписывает только свои секции и сохраняет чужие известные**. Оно не про аккуратность, а про сохранность - у него две стороны, и обе ломаются молча:

- **не узнать чужую секцию** значит не считать блок своим: прогон кладет свой блок сверху, и на карточке оказываются два блока связей с разной правдой;
- **узнать, но не сохранить** значит стереть чужую работу при замене.

Отсюда устройство: общий реестр заголовков (`KNOWN_HEADS`) знают все, свои (`OWN_HEADS`) - каждый свои. Порядок секций в блоке канонический (по реестру), а не по тому, кто писал последним: иначе два скрипта переставляли бы секции друг друга, каждый прогон видел бы изменение, и идемпотентности не было бы ни у одного.

Каркас живет в `asana-blockers.py` в одном экземпляре; `asana-project.py` подгружает его боковым импортом соседа по `scripts/` (тот же прием, что `md-docx` -> `md-pdf`). Копию не заводить: две копии разойдутся на первом же фиксе распознавания блока, а цена расхождения - стертое описание задачи.

**Блок первой редакции секций не имеет** (термин плюс голая ссылка). Читать его умеет только этот скрипт - и он же переписывает такой блок в новый формат на первом прогоне. `asana-project` с ним работать отказывается: слить старый блок он не умеет, а перезапись стерла бы записанные в нем блокировки. Наткнулся на отказ - прогони сначала `asana-blockers`.

**Заводя новую секцию**, добавь заголовок в реестр обоих скриптов сразу - иначе тот, который его не знает, начнет плодить вторые блоки.

**Смена исполнителя так не подхватывается, и это важно не перепутать.** Упоминание задачи динамическое, а упоминание человека - нет: в описании лежит gid того, кто был исполнителем в момент записи. Сменили исполнителя - в блоке останется прежний, пока не пройдет новый прогон. Переименование задачи прогона не требует, смена исполнителя требует.

## Когда применять

- На доске есть задачи, ждущие внешней стороны, и это надо показать не сдвигом срока.
- Связей несколько и они переживают неделю: цепочка "наполнить сайт -> запустить -> поставить метрику" держится месяцами и проставляется повторными прогонами.
- Тариф бесплатный: `POST /tasks/{gid}/addDependencies` отдает `402 Payment Required`.

## Когда НЕ применять

- **Одна связь на один раз** - дописать строку в описание руками (или прямым REST) дешевле, чем заводить конфиг.
- **Платный тариф с нативными зависимостями** - там связь ставится штатно, машинно читается и не трогает текст описания. Этот скилл - обход ограничения, а не улучшение.
- **Задача просрочена сама по себе**, без внешнего ожидания - связи тут нечего показывать, это вопрос срока и владельца.
- **Нужно подвинуть срок или закрыть задачу** - этот скрипт такого не делает намеренно: он описывает связи. Срок и пакетное закрытие - в `skills/asana-project` (`tasks` и `complete`, оба под своим гейтом); решение о переносе и закрытии в любом случае за человеком.
- **Трекер без упоминаний в описании.** Прием опирается на то, что трекер подставляет имя задачи и исполнителя по идентификатору. Где этого нет, придется писать текстом - и тогда возвращается вся морока с переименованиями.

## Жесткий гейт перед --send

Скрипт **переписывает описания живых задач на общей доске**: их сразу видят заказчик и команда, а откат - руками по каждой задаче.

1. **Всегда сначала dry-run** (дефолт, без `--send`). Он печатает по задачам: какие связи встанут и где блок будет снят (`СНЯТЬ БЛОК СВЯЗЕЙ`). Имена в выводе человеческие, хотя записываются упоминания, - иначе проверить строку глазами невозможно.
2. **Чей токен и чья доска - реши до `--send`.** Аккаунт может состоять сразу в нескольких организациях, и клиентская доска отличается от личной одним числом в конфиге. Разведены аккаунты - `--auth <файл>` обязателен в каждой команде, включая dry-run (`rules/mcp-multi-account.md`).
3. **Показать вывод пользователю и получить явное "ок"** на этот конкретный вывод - только потом `--send`. Прошло время или доску трогали - повторить dry-run: send перечитывает доску заново.
4. **Проверить формулировки глазами.** Названия задач скрипт не пишет, но Asana подставит их в упоминание при просмотре карточки - результат тот же: внутренняя рабочая формулировка в названии задачи ("ждем, пока клиент проснется") станет видна заказчику.

## CLI

```bash
python3 scripts/asana-blockers.py --board <ключ>          # предпросмотр одной доски
python3 scripts/asana-blockers.py --board <ключ> --send   # записать
python3 scripts/asana-blockers.py                         # все доски конфига (предпросмотр)
python3 scripts/asana-blockers.py --auth ~/.config/asana/auth-work.json --board <ключ>
```

Формат `.asana-blockers.json` в корне проекта (`--config` - другой путь):

```json
{
  "<ключ доски>": {
    "project": "<gid доски: последнее число в ее URL>",
    "chains": {
      "<задача, которая ждет>": ["<без чего ее не сделать>", "<и еще одна>"]
    }
  }
}
```

`chains` читается как "чтобы сделать A, нужны B и C". Обратную сторону скрипт выводит сам с меткой `ЭТА ЗАДАЧА ДЕРЖИТ` - руками ее не дублировать. В старых блоках была метка `БЛОКИРУЕТ:`; скрипт распознает ее при обновлении.

## Идемпотентность и повторные прогоны

- **Блок распознается по заголовку `СВЯЗИ ЗАДАЧИ` и линейке `- - -`** и заменяется целиком: повторный прогон ничего не дублирует и печатает `задач с изменениями: 0`. Исключение одно и оно в "Граблях": блок, сдвинутый вниз по описанию, скрипт уже не видит.
- **Трогаются только свои блоки.** Блок считается нашим, если состоит **целиком** из известных заголовков секций (см. реестр выше) и пунктов-упоминаний под ними - либо из строк первой редакции (`ЗАБЛОКИРОВАНА:`/`БЛОКИРУЕТ:` плюс ссылка). Описание, которое человек начал теми же словами, но продолжил обычным текстом, остается как есть - как и задача, у которой нет ни связей в конфиге, ни нашего блока.

  **Остаточный риск назовем прямо:** текст, воспроизводящий грамматику блока дословно - маркер, заголовок из реестра, пункты с @-упоминаниями, линейка, - будет опознан нашим и перезаписан, даже если его набрал человек. Распознавать мягче нельзя (тогда стирается ручной текст, начатый теми же словами), поэтому граница проведена здесь: под маркером `СВЯЗИ ЗАДАЧИ` пишем только мы.
- **Блоки первой редакции узнаются и переписываются.** На досках уже стоят блоки старого формата (термин плюс голая ссылка); прогон опознает их как свои и заменяет новыми. Не узнавать значило бы дописать второй блок сверху и оставить на карточке две противоречивые версии связей. Пишем только в новом формате, старый остается известен чтению.
- **Убранная из конфига цепочка снимает блок** с описания. Иначе доска врала бы про связь, которой больше нет. Предпросмотр показывает снимаемый блок построчно - видно, что именно исчезнет.
- **Статус блокирующей задачи (`открыта`/`закрыта`) обновляется прогоном.** Закрыли блокер - прогон перепишет строку; сам скрипт за доской не следит, ставить его в расписание или гонять руками решает проект.
- **Перед записью задача перечитывается.** У Asana нет ни ETag, ни условной записи: `PUT` перезаписывает поле целиком, и правка, сделанная человеком после предпросмотра, потерялась бы. Скрипт читает задачу заново прямо перед записью и накладывает блок на свежий текст, сообщая об этом в stderr. Окно этим сужено до миллисекунд, но не закрыто - правка ровно в момент записи все равно пропадет.
- **Четыре расхождения со снимком предпросмотра отменяют запись** по этой задаче (в stderr - причина, в конце - `пропущено: N`, код возврата ненулевой): задачу переименовали; ее убрали с этой доски; описание успели оформить (значит предупреждения про сплющивание человек не видел и согласия не давал); Asana вернула неполный ответ. Пропуск лечится повторным предпросмотром - молчаливая запись поверх такого расхождения не лечится ничем.
- **Имена сверяются дословно** (с точностью до краевых пробелов и формы записи Unicode). Переименовали задачу на доске - прогон печатает перечень ненайденных, ничего не пишет по этой доске и возвращает ненулевой код. Это защита: молча пропущенная связь неотличима от ее отсутствия (`rules/silent-failure.md`).
- **Две задачи с одинаковым именем среди упомянутых в конфиге** - тоже отказ: какую из них связывать, неоднозначно.
- **Ошибка в самом конфиге** (`project` не число, зависимость от самой себя, повтор ключа, перевод строки внутри имени) останавливает весь прогон до первого сетевого вызова - ни одна доска не тронута. Ошибка на конкретной доске (ненайденные или неоднозначные задачи) останавливает только ее; остальные обрабатываются, код возврата все равно ненулевой.

## Грабли

- **Маркеры блока видит заказчик.** Asana показывает описание как есть, HTML-комментарии в нем не прячутся - первая версия разметки читалась служебным мусором. Поэтому границы блока человеческие.
- **Блок опознается только в начале описания.** Если его сдвинули вниз (дописали текст сверху), прогон его не увидит и добавит новый блок над ним - старый останется и будет врать. Убирается руками; поиск блока по всему описанию не делается намеренно, чтобы регулярка не съела кусок чужого текста.
- **Работа идет с `html_notes`, а не с `notes`.** Упоминания живут только там, а заодно это снимает прежний дефект: запись плоского `notes` сбрасывала списки и жирный в чужом описании. Оформление теперь переживает прогон, и отдельный гейт "описание оформлено" ушел вместе с причиной.
- **Предпросмотр печатает имена, а не gid.** Он тут единственный канал контроля перед записью, а строку из идентификаторов глазами не проверить. Записываем упоминания, показываем имена.
- **Запись не транзакционна.** Задачи пишутся по одной; на `429` и временных ошибках сети скрипт ждет и повторяет (`Retry-After`), но постоянная ошибка на середине оставляет часть доски со связями, а часть без - односторонняя картина врет сильнее, чем ее отсутствие. Лечится повторным прогоном: он идемпотентен. Не лечится двумя случаями - постоянная ошибка (403 на чужую задачу, 400 на кривые данные) будет упираться в ту же задачу каждый раз, а удаленная блокирующая задача остановит доску целиком на ненайденном имени, пока не поправят конфиг.
- **Скрипт не двигает сроки и не закрывает задачи.** Только описывает связи.
- **Цикл в конфиге не ловится.** A ждет B, B ждет A запишется буквально в обе стороны - смысл цепочек проверяет человек.

## Прием шире Asana

Механика не про Asana: связь в тексте задачи со ссылкой на связанную ложится на любой трекер, где у задачи есть описание, - Trello, Redmine, Планфикс, issue в GitHub. Меняется только транспорт (API и поле), неизменны три вещи: обе стороны связи проставляются вместе, блок стоит первым и отбит человекочитаемым маркером, а прогон идемпотентен и снимает блок при снятии связи. Реализация под Asana дана как первая - под другой трекер переписывается движок, а не прием.

## Авторизация

Порядок выбора PAT - как в `asana-comments`, раздел "Авторизация".

## Связанные файлы

- `scripts/asana-blockers.py` в корне проекта - движок (dry-run по умолчанию).
- `.asana-blockers.json` в корне проекта - цепочки по доскам, ведется руками.
- `skills/asana-project` - пакетное создание задач, синк сроков и закрытие отчетного периода; берет отсюда каркас технического блока (секция "СВЯЗАННЫЕ ЗАДАЧИ"); `skills/asana-comments` - чтение ленты комментариев.
- `rules/tasks-tracking.md` - формат задач и запрет молча переносить просрочку.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.