Handbooks
Концепции

Рабочий каталог

Каждый артефакт, который производит конвейер: где он живёт, что его валидирует и что безопасно удалять.

Рабочий каталог (--work) — это место, где живёт всё, что производит конвейер. Один на каждый документируемый репозиторий.

<work>/
  phase1/
    graph.json          the call graph — everything downstream reads this
    functions.csv       every function, flat, for grepping or a spreadsheet
    graph.dot           Graphviz:  dot -Tsvg graph.dot -o graph.svg
    dropped-calls.json  calls we could NOT resolve, categorized — not hidden
    scan-coverage.json  files we could NOT read or fully parse — not counted as covered
  phase2/
    cards/<rel>.json    one card per source file, mirroring the source tree
    cards/_coverage.json  how many files got prose, and which did not
    cards/_rejected/    replies that produced no usable card (capped at 20)
    skeleton.yaml       the stage spine
    assignment.json     file → stage
    organization.yaml   intra-stage groups + reading order
    strategy.json       which strategy produced the above
  phase3/
    narration.json      stage and system prose
    registers.json      cross-stage state registers
    cache/              content-hash caches for prose and registers
  handbook/             the rendered output, once you run `render`
  run-manifest.json     model, phases, timings and token usage of the last good run

Три свойства, на которые стоит полагаться

Всё валидируется по схеме при чтении

Каждый артефакт несёт поле version и валидируется zod при загрузке. Повреждённый или отредактированный вручную артефакт падает громко и называет себя:

handbook: error: work/myrepo/phase2/skeleton.yaml: stages.3.id: Invalid

Он никогда не распространяется в следующую фазу.

Два намеренных исключения — оба про устойчивость, а не про попустительство:

  • Карточки — отдельный непарсящийся файл карточки пропускается, а не роняет всё. Один чужой или полусинхронизированный JSON в каталоге карточек иначе обрушивал бы возобновление, фазы 2b/2c/3 и каждую загрузку модели.
  • Метаданные languages в graph.json — опциональны, потому что механизма миграции артефактов здесь нет, и каждый граф, записанный до появления деклараций достоверности, должен продолжать проходить валидацию. По той же причине опционально и поле unparsedFiles: его отсутствие означает, что анализ старше самой этой записи, а не то, что сбоев не было.

Каждая запись атомарна

Запись во временный файл, затем переименование. Падение посреди записи никогда не оставляет полузаписанный артефакт, на котором подавится следующий запуск.

Один запуск на рабочий каталог

generate и resync берут одну и ту же реентерабельную блокировку каталога. Параллельные запуск CLI и задача Studio на одних артефактах перемежали бы записи; вместо этого второй получает отказ с понятным сообщением.

Что безопасно делать

ДействиеБезопасно?Примечание
Удалить весь рабочий каталогСнаружи него ничего не менялось. Перегенерируйте с нуля.
Закоммитить его в gitЭто сплошной текст. Удобно, чтобы ревьюить, что изменила перегенерация.
Удалить phase3/cache/Обойдётся в полное повторное повествование при следующем запуске.
Удалить phase2/cards/_rejected/Только диагностика. Автоматически очищается в начале нового прохода карточек.
Править skeleton.yaml вручнуюОн валидируется при чтении, а --skeleton существует ровно для этого.
Править graph.json вручную⚠️Он генерируется. Вместо этого перезапустите analyze.
Удалить phase2/strategy.json⚠️Следующий запуск откатится к file, что может не соответствовать артефактам.
Поделиться им публично⚠️Карточки цитируют и описывают ваши исходники. Обращайтесь как с исходниками.

Чтение вручную

Самое интересное — граф:

# how big is this codebase, really
jq '.metadata | {files: (.scannedFiles|length), nInternalFunctions, nEdges}' phase1/graph.json

# the busiest functions — where a change is most likely to fan out
jq -r '.nodes | to_entries | map(select(.value.kind=="internal"))
       | sort_by(-.value.nCallers) | .[:15]
       | .[] | "\(.value.nCallers)\t\(.value.qualname)\t\(.value.file)"' phase1/graph.json

# what could not be resolved, by category
jq '.metadata.byCategory' phase1/dropped-calls.json

# which files the scan could not turn into facts, and why
jq '.metadata.byReason, .files' phase1/scan-coverage.json

# which files never got prose
jq '.missing' phase2/cards/_coverage.json

functions.csv существует по той же причине — иногда самый быстрый инструмент — это электронная таблица.

Два файла покрытия отвечают на разные вопросы

_coverage.json отвечает на вопрос «какие файлы модель не смогла описать?». scan-coverage.json отвечает на вопрос, лежащий уровнем ниже: «какие файлы парсер вообще не сумел прочитать?» — с reason, равным unreadable, unparsable или partial.

Первые два не дают никаких фактов, поэтому их вдобавок убирают из scannedFiles в graph.json: ничто ниже по потоку не напишет карточку о файле, которого никто не открывал, чтобы затем зачесть его как описанный. Файлы partial остаются — tree-sitter оправился от синтаксической ошибки, и найденные им функции настоящие, просто неполные.

Пустой массив files означает, что каждый просканированный файл разобрался чисто. Это утверждение; отсутствие самого артефакта утверждением не является.

Куда ещё пишутся файлы

Handbooks пишет за пределами рабочего каталога ровно в двух местах, оба — по явному выбору запущенной вами команды:

  • <source>/.handbook-patches/ — создаётся командой apply, хранит резервные копии и их манифесты. В него автоматически записывается .gitignore, чтобы резервные копии никогда не попадали в git.
  • $HOME/.handbook-studio/ — реестр репозиториев Studio и его автоматически создаваемые рабочие каталоги. Перемещается флагом --state-dir.

На этой странице