Как устроено

Для разработчика tinyteam: где что лежит, кто что пишет и когда модель что получает. Один исполняемый файл cli.cjs (TypeScript, собран esbuild), без демона — всё происходит в момент вызова команды или hook; сам tinyteam в сеть не ходит (upgrade ставит выпуск из локального каталога выпуска); запускаемый им OpenSpec может отправлять свою телеметрию (OPENSPEC_TELEMETRY=0 её выключает).

человек: tinyteam …→файлы change→харнесс: /opsx-*→hook UserPromptExpansion→контекст модели→вызов инструмента→hook PreToolUse: пусто / ask / deny

Установка и подключение

install копирует проверенный по SHA256SUMS файл в ~/.local/share/tinyteam/cli.cjs, пишет запускатель bin/tinyteam и файл releases с путём к каталогу выпуска — клону tinyteam, папке в репозитории аналитика или распакованному архиву. upgrade запускает install выпуска, который сейчас лежит в записанном каталоге выпуска, дочерним процессом (git он не вызывает: каталог обновляет человек) — раскладку определяет новая версия. Запускатель bin/tinyteam сам проверяет node в PATH и его версию и при неподходящей выходит с кодом 2 и причиной.

Выпуск готовит разработчик tinyteam: npm run release -- . в корне репозитория (сначала полная проверка), затем коммит cli.cjs и SHA256SUMS и push. .gitattributes помечает оба файла -text, чтобы git не менял в них переводы строк.

init пишет в каталог харнесса (.gigacode/ или .qwen/):

ФайлЧто там
settings.jsonдве записи hook с именем tinyteam: UserPromptExpansion (matcher *) и PreToolUse (run_shell_command|write_file|edit). Команда — bash -c: проверить, что cli.cjs на месте и node подходит, иначе код 2 с причиной; затем exec node '<cli>' harness hook --harness <харнесс> — имя своего харнесса у каждой записи. Путь к Node не пишется; exec — чтобы сигнал остановки доходил до tinyteam.
commands/spec-consume.mdтекст /spec-consume с аргументами {{args}}
skills/tinyteam/SKILL.mdнавык управления
skills/tinyteam-schema/SKILL.md, skills/tinyteam-schema/presets/base-process.mdнавык схем и версионируемый пресет базового процесса

Hook: контекст команды

Харнесс раскрывает slash-команду и вызывает tinyteam harness hook с событием UserPromptExpansion: cwd, command_name, command_args, session_id. Change берётся только из первого слова аргументов этого вызова — hook не помнит прежних вызовов и не выбирает change по сессии. Ответ — additionalContext (Qwen режет всё длиннее 10000 символов, поэтому документы не вставляются, передаются пути к копиям). Части ответа по порядку:

ЧастьКогдаОткуда
аргументы по ролям/spec-consume с аргументамиразбор command_args
принятый источнику change есть source.jsonкопии в кэше, проверенные по git blob id; строка о прежней редакции — если есть source-history/ и нет reconciled для текущего commit
областьесть scope.jsonназначение, корни, зависимости и правила: менять только в корнях, планировать только свою часть, чужие требования — в «Вне области»
просьба задать областьпринят источник, нет scope.json, команда планирует или выполняет работу (propose, new, continue, ff, apply, update)предложить область одной командой scope set и остановиться
материалыесть materials.jsonпути к снимкам, проверенным по sha256
решениятолько /opsx-apply, есть выполненные задачи или решенияapprovals.json против tasks.md
дополненияесть непустые файлы для команды в этом харнессе.tinyteam/additions/<харнесс>/ — для харнесса и для схемы change; харнесс hook знает из своей записи в settings.json (harness hook --harness <харнесс>)

Команды tinyteam в этих текстах записаны так, как вызывается эта установка: tinyteam …, если первый tinyteam в PATH харнесса — её запускатель или сам cli.cjs, иначе node '<cli>' …. /spec-consume и навык пишутся при init по PATH терминала; обе формы считаются файлами этой установки, а doctor предупреждает, если файлы называют tinyteam, а PATH больше не ведёт на неё.

Срок ответа — 25 с (харнесс ждёт hook 30 с): не уложился — decision: block с причиной, чтобы команда не ушла модели без контекста. OpenSpec спрашивается о change не больше одного раза за вызов, каждый вызов git ограничен 20 с. Ожидаемая ошибка (повреждённый файл, неподтверждённая копия) — ответ decision: block с причиной: команда не уходит модели без части своего контекста. Неожиданная — код 2, харнесс тоже блокирует. Команда change (apply, continue, update, verify, sync, archive) без названного change блокируется с просьбой назвать его, если tinyteam хранит что-то (источник, область, материалы или решения) хотя бы для одного change. Какие части какой команде уходят, закреплено тестом-матрицей контекста.

Hook: защиты

На PreToolUse hook получает tool_name и tool_input и отвечает по порядку первой сработавшей защитой; allow не отвечает никогда — остальное решают права харнесса.

ВызовОтвет
shell с source accept, material add, scope set или любой approve <…> (в том числе ложное срабатывание вроде echo approve x)ask: харнесс спрашивает человека (по коду Qwen 0.21.10 — и в режиме без подтверждений; интерактивное окно проверяется по TARGET-CHECK); без интерфейса — отказ, проверено нативно
запись или правка source.json, source-history/*, materials/*, materials.json, approvals.json, scope.jsondeny
запись или правка commands/spec-consume.md, skills/tinyteam*/** в каталоге харнесса, .tinyteam/cache/**deny
запись или правка settings.json харнесса, .tinyteam/additions/**ask
запись вне корней области change, который команда этой сессии последней назвала с областью (переход к change без области её сбрасывает; openspec/ свободен)ask
всё прочее, непрочитанное событие (например, запись больше МиБ)пусто

Единственное исключение из «hook без состояния» — защита области: PreToolUse не знает change, поэтому UserPromptExpansion запоминает его в .tinyteam/cache/sessions/<session_id>, и только для ответа «спросить» (решение пользователя; риск общего session_id назван в ARCHITECTURE).

Файлы change

Всё лежит рядом с артефактами OpenSpec в openspec/changes/<change>/, коммитится с работой и пишется только командами tinyteam:

ФайлПишетСодержит
source.jsonsource acceptstore, путь source change, commit, каждый документ с ролью и git blob id
source-history/<n>-<commit>.jsonsource accept --updateпрежние записи принятия — до замены source.json, чтобы сбой между шагами ничего не терял
scope.jsonscope setназначение, корни изменения и проверки, зависимости
materials.json, materials/<id>.md, materials/history/material addточные снимки, происхождение, назначение, sha256
approvals.jsonapproveрешения человека: срез, тесты с объёмом, согласование с commit

Кэш и копии

.tinyteam/cache/ игнорирует себя в git (.gitignore с *). Копия документа принятой редакции — documents/<git blob id>.md; hook отдаёт путь, только если blob id копии совпадает, и восстанавливает недостающие копии из store на принятом commit. Одинаковый путь значит одинаковое содержимое — модель не перечитывает то, что уже читала в разговоре. Там же — файлы сессий защиты области.

Свои файлы харнесса

Команда и навыки несут во frontmatter отметку tinyteam: "<версия> sha256:<хеш остального текста>". По ней init и doctor отличают свой нетронутый файл от чужого или изменённого: чужой и изменённый не перезаписываются никогда, файл другой установки заменяет только init --update.

Журнал

Каждый вызов, включая hook, дописывает строку в $XDG_STATE_HOME/tinyteam/log.jsonl (по умолчанию ~/.local/state/tinyteam/log.jsonl): команда, каталог, длительность, итог и сводка решения (например, какие части контекста ушли и сколько символов) — без текстов документов и дополнений. Размер ограничен, TINYTEAM_LOG=0 выключает. Ошибка журнала не ломает команду. diagnose печатает версии и хвост журнала.

Ядра и адаптеры

src/
  cli/        точка сборки: команды, порты, hook, журнал вызова
  features/   ядра без знания о харнессе, git, OpenSpec и файловой системе:
    spec-consume/  просмотр, binding, обновление, контекст, защита принятия
    additions/     дополнения к командам
    scope/  materials/  approvals/  management/  schema/
  openspec/   запуск OpenSpec CLI       git/   чтение commit и diff
  fs/         файлы проекта без symlink в пути, атомарная запись
  qwen/       формат событий и ответов, settings.json, отметки файлов
  diagnostics/ журнал и версии окружения

Ядра получают эффекты параметром через узкие порты и не импортируют друг друга; это проверяет dependency-cruiser в npm run check. Контексты нескольких ядер склеивает cli/hook.ts.

Как это проверяется