Рецепт: добавляем пакет в 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, а не метаданные локали.
Проверьте результат:
- Запустите
pnpm run verify-package-metaиз корня репозитория: он проверит поля, экспорты ресурсов и покрытие публикуемыми файлами. - Переключите соответствующие записи установленного плагина в 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
Проверки поведения и покрытие, которые требуются новому пакету, описаны в политике тестирования репозитория.