0.2.0: harness-check SessionStart hook, adopt-project.sh
This commit is contained in:
parent
d604ae3588
commit
096f5586fb
@ -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<n>.<k>`, `C/I/M-n`, `R-n`, `O-n`, `H-n`; обязательный статус fixed / deferred / rejected.
|
||||
|
||||
@ -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
|
||||
|
||||
59
adopt-project.sh
Executable file
59
adopt-project.sh
Executable file
@ -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 <<MSG
|
||||
|
||||
Проект $NAME подключён к обвязке. Дальше:
|
||||
cd $(pwd) && claude
|
||||
«Опиши проект в CLAUDE.md по коду: разделы «Что это», «Стек», «Архитектура», «Команды». Bounded-задача.»
|
||||
MSG
|
||||
@ -71,6 +71,7 @@ Fix loop: раунды 1–3 продолжают того же implementer, 4
|
||||
|---|---|---|
|
||||
| Никаких изменений на main / detached HEAD | `guard-main.sh`, PreToolUse на Edit/Write/MultiEdit/Bash | проверяет ветку; для Bash блокирует команды, похожие на запись (`>`, `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), закрыты.
|
||||
|
||||
@ -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/<id>/`. В поле **Spec** плана указывается этот путь.
|
||||
- Перед architectural brainstorming прочитать релевантные файлы в `openspec/specs/` и ARCHITECTURE.md, если есть.
|
||||
|
||||
@ -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
|
||||
|
||||
29
hooks/harness-check.sh
Normal file
29
hooks/harness-check.sh
Normal file
@ -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 <<MSG
|
||||
[claude-harness] В проекте $root не подключена обвязка: нет: $(printf '%s; ' "${missing[@]}" | sed 's/; $//').
|
||||
Подключение: \`bash "$HOME/.claude/harness/adopt-project.sh" "$root"\` — скрипт сам делает ветку chore/harness-init, бутстрап, коммит и ff-merge; хук guard-main его пропускает.$dirty
|
||||
$mode После этого продолжай с запросом пользователя.
|
||||
MSG
|
||||
exit 0
|
||||
@ -4,13 +4,30 @@
|
||||
{
|
||||
"matcher": "Edit|Write|MultiEdit|Bash",
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "\"$HOME/.claude/hooks/guard-main.sh\"" }
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$HOME/.claude/hooks/guard-main.sh\""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Edit|Write|MultiEdit",
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "\"$HOME/.claude/hooks/require-plan.sh\"" }
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$HOME/.claude/hooks/require-plan.sh\""
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$HOME/.claude/hooks/harness-check.sh\""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
11
install.sh
11
install.sh
@ -39,7 +39,11 @@ fi
|
||||
cp "$HERE/global/CLAUDE.md" "$CC/CLAUDE.md"
|
||||
|
||||
# 2. Хуки
|
||||
cp "$HERE/hooks/guard-main.sh" "$HERE/hooks/require-plan.sh" "$CC/hooks/"
|
||||
cp "$HERE/hooks/guard-main.sh" "$HERE/hooks/require-plan.sh" "$HERE/hooks/harness-check.sh" "$CC/hooks/"
|
||||
# 2a. Скрипты бутстрапа проекта — агент запускает их из сессии
|
||||
mkdir -p "$CC/harness"
|
||||
cp -R "$HERE/adopt-project.sh" "$HERE/new-project.sh" "$HERE/project-template" "$CC/harness/"
|
||||
chmod +x "$CC/harness/"*.sh
|
||||
chmod +x "$CC/hooks/"*.sh
|
||||
|
||||
# 3. caveman off by default
|
||||
@ -59,6 +63,9 @@ def ours(entry):
|
||||
return any(("guard-main.sh" in x.get("command","") or "require-plan.sh" in x.get("command","")) for x in entry.get("hooks",[]))
|
||||
pre[:] = [e for e in pre if not ours(e)]
|
||||
pre.extend(h["hooks"]["PreToolUse"])
|
||||
ss = hooks.setdefault("SessionStart", [])
|
||||
ss[:] = [e for e in ss if not any("harness-check.sh" in x.get("command","") for x in e.get("hooks",[]))]
|
||||
ss.extend(h["hooks"]["SessionStart"])
|
||||
for ev in ("SessionStart","PreCompact"):
|
||||
if ev in hooks:
|
||||
for e in hooks[ev]:
|
||||
@ -69,6 +76,6 @@ json.dump(s, open(sp,"w"), ensure_ascii=False, indent=2)
|
||||
print("settings.json: hooks merged")
|
||||
PY
|
||||
|
||||
echo "done: $CC/CLAUDE.md, $CC/hooks/{guard-main,require-plan}.sh, ~/.config/caveman/config.json"
|
||||
echo "done: $CC/CLAUDE.md, $CC/hooks/{guard-main,require-plan,harness-check}.sh, $CC/harness/, ~/.config/caveman/config.json"
|
||||
echo "Плагины Claude Code ставятся вручную: /plugin install superpowers@claude-plugins-official"
|
||||
check || true
|
||||
|
||||
5
project-template/CLAUDE.harness-section.md
Normal file
5
project-template/CLAUDE.harness-section.md
Normal file
@ -0,0 +1,5 @@
|
||||
## OpenSpec и superpowers
|
||||
- Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать.
|
||||
- Когда brainstorming классифицировал задачу как architectural, структуру change создать через `openspec new change <id>` (или `/opsx:propose <id>`), а содержимое proposal.md, specs/ и design.md написать по итогам brainstorming. tasks.md из OpenSpec не заполнять — задачи живут в плане superpowers.
|
||||
- `/opsx:apply` не использовать: исполнение идёт через writing-plans и SDD/executing-plans.
|
||||
- `openspec archive <id>` — после merge PR, как часть чеклиста закрытия.
|
||||
Loading…
x
Reference in New Issue
Block a user