claude-harness 0.1.0: global CLAUDE.md, hooks, install/new-project scripts, docs
This commit is contained in:
commit
34a23260b2
7
CHANGELOG.md
Normal file
7
CHANGELOG.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.0 — 2026-09-27
|
||||||
|
- Первая версия после пилота на obsidian-voice-note (10 задач, 125 тестов, PR #2).
|
||||||
|
- Хуки: guard-main (main + detached HEAD, Bash-запись, исключение для openspec/), require-plan (.bounded).
|
||||||
|
- Глобальный CLAUDE.md: маршрутизация superpowers/OpenSpec/Gitea/codegraph, контроллер SDD на sonnet, чеклист закрытия из 6 пунктов, раздел «Decisions made without discussion».
|
||||||
|
- new-project.sh и шаблон проекта.
|
||||||
71
README.md
Normal file
71
README.md
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# claude-harness
|
||||||
|
|
||||||
|
Обвязка для разработки с Claude Code: superpowers как процесс, Gitea как память между сессиями, OpenSpec как библиотека спек, codegraph как карта кода, три блокирующих хука как жёсткие правила. Проверена на реальной фиче 27.09.2026 (см. `docs/PILOT-2026-09-27.md`).
|
||||||
|
|
||||||
|
## Состав
|
||||||
|
|
||||||
|
| Слой | Инструмент | Что помнит | Откуда |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Процесс внутри фичи | superpowers ≥ 6.4.1 | ledger плана, рулинги | плагин Claude Code |
|
||||||
|
| Задачи между сессиями | Gitea issues + PR (`tea`) | что делать, блокеры, human gate на merge | ваш сервер |
|
||||||
|
| Текущее поведение системы | OpenSpec | требования, дельты, история решений | `npm i -g @fission-ai/openspec` |
|
||||||
|
| Карта кода | codegraph (MCP) | связи символов для сабагентов | `brew install`/`npm i -g` |
|
||||||
|
| Контекст по модулям | вложенные CLAUDE.md, ARCHITECTURE.md | границы, конвенции | этот пакет |
|
||||||
|
| Жёсткие правила | `guard-main.sh`, `require-plan.sh` (PreToolUse) | то, что нельзя доверять промпту | этот пакет |
|
||||||
|
|
||||||
|
Что **не** входит и почему: beads (дублирует Gitea), template-bridge (устарел, дублирует ledger), caveman по умолчанию (сжимает отчёты и рулинги; остаётся доступен по `/caveman`).
|
||||||
|
|
||||||
|
## Файлы
|
||||||
|
|
||||||
|
```
|
||||||
|
global/CLAUDE.md глобальные правила процесса → ~/.claude/CLAUDE.md
|
||||||
|
hooks/guard-main.sh запрет изменений на main/detached HEAD (Edit/Write/Bash); исключение — коммиты только в openspec/
|
||||||
|
hooks/require-plan.sh правки в src/ только при наличии плана или файла .bounded
|
||||||
|
hooks/settings.hooks.json блок PreToolUse для ~/.claude/settings.json
|
||||||
|
caveman/config.json defaultMode: off → ~/.config/caveman/config.json
|
||||||
|
project-template/ CLAUDE.md, ARCHITECTURE.md, gitignore для нового проекта
|
||||||
|
examples/ реальный CLAUDE.md проекта-пилота
|
||||||
|
install.sh установка глобальной части, идемпотентно; --check проверяет окружение
|
||||||
|
new-project.sh каркас нового проекта: git, CLAUDE.md, openspec init, codegraph init, remote
|
||||||
|
docs/HARNESS.md как это работает: слои, цикл фичи, SDD, дисциплина закрытия
|
||||||
|
docs/PILOT-2026-09-27.md результаты пилота и находки
|
||||||
|
```
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Плагины Claude Code (внутри сессии claude)
|
||||||
|
/plugin install superpowers@claude-plugins-official
|
||||||
|
/plugin uninstall beads@beads-marketplace # если стоит
|
||||||
|
/plugin uninstall template-bridge@template-bridge-marketplace
|
||||||
|
|
||||||
|
# 2. CLI
|
||||||
|
brew install tea && tea login add --name home --url https://<gitea> --token <token> # scopes: read:user, write:repository, write:issue
|
||||||
|
npm i -g @fission-ai/openspec
|
||||||
|
# codegraph: https://github.com/colbymchenry/codegraph
|
||||||
|
|
||||||
|
# 3. Глобальная часть
|
||||||
|
bash install.sh --check
|
||||||
|
bash install.sh
|
||||||
|
|
||||||
|
# 4. Проверка хука: в любом репозитории на main запустить claude и попросить создать файл.
|
||||||
|
# Ожидается BLOCKED и предложение создать worktree.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Новый проект
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash new-project.sh ~/projects/my-app ssh://git@<gitea>:2222/<owner>/my-app.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Заполните `{{...}}` в `CLAUDE.md`, положите фикстуры, запустите `claude` и опишите задачу своими словами. Дальше по `docs/HARNESS.md`.
|
||||||
|
|
||||||
|
## Цикл одной фичи, коротко
|
||||||
|
|
||||||
|
фраза → агент заводит issue → brainstorming (вопросы, подходы, дизайн) → спека в `openspec/changes/<id>/` + раздел «Decisions made without discussion» → ваше утверждение → план с кодом → ваше утверждение → SDD (контроллер на sonnet, implementer-ы haiku/sonnet, ревью каждой задачи, финальное ревью на opus) → PR в Gitea с рулингами → **вы делаете merge** → «закрывай»: archive, close issue, удаление ветки/worktree/workspace, pull main.
|
||||||
|
|
||||||
|
Ручных шагов три: два утверждения и merge. Читать обязательно: спеку один раз, раздел решений каждый раз, когда он пополняется.
|
||||||
|
|
||||||
|
## Обновление
|
||||||
|
|
||||||
|
`git pull && bash install.sh`. Бэкапы `CLAUDE.md` и `settings.json` создаются автоматически. Раз в два месяца сверяйтесь с `RELEASE-NOTES.md` superpowers: семантика skills между версиями меняется.
|
||||||
1
caveman/config.json
Normal file
1
caveman/config.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{ "defaultMode": "off" }
|
||||||
68
docs/HARNESS.md
Normal file
68
docs/HARNESS.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Как работает обвязка
|
||||||
|
|
||||||
|
## Идея
|
||||||
|
|
||||||
|
Каждый инструмент отвечает за один вид памяти, и слои не пересекаются:
|
||||||
|
|
||||||
|
- **Gitea** помнит *что делать* между сессиями и неделями. Issue = фича, PR = точка человеческого контроля.
|
||||||
|
- **OpenSpec** помнит *как система должна себя вести*. `openspec/specs/` — текущее состояние, `openspec/changes/<id>/` — дельта текущей фичи. После merge дельта архивируется и вливается в specs.
|
||||||
|
- **Superpowers** ведёт *процесс внутри одной фичи*: brainstorming → writing-plans → subagent-driven-development → ревью → finishing-a-development-branch. Прогресс внутри плана живёт в ledger (`.superpowers/sdd/<plan>/progress.md`), который переживает компакцию и обрыв сессии, но удаляется после завершения.
|
||||||
|
- **codegraph** — карта кода для сабагентов: `codegraph_explore` вместо grep/Read по файлам.
|
||||||
|
- **Хуки** — то, что нельзя доверять промпту. Superpowers *просит*, хуки *запрещают*.
|
||||||
|
|
||||||
|
Клей между слоями — `global/CLAUDE.md`. Superpowers сам заявляет, что инструкции пользователя выше skills, поэтому вся маршрутизация живёт там.
|
||||||
|
|
||||||
|
## Цикл фичи
|
||||||
|
|
||||||
|
| # | Шаг | Кто | Артефакт |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | Формулируете цель одной фразой; агент заводит issue | вы + агент | issue #N |
|
||||||
|
| 2 | `brainstorming`: классификация spike / bounded / architectural, вопросы по одному, подходы, дизайн по частям | агент + вы | — |
|
||||||
|
| 3 | Спека в `openspec/changes/<id>/` (proposal, specs/*, design) + раздел «Decisions made without discussion»; **ваше утверждение** | агент, вы | change |
|
||||||
|
| 4 | `using-git-worktrees`: ветка `feat/N-slug` | агент | worktree |
|
||||||
|
| 5 | `writing-plans`: план с Global Constraints, Review Focus, полным кодом задач; Task 1 — тестовая инфраструктура; **ваше утверждение** | агент, вы | `docs/superpowers/plans/*.md` |
|
||||||
|
| 6 | SDD: контроллер (sonnet) → implementer на задачу (haiku/sonnet) → reviewer (sonnet) → fix loop до 5 раундов → ledger | агент автономно | коммиты, ledger |
|
||||||
|
| 7 | Финальное ревью всей ветки (opus), одна волна фиксов | агент | — |
|
||||||
|
| 8 | `finishing-a-development-branch`: PR в Gitea с «Rulings I made» | агент | PR |
|
||||||
|
| 9 | Ревью PR в Gitea; комментарии → `receiving-code-review` → правки в ветке; **вы делаете merge** | вы ↔ агент | merge |
|
||||||
|
| 10 | «Закрывай»: чеклист из 6 пунктов (см. CLAUDE.md) | агент | archive, closed issue, чистый main |
|
||||||
|
|
||||||
|
Шаги 6–8 идут без остановок. Агент останавливается только на необратимой операции, вопросе безопасности, побочном эффекте вне worktree или плане, который невозможно исполнить.
|
||||||
|
|
||||||
|
**Bounded-задача** (флаг, фикс) идёт короче: дизайн в чате, файл `.bounded` в корне worktree (пропускает хук require-plan), TDD, коммит, PR. Без OpenSpec change и без плана.
|
||||||
|
|
||||||
|
## Subagent-driven development
|
||||||
|
|
||||||
|
Три роли, один цикл на задачу:
|
||||||
|
|
||||||
|
- **Контроллер** видит план, спеку, ledger и короткие статусы. Режет план на брифы (`task-brief`), запускает сабагентов, выносит рулинги, ведёт ledger. Кода не пишет. Запускается фоновым сабагентом на sonnet.
|
||||||
|
- **Implementer** — свежий сабагент на задачу. Видит только свой бриф и интерфейсы соседних задач. TDD, коммит, отчёт в файл, статус DONE / DONE_WITH_CONCERNS / NEEDS_CONTEXT / BLOCKED.
|
||||||
|
- **Reviewer** — свежий сабагент, получает бриф, отчёт и diff файлом (`review-package`). Два вердикта: соответствие спеке и качество.
|
||||||
|
|
||||||
|
Fix loop: раунды 1–3 продолжают того же implementer, 4–5 — свежий на более сильной модели. После пятого контроллер адьюдицирует сам и записывает рулинг. Minor-замечания в цикл не входят, откладываются до финального ревью.
|
||||||
|
|
||||||
|
Шаблоны сабагентов входят в superpowers: `implementer-prompt.md`, `task-reviewer-prompt.md`, `re-review-prompt.md`, `code-reviewer.md`. Это роли процесса, а не доменные специалисты; доменная экспертиза приходит через бриф с кодом, проектный CLAUDE.md и доменные skills.
|
||||||
|
|
||||||
|
## Решения без обсуждения
|
||||||
|
|
||||||
|
Единственное место, где видно, что агент решил за вас. Правило в CLAUDE.md: любое такое решение — на этапе спеки, плана или исполнения — пишется в `openspec/changes/<id>/design.md`, раздел `## Decisions made without discussion`, с датой, обоснованием и ценой ошибки. Ledger удаляется, чат теряется, design.md архивируется со спекой.
|
||||||
|
|
||||||
|
Читать: спеку один раз при утверждении, раздел решений каждый раз, когда он пополняется (после спеки, после плана, в финальном отчёте SDD). Каждое решение — вопрос «согласен ли я»: согласен — ничего не делать; не согласен — сказать агенту, он поправит; непонятно — спросить.
|
||||||
|
|
||||||
|
## Хуки
|
||||||
|
|
||||||
|
| Правило | Файл | Механизм |
|
||||||
|
|---|---|---|
|
||||||
|
| Никаких изменений на 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` |
|
||||||
|
| Тесты зелёные перед коммитом | git pre-commit в проекте (не входит в пакет) | обычный git-хук с командой тестов |
|
||||||
|
|
||||||
|
Exit 2 блокирует вызов и показывает агенту stderr, поэтому агент сам делает то, что просит сообщение. Оба обхода, замеченные на пилоте (запись через `echo >` вместо Write; push через detached HEAD), закрыты.
|
||||||
|
|
||||||
|
## Дисциплина между сессиями
|
||||||
|
|
||||||
|
Новая сессия начинается с `tea issues ls` и `openspec list`, а не с памяти о прошлой. Незавершённый план возобновляется из ledger в worktree. Связка разваливается не на исполнении, а на закрытии — поэтому «закрывай» выполняет весь чеклист без остановок.
|
||||||
|
|
||||||
|
## Стоимость
|
||||||
|
|
||||||
|
Bootstrap superpowers (~3 КБ) вставляется на каждом старте и после компакции. SDD стоит свежий контекст на задачу и на ревью; подробный план с готовым кодом позволяет отдать большинство задач haiku. Для планов до 5 задач дешевле `executing-plans` (Native): один контекст, одно ревью в конце.
|
||||||
43
docs/PILOT-2026-09-27.md
Normal file
43
docs/PILOT-2026-09-27.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Пилот: obsidian-voice-note, core-recording-lifecycle
|
||||||
|
|
||||||
|
Первая подсистема плагина голосовых заметок для Obsidian (жизненный цикл записи и очередь отправки) прошла цикл целиком за один день: фраза → issue #1 → brainstorming → спека → план на 10 задач → SDD → PR #2 → merge → archive → close.
|
||||||
|
|
||||||
|
| Показатель | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Brainstorming → утверждённая спека | ~35 мин, 5 вопросов, 3 подхода, дизайн в 4 частях |
|
||||||
|
| План | 2528 строк, 10 задач с полным кодом, Review Focus 5 пунктов, spec coverage |
|
||||||
|
| Исполнение SDD | 1 ч 35 мин, без вопросов к человеку |
|
||||||
|
| Ревью с первого раза | 8 из 10; 2 раунда фиксов (один косметический: подпись коммита) |
|
||||||
|
| Финальное ревью (opus) | 0 critical, 1 important, 3 minor, одна волна фиксов, 7/7 addressed |
|
||||||
|
| Тесты | 125/125, golden сошёлся с первого прогона; `src/core` 670 строк |
|
||||||
|
| Модели исполнителей | haiku 7 задач, sonnet 3; ревьюеры sonnet, финал opus |
|
||||||
|
| Решения без обсуждения | 36 в design.md (28 спека/план, 8 исполнение) |
|
||||||
|
| Обрыв контроллера | случайная остановка перед Task 1; возобновился из ledger без потерь |
|
||||||
|
|
||||||
|
## Что подтвердилось
|
||||||
|
|
||||||
|
- Автотриггер brainstorming без вызова по имени; skills OpenSpec его не перехватили.
|
||||||
|
- Спека легла в `openspec/changes/`, issue завёл агент, worktree создал сам после блокировки хуком.
|
||||||
|
- Раздел «Decisions made without discussion» ведётся на всех этапах, включая контроллер SDD; агент сам убрал из него пункт, который предложил человек.
|
||||||
|
- Контроллер следовал спеке как источнику истины против плана (рулинг F-1) — правильно.
|
||||||
|
- Подробный план с кодом → большинство задач на haiku без брака.
|
||||||
|
|
||||||
|
## Находки и что изменено
|
||||||
|
|
||||||
|
1. **Контроллер «на среднем уровне» запустился на Opus.** Сессия поняла фразу как «уровнем ниже». → CLAUDE.md: контроллер — сабагент на sonnet, явно.
|
||||||
|
2. **codegraph в worktree возвращал пусто** даже после `codegraph init`; implementer-ы читали файлы напрямую. → CLAUDE.md: при пустом ответе отметить в ledger и не тратить раунды. Причина (индекс привязан к корню репо) — открытый вопрос.
|
||||||
|
3. **Push в main через detached HEAD.** При архивации агент сделал `git switch --detach origin/main` и запушил; `git branch --show-current` пуст, хук пропустил. → guard-main: detached HEAD защищён, коммит/push в main только с файлами `openspec/`; архивация объявлена единственным исключением.
|
||||||
|
4. Хвосты закрытия (ветка, worktree, pull main) оставались человеку. → чеклист закрытия из 6 пунктов в CLAUDE.md.
|
||||||
|
|
||||||
|
## Мелкие наблюдения
|
||||||
|
|
||||||
|
- Первый тест хука: агент обошёл блокировку Write через `echo >`. Bash добавлен в matcher.
|
||||||
|
- Вставленный текст без своей строки вызывает вопрос-подтверждение; добавлять слово перед вставкой.
|
||||||
|
- haiku дважды подписал коммит своим именем вместо трейлера из плана.
|
||||||
|
- Версии зависимостей не зафиксированы (TypeScript 7, vitest 5, esbuild 0.28).
|
||||||
|
|
||||||
|
## Отложено до следующей фичи
|
||||||
|
|
||||||
|
- T1: Node-глобалы (`process`, `Buffer`) проходят проверку типов в `src/core`; страж ловит только импорты. Bounded-фикс до обёртки.
|
||||||
|
- Зафиксировать версии зависимостей. Bounded-фикс.
|
||||||
|
- Провайдер распознавания (AITUNNEL, OpenAI-compatible) — issue #2. Обёртка Obsidian — issue #3.
|
||||||
28
examples/obsidian-voice-note/CLAUDE.md
Normal file
28
examples/obsidian-voice-note/CLAUDE.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Obsidian Voice Notes — плагин голосовых заметок
|
||||||
|
|
||||||
|
## Что это
|
||||||
|
Плагин Obsidian: запись голоса с телефона или десктопа → отправка файлом во внешний сервис распознавания → текст в заметку. Mobile-first: основной сценарий — iPhone, десктоп получает то же.
|
||||||
|
|
||||||
|
## Стек и ограничения
|
||||||
|
- TypeScript, esbuild (стандартный шаблон obsidian-sample-plugin), vitest для тестов.
|
||||||
|
- `isDesktopOnly: false` в manifest.json. Никакого Node API (`fs`, `child_process`, `path`), только Web API и API Obsidian (`requestUrl`, `app.vault`).
|
||||||
|
- Запись: `MediaRecorder`. Форматы на входе различаются: iOS даёт audio/mp4, Android — audio/webm, десктоп — webm/ogg. Провайдер должен принимать все три или конвертировать.
|
||||||
|
- Отправка файлом, без стриминга.
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
- `src/core/` — чистый TypeScript без импортов из `obsidian`: машина состояний записи (created → saved → sent → transcribed → inserted, ветки на потерю сети и обрыв записи), очередь отправки, провайдеры распознавания, формирование markdown-заметки. Покрывается vitest полностью.
|
||||||
|
- `src/obsidian/` — тонкая обёртка: команды, настройки, UI, работа с vault. Юнит-тестов нет, проверяется руками в тестовом vault.
|
||||||
|
- Провайдер — интерфейс с реализациями: `openai-compatible` (AITUNNEL по умолчанию; Groq, OpenAI, whisper на VPS — тот же код, другой URL) и при необходимости `yandex`. Переключение на следующего провайдера по 403/таймауту.
|
||||||
|
- Ключи API — в настройках плагина (`data.json`), в git не попадают.
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
- `npm run dev` — сборка в watch-режиме в тестовый vault.
|
||||||
|
- `npm test` — vitest, вся папка `tests/`. Зелёным считается весь набор.
|
||||||
|
- `npm run build` — продакшн-сборка `main.js`.
|
||||||
|
|
||||||
|
## Тесты
|
||||||
|
- Golden-тесты: `tests/fixtures/<name>.m4a` + `tests/fixtures/<name>.txt` (эталонный транскрипт). Провайдер в тестах мокается; golden-файлы проверяют формирование заметки и обработку ответа, а не сам сервис.
|
||||||
|
- Тестовый vault: `<путь укажите>` — синхронизируется между Mac и телефоном.
|
||||||
|
|
||||||
|
## Первая подсистема
|
||||||
|
Жизненный цикл записи и очередь (core), затем провайдер, затем обёртка. Тестовая инфраструктура — Task 1 первого плана.
|
||||||
34
global/CLAUDE.md
Normal file
34
global/CLAUDE.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Процесс разработки
|
||||||
|
|
||||||
|
## Роли инструментов
|
||||||
|
- Superpowers ведёт процесс внутри одной фичи: brainstorming → writing-plans → исполнение → ревью → finishing-a-development-branch.
|
||||||
|
- Gitea (CLI `tea`) хранит задачи между сессиями. Одна фича = одна issue = одна ветка = один PR. Issue заводит агент (`tea issues create`) из формулировки цели, до brainstorming.
|
||||||
|
- OpenSpec хранит требования. `openspec/specs/` — текущее поведение системы, `openspec/changes/<id>/` — дельта текущей фичи. Спека — единственный источник истины при рулингах.
|
||||||
|
- Ledger superpowers (`.superpowers/sdd/`) — прогресс только внутри текущего плана. Задачи плана в Gitea не дублировать.
|
||||||
|
|
||||||
|
## Маршрутизация
|
||||||
|
- Bounded-задача (флаг, фикс, мелкая правка): без OpenSpec change и без плана. Дизайн в чате, затем создать файл `.bounded` в корне worktree (разрешает правки в src/ без плана), TDD, коммит, PR.
|
||||||
|
- Architectural-задача: brainstorming пишет спеку не в `docs/superpowers/specs/`, а в `openspec/changes/<id>/`. В поле **Spec** плана указывается этот путь.
|
||||||
|
- Перед architectural brainstorming прочитать релевантные файлы в `openspec/specs/` и ARCHITECTURE.md, если есть.
|
||||||
|
- Карта кода — codegraph (MCP). Перед изменением implementer вызывает `codegraph_explore` по своей области вместо grep/Read по файлам. Это инструмент внутри процесса, а не замена skill-check. В новом проекте: `codegraph init` один раз. Если `codegraph_explore` возвращает пусто (известная проблема в worktree под `.claude/worktrees/`), один раз сообщить об этом в ledger и дальше использовать Read, не тратя раунды на переиндексацию.
|
||||||
|
|
||||||
|
## Исполнение
|
||||||
|
- Работа только в worktree. Ветка называется по issue: `feat/<issue-id>-<slug>`.
|
||||||
|
- Режим по умолчанию: subagent-driven-development. Контроллер SDD запускается как фоновый сабагент **на модели sonnet** (не на модели сессии) — сессия только ждёт его отчёт. Для планов до 5 задач — executing-plans (Native).
|
||||||
|
- Модели исполнителей выбираются по SDD Model Selection: задача с готовым кодом в плане — haiku, интеграция — sonnet, ревью всей ветки — opus. Модель в dispatch указывать всегда.
|
||||||
|
|
||||||
|
## Завершение
|
||||||
|
- finishing-a-development-branch создаёт PR в Gitea (`tea pulls create`) и ссылается на issue. Комментарии к PR обрабатываются через receiving-code-review: агент читает их из Gitea, правит в той же ветке, отвечает в PR. Merge — только по явной команде человека.
|
||||||
|
- После merge по команде «закрывай» выполняется весь чеклист закрытия, без остановок между пунктами:
|
||||||
|
1. `openspec archive <id>`, коммит и push архива прямо в main (единственное разрешённое исключение из правила «в main только через PR»: хук пропускает коммиты, затрагивающие только `openspec/`).
|
||||||
|
2. `tea issues close <id>` с комментарием-ссылкой на PR.
|
||||||
|
3. Удалить SDD workspace (`.superpowers/sdd/<plan>/`).
|
||||||
|
4. Удалить ветку фичи на сервере и локально (`git push origin --delete`, `git branch -D`), удалить worktree (`git worktree remove`). Остальные worktree не трогать.
|
||||||
|
5. Обновить основной checkout: если рабочее дерево там чистое — `git pull --ff-only`; если есть незакоммиченные изменения — не трогать, а перечислить их в итоговом сообщении.
|
||||||
|
6. Итоговое сообщение: что сделано по пунктам 1–5, что осталось человеку.
|
||||||
|
Любой другой push в main — только через PR.
|
||||||
|
- Список "Rulings I made" из финального сообщения копируется в описание PR.
|
||||||
|
- Любое решение, принятое агентом без обсуждения с человеком — на этапе спеки, плана или исполнения — дописывается в `openspec/changes/<id>/design.md` в раздел `## Decisions made without discussion`: дата, решение, обоснование, что стоит ошибка. Ledger SDD удаляется, чат теряется; design.md архивируется вместе со спекой и остаётся в истории.
|
||||||
|
|
||||||
|
## Стиль
|
||||||
|
- Отчёты сабагентов, рулинги и ledger не сжимать. Caveman включается только по явной команде.
|
||||||
44
hooks/guard-main.sh
Executable file
44
hooks/guard-main.sh
Executable file
@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# PreToolUse hook: block file changes on main/master and on detached HEAD.
|
||||||
|
# Covers Edit/Write/MultiEdit directly and Bash commands that look like writes.
|
||||||
|
# Single exception: commits/pushes to main that touch only openspec/ (archive step).
|
||||||
|
set -euo pipefail
|
||||||
|
input=$(cat)
|
||||||
|
eval "$(printf '%s' "$input" | python3 -c '
|
||||||
|
import sys, json, shlex
|
||||||
|
d = json.load(sys.stdin); ti = d.get("tool_input", {}) or {}
|
||||||
|
print("cwd=" + shlex.quote(d.get("cwd", "")))
|
||||||
|
print("tool=" + shlex.quote(d.get("tool_name", "")))
|
||||||
|
print("cmd=" + shlex.quote(ti.get("command", "") or ""))' 2>/dev/null || echo 'cwd=; tool=; cmd=')"
|
||||||
|
[ -n "$cwd" ] || exit 0
|
||||||
|
git -C "$cwd" rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
|
||||||
|
branch=$(git -C "$cwd" branch --show-current 2>/dev/null || true)
|
||||||
|
case "$branch" in main|master|"") ;; *) exit 0 ;; esac
|
||||||
|
label=${branch:-"detached HEAD"}
|
||||||
|
|
||||||
|
only_openspec() { # $1 = git diff range/args; true if every changed path is under openspec/
|
||||||
|
local files
|
||||||
|
files=$(git -C "$cwd" diff --name-only $1 2>/dev/null || true)
|
||||||
|
[ -n "$files" ] && ! printf '%s\n' "$files" | grep -vq '^openspec/'
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$tool" = "Bash" ]; then
|
||||||
|
case "$cmd" in
|
||||||
|
*"git worktree"*|*"git checkout -b"*|*"git switch -c"*|*"openspec archive"*) 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
|
||||||
|
only_openspec "--cached" && exit 0
|
||||||
|
fi
|
||||||
|
if printf '%s' "$cmd" | grep -Eq '\bgit\s+push\b'; then
|
||||||
|
only_openspec "origin/main...HEAD" && exit 0
|
||||||
|
fi
|
||||||
|
if printf '%s' "$cmd" | grep -Eq '(^|[^<])>{1,2}[^&]|\btee\b|\bsed\s+-i|\bmv\b|\bcp\b|\brm\b|\btouch\b|\bmkdir\b|\bgit\s+(add|commit|push|apply|stash\s+pop)\b|\bnpm\s+(i|install|init)\b|\bpip\s+install\b'; then
|
||||||
|
echo "BLOCKED: shell write on $label ($(printf '%s' "$cmd" | head -c 80)). Create an isolated worktree first (superpowers:using-git-worktrees). Only 'openspec archive' commits may land on main directly." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "BLOCKED: you are on $label. Create an isolated worktree first (superpowers:using-git-worktrees), never edit main directly." >&2
|
||||||
|
exit 2
|
||||||
24
hooks/require-plan.sh
Executable file
24
hooks/require-plan.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# PreToolUse hook: edits under src/ require a plan for this branch or a .bounded marker.
|
||||||
|
set -euo pipefail
|
||||||
|
input=$(cat)
|
||||||
|
read -r cwd fpath < <(printf '%s' "$input" | python3 -c '
|
||||||
|
import sys,json
|
||||||
|
d=json.load(sys.stdin); ti=d.get("tool_input",{}) or {}
|
||||||
|
print(d.get("cwd",""), ti.get("file_path") or ti.get("path") or "")' 2>/dev/null || echo " ")
|
||||||
|
[ -n "$cwd" ] && [ -n "$fpath" ] || exit 0
|
||||||
|
root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null || true)
|
||||||
|
[ -n "$root" ] || exit 0
|
||||||
|
case "$fpath" in
|
||||||
|
"$root"/src/*|src/*) ;;
|
||||||
|
*) exit 0 ;;
|
||||||
|
esac
|
||||||
|
[ -f "$root/.bounded" ] && exit 0
|
||||||
|
branch=$(git -C "$cwd" branch --show-current 2>/dev/null || true)
|
||||||
|
if ls "$root"/docs/superpowers/plans/*.md >/dev/null 2>&1; then
|
||||||
|
if [ -z "$branch" ] || grep -lq -- "$branch" "$root"/docs/superpowers/plans/*.md 2>/dev/null; then exit 0; fi
|
||||||
|
# any plan exists but none names this branch: still allow if only one plan (single-feature repo)
|
||||||
|
[ "$(ls "$root"/docs/superpowers/plans/*.md | wc -l)" -eq 1 ] && exit 0
|
||||||
|
fi
|
||||||
|
echo "BLOCKED: editing $fpath without a plan. Run superpowers:brainstorming; for a bounded change create '$root/.bounded' first." >&2
|
||||||
|
exit 2
|
||||||
18
hooks/settings.hooks.json
Normal file
18
hooks/settings.hooks.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Edit|Write|MultiEdit|Bash",
|
||||||
|
"hooks": [
|
||||||
|
{ "type": "command", "command": "\"$HOME/.claude/hooks/guard-main.sh\"" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "Edit|Write|MultiEdit",
|
||||||
|
"hooks": [
|
||||||
|
{ "type": "command", "command": "\"$HOME/.claude/hooks/require-plan.sh\"" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
74
install.sh
Executable file
74
install.sh
Executable file
@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# claude-harness: установка глобальной части (CLAUDE.md, хуки, конфиг caveman, слияние settings.json).
|
||||||
|
# Идемпотентно. Запуск: bash install.sh | проверка окружения: bash install.sh --check
|
||||||
|
set -euo pipefail
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
CC="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
||||||
|
|
||||||
|
check() {
|
||||||
|
local ok=0
|
||||||
|
echo "== Проверка окружения"
|
||||||
|
for bin in claude git python3 node npm tea openspec codegraph; do
|
||||||
|
if command -v "$bin" >/dev/null 2>&1; then echo " ok $bin ($(command -v "$bin"))"; else echo " MISS $bin"; ok=1; fi
|
||||||
|
done
|
||||||
|
if [ -f "$CC/plugins/installed_plugins.json" ]; then
|
||||||
|
for p in superpowers caveman; do
|
||||||
|
if grep -q "\"$p@" "$CC/plugins/installed_plugins.json"; then echo " ok plugin $p"; else echo " MISS plugin $p (/plugin install ...)"; fi
|
||||||
|
done
|
||||||
|
for p in beads template-bridge; do
|
||||||
|
grep -q "\"$p@" "$CC/plugins/installed_plugins.json" && echo " WARN plugin $p установлен — конфликтует с обвязкой, удалить" || true
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo " WARN не найден $CC/plugins/installed_plugins.json"
|
||||||
|
fi
|
||||||
|
if [ -f "$CC/settings.json" ] && grep -q 'codegraph prompt-hook' "$CC/settings.json"; then
|
||||||
|
echo " WARN codegraph prompt-hook в settings.json — рекомендуется отключить на время пилота"
|
||||||
|
fi
|
||||||
|
return $ok
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--check" ]; then check; exit $?; fi
|
||||||
|
|
||||||
|
mkdir -p "$CC/hooks" "$HOME/.config/caveman"
|
||||||
|
stamp=$(date +%Y%m%d-%H%M%S)
|
||||||
|
|
||||||
|
# 1. Глобальный CLAUDE.md
|
||||||
|
if [ -f "$CC/CLAUDE.md" ] && ! cmp -s "$HERE/global/CLAUDE.md" "$CC/CLAUDE.md"; then
|
||||||
|
cp "$CC/CLAUDE.md" "$CC/CLAUDE.md.bak.$stamp"; echo "backup: CLAUDE.md.bak.$stamp"
|
||||||
|
fi
|
||||||
|
cp "$HERE/global/CLAUDE.md" "$CC/CLAUDE.md"
|
||||||
|
|
||||||
|
# 2. Хуки
|
||||||
|
cp "$HERE/hooks/guard-main.sh" "$HERE/hooks/require-plan.sh" "$CC/hooks/"
|
||||||
|
chmod +x "$CC/hooks/"*.sh
|
||||||
|
|
||||||
|
# 3. caveman off by default
|
||||||
|
cp "$HERE/caveman/config.json" "$HOME/.config/caveman/config.json"
|
||||||
|
|
||||||
|
# 4. settings.json: слить блок hooks, вычистить хвосты beads/template-bridge
|
||||||
|
SETTINGS="$CC/settings.json"
|
||||||
|
[ -f "$SETTINGS" ] || echo '{}' > "$SETTINGS"
|
||||||
|
cp "$SETTINGS" "$SETTINGS.bak.$stamp"
|
||||||
|
python3 - "$SETTINGS" "$HERE/hooks/settings.hooks.json" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
sp, hp = sys.argv[1], sys.argv[2]
|
||||||
|
s = json.load(open(sp)); h = json.load(open(hp))
|
||||||
|
hooks = s.setdefault("hooks", {})
|
||||||
|
pre = hooks.setdefault("PreToolUse", [])
|
||||||
|
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"])
|
||||||
|
for ev in ("SessionStart","PreCompact"):
|
||||||
|
if ev in hooks:
|
||||||
|
for e in hooks[ev]:
|
||||||
|
e["hooks"] = [x for x in e.get("hooks",[]) if "bd prime" not in x.get("command","") and "WORKFLOW REMINDER" not in x.get("command","")]
|
||||||
|
hooks[ev] = [e for e in hooks[ev] if e.get("hooks")]
|
||||||
|
if not hooks[ev]: del hooks[ev]
|
||||||
|
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 "Плагины Claude Code ставятся вручную: /plugin install superpowers@claude-plugins-official"
|
||||||
|
check || true
|
||||||
40
new-project.sh
Executable file
40
new-project.sh
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# claude-harness: каркас нового проекта под обвязку.
|
||||||
|
# Запуск: bash new-project.sh <папка> [ssh-url репозитория в Gitea]
|
||||||
|
set -euo pipefail
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
DIR="${1:?usage: new-project.sh <dir> [git-remote-url]}"
|
||||||
|
REMOTE="${2:-}"
|
||||||
|
NAME="$(basename "$DIR")"
|
||||||
|
|
||||||
|
mkdir -p "$DIR" && cd "$DIR"
|
||||||
|
[ -d .git ] || git init -b main
|
||||||
|
sed "s/{{PROJECT_NAME}}/$NAME/" "$HERE/project-template/CLAUDE.md" > CLAUDE.md
|
||||||
|
cp "$HERE/project-template/ARCHITECTURE.md" ARCHITECTURE.md
|
||||||
|
cp "$HERE/project-template/gitignore" .gitignore
|
||||||
|
mkdir -p tests/fixtures docs/superpowers/plans
|
||||||
|
|
||||||
|
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 не удался"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git add -A
|
||||||
|
git commit -qm "chore: bootstrap project skeleton (claude-harness)" || true
|
||||||
|
if [ -n "$REMOTE" ]; then
|
||||||
|
git remote get-url origin >/dev/null 2>&1 || git remote add origin "$REMOTE"
|
||||||
|
git push -u origin main
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<MSG
|
||||||
|
|
||||||
|
Проект $NAME готов. Дальше:
|
||||||
|
1. Заполните {{...}} в CLAUDE.md и ARCHITECTURE.md.
|
||||||
|
2. Положите первые фикстуры в tests/fixtures/.
|
||||||
|
3. cd $DIR && claude
|
||||||
|
«Хочу сделать ... Описание и ограничения в CLAUDE.md. Давай начнём.»
|
||||||
|
MSG
|
||||||
15
project-template/ARCHITECTURE.md
Normal file
15
project-template/ARCHITECTURE.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Архитектура
|
||||||
|
|
||||||
|
{{Карта модулей на одну страницу: что где лежит, границы, потоки данных. Читается агентом перед architectural brainstorming. Обновляется вместе со спеками.}}
|
||||||
|
|
||||||
|
## Модули
|
||||||
|
|
||||||
|
| Модуль | Ответственность | Зависит от |
|
||||||
|
|---|---|---|
|
||||||
|
| | | |
|
||||||
|
|
||||||
|
## Границы
|
||||||
|
- Что не должно импортировать что.
|
||||||
|
|
||||||
|
## Открытые вопросы
|
||||||
|
-
|
||||||
31
project-template/CLAUDE.md
Normal file
31
project-template/CLAUDE.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Что это
|
||||||
|
{{ОДНИМ АБЗАЦЕМ: что делает система, для кого, главный сценарий.}}
|
||||||
|
|
||||||
|
## Стек и ограничения
|
||||||
|
- {{Язык, сборка, тесты — например: Python 3.12, uv, pytest.}}
|
||||||
|
- {{Жёсткие ограничения среды исполнения — без чего код не должен собираться.}}
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
- `src/core/` (или {{ваш путь}}) — чистая логика без зависимостей от фреймворка/платформы. Покрывается тестами полностью.
|
||||||
|
- `src/{{adapter}}/` — тонкая обёртка над платформой. Тестируется руками или интеграционно.
|
||||||
|
- {{Ключевые интерфейсы/порты между ними, если есть.}}
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
- `{{npm test / pytest}}` — весь набор тестов. Зелёным считается только весь набор.
|
||||||
|
- `{{npm run build / ...}}` — сборка.
|
||||||
|
- `{{npm run dev / ...}}` — локальный запуск.
|
||||||
|
|
||||||
|
## Тесты
|
||||||
|
- Golden-тесты: `tests/fixtures/<name>.*` + эталон `<name>.expected.*`. Внешние сервисы в тестах мокаются.
|
||||||
|
- {{Где лежит тестовое окружение (vault, БД, песочница).}}
|
||||||
|
|
||||||
|
## Первая подсистема
|
||||||
|
{{Что делается первым и почему. Тестовая инфраструктура — Task 1 первого плана.}}
|
||||||
|
|
||||||
|
## 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, как часть чеклиста закрытия.
|
||||||
9
project-template/gitignore
Normal file
9
project-template/gitignore
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
node_modules/
|
||||||
|
main.js
|
||||||
|
*.js.map
|
||||||
|
data.json
|
||||||
|
.superpowers/
|
||||||
|
.bounded
|
||||||
|
tests/fixtures/*.m4a
|
||||||
|
tests/fixtures/*.webm
|
||||||
|
.DS_Store
|
||||||
Loading…
x
Reference in New Issue
Block a user