commit 34a23260b26b4d546bbb648b2632f11ffcc31626 Author: andrey Date: Sun Sep 27 15:44:38 2026 +0000 claude-harness 0.1.0: global CLAUDE.md, hooks, install/new-project scripts, docs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b31396d --- /dev/null +++ b/CHANGELOG.md @@ -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 и шаблон проекта. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a1a635 --- /dev/null +++ b/README.md @@ -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:// --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@:2222//my-app.git +``` + +Заполните `{{...}}` в `CLAUDE.md`, положите фикстуры, запустите `claude` и опишите задачу своими словами. Дальше по `docs/HARNESS.md`. + +## Цикл одной фичи, коротко + +фраза → агент заводит issue → brainstorming (вопросы, подходы, дизайн) → спека в `openspec/changes//` + раздел «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 между версиями меняется. diff --git a/caveman/config.json b/caveman/config.json new file mode 100644 index 0000000..6cc7591 --- /dev/null +++ b/caveman/config.json @@ -0,0 +1 @@ +{ "defaultMode": "off" } diff --git a/docs/HARNESS.md b/docs/HARNESS.md new file mode 100644 index 0000000..3e8e06c --- /dev/null +++ b/docs/HARNESS.md @@ -0,0 +1,68 @@ +# Как работает обвязка + +## Идея + +Каждый инструмент отвечает за один вид памяти, и слои не пересекаются: + +- **Gitea** помнит *что делать* между сессиями и неделями. Issue = фича, PR = точка человеческого контроля. +- **OpenSpec** помнит *как система должна себя вести*. `openspec/specs/` — текущее состояние, `openspec/changes//` — дельта текущей фичи. После merge дельта архивируется и вливается в specs. +- **Superpowers** ведёт *процесс внутри одной фичи*: brainstorming → writing-plans → subagent-driven-development → ревью → finishing-a-development-branch. Прогресс внутри плана живёт в ledger (`.superpowers/sdd//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//` (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//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): один контекст, одно ревью в конце. diff --git a/docs/PILOT-2026-09-27.md b/docs/PILOT-2026-09-27.md new file mode 100644 index 0000000..3eb1039 --- /dev/null +++ b/docs/PILOT-2026-09-27.md @@ -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. diff --git a/examples/obsidian-voice-note/CLAUDE.md b/examples/obsidian-voice-note/CLAUDE.md new file mode 100644 index 0000000..6c28e5b --- /dev/null +++ b/examples/obsidian-voice-note/CLAUDE.md @@ -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/.m4a` + `tests/fixtures/.txt` (эталонный транскрипт). Провайдер в тестах мокается; golden-файлы проверяют формирование заметки и обработку ответа, а не сам сервис. +- Тестовый vault: `<путь укажите>` — синхронизируется между Mac и телефоном. + +## Первая подсистема +Жизненный цикл записи и очередь (core), затем провайдер, затем обёртка. Тестовая инфраструктура — Task 1 первого плана. diff --git a/global/CLAUDE.md b/global/CLAUDE.md new file mode 100644 index 0000000..eca5dba --- /dev/null +++ b/global/CLAUDE.md @@ -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//` — дельта текущей фичи. Спека — единственный источник истины при рулингах. +- Ledger superpowers (`.superpowers/sdd/`) — прогресс только внутри текущего плана. Задачи плана в Gitea не дублировать. + +## Маршрутизация +- Bounded-задача (флаг, фикс, мелкая правка): без OpenSpec change и без плана. Дизайн в чате, затем создать файл `.bounded` в корне worktree (разрешает правки в src/ без плана), TDD, коммит, PR. +- Architectural-задача: brainstorming пишет спеку не в `docs/superpowers/specs/`, а в `openspec/changes//`. В поле **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/-`. +- Режим по умолчанию: 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 `, коммит и push архива прямо в main (единственное разрешённое исключение из правила «в main только через PR»: хук пропускает коммиты, затрагивающие только `openspec/`). + 2. `tea issues close ` с комментарием-ссылкой на PR. + 3. Удалить SDD workspace (`.superpowers/sdd//`). + 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//design.md` в раздел `## Decisions made without discussion`: дата, решение, обоснование, что стоит ошибка. Ledger SDD удаляется, чат теряется; design.md архивируется вместе со спекой и остаётся в истории. + +## Стиль +- Отчёты сабагентов, рулинги и ledger не сжимать. Caveman включается только по явной команде. diff --git a/hooks/guard-main.sh b/hooks/guard-main.sh new file mode 100755 index 0000000..6311cbf --- /dev/null +++ b/hooks/guard-main.sh @@ -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 diff --git a/hooks/require-plan.sh b/hooks/require-plan.sh new file mode 100755 index 0000000..4297ce9 --- /dev/null +++ b/hooks/require-plan.sh @@ -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 diff --git a/hooks/settings.hooks.json b/hooks/settings.hooks.json new file mode 100644 index 0000000..c654137 --- /dev/null +++ b/hooks/settings.hooks.json @@ -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\"" } + ] + } + ] + } +} diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..a688b36 --- /dev/null +++ b/install.sh @@ -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 diff --git a/new-project.sh b/new-project.sh new file mode 100755 index 0000000..262a890 --- /dev/null +++ b/new-project.sh @@ -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 [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 <.*` + эталон `.expected.*`. Внешние сервисы в тестах мокаются. +- {{Где лежит тестовое окружение (vault, БД, песочница).}} + +## Первая подсистема +{{Что делается первым и почему. Тестовая инфраструктура — Task 1 первого плана.}} + +## OpenSpec и superpowers +- Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать. +- Когда brainstorming классифицировал задачу как architectural, структуру change создать через `openspec new change ` (или `/opsx:propose `), а содержимое proposal.md, specs/ и design.md написать по итогам brainstorming. tasks.md из OpenSpec не заполнять — задачи живут в плане superpowers. +- `/opsx:apply` не использовать: исполнение идёт через writing-plans и SDD/executing-plans. +- `openspec archive ` — после merge PR, как часть чеклиста закрытия. diff --git a/project-template/gitignore b/project-template/gitignore new file mode 100644 index 0000000..ca0fe32 --- /dev/null +++ b/project-template/gitignore @@ -0,0 +1,9 @@ +node_modules/ +main.js +*.js.map +data.json +.superpowers/ +.bounded +tests/fixtures/*.m4a +tests/fixtures/*.webm +.DS_Store