diff --git a/CHANGELOG.md b/CHANGELOG.md index bc3603b..2994b3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## 0.2.0 — 2026-09-27 +- SessionStart-хук `harness-check.sh`: при старте сессии в неподключённом проекте агент предлагает (или с `HARNESS_AUTO_ADOPT=1` — сразу выполняет) `adopt-project.sh`; скрипты бутстрапа ставятся в `~/.claude/harness/`. +- `adopt-project.sh` — подключение обвязки к существующему репозиторию (ветка → бутстрап → ff-merge); `project-template/CLAUDE.harness-section.md`. - codegraph: `codegraph init` в worktree и `projectPath` в вызовах `codegraph_explore` — MCP-сервер видит только индекс папки запуска. - Порядок старта фичи: worktree на временной ветке → issue → переименование ветки (хук guard-main блокирует запись на main). - Маркировка находок и решений: `T.`, `C/I/M-n`, `R-n`, `O-n`, `H-n`; обязательный статус fixed / deferred / rejected. diff --git a/README.md b/README.md index 4a1a635..1bdeb48 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,14 @@ docs/HARNESS.md как это работает: слои, цикл ф docs/PILOT-2026-09-27.md результаты пилота и находки ``` +## Существующий проект + +``` +bash ~/projects/cc-skills/claude-harness/adopt-project.sh ~/projects/foo # [--no-merge] [--push] +``` + +Запускать из терминала, не из Claude Code: хук guard-main не даёт агенту писать на main, а бутстрап — это запись на main. Скрипт делает ветку `chore/harness-init`, дописывает секцию харнеса в существующий `CLAUDE.md` (или создаёт его из шаблона), добавляет `ARCHITECTURE.md`, строки в `.gitignore`, `openspec init`, `codegraph init`, коммитит и вливает в main fast-forward. Заполнить описание проекта в `CLAUDE.md` потом можно агентом bounded-задачей на ветке. + ## Установка ```bash diff --git a/adopt-project.sh b/adopt-project.sh new file mode 100755 index 0000000..1cf2475 --- /dev/null +++ b/adopt-project.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# claude-harness: подключение обвязки к СУЩЕСТВУЮЩЕМУ репозиторию. +# Запуск из терминала (не из Claude Code): bash adopt-project.sh [папка] [--no-merge] [--push] +# Делает всё на ветке chore/harness-init и по умолчанию вливает её в main fast-forward. +set -euo pipefail +HERE="$(cd "$(dirname "$0")" && pwd)" +DIR="."; MERGE=1; PUSH=0 +for a in "$@"; do case "$a" in --no-merge) MERGE=0;; --push) PUSH=1;; *) DIR="$a";; esac; done +cd "$DIR" +git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo "ERR: $DIR — не git-репозиторий"; exit 1; } +[ -z "$(git status --porcelain)" ] || { echo "ERR: рабочее дерево не чистое — закоммить или спрячь изменения"; exit 1; } +NAME="$(basename "$(git rev-parse --show-toplevel)")" +BASE="$(git branch --show-current)" +[ -n "$BASE" ] || { echo "ERR: detached HEAD"; exit 1; } +BR=chore/harness-init +git switch -c "$BR" >/dev/null + +# CLAUDE.md: свой — дописать секцию харнеса; нет — из шаблона +if [ -f CLAUDE.md ]; then + grep -q '^## OpenSpec и superpowers' CLAUDE.md || { printf '\n' >> CLAUDE.md; cat "$HERE/project-template/CLAUDE.harness-section.md" >> CLAUDE.md; echo "CLAUDE.md: добавлена секция «OpenSpec и superpowers»"; } +else + sed "s/{{PROJECT_NAME}}/$NAME/" "$HERE/project-template/CLAUDE.md" > CLAUDE.md + echo "CLAUDE.md: создан из шаблона — заполни {{...}} (или попроси агента описать проект по коду на отдельной ветке)" +fi +[ -f ARCHITECTURE.md ] || { cp "$HERE/project-template/ARCHITECTURE.md" ARCHITECTURE.md; echo "ARCHITECTURE.md: заготовка"; } + +# .gitignore: только строки обвязки +touch .gitignore +for line in '.superpowers/' '.bounded' '.DS_Store'; do + grep -qxF "$line" .gitignore || echo "$line" >> .gitignore +done +mkdir -p docs/superpowers/plans && touch docs/superpowers/plans/.gitkeep + +if command -v openspec >/dev/null 2>&1; then + [ -d openspec ] || openspec init --tools claude 2>/dev/null || openspec init +else echo "WARN: openspec не установлен (npm i -g @fission-ai/openspec)"; fi +if command -v codegraph >/dev/null 2>&1; then + [ -d .codegraph ] || codegraph init >/dev/null 2>&1 || echo "WARN: codegraph init не удался" +else echo "WARN: codegraph не установлен"; fi + +git add -A +if git diff --cached --quiet; then + echo "Нечего менять: обвязка уже подключена"; git switch "$BASE" >/dev/null; git branch -d "$BR" >/dev/null; exit 0 +fi +git commit -qm "chore: claude harness bootstrap" +if [ "$MERGE" = 1 ]; then + git switch "$BASE" >/dev/null && git merge --ff-only "$BR" >/dev/null && git branch -d "$BR" >/dev/null + echo "Влито в $BASE fast-forward." + [ "$PUSH" = 1 ] && git push origin "$BASE" +else + echo "Ветка $BR оставлена, в $BASE не влита." + [ "$PUSH" = 1 ] && git push -u origin "$BR" +fi +cat <`, `tee`, `sed -i`, `mv`, `rm`, `git commit/push`…), пропускает `git worktree add`; коммит/push в main разрешены только если изменены исключительно файлы в `openspec/` (архивация) | | Правки в `src/` только при плане или `.bounded` | `require-plan.sh`, PreToolUse на Edit/Write/MultiEdit | ищет план в `docs/superpowers/plans/` для текущей ветки или файл `.bounded` | +| Обвязка подключена к проекту | `harness-check.sh`, SessionStart | при старте сессии проверяет `openspec/`, `.codegraph/`, секцию в CLAUDE.md; если чего-то нет — пишет агенту инструкцию запустить `~/.claude/harness/adopt-project.sh` (спросив пользователя; `HARNESS_AUTO_ADOPT=1` — без вопроса) | | Тесты зелёные перед коммитом | git pre-commit в проекте (не входит в пакет) | обычный git-хук с командой тестов | Exit 2 блокирует вызов и показывает агенту stderr, поэтому агент сам делает то, что просит сообщение. Оба обхода, замеченные на пилоте (запись через `echo >` вместо Write; push через detached HEAD), закрыты. diff --git a/global/CLAUDE.md b/global/CLAUDE.md index 6076fe3..67eb7f1 100644 --- a/global/CLAUDE.md +++ b/global/CLAUDE.md @@ -7,6 +7,7 @@ - Ledger superpowers (`.superpowers/sdd/`) — прогресс только внутри текущего плана. Задачи плана в Gitea не дублировать. ## Маршрутизация +- Сообщение `[claude-harness]` в начале сессии означает, что обвязка к проекту не подключена: действовать по его инструкции до всего остального. Скрипт `~/.claude/harness/adopt-project.sh` — единственная разрешённая запись на main помимо архивации OpenSpec. - Bounded-задача (флаг, фикс, мелкая правка): без OpenSpec change и без плана. Дизайн в чате, затем создать файл `.bounded` в корне worktree (разрешает правки в src/ без плана; файл в `.gitignore`, в коммит не попадает), TDD, коммит, PR. - Architectural-задача: brainstorming пишет спеку не в `docs/superpowers/specs/`, а в `openspec/changes//`. В поле **Spec** плана указывается этот путь. - Перед architectural brainstorming прочитать релевантные файлы в `openspec/specs/` и ARCHITECTURE.md, если есть. diff --git a/hooks/guard-main.sh b/hooks/guard-main.sh index 6311cbf..d539549 100755 --- a/hooks/guard-main.sh +++ b/hooks/guard-main.sh @@ -24,7 +24,7 @@ only_openspec() { # $1 = git diff range/args; true if every changed path is und if [ "$tool" = "Bash" ]; then case "$cmd" in - *"git worktree"*|*"git checkout -b"*|*"git switch -c"*|*"openspec archive"*) exit 0 ;; + *"git worktree"*|*"git checkout -b"*|*"git switch -c"*|*"openspec archive"*|*"/.claude/harness/adopt-project.sh"*) exit 0 ;; esac # archive exception: commit of staged openspec-only changes, push of openspec-only commits if printf '%s' "$cmd" | grep -Eq '\bgit\s+commit\b'; then diff --git a/hooks/harness-check.sh b/hooks/harness-check.sh new file mode 100644 index 0000000..c1a8ea9 --- /dev/null +++ b/hooks/harness-check.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# SessionStart hook: проверяет, подключена ли обвязка к проекту в cwd. +# Всё на месте — молчит. Чего-то нет — пишет в контекст сессии инструкцию агенту. +# HARNESS_AUTO_ADOPT=1 в окружении → агенту велено подключать без вопроса. +set -uo pipefail +input=$(cat) +cwd=$(printf '%s' "$input" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("cwd",""))' 2>/dev/null) +[ -n "$cwd" ] || exit 0 +git -C "$cwd" rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0 +root=$(git -C "$cwd" rev-parse --show-toplevel) +missing=() +[ -d "$root/openspec" ] || missing+=("openspec/") +[ -d "$root/.codegraph" ] || missing+=(".codegraph/") +grep -qs '^## OpenSpec и superpowers' "$root/CLAUDE.md" || missing+=("секция «OpenSpec и superpowers» в CLAUDE.md") +[ ${#missing[@]} -eq 0 ] && exit 0 +if [ -n "$(git -C "$root" status --porcelain)" ]; then + dirty=" Рабочее дерево не чистое — скрипт откажется; сначала попроси пользователя закоммитить или спрятать изменения." +else dirty=""; fi +if [ "${HARNESS_AUTO_ADOPT:-0}" = "1" ]; then + mode="Выполни это сейчас, до любых других действий, без вопроса, и одной строкой сообщи пользователю, что сделано." +else + mode="Прежде чем что-либо делать, спроси пользователя одним вопросом, подключить ли обвязку, и по «да» выполни." +fi +cat <` (или `/opsx:propose `), а содержимое proposal.md, specs/ и design.md написать по итогам brainstorming. tasks.md из OpenSpec не заполнять — задачи живут в плане superpowers. +- `/opsx:apply` не использовать: исполнение идёт через writing-plans и SDD/executing-plans. +- `openspec archive ` — после merge PR, как часть чеклиста закрытия.