Agent Dev 101

Модуль 16 · Урок 16.16

Урок 16.16. Артефакты: инвентаризация и композиция слоев

На этой странице
  1. Зачем это нужно
  2. Основная идея
  3. artifact list
  4. artifact info
  5. artifact lookup
  6. Артефакты-хуки
  7. JSON-ошибки
  8. Практика
  9. Краткое резюме
  10. Вопросы для самопроверки
  11. Что дальше
  12. Источники

Зачем это нужно

Блок кастомизации (уроки 16.11-16.15) оставляет практический вопрос: в проекте стоят ядро, два пресета, три расширения и локальные оверрайды - что фактически видит агент? Какой слой победил для plan-template.md, а какой скрыт? Почему команда появилась/исчезла после установки пресета?

Группа команд specify artifact - read-only-поверхность интроспекции над этим инвентарем. Если specify preset resolve отвечает «какой файл выигрывает для этого имени», то specify artifact отвечает «что существует вообще и каков полный стек композиции за этим» - включая built-in артефакты, которых ни один пресет не касался.

Основная идея

Артефакт - любая команда, шаблон, скрипт или хук, который Spec Kit выставляет в проекте, независимо от слоя-источника: built-in-ассеты, установленный пресет, расширение или project-local оверрайд в .specify/templates/overrides/. artifact list дает полный инвентарь со стеком композиции; artifact info - стек одного артефакта; artifact lookup - манифестную декларацию, стоящую за слоем.

Все субкоманды сейчас требуют --json (без него - exit 2 и usage в stderr, stdout пуст): текстовый рендер отложен, JSON-форма - единственный контракт.

artifact list

bash
specify artifact list --json

Выводит полный инвентарь видимых артефактов - по строке на пару (kind, name) со стеком композиции. Именованные сортируются по типу (command, затем template, затем script) и имени; строки хуков - за ними, по событию и приоритету первой декларации:

json
[
  {
    "id": "command:speckit.specify",
    "name": "speckit.specify",
    "kind": "command",
    "description": "Create or update the feature specification.",
    "stack": [
      {
        "id": "command:speckit.specify",
        "layer": null,
        "sourceId": null,
        "presetId": null,
        "presetName": null,
        "strategy": "replace",
        "active": true,
        "hidden": false,
        "manifestPath": null,
        "lookupId": null,
        "sourcePath": null
      }
    ]
  }
]
Поле Описание
id {kind}:{name} - сокращение, которое принимает artifact info
name логическое имя (команды используют namespace speckit.<stem>)
kind command, template, script или hook
description описание от высшего по приоритету слоя, имеющего описание; иначе ""
stack стек композиции, той же формы строки, что у artifact info

Built-in артефакты присутствуют всегда, даже если их никто не перекрывает. Описания берутся от высшего слоя: пресет или project-оверрайд, скрывающий встроенную команду, репортит собственное описание, а не текст скрытого built-in. Skills (.github/skills/**/SKILL.md) исключены - это интеграционно-специфический вывод, не семейство поставляемых ассетов.

artifact info

bash
specify artifact info <name> --json   # --kind command|template|script|hook

<name> - голое имя (speckit.specify) или сокращение kind:name (command:speckit.specify); при обоих --kind обязан согласоваться. Пример - команда, перекрытая пресетом compliance:

json
{
  "id": "command:speckit.specify",
  "name": "speckit.specify",
  "kind": "command",
  "description": "Create or update the feature specification.",
  "stack": [
    {
      "id": "command:speckit.specify",
      "layer": "preset",
      "sourceId": "compliance",
      "presetId": "compliance",
      "presetName": "Compliance Preset",
      "strategy": "replace",
      "active": true,
      "hidden": false,
      "manifestPath": ".specify/presets/compliance/preset.yml",
      "lookupId": "preset:compliance:command:speckit.specify",
      "sourcePath": ".github/skills/speckit-specify/SKILL.md"
    },
    {
      "id": "command:speckit.specify",
      "layer": null,
      "active": false,
      "hidden": true
    }
  ]
}

Индекс 0 стека - выигравший слой; вторая строка - скрытый built-in.

Семантика стека

Для command/template/script стек упорядочен по приоритету резолюции: индекс 0 - победитель. Стеки хуков аддитивны, а не winner-based (см. ниже). Каждая строка описывает один вкладывающий слой:

Поле Описание
id {kind}:{name} - source-агностический round-trip-ключ, одинаков во всех строках стека
layer project, preset или extension; null для built-in
sourceId ID установленного пресета/расширения для резолвера; null без происхождения
presetId ID каталога пресет-пака; null на built-in/project/extension-строках
presetName отображаемое имя пресета из манифеста, иначе pack id
strategy replace, wrap, prepend, append или additive
active активен ли слой; для именованных артефактов true только на индексе 0
hidden true, когда replace-слой с меньшим индексом вырезает этот слой из композиции
manifestPath путь к декларирующему манифесту относительно проекта
lookupId детерминированный {layer}:{sourceId}:{kind}:{name}; null для built-in
sourcePath POSIX-путь к конкретному файлу слоя; null для built-in/синтетических

active и hidden - независимые метки, не противоположности. active идентифицирует высший слой по порядку резолюции - но не валидирует, что содержимое слоя читается и компонуется (сохраняется диагностическое поведение preset resolve: цепочка слоев репортится, даже если композиция потом даст warning). Композиционные стратегии (wrap, prepend, append) удерживают нижние слои в выводе - неактивный слой не обязательно скрыт; hidden помечаются только слои под первым replace.

Provenance (происхождение) слоя: установленный пресет/расширение, манифест декларации и конкретный файл. Lookup-ID используют установленный ID - имя реестра/каталога, которое может отличаться от логического id внутри манифеста; это держит различные установленные каталоги раздельными даже при одинаковых логических ID в манифестах. Project-local оверрайды несут синтетический project:_:{kind}:{name}; built-in слои lookupId не имеют. lookupId - не round-trip-ключ: для artifact info используйте id.

artifact lookup

bash
specify artifact lookup <lookupId> --json

Резолвит манифестный lookupId стека в валидированную декларацию пресета или расширения, которой пользуется Spec Kit. Ответ включает стабильный lookup-ID, координаты провайдера, пути манифеста и источника и эффективную декларацию под contribution - она отражает нормальную обработку манифеста (канонические имена команд расширений, инжектированные дефолты вроде пустых списков алиасов, строчные стратегии пресетов), а не дословный YAML.

Cross-reference остается внутри artifact-команд: формы вывода команд пресетов/расширений и манифестов не меняются. Contribution-только-по-конвенции и project-оверрайды не имеют исходной манифестной декларации - lookup вернет {"error": "unknown contribution <lookupId>"} с exit 1. У built-in строк lookupId нет вовсе.

Артефакты-хуки

Строки хуков проецируют декларации хуков расширений, включаемых стандартным резолвером. Round-trip-сокращение:

text
hook:{encodedEventName}:{encodedTargetCommand}

Оба вызова выбирают один хук-артефакт:

bash
specify artifact info hook:before_specify:speckit.compliance.pre-check --json
specify artifact info before_specify:speckit.compliance.pre-check --kind hook --json

Компоненты события и целевой команды кодируются UTF-8 percent-encoding (URL-quoting): ASCII-буквы, цифры, -, ., _, ~ остаются литеральными, остальные байты кодируются. Кодировка ограничена компонентами хука в id/name/lookupId; сами манифесты и рантайм-биндинги не меняются. Пример - событие custom:after с целью /skill:speckit-test-ext-hello:

text
hook:custom%3Aafter:%2Fskill%3Aspeckit-test-ext-hello

Хук-строки добавляют три top-level-поля:

Поле Описание
eventName событие, триггерирующее декларацию
targetCommand команда, вызываемая декларацией
registered true, когда хотя бы одна декларирующая строка стека имеет включенный рантайм-биндинг

Каждая строка стека хука несет нормализованные priority и optional. Строки хуков всегда strategy: "additive" и hidden: false: несколько расширений могут декларировать одну пару событие-команда, и каждая включенная декларация остается активной. Сортировка - по приоритету хука с детерминированным порядком деклараций резолвера как tiebreaker; порядок описывает инвентарь деклараций и не обещает, что равноприоритетные хуки исполняются в том же порядке в рантайме.

Для хуков active отражает состояние регистрации из .specify/extensions.yml, а не вычисление условий: декларация активна, только если рантайм-конфигурация содержит включенный биндинг с теми же extension id, событием и командой; условия вычисляются позже, при исполнении хука. Отсутствующая/битая рантайм-конфигурация оставляет декларации видимыми с registered: false и active: false.

Декларированные-но-незарегистрированные хуки видны, пока расширение включено резолвером. Реестрово-выключенные расширения исключаются целиком - как и их прочие вклады; невалидные индивидуальные манифесты опускаются резолвером и диагностируются командами инспекции расширений.

Lookup-ID хуков: {layer}:{sourceId}:hook:{encodedEventName}:{encodedTargetCommand}, где sourceId - ID установленного провайдера. Provenance хуков ограничена слоями preset и extension - built-in/core-слоя у хуков не бывает; сегодня манифестный API открывает декларации расширений, поэтому строки используют слой extension, а preset зарезервирован грамматикой идентификаторов на будущее.

JSON-ошибки

При сбое stdout пуст; в stderr уходит одно-ключевой JSON-конверт, exit 1:

json
{ "error": "unknown artifact command:nope" }
Сообщение Причина
not a Spec Kit project: no .specify/ directory found запуск вне инициализированного проекта
unknown artifact <name> нет артефакта с таким именем (и типом, если задан)
ambiguous artifact <name>: matches kinds [...] голое имя совпало в нескольких типах - перезапустите с --kind
artifact resolution failed реестр расширений не читается или сбор стека слоев упал

Exit 2 зарезервирован за usage-ошибками (отсутствие --json, невалидный --kind) - обычный текст в stderr вместо JSON-конверта.

Практика

  1. В проекте с пресетом и расширением выполните specify artifact list --json и найдите артефакты со стеком длиннее одной строки: какой слой active, какие hidden?
  2. Возьмите команду, перекрытую пресетом, и пройдите цепочку: artifact info (стек) -> artifact lookup по lookupId победившего слоя (декларация манифеста) -> сверьте с manifestPath.
  3. Сравните поведение hidden у replace и wrap: сделайте wrap-пресет и убедитесь, что нижний слой не помечен hidden, хотя не active.

Краткое резюме

specify artifact - read-only-интроспекция инвентаря проекта: полный список (kind, name) со стеками композиции, стек одного артефакта (индекс 0 - победитель; active и hidden независимы) и lookup от lookupId к манифестной декларации. Хуки аддитивны, их active отражает рантайм-регистрацию, а не условия. Контракт - только JSON (--json обязателен), ошибки - одно-ключевой конверт в stderr.

Главное из урока:

  • built-in строки всегда в инвентаре, но без provenance (layer, lookupId = null);
  • hidden ставится только слоям под первым replace; композиционные стратегии держат нижние слои видимыми;
  • lookupId использует установленный ID, который может отличаться от логического ID манифеста; round-trip-ключ - id;
  • компоненты хуков в идентификаторах percent-кодируются;
  • active не гарантирует успешную композицию содержимого - это диагностическая, не валидирующая метка.

Вопросы для самопроверки

  1. Чем вопрос specify artifact отличается от вопроса specify preset resolve? Когда какого достаточно?
  2. Опишите поля строки стека: layer, sourceId, strategy, active, hidden, lookupId, sourcePath. Что означает null в каждом?
  3. Почему active и hidden - не противоположности? Приведите пример неактивного, но не скрытого слоя.
  4. Чем стек хука отличается от стека команды? Что означает registered: false при видимой декларации?
  5. Почему lookupId строится на установленном ID, а не на логическом ID манифеста? К чему это приводит при одноименных манифестах?
  6. Какие ошибки дают exit 1 с JSON-конвертом, а какие exit 2 с текстом? Почему --json обязателен?

Что дальше

Блок D завершен: все механизмы разобраны. Урок 16.17 переносит их в реальные условия - внедрение Spec Kit в существующую кодовую базу и организацию работы в монорепозитории.

Источники

Обновлено 03.10.2026