Рецепт: добавляем пакет в workspace

Чек-лист по файлам для нового пакета @deepseek-ai/dsh-<name>. Он сверен с пакетами bash и адаптеров, которые служат шаблонами; если чек-лист разошёлся с ними, чините его здесь.

1. Создайте пакет

packages/<group>/<pkg>/
  package.json     # скопируйте из packages/core/tools, поправьте name/description/deps
  tsconfig.json    # extends ../../../tsconfig.base.json, rootDir src,
                   # outDir lib/types, references: ../../../vendor/cosmokit,
                   # ../../../vendor/cordis (+ ../../../vendor/schemastery, если
                   # используете Config, + ../../<group>/<dep> на каждую зависимость dsh)
  src/index.ts     # экспорт сервиса по умолчанию или плагин (name/inject/apply/Config)
  locale/en.json   # необязательные метаданные для отображения: meta.title и meta.description
  locale/zh.json   # переводы с теми же полями
  README.md        # API сервиса, события, точки расширения, заметки о дизайне,
                   # + проверяемые блоки контекста Model Experience или короткая форма
                   # + проверяемый раздел «Known Limitations and Deferred Work»
                   # (или запись в белом списке scripts/verify-package-readme-limitations.ts)

Выбирайте существующую группу, если она подходит роли пакета (core, llm, shell, compaction, subagent, todo, session, client/host, util или test-support). Новую группу завести можно, но это чистый контейнер: без package.json, без исходников, и пакеты по-прежнему лежат ровно на один уровень ниже неё.

Инварианты package.json (их проверяет pnpm run constraints / scripts/check-workspace-constraints.ts): private: true, version, совпадающая с корневым package.json, type: module, main: "lib/index.js", types: "lib/types/index.d.ts", exports["."].types: "./lib/types/index.d.ts", exports["."].default: "./lib/index.js", @deepseek-ai/cordis И в peerDependencies, И в devDependencies (с одним диапазоном). Каждую peer-зависимость на пакет dsh дублируйте в devDependencies. @deepseek-ai/schemastery идёт в dependencies (это валидатор времени выполнения), как в agent-loop. Список files содержит ровно lib/index.js, lib/types/**/*.d.ts и артефакты времени выполнения, специфичные для пакета и известные проверке; пакет, который публикует ./invariant, добавляет ещё lib/invariant.js. Пакет, чей runtime-экспорт указывает внутрь скомпилированного дерева, добавляет ещё и lib/types/**/*.js. Не публикуйте src, карты деклараций, JS-карты и устаревшие корневые файлы деклараций. Пакеты CLI-приложений с полем bin ставят lib/bin.js в files сразу после lib/index.js.

Относительные импорты внутри пакета в исходниках пишутся с явным .ts (например, export * from './types.ts'). Компилятор переписывает их в .js в собранном JS, а в декларациях оставляет явные .ts; стандартные потребители TypeScript с NodeNext/Node16 разрешают их в соседние файлы .d.ts.

2. Зарегистрируйте его в корневых конфигах

ФайлИзменение
tsconfig.base.jsonдля существующей группы ничего не правим; для новой группы добавьте кандидата ./packages/<group>/*/src в подстановку @deepseek-ai/dsh-*
tsconfig.host.json (пакет Host) или tsconfig.client.json (пакет Client)добавьте { "path": "./packages/<group>/<pkg>" } в references — обычный пакет входит ровно в один агрегат, никогда в оба. У api/remotes особое для репозитория разделение, потому что Host генерирует контракт, который Client потребляет на более поздней фазе; новым пакетам копировать его нельзя (структура)

Пакет из packages/client/* вдобавок расширяет tsconfig.base.client.json вместо tsconfig.base.json, а пакет клиентского плагина объявляет dsh.client в package.json, экспортирует ./client и вызывает общий пресет tsdown (packages/client/tsdown.client.ts) — контракт клиентской стороны описан в packages/client/AGENTS.md.

Подхватываются автоматически через глобы или обнаружение по манифестам пакетов — править не нужно: workspaces в корневом package.json, scripts/publint-all.ts, tsdown.config.ts, .oxlintrc.json, scripts/check-workspace-constraints.ts.

3. Определитесь с топологией пакета

Для заменяемой возможности разносите роли «определение сервиса» / «провайдер сервиса» / «потребитель» по отдельным пакетам, когда они развиваются независимо (см. docs/architecture.md, § «Capability seams» — шаблоном служит тройка shell). Плагин с одной задачей остаётся одним пакетом.

Называйте ту роль, которая есть

Называйте устойчивую текущую обязанность. Не называйте первую реализацию, возможное будущее расширение или базовый класс Cordis. Пакет-интерфейс называет возможность. Пакет-реализация добавляет механизм, протокол, окружение или вендора, который её отличает. local используйте только тогда, когда выполнение на той же машине входит в контракт.

Ключ ctx в единственном числе — для одного движка, рантайма, политики, контроллера, резолвера, хранилища или текущей конфигурации. Ключ во множественном числе — для реестра или сервиса, который владеет несколькими именованными участниками. Роль класса и число ключа должны совпадать. Не используйте один ключ Context Cordis для несовместимых объявлений Host и Client: слияние деклараций TypeScript видит обе стороны, даже если во время выполнения у них разные контексты. Добавляйте суффикс роли, когда естественное множественное число уже занято другой стороной.

СловоКогда использоватьКогда не использовать
ControllerПринимает команды или намерение пользователя и меняет одно существующее состояние домена или представления.Выполняет произвольную работу, владеет парком провайдеров или только преобразует значения для отображения.
StoreВладеет одним набором данных и в основном предлагает CRUD, снимки или подписки на эти данные.Проверяет конечный автомат, разрешает споры о полномочиях, распределяет работу или владеет приоритетом провайдеров. Наличие map не делает класс хранилищем.
DirectoryОткрывает записи и метаданные для обнаружения или выбора.Производители регистрируют в нём произвольные реализации, или вызывающие стороны выполняют через него работу.
PresenterЧистое преобразование доменных значений или аргументов инструмента в намерение рендера.Делает I/O, подписывается, меняет состояние или владеет жизненным циклом.
RegistryВладеет динамическим набором именованных регистраций, включая поиск, правила дубликатов и приоритета, время жизни и освобождение.Его главный контракт — диспетчеризация, выполнение, отмена, политика или оркестрация.
RuntimeВыполняет живую работу и владеет диспетчеризацией, отменой, координацией провайдеров или жизненным циклом операций между вызовами.Только хранит записи, возвращает каталог, разрешает одно значение или держит конфигурацию.
ResolverВычисляет или находит один ответ по переданным входным данным, не владея жизненным циклом этого ответа.Владеет изменяемой коллекцией или длительным выполнением.
BinderПривязывает один объявленный интерфейс к контексту или жизненному циклу вызывающей стороны и возвращает привязанное значение.Владеет значением как коллекцией, управляет его доменным состоянием или только преобразует данные.
EngineРеализует доменный алгоритм или модель выполнения с состоянием.Только выбирает провайдера или пробрасывает вызовы через границу протокола.
PolicyРешает, что разрешено, выбрано, ограничено или наблюдается.Выполняет механизм, который это решение разрешает.
ExecutorВыполняет один явный запрос или разрешённую спецификацию в одной возможности.Владеет широким жизненным циклом приложения или каталогом провайдеров.
GatewayАдаптирует границу процесса, сети, RPC или API.Только регистрирует сервисы в том же процессе или хранит метаданные.
ProviderПоставляет одну реализацию определения возможности. Добавляйте квалификатор механизма или вендора, когда реализаций может быть несколько.Это определение возможности, реестр провайдеров или рантайм потребителя.
BackendРеализует заменяемое низкоуровневое хранение, транспорт или выполнение за определённым интерфейсом.Это пользовательский сервис или одна возвращённая ссылка на живой ресурс.
HandleСсылается на один живой ресурс и управляет им или наблюдает за ним.Создаёт полный пул ресурсов и управляет им.
ConfigВладеет одним разрешённым значением конфигурации или одной чётко ограниченной записью и контрактом её обновления.Хранит общую коллекцию, выполняет работу или открывает несвязанные настройки.
ServiceВладеет цельным доменным сервисом, который ни одна более точная роль выше не описывает честно.Имя существует только потому, что класс наследует Service из Cordis.

SDK используйте только для протокола клиент/сервер JSON-RPC, на котором работают поддерживаемые Python и TypeScript SDK. Сам DeepSeek Harness — agent harness («каркас агента»), а не SDK-проект. Пишите каноническое название продукта Typert, никогда TypeRT или typeRT.

4. Напишите README пакета

Сначала — специфичные для пакета API сервиса, конфиг, события, точки расширения и заметки о дизайне. kind во frontmatter выбирайте из четырёх меток в справочнике метаданных dsh-doc — group, reference, library или bundle — по положению пакета в репозитории и форме его точки входа; каждая метка выбирает свой шаблон README. Раздел ограничений фиксирует долгоживущие пробелы для потребителей и неочевидные ограничения для сопровождающих, которыми владеет этот пакет; обычная уборка остаётся в TODO исходников или в Agent Note. Косвенное предложение в Model Experience может назвать потребителя, через которого виден вклад этого пакета, но не пересказывает реализацию того потребителя. README пакета заканчивается такой канонической последовательностью:

## Model Experience

### Request context and condition

#### What the model sees

The exact data-dependent fields, an anchored generated-catalog link, or an introduction to the verbatim literal below.

##### Verbatim text for this field, when needed

```markdown
Stable system-prompt prose of any length, or another long non-generated literal, copied exactly from source.
```

#### Token effect

Fixed, conditional, retained, replaced, capped, or zero-direct token effect.

#### KV Cache effect

Append-only, prefix-stable, replacing, or independent behavior, including the exact conditions that may invalidate reuse.

## Known Limitations and Deferred Work

- **Consumer-visible gap** — exact missing operation or case, its consequence, and any maintainer constraint.

Model Experience заполняйте по реализации. Один H3 на каждую прямую, условную, ограниченную, пожизненную или вспомогательную запись контекста модели, с тремя упорядоченными полями H4, как показано выше, и одним абзацем прозы под каждым. Цитируйте стабильный текст, которым владеет пакет: проза системного промпта идёт в озаглавленный H5 плюс блок кода markdown под тем полем, которое её вводит — обычно What the model sees; другие короткие литералы остаются в строке с именованными плейсхолдерами, другие длинные литералы используют ту же вложенную форму. Только зависящий от данных или принадлежащий провайдеру текст можно пересказывать. Запись о схеме инструмента ссылается на свой раздел с якорем в сгенерированном каталоге инструментов и описывает только отличия, которых там нет. Записи о промпте и о схеме держите раздельно, если область видимости может скрыть одно без другого. В KV Cache effect различайте рост только добавлением, стабильный повторяющийся префикс, замену токенов более раннего запроса и независимый запрос к модели, а затем назовите изменения на стороне пакета, которые могут сломать переиспользование. «Does not invalidate» означает, что пакет сохраняет уже переиспользуемый префикс; доступность кэша провайдера и его вытеснение остаются за пределами контракта пакета. Полноту и владение регулирует стандарт прозы; верификатор следит за обязательной структурой разделов.

Пакет без влияния на контекст или с одним путём, которым владеет потребитель, использует выверенное предложение None, as или Indirectly, through из SENTENCE_MODEL_EXPERIENCE, за которым идёт H4 KV Cache effect и один непустой абзац; универсальный пакет, не зависящий от модели, может вместо этого войти в NO_MODEL_EXPERIENCE_SECTION. Ни один из этих случаев не разворачивайте в описание работы другого пакета. Список разрешённых для ограничений от этого не зависит. Обоснование записано в Agent Note о Model Experience.

5. Добавьте необязательные метаданные для отображения плагина

Для плагина в виде npm-пакета заголовок и описание задаются в locale/en.json. Файлы других языков, например locale/zh.json, используют те же поля:

{
  "meta": {
    "title": "Workspace Tools",
    "description": "Tools for your workspace."
  }
}

Добавьте эти записи в package.json, сохранив существующие экспорты времени выполнения и публикуемые файлы:

{
  "exports": {
    "./package.json": "./package.json",
    "./locale/*.json": "./locale/*.json"
  },
  "files": ["locale/*.json"]
}

Держите языковые файлы вместе в locale/, точкой обнаружения служит en.json. Поля необязательны, но если значение есть, это должна быть непустая строка. Отсутствующий файл или поле разрешает откат к запасному значению, а некорректный JSON или неверные поля дают диагностику для конкретного плагина.

Каждое поле откатывается независимо, ещё до форматирования имени под конкретное представление, и сначала по существующей цепочке языков локали:

  • Заголовок: meta.title из локали → package.json.name → полное имя плагина Cordis.
  • Описание: meta.description из локали → package.json.description → без описания.

Экспортируйте <package name>/locale/en.json для поиска локали; откройте <package name>/package.json, если нужен откат к полям пакета или объявление иконки.

Чтобы у карточек бандла, страницы подробностей и строк компонентов была картинка, задайте в этом экспортированном манифесте поле верхнего уровня "icon": "./icon.svg" и включите изображение в files. Путь отсчитывается от каталога манифеста, в котором он объявлен, в том числе для отдельно экспортируемых манифестов плагинов. Поддерживаются SVG, PNG, JPEG (.jpg/.jpeg) и WebP размером до 256 KiB. Абсолютные пути, URL, пути за пределами этого каталога и символические ссылки, ведущие наружу, отклоняются. Отдельный экспорт изображению не нужен, но оно должно быть самодостаточным; SVG рендерится как картинка, а не как встроенный HTML. Host возвращает data URL, не активируя плагин. Некорректное объявление или нечитаемый файл дают диагностику метаданных, а корректный текст при этом сохраняется; если изображения нет или его не удаётся декодировать, панель показывает картинку по умолчанию.

Эти метаданные показывают карточки и подробности установленных бандлов, списки компонентов и подробности конфигурации, а также список плагинов в Settings, включая отключённые и предустановленные плагины. Чтение метаданных плагины не активирует.

Только Settings сокращает запасные имена, взятые буквально из имени пакета или модуля: убирает npm-scope и префиксы Cordis/DSH; Plugin Manager показывает полные имена. Заголовки и описания из локали не меняются. Страница конфигурации строки может использовать зарегистрированную сводку, если у плагина нет описания для отображения.

Представление Install по-прежнему берёт данные реестра npm из pnpm view, а не метаданные локали.

Проверьте результат:

  1. Запустите pnpm run verify-package-meta из корня репозитория: он проверит поля, экспорты ресурсов и покрытие публикуемыми файлами.
  2. Переключите соответствующие записи установленного плагина в Plugin Manager и Settings между английским и китайским; проверьте заголовок, описание, откат по каждому полю и сокращённые технические имена, которые есть только в Settings.

Почему ресурсами владеет пакет и почему чтение не активирует плагин, объясняет Agent Note о метаданных плагинов.

6. Проверьте

pnpm install        # регистрирует workspace
pnpm run doc-sync
pnpm run constraints && pnpm run typecheck && pnpm run lint
pnpm run build && pnpm run hygiene

Проверки поведения и покрытие, которые требуются новому пакету, описаны в политике тестирования репозитория.

перевод и редактура:

обновлено

⌕ ESC