Как устроено
Для разработчика 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.json | deny |
запись или правка 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.json | source accept | store, путь source change, commit, каждый документ с ролью и git blob id |
source-history/<n>-<commit>.json | source accept --update | прежние записи принятия — до замены source.json, чтобы сбой между шагами ничего не терял |
scope.json | scope set | назначение, корни изменения и проверки, зависимости |
materials.json, materials/<id>.md, materials/history/ | material add | точные снимки, происхождение, назначение, sha256 |
approvals.json | approve | решения человека: срез, тесты с объёмом, согласование с 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.
Как это проверяется
- Поведение — на собранном
dist/cli.cjs; отсутствие записи сверяется отпечатком всего дерева, включая.git. - Нативно — в закреплённом Qwen 0.21.10 (GigaCode построен на Qwen Code) с mock-моделью: что именно уходит модели и что делает харнесс с ответами hook.
- Модельно — на
glm-5.3-flash:scripts/harness-run.mjs(весь путь),scripts/intent-run.mjs(намерения R5). - Установка — стенд Debian 12 с Node 20 в Docker без сети.
- Защитные проверки подтверждаются мутацией: сломать реализацию — должен упасть нужный тест.
- На целевой машине —
docs/TARGET-CHECK.md, по разделу на каждый change. - Итог аудитов и прогонов —
docs/FINAL-CHECK.md. Известные ограничения: одновременныеapproveилиmaterial addв одном change могут потерять запись; защита путей чувствительна к регистру и не видит записи через shell; символы направления текста в выводе не экранируются (управляющие C0/C1 показываются как\xNN); перенос намерения источника в план без подсказки (R5-02) модель делает не всегда.