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:
+127
-70
@@ -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
|
||||
frontend)
|
||||
LANG_BUNDLED="typescript-standards"
|
||||
PROFILE_PUBLIC=("anthropics/skills:frontend-design")
|
||||
;;
|
||||
backend)
|
||||
LANG_BUNDLED="go-standards rust-standards python-standards"
|
||||
;;
|
||||
qa) # TODO: уточнить стек тестировщиков; по умолчанию — Python и TS (автотесты)
|
||||
LANG_BUNDLED="python-standards typescript-standards"
|
||||
;;
|
||||
mobile) # Android (Kotlin) + iOS (Swift)
|
||||
LANG_BUNDLED="kotlin-standards swift-standards"
|
||||
;;
|
||||
pm) # Продакт-менеджер: постановка задач, без языковых стандартов кода
|
||||
LANG_BUNDLED=""
|
||||
EXTRA_BUNDLED="pm-task-spec"
|
||||
;;
|
||||
*) # all: не гейтим, включаем все языковые скилы
|
||||
LANG_BUNDLED="go-standards rust-standards python-standards typescript-standards kotlin-standards swift-standards"
|
||||
;;
|
||||
esac
|
||||
|
||||
# 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)
|
||||
add_word LANG_BUNDLED "typescript-standards"
|
||||
PROFILE_PUBLIC+=("anthropics/skills:frontend-design")
|
||||
;;
|
||||
backend)
|
||||
add_word LANG_BUNDLED "go-standards rust-standards python-standards"
|
||||
;;
|
||||
qa) # TODO: уточнить стек тестировщиков; по умолчанию — Python и TS (автотесты)
|
||||
add_word LANG_BUNDLED "python-standards typescript-standards"
|
||||
;;
|
||||
mobile) # Android (Kotlin) + iOS (Swift)
|
||||
add_word LANG_BUNDLED "kotlin-standards swift-standards"
|
||||
;;
|
||||
pm) # Продакт-менеджер: постановка задач, без языковых стандартов кода
|
||||
add_word EXTRA_BUNDLED "pm-task-spec"
|
||||
;;
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user