0.2.0: harness-check SessionStart hook, adopt-project.sh
This commit is contained in:
parent
d604ae3588
commit
096f5586fb
@ -1,6 +1,8 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.2.0 — 2026-09-27
|
## 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-сервер видит только индекс папки запуска.
|
- codegraph: `codegraph init` в worktree и `projectPath` в вызовах `codegraph_explore` — MCP-сервер видит только индекс папки запуска.
|
||||||
- Порядок старта фичи: worktree на временной ветке → issue → переименование ветки (хук guard-main блокирует запись на main).
|
- Порядок старта фичи: worktree на временной ветке → issue → переименование ветки (хук guard-main блокирует запись на main).
|
||||||
- Маркировка находок и решений: `T<n>.<k>`, `C/I/M-n`, `R-n`, `O-n`, `H-n`; обязательный статус fixed / deferred / rejected.
|
- Маркировка находок и решений: `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 результаты пилота и находки
|
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
|
```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/` (архивация) |
|
| Никаких изменений на 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` |
|
| Правки в `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-хук с командой тестов |
|
| Тесты зелёные перед коммитом | git pre-commit в проекте (не входит в пакет) | обычный git-хук с командой тестов |
|
||||||
|
|
||||||
Exit 2 блокирует вызов и показывает агенту stderr, поэтому агент сам делает то, что просит сообщение. Оба обхода, замеченные на пилоте (запись через `echo >` вместо Write; push через detached HEAD), закрыты.
|
Exit 2 блокирует вызов и показывает агенту stderr, поэтому агент сам делает то, что просит сообщение. Оба обхода, замеченные на пилоте (запись через `echo >` вместо Write; push через detached HEAD), закрыты.
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
- Ledger superpowers (`.superpowers/sdd/`) — прогресс только внутри текущего плана. Задачи плана в Gitea не дублировать.
|
- Ledger superpowers (`.superpowers/sdd/`) — прогресс только внутри текущего плана. Задачи плана в Gitea не дублировать.
|
||||||
|
|
||||||
## Маршрутизация
|
## Маршрутизация
|
||||||
|
- Сообщение `[claude-harness]` в начале сессии означает, что обвязка к проекту не подключена: действовать по его инструкции до всего остального. Скрипт `~/.claude/harness/adopt-project.sh` — единственная разрешённая запись на main помимо архивации OpenSpec.
|
||||||
- Bounded-задача (флаг, фикс, мелкая правка): без OpenSpec change и без плана. Дизайн в чате, затем создать файл `.bounded` в корне worktree (разрешает правки в src/ без плана; файл в `.gitignore`, в коммит не попадает), TDD, коммит, PR.
|
- Bounded-задача (флаг, фикс, мелкая правка): без OpenSpec change и без плана. Дизайн в чате, затем создать файл `.bounded` в корне worktree (разрешает правки в src/ без плана; файл в `.gitignore`, в коммит не попадает), TDD, коммит, PR.
|
||||||
- Architectural-задача: brainstorming пишет спеку не в `docs/superpowers/specs/`, а в `openspec/changes/<id>/`. В поле **Spec** плана указывается этот путь.
|
- Architectural-задача: brainstorming пишет спеку не в `docs/superpowers/specs/`, а в `openspec/changes/<id>/`. В поле **Spec** плана указывается этот путь.
|
||||||
- Перед architectural brainstorming прочитать релевантные файлы в `openspec/specs/` и ARCHITECTURE.md, если есть.
|
- Перед 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
|
if [ "$tool" = "Bash" ]; then
|
||||||
case "$cmd" in
|
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
|
esac
|
||||||
# archive exception: commit of staged openspec-only changes, push of openspec-only commits
|
# archive exception: commit of staged openspec-only changes, push of openspec-only commits
|
||||||
if printf '%s' "$cmd" | grep -Eq '\bgit\s+commit\b'; then
|
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",
|
"matcher": "Edit|Write|MultiEdit|Bash",
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{ "type": "command", "command": "\"$HOME/.claude/hooks/guard-main.sh\"" }
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "\"$HOME/.claude/hooks/guard-main.sh\""
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matcher": "Edit|Write|MultiEdit",
|
"matcher": "Edit|Write|MultiEdit",
|
||||||
"hooks": [
|
"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"
|
cp "$HERE/global/CLAUDE.md" "$CC/CLAUDE.md"
|
||||||
|
|
||||||
# 2. Хуки
|
# 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
|
chmod +x "$CC/hooks/"*.sh
|
||||||
|
|
||||||
# 3. caveman off by default
|
# 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",[]))
|
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[:] = [e for e in pre if not ours(e)]
|
||||||
pre.extend(h["hooks"]["PreToolUse"])
|
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"):
|
for ev in ("SessionStart","PreCompact"):
|
||||||
if ev in hooks:
|
if ev in hooks:
|
||||||
for e in hooks[ev]:
|
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")
|
print("settings.json: hooks merged")
|
||||||
PY
|
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"
|
echo "Плагины Claude Code ставятся вручную: /plugin install superpowers@claude-plugins-official"
|
||||||
check || true
|
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