fix(install): gate confluence for every profile and dedup PROFILE selection

- Add 'confluence' (always-enabled, per README) to COMMON_BUNDLED so a
  default install (no PI_KIT_PROFILE / no tty) no longer silently strips
  it from settings.json. The allowlist now covers every skill under skills/.
- Deduplicate PROFILES in profiles_from_text: skip a canonical name already
  present, so 'frontend,1' or 'backend backend' no longer causes a redundant
  'npx skills add' call or a misleading 'Профили: frontend frontend'.
- Ship the multi-profile refactor it builds on: merged profile sets,
  profile_skills()/add_word() helpers, README updates, and the pm-task-spec
  bundled skill.

Validated: bash -n; functional checks for dedup and allowlist coverage.
This commit is contained in:
Aleksey Shakhmatov
2026-08-10 12:11:53 +03:00
parent 0c6e590273
commit 3c44853cde
3 changed files with 244 additions and 80 deletions
+17 -10
View File
@@ -17,8 +17,8 @@ curl -fsSL https://git.codelab.vc/ai/pi-kit/raw/branch/stable/install.sh | bash
```
Скрипт проверит `git` и Node.js (>= 20) — оба обязательны: git нужен для `pi install git:...`,
Node для Pi. Затем поставит Pi (если нет), установит этот пакет, **спросит твой профиль**, поставит
нужные скилы и **интерактивно настроит окружение** (`TRACKER_URL`, `CONFLUENCE_URL`,
Node для Pi. Затем поставит Pi (если нет), установит этот пакет, **спросит один или несколько твоих профилей**,
поставит нужные скилы (их наборы объединяются) и **интерактивно настроит окружение** (`TRACKER_URL`, `CONFLUENCE_URL`,
`JIRA_TOKEN`, `CONFLUENCE_TOKEN`, `GITLAB_TOKEN`, ключ LLM-провайдера). Секреты вводятся скрыто и
сохраняются в
`~/.config/pi-kit/env.sh` (`chmod 600`), который подключается из твоего `~/.zshrc`/`~/.bashrc`.
@@ -30,12 +30,16 @@ Pi провайдер-агностичен — модель/провайдера
Пропустить интерактивную настройку окружения: `PI_KIT_SETUP_ENV=0`. Значения потом можно
поменять прямо в `~/.config/pi-kit/env.sh` или перезапустив `./install.sh`.
Профиль можно задать заранее (для CI/неинтерактивной установки):
Профили можно задать заранее (для CI/неинтерактивной установки) — через запятую:
```bash
PI_KIT_PROFILE=backend curl -fsSL https://git.codelab.vc/ai/pi-kit/raw/branch/stable/install.sh | bash
PI_KIT_PROFILE=backend,qa curl -fsSL https://git.codelab.vc/ai/pi-kit/raw/branch/stable/install.sh | bash
```
Один профиль тоже подходит: `PI_KIT_PROFILE=backend`. Скилы выбранных профилей
**объединяются** (дубли убираются), а `jira-workflow`/`repo-map`/`docs-map` и общие
публичные скилы ставятся всегда.
### Профили и скилы
Общее для всех: команды `/bugfix /feature /review /rfc /kit-config /kit-doctor /kit-help`,
@@ -54,7 +58,8 @@ guardrails, скиллы `jira-workflow`, `confluence`, `repo-map`, `docs-map`,
сжатой постановки задачи до структурированной, пригодной для передачи в разработку)
без языковых стандартов кода.
Сменить профиль позже: `PI_KIT_PROFILE=frontend ./install.sh` (или перезапусти установку).
Сменить/добавить профиль позже: `PI_KIT_PROFILE=frontend,qa ./install.sh` (или перезапусти установку
и выбери несколько). Уже установленные наборы скилов можно дополнить, указав нужные профили ещё раз.
Либо вручную (канал `stable` по умолчанию):
@@ -208,17 +213,19 @@ pi-kit/
### Профили и гейтинг скилов
`install.sh` спрашивает профиль (или берёт `PI_KIT_PROFILE`) и делает две вещи:
`install.sh` спрашивает один или несколько профилей (или берёт `PI_KIT_PROFILE`) и делает две вещи:
1. **Гейтит наши скилы** — переписывает запись пакета в `~/.pi/agent/settings.json` в
object-форму `{"source": "...", "skills": ["skills/go-standards", ...]}`, чтобы грузились
только скилы профиля. Prompts и расширения не гейтятся (их ключи опущены → грузятся все).
только скилы выбранных профилей. Prompts и расширения не гейтятся (их ключи опущены → грузятся все).
2. **Ставит публичные скилы** через `npx skills add <repo> --skill <name> --global` в
`~/.agents/skills/` (Pi читает эту папку). Чужой код в наш репо не вендорится.
Чтобы поменять маппинг профиль→скилы — правь блок `case "$PROFILE"` и `COMMON_PUBLIC_SKILLS`
в `install.sh`. Языковой скилл добавляется как обычный (см. ниже) и подключается к нужному
профилю в этом `case`. Профили: `frontend`, `backend`, `qa`, `mobile`, `pm`.
Наборы скилов нескольких профилей объединяются (путь `frontend+qa` даст и `typescript`
и `python` стандарты). Чтобы поменять маппинг профиль→скилы — правь функцию `profile_skills()`
и `COMMON_PUBLIC_SKILLS` в `install.sh`. Языковой скилл добавляется как обычный (см. ниже)
и подключается к нужному профилю в этой функции. Профили: `frontend`, `backend`, `qa`,
`mobile`, `pm` — любые сочетаются между собой.
### Как добавить шаблон-команду
+117 -60
View File
@@ -7,9 +7,8 @@
# # or, from a checkout:
# ./install.sh
#
# Profile selection:
# Interactive by default (prompts via /dev/tty, so it works under curl | bash).
# Non-interactive: set PI_KIT_PROFILE=frontend|backend|qa|mobile before running.
# Profile selection (multiple allowed, merged):
# Non-interactive: set PI_KIT_PROFILE=frontend or PI_KIT_PROFILE=frontend,qa before running.
#
set -euo pipefail
@@ -80,70 +79,132 @@ info "Устанавливаю корпоративный пакет: ${PI_KIT_R
pi install "$PI_KIT_REPO"
info "Пакет установлен. Обновляться потом: pi update --extensions"
# --- 4. Resolve profile -----------------------------------------------------
# Precedence: PI_KIT_PROFILE env > interactive prompt > "all" (no gating).
PROFILE="${PI_KIT_PROFILE:-}"
case "$PROFILE" in
frontend | backend | qa | mobile | pm) ;; # valid, keep
"") # ask if we have a terminal, even under `curl | bash` (read from /dev/tty)
if [ -r /dev/tty ]; then
{
printf 'Выбери профиль:\n'
printf ' 1) frontend — TS/UI\n'
printf ' 2) backend — Go/Rust/Python\n'
printf ' 3) qa — тестирование\n'
printf ' 4) mobile — Android/iOS\n'
printf ' 5) pm — постановка задач продакт-менеджера\n'
printf 'Номер или имя [1-5]: '
} >/dev/tty
read -r ans </dev/tty || ans=""
case "$ans" in
1 | frontend) PROFILE=frontend ;;
2 | backend) PROFILE=backend ;;
3 | qa) PROFILE=qa ;;
4 | mobile) PROFILE=mobile ;;
5 | pm) PROFILE=pm ;;
*) PROFILE=all ;;
# --- 4. Resolve profiles --------------------------------------------------
# Several profiles can be selected at once; their skill sets are merged.
# Precedence: PI_KIT_PROFILE env > interactive prompt.
# Env is comma- or space-separated: PI_KIT_PROFILE="backend,qa" (a single
# value like `backend` still works). Empty env + no tty => all profiles.
profile_name() { # <token> -> canonical profile name, or empty if unknown
case "$1" in
1 | frontend) echo frontend ;;
2 | backend) echo backend ;;
3 | qa) echo qa ;;
4 | mobile) echo mobile ;;
5 | pm) echo pm ;;
esac
}
is_profile() { # <token> — 0 if it's a known profile name/number
[ -n "$(profile_name "$1")" ]
}
# Parse a comma/space separated selection into the PROFILES array (deduplicated).
# Unknown tokens are dropped with a warning.
profiles_from_text() { # <text>
local raw="$1" arr p name
PROFILES=()
IFS=', ' read -r -a arr <<<"$raw"
for p in "${arr[@]}"; do
[ -z "$p" ] && continue
if is_profile "$p"; then
name="$(profile_name "$p")"
case " ${PROFILES[*]} " in
*" $name "*) ;; # duplicate token/alias — already present
*) PROFILES+=("$name") ;;
esac
else
PROFILE=all
warn "Неизвестный профиль/номер '${p}' — пропущен."
fi
;;
*)
warn "Неизвестный PI_KIT_PROFILE='${PROFILE}', использую 'all' (без гейтинга скилов)."
PROFILE=all
;;
esac
info "Профиль: ${PROFILE}"
done
}
# Bundled language skills per profile. Common skills (jira-workflow/repo-map/
# docs-map) are always enabled. Prompts and guardrail extensions are never gated.
COMMON_BUNDLED="jira-workflow repo-map docs-map"
# Extra bundled skills beyond language standards, added per profile (e.g. product/PM tooling).
all_profiles() { PROFILES=(frontend backend qa mobile pm); }
PROFILES=()
if [ -n "${PI_KIT_PROFILE:-}" ]; then
profiles_from_text "$PI_KIT_PROFILE"
if [ "${#PROFILES[@]}" -eq 0 ]; then
warn "В PI_KIT_PROFILE нет распознанных профилей — ставлю все."
all_profiles
fi
elif [ -r /dev/tty ]; then
# ask if we have a terminal, even under `curl | bash` (read from /dev/tty)
{
printf 'Выбери один или несколько профилей (через пробел или запятую):\n'
printf ' frontend — TS/UI\n'
printf ' backend — Go/Rust/Python\n'
printf ' qa — тестирование\n'
printf ' mobile — Android/iOS\n'
printf ' pm — постановка задач продакт-менеджера\n'
printf 'Пример: backend qa | frontend,pm: '
} >/dev/tty
read -r ans </dev/tty || ans=""
if [ -z "$ans" ]; then
all_profiles
else
profiles_from_text "$ans"
if [ "${#PROFILES[@]}" -eq 0 ]; then
warn "Не распознаны профили — ставлю все."
all_profiles
fi
fi
else
all_profiles
fi
# Bundled language/extra skills and public skills per profile. Common skills
# (confluence/jira-workflow/repo-map/docs-map) are always enabled; prompts and
# guardrail extensions are never gated. For several selected profiles their sets are merged.
COMMON_BUNDLED="confluence jira-workflow repo-map docs-map"
LANG_BUNDLED=""
EXTRA_BUNDLED=""
PROFILE_PUBLIC=()
case "$PROFILE" in
# Add word(s) to a space-separated variable, skipping duplicates.
# Accepts one space-separated word list ("a b c") and/or separate args.
add_word() { # <var-name> <words...>
local var="$1" w cur all new
cur="${!var}"
shift
all="$*"
new=""
for w in $all; do
case " $cur " in
*" $w "*) ;; # already present
*) new="$new $w" ;;
esac
done
[ -n "$new" ] && printf -v "$var" '%s%s' "$cur" "$new"
}
# Accumulate the skill sets of one selected profile.
profile_skills() { # <profile>
case "$1" in
frontend)
LANG_BUNDLED="typescript-standards"
PROFILE_PUBLIC=("anthropics/skills:frontend-design")
add_word LANG_BUNDLED "typescript-standards"
PROFILE_PUBLIC+=("anthropics/skills:frontend-design")
;;
backend)
LANG_BUNDLED="go-standards rust-standards python-standards"
add_word LANG_BUNDLED "go-standards rust-standards python-standards"
;;
qa) # TODO: уточнить стек тестировщиков; по умолчанию — Python и TS (автотесты)
LANG_BUNDLED="python-standards typescript-standards"
add_word LANG_BUNDLED "python-standards typescript-standards"
;;
mobile) # Android (Kotlin) + iOS (Swift)
LANG_BUNDLED="kotlin-standards swift-standards"
add_word LANG_BUNDLED "kotlin-standards swift-standards"
;;
pm) # Продакт-менеджер: постановка задач, без языковых стандартов кода
LANG_BUNDLED=""
EXTRA_BUNDLED="pm-task-spec"
add_word EXTRA_BUNDLED "pm-task-spec"
;;
*) # all: не гейтим, включаем все языковые скилы
LANG_BUNDLED="go-standards rust-standards python-standards typescript-standards kotlin-standards swift-standards"
;;
esac
esac
}
for p in "${PROFILES[@]}"; do
profile_skills "$p"
done
LANG_BUNDLED="$(printf '%s' "$LANG_BUNDLED" | sed 's/^ *//;s/ *$//')"
info "Профили: ${PROFILES[*]}"
# --- 5. Gate bundled skills via settings object-form filter -----------------
# Rewrites the pi-kit package entry in settings.json to {source, skills:[...]}
@@ -176,12 +237,8 @@ console.log(patched ? "gated bundled skills -> " + allowed.join(", ") : "pi-kit
NODE
}
if [ "$PROFILE" = "all" ]; then
info "Профиль 'all' — оставляю все скилы пакета без гейтинга."
else
info "Гейчу скилы пакета под профиль '${PROFILE}'..."
gate_bundled_skills "${COMMON_BUNDLED} ${LANG_BUNDLED} ${EXTRA_BUNDLED}" || warn "не удалось отфильтровать скилы (см. выше)."
fi
info "Гейчу скилы пакета под профили '${PROFILES[*]}'..."
gate_bundled_skills "${COMMON_BUNDLED} ${LANG_BUNDLED} ${EXTRA_BUNDLED}" || warn "не удалось отфильтровать скилы (см. выше)."
# --- 6. Install public skills ----------------------------------------------
# Uses the skills.sh CLI (`npx skills add`). Failures are non-fatal.
@@ -200,7 +257,7 @@ for entry in "${COMMON_PUBLIC_SKILLS[@]}"; do
add_public_skill "${entry%%:*}" "${entry##*:}"
done
if [ "${#PROFILE_PUBLIC[@]}" -gt 0 ]; then
info "Ставлю публичные скилы профиля '${PROFILE}'..."
info "Ставлю публичные скилы профилей '${PROFILES[*]}'..."
for entry in "${PROFILE_PUBLIC[@]}"; do
add_public_skill "${entry%%:*}" "${entry##*:}"
done
@@ -379,13 +436,13 @@ fi
cat <<EOF
────────────────────────────────────────────────────────
Готово! Профиль: ${PROFILE} Канал: ${PI_KIT_CHANNEL}
Готово! Профили: ${PROFILES[*]} Канал: ${PI_KIT_CHANNEL}
Запуск: pi
Обновление: pi update --extensions (следует за каналом ${PI_KIT_CHANNEL})
Окружение: ${ENV_FILE} (JIRA_TOKEN/CONFLUENCE_TOKEN/GITLAB_TOKEN/ключ провайдера)
URL-адреса — дефолты из config/company.json (можно переопределить в env.sh)
Сменить профиль: PI_KIT_PROFILE=backend ./install.sh (или перезапусти и выбери; есть и 'pm')
Сменить профиль: PI_KIT_PROFILE=frontend,qa ./install.sh (или перезапусти и выбери несколько)
Сменить канал: PI_KIT_CHANNEL=beta ./install.sh
Команды: /bugfix <тикет> /feature <тикет> /review [фокус] /rfc <тема>
/kit-config /kit-doctor /kit-help
+100
View File
@@ -0,0 +1,100 @@
---
name: pm-task-spec
description: Используй, когда продакт-менеджер приносит сжатую/неполную постановку задачи и нужно её довести до ясной, непротиворечивой формулировки, пригодной для передачи в разработку. Скилл дополнительно подходит для профиля продукт-менеджера (pm): он опрашивает по ключевым доменам постановки, находит слабые места и выдаёт готовую структурированную постановку задачи.
---
# Постановка задачи (для продакт-менеджера)
Скилл помогает продакт-менеджеру (ПМ) превратить сжатую или неполную постановку задачи в
**готовую структурированную постановку**, в которой нет противоречий и все ключевые моменты
понятны и ясны перед передачей в разработку.
## Принцип работы
1. Принимаешь исходный текст постановки от ПМ (он может быть очень коротким).
2. Анализируешь её **адаптивно** — без жёсткого зачитывания фиксированного чек-листа:
определяешь, каких из ключевых доменов не хватает или где есть неоднозначность/противоречие.
3. Задаёшь **точечные уточняющие вопросы** только по найденным слабым местам, простым языком,
объясняя термины (ПМ может быть не техническим).
4. **Не завершаешь опрос, пока каждый ключевой домен постановки не прояснён до однозначного
текста.** Непрояснённые/спорные пункты не замалчиваешь — выносишь в раздел «Открытые вопросы».
5. В конце выдаёшь **готовую постановку** готовым markdown-шаблоном, который ПМ копирует
в нужное место (Jira-тикет, wiki, описание в MR). Никакого автосоздания карточек.
## Ключевые домены постановки
Проходишь по ним, но вопрос задаёшь, только если домен пуст, неоднозначен или противоречит
другим уже данным ответам:
- **Суть / заголовок** — одна фраза, что делаем и зачем.
- **Контекст и бизнес-цель** — какую проблему/метрику закрываем, почему сейчас.
- **Пользователи / аудитория** — для кого, сегменты.
- **Сценарии и шаги** — основной сценарий + ключевые вариации (happy path и отступления).
- **Границы scope** — что входит, что явно НЕ входит.
- **Критерии приёмки** — как понять, что задача сделана и работает.
- **Риски и зависимости** — от чего зависит, что может пойти не так.
- **Открытые вопросы** — то, что осталось неопределённым на момент выдачи.
## Правила ведения опроса
- Говори просто и по делу. Если используешь термин, сразу объясни его другим словом.
- Один вопрос за раз, маленькими порциями. Не заваливай ПМ списком из десятка пунктов.
- Если ответ противоречит уже сказанному — укажи на противоречие и попроси подсказать,
какой вариант верный (не решай за ПМ).
- Если ПМ не знает техническую деталь — не додумывай, а отметь это в открытых вопросах
(техрешения по реализации оставь разработчикам — это вне scope скилла).
- Будь настойчивым, но доброжелательным: цель — закрыть пробелы, а не выпустить сырой текст.
## Шаблон итоговой постановки
В конце выдай маркдаун-шаблон. Для каждого раздела проставляй значение из ответов ПМ;
если что-то осталось неопределённым — честно помечай `не определено` и добавляй в «Открытые
вопросы» с пояснением, что стоит уточнить.
```markdown
## Постановка задачи
### 1. Суть / заголовок
<одна фраза: что делаем и для чего>
### 2. Контекст и бизнес-цель
- Проблема: <что не так сегодня>
- Цель/метрика: <что должно измениться, на чём меряем успех>
- Почему сейчас: <предпосылки/драйвер>
### 3. Пользователи / аудитория
<кто, сегменты>
### 4. Сценарии и шаги
- Основной сценарий: <шаг за шагом>
- Вариации / отступления: <ключевые edge-случаи>
### 5. Границы scope
- Входит: <...>
- Не входит: <явные исключения>
### 6. Критерии приёмки
<как понять, что задача решена; желательно проверяемо>
### 7. Риски и зависимости
<от чего зависит, что может пойти не так>
### 8. Открытые вопросы
<пункты, которые осталось уточнить; пометь, кому адресован>
```
## Non-goals
Этот скилл — **организатор постановки**, а не техконсультант-исполнитель. Он:
- **не** консультирует по архитектуре/реализации в Go и других языках;
- **не** оценивает трудозатраты и сроки;
- **не** создаёт/обновляет/таскает Jira-тикеты автоматически;
- **не** заменяет прямое обсуждение ПМ с разработчиками — наоборот, готовит материал
для такого обсуждения.
## Интеграция
Контекст — наша компания: трекер **Jira** (см. скилл `jira-workflow`), git-хост
**gitlab.tech.mvideo.ru** (см. `repo-map`), документация **wiki.mvideo.ru** (см. `docs-map`).
Скилл автономен: выдаёт готовый текст постановки, ПМ сам переносит его в нужное место.