dsh-desktop-kit 2.1.3 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +29 -13
- package/client.js +16 -1
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/COMPATIBILITY.md +16 -2
- package/docs/INSTALL.md +2 -2
- package/docs/PRIVACY.md +2 -0
- package/docs/TEST-0.2.2.md +54 -0
- package/docs/TOKENS.md +1 -1
- package/docs/UPGRADING.md +70 -0
- package/docs/images/022-peak-guard.png +0 -0
- package/docs/images/022-skills-studio.png +0 -0
- package/docs/images/022-token-lab.png +0 -0
- package/docs/images/022-transfer.png +0 -0
- package/docs/images/hero.png +0 -0
- package/index.js +120 -16
- package/lib/skills-sync.js +245 -0
- package/package.json +7 -3
- package/plugins/dsh-skill-autopilot/index.js +21 -4
- package/plugins/dsh-skill-autopilot/package.json +1 -1
- package/skills/manifest.json +132 -0
- package/skills-baseline.json +59 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.2.0 — 2026-11-06
|
|
4
|
+
|
|
5
|
+
Безопасные обновления: пакет больше не оставляет старые навыки «навсегда» и не затирает правки пользователя.
|
|
6
|
+
|
|
7
|
+
- **Манифест и состояние навыков.** `skills/manifest.json` хранит SHA-256 каждого навыка; `~/.dsh/skills/.dsh-kit-skills.json` — что установлено и когда. При обновлении неизменённые навыки обновляются, изменённые пользователем — сохраняются и попадают в конфликты.
|
|
8
|
+
- **Бэкап перед заменой.** Старая версия навыка уходит в `~/.dsh/skills/.dsh-kit-backups/skills/<name>-<дата>`.
|
|
9
|
+
- **Команда `/kit`:** `status`, `sync`, `conflicts`, `force` (принудительно, с бэкапом).
|
|
10
|
+
- **Изоляция клиентских частей.** Ошибка одного клиентского плагина не снимает остальные; версия и ошибки видны в `window.__DSH_DESKTOP_KIT__`.
|
|
11
|
+
- **Диагностика установки:** `doctor.ps1` показывает версию пакета, состояние синхронизации и конфликты.
|
|
12
|
+
- **0.2.2-rc.1:** исправлен разбор `user/message` для автопилота; добавлен отчёт [docs/TEST-0.2.2.md](docs/TEST-0.2.2.md).
|
|
13
|
+
|
|
14
|
+
## 2.1.4 — 2026-11-06
|
|
15
|
+
|
|
16
|
+
- Автопилот читает оба формата `user/message` (0.2.1 и 0.2.2) + fallback на `session.snapshotEvents()`.
|
|
17
|
+
- Отчёт тестирования на DSH 0.2.2-rc.1 win-x64.
|
|
18
|
+
|
|
19
|
+
## 2.1.3 — 2026-11-06
|
|
20
|
+
|
|
21
|
+
- Исправлены машинные переводы RU-словаря и добавлен живой DOM-fixer (retry/stream/cache/queue).
|
|
22
|
+
|
|
23
|
+
## 2.1.2 — 2026-11-06
|
|
24
|
+
|
|
25
|
+
- Убрана дублирующая строка стека в дайджесте памяти.
|
|
26
|
+
|
|
27
|
+
## 2.1.1 — 2026-11-06
|
|
28
|
+
|
|
29
|
+
- Стек `Node.js` для проектов без фреймворков; паспорт проекта заполняется из автоскана.
|
|
30
|
+
|
|
31
|
+
## 2.1.0 — 2026-11-06
|
|
32
|
+
|
|
33
|
+
- Память проектов 2.1: авто-распознавание, локальный автоскан, `brief`/`scan`, auto-recall, redaction, `/memory`.
|
|
34
|
+
- Раздел «Окно-напоминание» и hero 2.1.
|
|
35
|
+
|
|
36
|
+
## 2.0.1 — 2026-11-06
|
|
37
|
+
|
|
38
|
+
- Исправлены ссылки на изображения и документация.
|
|
39
|
+
|
|
40
|
+
## 2.0.0 — 2026-11-06
|
|
41
|
+
|
|
42
|
+
- 25 компактных навыков, автопилот, Token Lab, Skills Studio, адаптивный сайдбар.
|
|
43
|
+
|
|
44
|
+
## 1.0.0 — 2026-11-06
|
|
45
|
+
|
|
46
|
+
- Первый публичный выпуск: русский UI, пиковый тариф, память, перенос чатов и профиля.
|
package/README.md
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
# DSH Desktop Kit 2.
|
|
1
|
+
# DSH Desktop Kit 2.2
|
|
2
2
|
|
|
3
3
|
**Навыки, которые применяются сами. Контекст, который видно. Интерфейс, который не мешает.**
|
|
4
4
|
|
|
5
5
|
Набор из **7 плагинов и 25 навыков** для DeepSeek Harness 0.2.x: русский интерфейс, автопилот навыков, токен-лаборатория, студия навыков, память проектов, перенос чатов и резервные копии профиля.
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|
|
|
9
|
-
**v2.
|
|
9
|
+
**v2.2.0** · MIT · проверено на DSH `0.2.1-rc.5` и `0.2.2-rc.1` (Windows 11) · без внешних зависимостей и телеметрии
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## Что нового
|
|
14
14
|
|
|
15
|
+
- **Обновления без потерь 2.2.0.** Навыки синхронизируются по манифесту SHA-256: неизменённые обновляются, ваши правки сохраняются и попадают в `/kit conflicts`; перед заменой — бэкап. Клиентские плагины изолированы: ошибка одной части не отключает остальные. Подробности — [docs/UPGRADING.md](docs/UPGRADING.md).
|
|
16
|
+
- **Проверено на DSH `0.2.2-rc.1` (win-x64).** Установка из репозитория, все 7 плагинов, 25 навыков, Skills Studio, Token Lab, Peak Guard, перенос чатов/профиля и память проектов. Найден и исправлен сдвиг формата `user/message` в 0.2.2, из-за которого автопилот молчал. Отчёт: [docs/TEST-0.2.2.md](docs/TEST-0.2.2.md).
|
|
15
17
|
- **Русский интерфейс 2.1.3.** Исправлены машинные переводы: «Этот повтор не удался» → «Не удалось выполнить запрос», ошибка потока DeepSeek, «Кэш попаданий», очередь сообщений и ещё 18 формулировок — в словаре и на живом экране.
|
|
16
18
|
- **Память проектов 2.1.** Проект распознаётся сам по рабочей папке, локальный автоскан (без LLM и сети) сразу даёт стек, команды, точки входа и тесты; дайджест сам подмешивает фрагменты досье, похожие на текущий запрос. Секреты и PII вычищаются до записи, файлы приватные (`0700/0600`). Новые действия `brief`/`scan` и команда `/memory`.
|
|
17
19
|
- **Навыки стали в 3–5 раз дешевле.** Ядро `content-writing` — 2,1k токенов вместо 11k, `seo` — 2,0k вместо 11k. Вся библиотека из 25 навыков — ≈37,6k вместо ≈60,6k, а грузятся только нужные ядра.
|
|
@@ -58,19 +60,19 @@
|
|
|
58
60
|
|
|
59
61
|
### Skills Studio: каталог, стоимость и редактор
|
|
60
62
|
|
|
61
|
-

|
|
62
64
|
|
|
63
|
-

|
|
64
66
|
|
|
65
67
|
### Token Lab: куда уходит контекст
|
|
66
68
|
|
|
67
|
-

|
|
68
70
|
|
|
69
71
|
### Тёмная тема и мобильный
|
|
70
72
|
|
|
71
|
-

|
|
72
74
|
|
|
73
|
-
<img src="https://cdn.jsdelivr.net/npm/dsh-desktop-kit@2.
|
|
75
|
+
<img src="https://cdn.jsdelivr.net/npm/dsh-desktop-kit@2.2.0/plugins/dsh-gkb-ru-ui/screenshots/05-skills-390-light.png" alt="Каталог навыков на 390 px" width="390">
|
|
74
76
|
|
|
75
77
|
## Окно-напоминание: пиковый тариф
|
|
76
78
|
|
|
@@ -80,11 +82,11 @@ DeepSeek включает двойной тариф в пиковые часы (
|
|
|
80
82
|
- **в окне** — красная карточка: причина, границы окна в местном времени и UTC, живой отсчёт до конца и рекомендация, когда вернуться;
|
|
81
83
|
- **после окна** — карточка исчезает; состояние всегда доступно через `window.__DSH_PEAK_GUARD__.state()`.
|
|
82
84
|
|
|
83
|
-

|
|
84
86
|
|
|
85
|
-

|
|
86
88
|
|
|
87
|
-
<img src="https://cdn.jsdelivr.net/npm/dsh-desktop-kit@2.
|
|
89
|
+
<img src="https://cdn.jsdelivr.net/npm/dsh-desktop-kit@2.2.0/plugins/dsh-peak-guard/screenshots/05-peak-card-390-light.png" alt="Окно-напоминание на 390 px" width="390">
|
|
88
90
|
|
|
89
91
|
Подробности и настройки — [plugins/dsh-peak-guard/README.md](plugins/dsh-peak-guard/README.md).
|
|
90
92
|
|
|
@@ -155,10 +157,21 @@ powershell -ExecutionPolicy Bypass -File "$env:USERPROFILE\dsh-desktop-kit\insta
|
|
|
155
157
|
| Перенос чатов | `...\dsh-desktop-kit\plugins\dsh-session-move` |
|
|
156
158
|
| Перенос и бэкапы | `...\dsh-desktop-kit\plugins\dsh-profile-transfer` |
|
|
157
159
|
|
|
160
|
+
## Обновление без сюрпризов
|
|
161
|
+
|
|
162
|
+
Обновляйтесь поверх старой версии — данные и правки сохраняются:
|
|
163
|
+
|
|
164
|
+
- **Навыки.** `skills/manifest.json` хранит хэши, `~/.dsh/skills/.dsh-kit-skills.json` — что установлено. Неизменённые навыки обновляются, ваши правки **не перезаписываются** и видны в `/kit conflicts`; перед заменой создаётся копия в `.dsh-kit-backups/skills/`.
|
|
165
|
+
- **Команда `/kit`:** `status`, `sync`, `conflicts`, `force` (принудительно, с бэкапом).
|
|
166
|
+
- **Настройки.** `dsh-autopilot.json` и память проектов дочитываются с дефолтами; новые поля не ломают старые файлы.
|
|
167
|
+
- **DSH.** Пакет не фиксирует `@deepseek-ai/*` и не задаёт жёсткий `engines.dsh`; дочерние плагины и клиентские части изолированы. Ошибки видны в `window.__DSH_DESKTOP_KIT__` и `doctor.ps1`.
|
|
168
|
+
|
|
169
|
+
Полный протокол и откат — [docs/UPGRADING.md](docs/UPGRADING.md), история — [CHANGELOG.md](CHANGELOG.md).
|
|
170
|
+
|
|
158
171
|
## Совместимость
|
|
159
172
|
|
|
160
|
-
- DSH `0.2.1-rc.5` — проверено вживую: хост-часть, клиентские плагины, Skills Studio, Token Lab,
|
|
161
|
-
- DSH `0.2.2` —
|
|
173
|
+
- DSH `0.2.1-rc.5` — проверено вживую: хост-часть, клиентские плагины, Skills Studio, Token Lab, автопилот, синхронизация навыков 2.2.0.
|
|
174
|
+
- DSH `0.2.2-rc.1` (win-x64) — проверено 2026-11-06: установка из репозитория, 4 плитки, 25 навыков, редактор, Token Lab, Peak Guard, Session Move, Profile Transfer, память проектов и автопилот. Подробности и ограничения — [docs/TEST-0.2.2.md](docs/TEST-0.2.2.md).
|
|
162
175
|
- Пакет **не тянет** версии `@deepseek-ai/*`: обновление клиента не ломает плагины.
|
|
163
176
|
|
|
164
177
|
Подробности — [docs/COMPATIBILITY.md](docs/COMPATIBILITY.md).
|
|
@@ -171,6 +184,9 @@ powershell -ExecutionPolicy Bypass -File "$env:USERPROFILE\dsh-desktop-kit\insta
|
|
|
171
184
|
|
|
172
185
|
## Документация
|
|
173
186
|
|
|
187
|
+
- [docs/TEST-0.2.2.md](docs/TEST-0.2.2.md) — отчёт тестирования на DSH 0.2.2-rc.1.
|
|
188
|
+
- [docs/UPGRADING.md](docs/UPGRADING.md) — обновление, конфликты навыков, откат.
|
|
189
|
+
- [CHANGELOG.md](CHANGELOG.md) — история версий.
|
|
174
190
|
- [docs/TOKENS.md](docs/TOKENS.md) — экономика токенов, бюджеты, как измерять.
|
|
175
191
|
- [docs/ROADMAP.md](docs/ROADMAP.md) — развитие плагинов и навыков.
|
|
176
192
|
- [docs/INSTALL.md](docs/INSTALL.md) — все способы установки и проверка.
|
package/client.js
CHANGED
|
@@ -4147,6 +4147,16 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
4147
4147
|
return module.exports;
|
|
4148
4148
|
})());
|
|
4149
4149
|
|
|
4150
|
+
const __partNames = ["dsh-gkb-ru-ui","dsh-token-lab","dsh-peak-guard","dsh-session-move","dsh-profile-transfer"];
|
|
4151
|
+
const __kitErrors = [];
|
|
4152
|
+
const __pushKitError = (label, error) => {
|
|
4153
|
+
const message = String(error && error.message ? error.message : error);
|
|
4154
|
+
if (__kitErrors.length < 50) __kitErrors.push({ label: String(label || ""), message });
|
|
4155
|
+
if (__kitErrors.length <= 5) console.error("[dsh-desktop-kit] registration failed:", label, message);
|
|
4156
|
+
};
|
|
4157
|
+
try {
|
|
4158
|
+
window.__DSH_DESKTOP_KIT__ = { version: "2.2.0", parts: __partNames, errors: __kitErrors, ready: false };
|
|
4159
|
+
} catch {}
|
|
4150
4160
|
const __inject = [];
|
|
4151
4161
|
for (const __part of __parts) {
|
|
4152
4162
|
for (const __name of __part.inject || []) {
|
|
@@ -4155,13 +4165,18 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
4155
4165
|
}
|
|
4156
4166
|
exports.inject = __inject;
|
|
4157
4167
|
exports.apply = function apply(ctx) {
|
|
4168
|
+
/* Keep each plugin isolated at the part boundary. Do not swallow errors
|
|
4169
|
+
* inside ctx.effect: plugins use those synchronous errors for feature
|
|
4170
|
+
* detection (e.g. "ru locale already registered") and must see them. */
|
|
4158
4171
|
for (let index = 0; index < __parts.length; index += 1) {
|
|
4159
4172
|
try {
|
|
4160
4173
|
__parts[index].apply(ctx);
|
|
4161
4174
|
} catch (error) {
|
|
4162
|
-
|
|
4175
|
+
__pushKitError(__partNames[index] || String(index), error);
|
|
4176
|
+
console.error("[dsh-desktop-kit] browser part " + index + " failed to start", String(error && error.message ? error.message : error));
|
|
4163
4177
|
}
|
|
4164
4178
|
}
|
|
4179
|
+
try { window.__DSH_DESKTOP_KIT__.ready = true; } catch {}
|
|
4165
4180
|
};
|
|
4166
4181
|
return module.exports;
|
|
4167
4182
|
},
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -34,7 +34,7 @@ DSH ставит плагин как пакет профиля и загружа
|
|
|
34
34
|
- отключение `dsh-desktop-kit` в списке плагинов снимает все семь;
|
|
35
35
|
- ошибка монтирования одного плагина логируется и не мешает остальным.
|
|
36
36
|
|
|
37
|
-
При старте `installSkills()`
|
|
37
|
+
При старте `installSkills()` вызывает `syncSkills()` из `lib/skills-sync.js`. Манифест `skills/manifest.json` хранит SHA-256 каждого навыка, состояние `~/.dsh/skills/.dsh-kit-skills.json` — что установлено. Неизменённые навыки обновляются с бэкапом в `.dsh-kit-backups/skills/`, изменённые пользователем не перезаписываются и попадают в `/kit conflicts`. Установка без state-файла принимается только при совпадении с базой из `skills-baseline.json`.
|
|
38
38
|
|
|
39
39
|
## Автопилот навыков
|
|
40
40
|
|
package/docs/COMPATIBILITY.md
CHANGED
|
@@ -4,10 +4,24 @@
|
|
|
4
4
|
|
|
5
5
|
| DSH | Состояние | Что проверено |
|
|
6
6
|
|---|---|---|
|
|
7
|
-
| `0.2.1-rc.5` | работает | **2.
|
|
8
|
-
| `0.2.2-rc.1` | работает (
|
|
7
|
+
| `0.2.1-rc.5` | работает | **2.2 проверена вживую**: память проектов (авто-распознавание, автоскан, `brief`/`scan`, `/memory`, redaction), Skills Studio, Token Lab, автопилот, 25 навыков, синхронизация навыков с бэкапом |
|
|
8
|
+
| `0.2.2-rc.1` | работает (2.2.0+) | **проверено 2026-11-06**: установка из репозитория, 4 плитки, 25 навыков, редактор с бэкапом, Token Lab (27 217 токенов / 24 узла), Peak Guard (карточка + бейдж), Session Move, Profile Transfer, память проектов (`/memory`, `scan.json`, дайджест), автопилот после фикса формата `user/message`, обновление 2.2.0 с сохранением правки навыка и `/kit status` (7/7). Отчёт: [TEST-0.2.2.md](TEST-0.2.2.md) |
|
|
9
9
|
| новее `0.2.2` | не проверено | API плагинов в 0.2.x ещё меняется; см. «Если сломалось» |
|
|
10
10
|
|
|
11
|
+
## Политика обновлений
|
|
12
|
+
|
|
13
|
+
- **Нет жёстких версий DSH.** Пакет не объявляет `engines.dsh` и не тянет `@deepseek-ai/*`: диапазон сам стал бы причиной «плагины несовместимы» после обновления клиента.
|
|
14
|
+
- **Нет потери правок.** Навыки обновляются по SHA-256-манифесту; изменённые пользователем остаются нетронутыми, перед заменой — бэкап. Состояние: `~/.dsh/skills/.dsh-kit-skills.json`; команда `/kit conflicts`.
|
|
15
|
+
- **Изоляция.** Каждый дочерний хост-плагин монтируется отдельно; каждая клиентская часть набора изолирована — ошибка одной не снимает остальные. Диагностика: `window.__DSH_DESKTOP_KIT__`, `doctor.ps1`.
|
|
16
|
+
- **Проверка перед релизом.** Матрица выше обновляется отчётом `docs/TEST-<версия>.md`; для нового DSH прогоняются установка из репозитория, все 7 плагинов и `/kit status`.
|
|
17
|
+
- **Откат.** Предыдущая версия ставится поверх; навыки возвращаются из `.dsh-kit-backups/skills/`. Полный протокол — [UPGRADING.md](UPGRADING.md).
|
|
18
|
+
|
|
19
|
+
### 0.2.2-rc.1: что отличается
|
|
20
|
+
|
|
21
|
+
- `user/message` storing: Message лежит в `event.data`, а не в `event.data.message`; автопилот теперь читает оба формата и дополнительно смотрит `session.snapshotEvents()`.
|
|
22
|
+
- Путь рабочей области по умолчанию на русской Windows создаётся с битой кодировкой (`Документы`) и не существует — это дефект 0.2.2, не пакета; для теста путь заменён.
|
|
23
|
+
- Headless-профиль по умолчанию использует `deepseek-official` API-key route; без `DEEPSEEK_API_KEY` ход падает `MISSING_CREDENTIAL`. Web-профиль с `deepseek-account` работает.
|
|
24
|
+
|
|
11
25
|
## Что использует 2.x
|
|
12
26
|
|
|
13
27
|
| Возможность DSH | Где используется | Проверено |
|
package/docs/INSTALL.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Требования
|
|
4
4
|
|
|
5
5
|
- Windows 10/11.
|
|
6
|
-
- DeepSeek Harness **0.2.1-rc.5** или
|
|
6
|
+
- DeepSeek Harness **0.2.1-rc.5** или новее; `0.2.1-rc.5` и `0.2.2-rc.1` проверены вживую на пакете 2.2.0.
|
|
7
7
|
- Профиль `desktop` уже создан: запустите DSH один раз и закройте окно.
|
|
8
8
|
|
|
9
9
|
## Способ 0. Пакет из npm (быстрее всего)
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
В DSH: **Плагины → Add plugin** → вставьте:
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
dsh-desktop-kit@2.
|
|
14
|
+
dsh-desktop-kit@2.2.0
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Либо локально: `npm i dsh-desktop-kit@2.0.0` — но для GUI всё равно нужен Add plugin/bundle.
|
package/docs/PRIVACY.md
CHANGED
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
| Файл | Что содержит | Можно удалить |
|
|
32
32
|
|---|---|---|
|
|
33
33
|
| `~/.dsh/dsh-autopilot.json` | режим автопилота и лимит навыков | да, вернётся к значениям по умолчанию |
|
|
34
|
+
| `~/.dsh/skills/.dsh-kit-skills.json` | хэши установленных навыков и конфликты (без текста навыков) | да, тогда пакет заново сравнит файлы |
|
|
35
|
+
| `~/.dsh/skills/.dsh-kit-backups/skills/<name>-<дата>/` | копии навыков перед обновлением или `/kit force` | да, это только копии |
|
|
34
36
|
| `~/.dsh/skills/.dsh-kit-backups/*.md` | резервные копии `SKILL.md` перед сохранением в Studio | да, это только копии |
|
|
35
37
|
| `~/.dsh/skills/.dsh-kit-trash/<дата>-<навык>/` | навыки, удалённые через Studio | да, после проверки |
|
|
36
38
|
| `~/.dsh/projects-memory/` | локальные досье проектов, журнал, `scan.json`, реестр | да, но потеряется память проектов |
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Тест на DeepSeek Harness 0.2.2-rc.1 (win-x64)
|
|
2
|
+
|
|
3
|
+
Дата: **2026-11-06** · пакет: **dsh-desktop-kit 2.2.0** (первый прогон — 2.1.3, фикс автопилота — 2.1.4) · ОС: Windows 11.
|
|
4
|
+
|
|
5
|
+
## Стенд
|
|
6
|
+
|
|
7
|
+
- Установщик: `deepseek-harness-0.2.2-rc.1-win-x64.exe` (293 425 336 байт), распакован в `C:\Users\bulav\dsh-022-runtime` (7-Zip, без установки поверх рабочего DSH).
|
|
8
|
+
- Версия рантайма: `0.2.2-rc.1`, Node `24.21.0`, pnpm `11.28.5`.
|
|
9
|
+
- Изолированный `DSH_HOME`: `C:\Users\bulav\dsh-022-home` — рабочий профиль пользователя не тронут.
|
|
10
|
+
- Профиль `probe` создан из шаблона `web`:
|
|
11
|
+
```powershell
|
|
12
|
+
$env:DSH_HOME = 'C:\Users\bulav\dsh-022-home'
|
|
13
|
+
dsh --profile probe --from-default-profile web --dump-config
|
|
14
|
+
dsh plugin --profile probe add C:\Users\bulav\OneDrive\Desktop\Projects\dsh
|
|
15
|
+
```
|
|
16
|
+
Установка из репозитория прошла как `link:...\Projects\dsh`; `--dump-config` показывает ряд `dsh-desktop-kit`.
|
|
17
|
+
- Для UI-проверок скопирован локальный `.credentials.yaml`, модель переключена на `provider: deepseek-account` (иначе web-профиль 0.2.2 по умолчанию идёт в `deepseek-official` без API-ключа).
|
|
18
|
+
|
|
19
|
+
## Результаты
|
|
20
|
+
|
|
21
|
+
| Плагин / сценарий | Результат | Доказательство |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Хост-часть, 7 плагинов | монтируется, ошибок в stderr нет | профиль, 25 навыков в `DSH_HOME/skills`, `_access.log` памяти |
|
|
24
|
+
| Сайдбар: 4 плитки | «Навыки / Токены / Баланс / Расход» | скриншот `docs/images/022-skills-studio.png` (фон) |
|
|
25
|
+
| `dsh-gkb-ru-ui` | 25 карточек, категории, поиск, редактор | 25 `.gkb-sk-card`; сохранение навыка + бэкап `.dsh-kit-backups` |
|
|
26
|
+
| Skills Studio save | `Сохранили`, файл обновлён, резервная копия создана | `code-review-checklist-…md` в бэкапах |
|
|
27
|
+
| `dsh-token-lab` | панель, живой замер 27 217 токенов / 24 узла / 3% | `docs/images/022-token-lab.png` |
|
|
28
|
+
| `dsh-peak-guard` | карточка «Пиковый тариф ×2» + бейдж в шапке (после активной сессии) | `docs/images/022-peak-guard.png` |
|
|
29
|
+
| `dsh-session-move` | пункт «Переместить в рабочую область…» в меню сессии | меню сессии, `window.__DSH_SESSION_MOVE__` |
|
|
30
|
+
| `dsh-profile-transfer` | раздел «Перенос и резервные копии», экспорт 7/11 | `docs/images/022-transfer.png` |
|
|
31
|
+
| `dsh-project-memory` | авто-регистрация проекта, `scan.json`, досье, `/memory status` | `projects-memory/dsh`, `command/done` в сессии |
|
|
32
|
+
| Дайджест памяти в промпте | `<project-memory slug="dsh">` | session log, user/runtime context |
|
|
33
|
+
| `dsh-skill-autopilot` | `<skill-autopilot>` с wow-design/ui-design-system/seo | session log после фикса 2.1.4 |
|
|
34
|
+
| Переводы RU | «Этот повтор не удался» → «Не удалось выполнить запрос», поток/кэш/очередь | DOM-probe на 0.2.2 |
|
|
35
|
+
| Консоль | только `[deepseek-account] account read failed {field: profile}` | как и на 0.2.1, к плагинам не относится |
|
|
36
|
+
|
|
37
|
+
## Найденные отличия 0.2.2
|
|
38
|
+
|
|
39
|
+
1. **Автопилот не видел сообщение.** В 0.2.2 `user/message` хранит Message в `event.data`, а не в `event.data.message`; старый код возвращал пустой текст, и подсказка не появлялась. Исправлено в 2.1.4: поддержаны оба формата + fallback на `session.snapshotEvents()`. После фикса `<skill-autopilot>` присутствует в системном контексте.
|
|
40
|
+
2. **Путь рабочей области по умолчанию.** На русской Windows 0.2.2 создаёт `C:\Users\<user>\OneDrive\Документы\deepseek-harness\default-workspace` — битая кодировка «Документы», каталог не существует. Это поведение самого 0.2.2, не пакета; для теста путь рабочей области заменён на репозиторий.
|
|
41
|
+
3. **Headless-профиль** по умолчанию использует API-key route `deepseek-official`; без `DEEPSEEK_API_KEY` ход падает `MISSING_CREDENTIAL`. Web-профиль с `deepseek-account` работает; это ограничение окружения, не плагинов.
|
|
42
|
+
|
|
43
|
+
## Обновление до 2.2.0 (проверено на этом же стенде)
|
|
44
|
+
|
|
45
|
+
- В `C:\Users\bulav\dsh-022-home` не было state-файла; один навык (`code-review-checklist`) был намеренно изменён в Skills Studio.
|
|
46
|
+
- После запуска 2.2.0: 24 навыка приняты как пакетные, **1 конфликт** (`modified-unknown-base`), правка сохранена в файле; в UI по-прежнему 25 карточек.
|
|
47
|
+
- `/kit status` вернул `DSH Desktop Kit 2.2.0`, `Плагины: 7/7`, `конфликтов: 1`, путь состояния.
|
|
48
|
+
- `window.__DSH_DESKTOP_KIT__`: version `2.2.0`, 5 частей, `errors: []`.
|
|
49
|
+
|
|
50
|
+
## Ограничения теста
|
|
51
|
+
|
|
52
|
+
- Проверялся профиль `web` и UI через Playwright; отдельный `desktop`-шелл 0.2.2 не запускался (он использует тот же рантайм).
|
|
53
|
+
- Модель работала через аккаунт; часть запросов в тесте была короткой, чтобы не тратить токены.
|
|
54
|
+
- Скриншоты 0.2.2 лежат в `docs/images/022-*.png`; полный набор — в `qa/022/` (локально, вне npm).
|
package/docs/TOKENS.md
CHANGED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Обновление без потери данных
|
|
2
|
+
|
|
3
|
+
Пакет рассчитан на то, что пользователь обновляется поверх старой версии. Ниже — что происходит с навыками, настройками и DSH.
|
|
4
|
+
|
|
5
|
+
## 1. Навыки
|
|
6
|
+
|
|
7
|
+
Каждый навык в пакете имеет SHA-256 в `skills/manifest.json`. После установки пакет пишет состояние:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
~/.dsh/skills/.dsh-kit-skills.json
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
При следующем запуске:
|
|
14
|
+
|
|
15
|
+
| Ситуация | Действие |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Навыка нет | устанавливается |
|
|
18
|
+
| Навык не менялся с прошлой синхронизации | обновляется, старая версия — в `.dsh-kit-backups/skills/` |
|
|
19
|
+
| Навык изменён пользователем | **не трогается**, попадает в `/kit conflicts` |
|
|
20
|
+
| Нет state-файла, но содержимое совпадает с базой 2.1.4/2.2.0 | обновляется |
|
|
21
|
+
| Нет state-файла, содержимое неизвестно | считается пользовательским, не трогается |
|
|
22
|
+
|
|
23
|
+
Проверить и разрулить:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
/kit status # версия, состояние, конфликты
|
|
27
|
+
/kit sync # синхронизировать без перезаписи правок
|
|
28
|
+
/kit conflicts # список сохранённых пользовательских версий
|
|
29
|
+
/kit force # принудительно заменить (сначала бэкап)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Принудительная замена делает копию в `~/.dsh/skills/.dsh-kit-backups/skills/<name>-<дата>`; оттуда можно вернуть свою версию.
|
|
33
|
+
|
|
34
|
+
## 2. Настройки
|
|
35
|
+
|
|
36
|
+
- `~/.dsh/dsh-autopilot.json` и `~/.dsh/projects-memory/registry.json` читаются с дефолтами: новые ключи появляются автоматически, старые значения сохраняются.
|
|
37
|
+
- `~/.dsh/projects-memory/<slug>/` не мигрируется и не удаляется.
|
|
38
|
+
- `.dsh-kit-backups/` и `.dsh-kit-trash/` — данные пользователя, пакет их не чистит.
|
|
39
|
+
|
|
40
|
+
## 3. Обновление DSH
|
|
41
|
+
|
|
42
|
+
Пакет **не фиксирует** `@deepseek-ai/*` и не задаёт `engines.dsh`: жёсткий диапазон сам стал бы причиной «плагины несовместимы» после обновления клиента. Вместо этого:
|
|
43
|
+
|
|
44
|
+
- каждый дочерний плагин монтируется отдельно; ошибка одного не снимает остальные;
|
|
45
|
+
- каждая клиентская часть (плагин набора) изолирована: ошибка внутри одной не снимает остальные;
|
|
46
|
+
- ошибки клиента доступны в `window.__DSH_DESKTOP_KIT__` (версия, части, список ошибок);
|
|
47
|
+
- `doctor.ps1` показывает версию, bundle, состояние навыков и закреплённые пакеты.
|
|
48
|
+
|
|
49
|
+
Проверенная матрица: `0.2.1-rc.5`, `0.2.2-rc.1` (win-x64). Новые версии DSH проверяются отдельным прогоном; отчёт — `docs/TEST-<версия>.md`.
|
|
50
|
+
|
|
51
|
+
## 4. Откат
|
|
52
|
+
|
|
53
|
+
1. Поставьте предыдущую версию пакета через «Плагины» (`dsh-desktop-kit@2.1.4`) или `npm i`.
|
|
54
|
+
2. Навыки: верните нужные папки из `.dsh-kit-backups/skills/`.
|
|
55
|
+
3. Сброс состояния навыков (если нужно заново принять пакетные версии):
|
|
56
|
+
|
|
57
|
+
```powershell
|
|
58
|
+
Remove-Item "$env:USERPROFILE\.dsh\skills\.dsh-kit-skills.json"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
После этого `/kit sync` заново сравнит хэши; пользовательские правки всё равно не будут перезаписаны без `/kit force`.
|
|
62
|
+
|
|
63
|
+
## 5. Что проверить после обновления
|
|
64
|
+
|
|
65
|
+
- `/kit status` — версия и конфликты.
|
|
66
|
+
- Плитки «Навыки / Токены / Баланс / Расход».
|
|
67
|
+
- Skills Studio: открывается, карточки на месте.
|
|
68
|
+
- Token Lab: панель считает текущую сессию.
|
|
69
|
+
- Память: `/memory status` для рабочей папки.
|
|
70
|
+
- `doctor.ps1` — нет закреплённых `@deepseek-ai/*`.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/images/hero.png
CHANGED
|
Binary file
|
package/index.js
CHANGED
|
@@ -15,6 +15,13 @@ import fs from "node:fs";
|
|
|
15
15
|
import os from "node:os";
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
|
+
import {
|
|
19
|
+
readManifest,
|
|
20
|
+
readBaseline,
|
|
21
|
+
syncSkills,
|
|
22
|
+
loadSkillState,
|
|
23
|
+
STATE_FILE,
|
|
24
|
+
} from "./lib/skills-sync.js";
|
|
18
25
|
|
|
19
26
|
import * as gkbRuUi from "./plugins/dsh-gkb-ru-ui/lib/index.js";
|
|
20
27
|
import * as peakGuard from "./plugins/dsh-peak-guard/index.js";
|
|
@@ -25,6 +32,13 @@ import * as tokenLab from "./plugins/dsh-token-lab/lib/index.js";
|
|
|
25
32
|
import * as skillAutopilot from "./plugins/dsh-skill-autopilot/index.js";
|
|
26
33
|
|
|
27
34
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
const KIT_VERSION = (() => {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(fs.readFileSync(path.join(here, "package.json"), "utf8")).version ?? "unknown";
|
|
38
|
+
} catch {
|
|
39
|
+
return "unknown";
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
28
42
|
|
|
29
43
|
const parts = [
|
|
30
44
|
["dsh-gkb-ru-ui", gkbRuUi],
|
|
@@ -39,32 +53,73 @@ const parts = [
|
|
|
39
53
|
/** The kit itself needs no service; each child declares its own. */
|
|
40
54
|
export const inject = [];
|
|
41
55
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
const runtimeState = {
|
|
57
|
+
kitVersion: KIT_VERSION,
|
|
58
|
+
startedAt: new Date().toISOString(),
|
|
59
|
+
skills: null,
|
|
60
|
+
mountErrors: [],
|
|
61
|
+
mounted: [],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function skillsHome() {
|
|
65
|
+
return process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Synchronise bundled skills without ever overwriting a user edit.
|
|
70
|
+
* Unchanged skills update; modified skills stay and are reported.
|
|
71
|
+
*/
|
|
72
|
+
export function installSkills(logger, options = {}) {
|
|
73
|
+
const home = skillsHome();
|
|
45
74
|
const source = path.join(here, "skills");
|
|
46
75
|
const target = path.join(home, "skills");
|
|
47
|
-
if (!fs.existsSync(source)) return
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
76
|
+
if (!fs.existsSync(source)) return null;
|
|
77
|
+
const manifest = readManifest(here);
|
|
78
|
+
const baseline = readBaseline(here);
|
|
79
|
+
const result = syncSkills({
|
|
80
|
+
source,
|
|
81
|
+
target,
|
|
82
|
+
kitVersion: KIT_VERSION,
|
|
83
|
+
manifest,
|
|
84
|
+
baseline,
|
|
85
|
+
force: options.force === true,
|
|
86
|
+
logger,
|
|
87
|
+
});
|
|
88
|
+
runtimeState.skills = result;
|
|
89
|
+
if (result.conflicts.length > 0) {
|
|
90
|
+
logger?.warn?.(`[dsh-desktop-kit] local edits kept for: ${result.conflicts.join(", ")} (see /kit conflicts)`);
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function kitStatusText() {
|
|
96
|
+
const home = skillsHome();
|
|
97
|
+
const state = loadSkillState(path.join(home, "skills"));
|
|
98
|
+
const skills = runtimeState.skills;
|
|
99
|
+
const tracked = Object.keys(state.skills).length;
|
|
100
|
+
const conflicts = Object.keys(state.conflicts).length;
|
|
101
|
+
const lines = [
|
|
102
|
+
`DSH Desktop Kit ${KIT_VERSION}`,
|
|
103
|
+
`Плагины: ${runtimeState.mounted.length}/${parts.length} смонтированы${runtimeState.mountErrors.length > 0 ? `, ошибок: ${runtimeState.mountErrors.length}` : ""}`,
|
|
104
|
+
`Навыки: пакетных ${tracked} · конфликтов ${conflicts} · состояние ${state.updatedAt || "ещё не записано"}`,
|
|
105
|
+
];
|
|
106
|
+
if (skills) {
|
|
107
|
+
lines.push(`Последняя синхронизация: +${skills.added.length} ~${skills.updated.length} · конфликтов: ${skills.conflicts.length}`);
|
|
108
|
+
if (skills.conflicts.length > 0) lines.push(`Конфликты (сохранены ваши правки): ${skills.conflicts.join(", ")}`);
|
|
56
109
|
}
|
|
57
|
-
if (
|
|
58
|
-
|
|
110
|
+
if (runtimeState.mountErrors.length > 0) {
|
|
111
|
+
for (const error of runtimeState.mountErrors.slice(0, 5)) lines.push(`Ошибка ${error.name}: ${error.message}`);
|
|
59
112
|
}
|
|
60
|
-
|
|
113
|
+
lines.push(`Состояние: ${path.join(home, "skills", STATE_FILE)}`);
|
|
114
|
+
lines.push("Команды: /kit status | sync | conflicts | force (перезаписать правки)");
|
|
115
|
+
return lines.join("\n");
|
|
61
116
|
}
|
|
62
117
|
|
|
63
118
|
export function apply(ctx) {
|
|
64
119
|
try {
|
|
65
120
|
installSkills(ctx.logger);
|
|
66
121
|
} catch (error) {
|
|
67
|
-
ctx.logger?.warn?.(`[dsh-desktop-kit] skill
|
|
122
|
+
ctx.logger?.warn?.(`[dsh-desktop-kit] skill sync skipped: ${String(error)}`);
|
|
68
123
|
}
|
|
69
124
|
|
|
70
125
|
for (const [name, namespace] of parts) {
|
|
@@ -75,10 +130,59 @@ export function apply(ctx) {
|
|
|
75
130
|
} else if (typeof plugin.apply === "function") {
|
|
76
131
|
plugin.apply(ctx);
|
|
77
132
|
}
|
|
133
|
+
runtimeState.mounted.push(name);
|
|
78
134
|
ctx.logger?.debug?.(`[dsh-desktop-kit] mounted ${name}`);
|
|
79
135
|
} catch (error) {
|
|
136
|
+
runtimeState.mountErrors.push({ name, message: error instanceof Error ? error.message : String(error) });
|
|
80
137
|
ctx.logger?.warn?.(`[dsh-desktop-kit] ${name} failed to mount: ${String(error)}`);
|
|
81
138
|
console.error(`[dsh-desktop-kit] ${name} failed to mount`, error);
|
|
82
139
|
}
|
|
83
140
|
}
|
|
141
|
+
|
|
142
|
+
/* Optional diagnostics command: useful after a DSH/package update. */
|
|
143
|
+
try {
|
|
144
|
+
ctx.inject(["commands"], (commandCtx) => {
|
|
145
|
+
try {
|
|
146
|
+
const dispose = commandCtx.commands.register({
|
|
147
|
+
name: "kit",
|
|
148
|
+
description: "DSH Desktop Kit: состояние, синхронизация навыков, конфликты",
|
|
149
|
+
input: { hint: "status | sync | conflicts | force" },
|
|
150
|
+
recordInput: false,
|
|
151
|
+
handler: ({ rawInput }) => {
|
|
152
|
+
const command = String(rawInput ?? "").trim().toLowerCase();
|
|
153
|
+
try {
|
|
154
|
+
if (command === "sync" || command === "force") {
|
|
155
|
+
const force = command === "force";
|
|
156
|
+
const result = installSkills(ctx.logger, { force });
|
|
157
|
+
return {
|
|
158
|
+
kind: "success",
|
|
159
|
+
text: result
|
|
160
|
+
? `${force ? "Принудительная синхронизация" : "Синхронизация"}: добавлено ${result.added.length}, обновлено ${result.updated.length}, конфликтов ${result.conflicts.length}.\n${kitStatusText()}`
|
|
161
|
+
: "Каталог навыков пакета не найден.",
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (command === "conflicts") {
|
|
165
|
+
const skills = runtimeState.skills;
|
|
166
|
+
if (!skills || skills.conflicts.length === 0) return { kind: "success", text: "Конфликтов нет: все навыки принадлежат пакету." };
|
|
167
|
+
const lines = skills.conflicts.map((name) => {
|
|
168
|
+
const conflict = skills.state?.conflicts?.[name];
|
|
169
|
+
return `- ${name}: ${conflict?.reason ?? "изменён пользователем"} — ваша версия сохранена`;
|
|
170
|
+
});
|
|
171
|
+
return { kind: "success", text: `Конфликты (${lines.length}):\n${lines.join("\n")}\nОбновить принудительно: /kit force (ваши версии уйдут в бэкап)"` };
|
|
172
|
+
}
|
|
173
|
+
return { kind: "success", text: kitStatusText() };
|
|
174
|
+
} catch (error) {
|
|
175
|
+
return { kind: "error", text: `DSH Desktop Kit: ${error instanceof Error ? error.message : String(error)}` };
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
ctx.effect(() => dispose, "dsh-desktop-kit: /kit command");
|
|
180
|
+
} catch (error) {
|
|
181
|
+
ctx.logger?.warn?.(`[dsh-desktop-kit] /kit command skipped: ${String(error)}`);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
} catch (error) {
|
|
185
|
+
ctx.logger?.debug?.(`[dsh-desktop-kit] commands service unavailable: ${String(error)}`);
|
|
186
|
+
}
|
|
84
187
|
}
|
|
188
|
+
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update-safe skill synchronisation.
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* - a missing skill is installed;
|
|
6
|
+
* - a skill that still matches the hash recorded at the last sync is updated
|
|
7
|
+
* (with a backup);
|
|
8
|
+
* - a skill the user edited is never overwritten — it is reported as a
|
|
9
|
+
* conflict and stays untouched;
|
|
10
|
+
* - an installation without a state file is adopted only when its content
|
|
11
|
+
* matches a known baseline hash; otherwise it is treated as user-owned.
|
|
12
|
+
*
|
|
13
|
+
* The state file lives next to the skills and is written atomically.
|
|
14
|
+
*/
|
|
15
|
+
import crypto from "node:crypto";
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
|
|
19
|
+
export const SKILLS_SYNC_SCHEMA = 1;
|
|
20
|
+
export const STATE_FILE = ".dsh-kit-skills.json";
|
|
21
|
+
export const BACKUP_DIR = ".dsh-kit-backups";
|
|
22
|
+
const SKIP_FILES = new Set([".DS_Store", "Thumbs.db", STATE_FILE]);
|
|
23
|
+
|
|
24
|
+
function sha256(data) {
|
|
25
|
+
return crypto.createHash("sha256").update(data).digest("hex");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function hashText(text) {
|
|
29
|
+
return sha256(String(text ?? ""));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Deterministic directory hash: sorted relative paths + bytes. */
|
|
33
|
+
export function hashSkillDir(directory) {
|
|
34
|
+
const hash = crypto.createHash("sha256");
|
|
35
|
+
let files = 0;
|
|
36
|
+
let bytes = 0;
|
|
37
|
+
const walk = (current, relative) => {
|
|
38
|
+
let entries;
|
|
39
|
+
try {
|
|
40
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
41
|
+
} catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
entries.sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0));
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
if (SKIP_FILES.has(entry.name)) continue;
|
|
47
|
+
const full = path.join(current, entry.name);
|
|
48
|
+
const rel = relative.length > 0 ? `${relative}/${entry.name}` : entry.name;
|
|
49
|
+
if (entry.isDirectory()) {
|
|
50
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
51
|
+
walk(full, rel);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (!entry.isFile()) continue;
|
|
55
|
+
try {
|
|
56
|
+
const content = fs.readFileSync(full);
|
|
57
|
+
hash.update(rel);
|
|
58
|
+
hash.update("\0");
|
|
59
|
+
hash.update(String(content.length));
|
|
60
|
+
hash.update("\0");
|
|
61
|
+
hash.update(content);
|
|
62
|
+
files += 1;
|
|
63
|
+
bytes += content.length;
|
|
64
|
+
} catch {
|
|
65
|
+
/* unreadable file: ignore, the hash stays stable */
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
walk(directory, "");
|
|
70
|
+
return { hash: hash.digest("hex"), files, bytes };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function readManifest(packageRoot) {
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(packageRoot, "skills", "manifest.json"), "utf8"));
|
|
76
|
+
return parsed && parsed.schema === SKILLS_SYNC_SCHEMA && parsed.skills ? parsed : null;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function readBaseline(packageRoot) {
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(packageRoot, "skills-baseline.json"), "utf8"));
|
|
85
|
+
return parsed && parsed.versions ? parsed.versions : {};
|
|
86
|
+
} catch {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function loadSkillState(target) {
|
|
92
|
+
try {
|
|
93
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(target, STATE_FILE), "utf8"));
|
|
94
|
+
if (!parsed || parsed.schema !== SKILLS_SYNC_SCHEMA) return emptyState();
|
|
95
|
+
return {
|
|
96
|
+
...emptyState(),
|
|
97
|
+
...parsed,
|
|
98
|
+
skills: parsed.skills && typeof parsed.skills === "object" ? parsed.skills : {},
|
|
99
|
+
conflicts: parsed.conflicts && typeof parsed.conflicts === "object" ? parsed.conflicts : {},
|
|
100
|
+
};
|
|
101
|
+
} catch {
|
|
102
|
+
return emptyState();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function emptyState() {
|
|
107
|
+
return { schema: SKILLS_SYNC_SCHEMA, kitVersion: "", updatedAt: "", skills: {}, conflicts: {} };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function writeAtomic(file, text) {
|
|
111
|
+
const temp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
112
|
+
fs.writeFileSync(temp, text, "utf8");
|
|
113
|
+
fs.renameSync(temp, file);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function saveSkillState(target, state) {
|
|
117
|
+
try {
|
|
118
|
+
fs.mkdirSync(target, { recursive: true });
|
|
119
|
+
writeAtomic(path.join(target, STATE_FILE), `${JSON.stringify(state, null, 2)}\n`);
|
|
120
|
+
} catch {
|
|
121
|
+
/* state is an optimization; a failed write must not break skills */
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function copySkill(source, destination) {
|
|
126
|
+
const parent = path.dirname(destination);
|
|
127
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
128
|
+
const temp = path.join(parent, `.${path.basename(destination)}.kit-tmp-${process.pid}-${Date.now()}`);
|
|
129
|
+
fs.rmSync(temp, { recursive: true, force: true });
|
|
130
|
+
try {
|
|
131
|
+
fs.cpSync(source, temp, { recursive: true });
|
|
132
|
+
} catch (error) {
|
|
133
|
+
fs.rmSync(temp, { recursive: true, force: true });
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
return temp;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function moveWithBackup(target, source, name) {
|
|
140
|
+
const hadTarget = fs.existsSync(target);
|
|
141
|
+
let backup = null;
|
|
142
|
+
if (hadTarget) {
|
|
143
|
+
const backupRoot = path.join(path.dirname(target), BACKUP_DIR, "skills");
|
|
144
|
+
fs.mkdirSync(backupRoot, { recursive: true });
|
|
145
|
+
const stamp = new Date().toISOString().replace(/[:.]/gu, "-");
|
|
146
|
+
backup = path.join(backupRoot, `${name}-${stamp}`);
|
|
147
|
+
fs.renameSync(target, backup);
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
fs.renameSync(source, target);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (backup && !fs.existsSync(target)) {
|
|
153
|
+
try { fs.renameSync(backup, target); } catch { /* best effort */ }
|
|
154
|
+
}
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
return backup;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Synchronise bundled skills into `target`.
|
|
162
|
+
* @returns {{added:string[],updated:string[],adopted:string[],kept:string[],conflicts:string[],removed:string[],state:object}}
|
|
163
|
+
*/
|
|
164
|
+
export function syncSkills({ source, target, kitVersion, manifest, baseline, force = false, logger } = {}) {
|
|
165
|
+
const result = { added: [], updated: [], adopted: [], kept: [], conflicts: [], removed: [], state: emptyState() };
|
|
166
|
+
if (typeof source !== "string" || typeof target !== "string" || !fs.existsSync(source)) return result;
|
|
167
|
+
fs.mkdirSync(target, { recursive: true });
|
|
168
|
+
const state = loadSkillState(target);
|
|
169
|
+
state.kitVersion = kitVersion ?? state.kitVersion;
|
|
170
|
+
state.updatedAt = new Date().toISOString();
|
|
171
|
+
state.schema = SKILLS_SYNC_SCHEMA;
|
|
172
|
+
const knownBaselines = new Map();
|
|
173
|
+
for (const version of Object.values(baseline ?? {})) {
|
|
174
|
+
for (const [name, hash] of Object.entries(version ?? {})) {
|
|
175
|
+
if (!knownBaselines.has(name)) knownBaselines.set(name, new Set());
|
|
176
|
+
knownBaselines.get(name).add(hash);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const bundled = [];
|
|
180
|
+
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
181
|
+
if (entry.isDirectory()) bundled.push(entry.name);
|
|
182
|
+
}
|
|
183
|
+
bundled.sort();
|
|
184
|
+
|
|
185
|
+
for (const name of bundled) {
|
|
186
|
+
const sourceDir = path.join(source, name);
|
|
187
|
+
const targetDir = path.join(target, name);
|
|
188
|
+
const sourceInfo = hashSkillDir(sourceDir);
|
|
189
|
+
const manifestHash = manifest?.skills?.[name]?.hash;
|
|
190
|
+
const sourceHash = sourceInfo.hash;
|
|
191
|
+
const targetExists = fs.existsSync(targetDir);
|
|
192
|
+
const entry = state.skills[name];
|
|
193
|
+
const targetHash = targetExists ? hashSkillDir(targetDir).hash : "";
|
|
194
|
+
|
|
195
|
+
if (!targetExists) {
|
|
196
|
+
const temp = copySkill(sourceDir, targetDir);
|
|
197
|
+
moveWithBackup(targetDir, temp, name);
|
|
198
|
+
state.skills[name] = { hash: sourceHash, updatedAt: state.updatedAt, kitVersion: state.kitVersion };
|
|
199
|
+
delete state.conflicts[name];
|
|
200
|
+
result.added.push(name);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (targetHash === sourceHash || (manifestHash && targetHash === manifestHash && sourceHash === manifestHash)) {
|
|
204
|
+
state.skills[name] = { hash: sourceHash, updatedAt: entry?.updatedAt ?? state.updatedAt, kitVersion: state.kitVersion };
|
|
205
|
+
delete state.conflicts[name];
|
|
206
|
+
if (!entry) result.adopted.push(name);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const unchangedSinceSync = Boolean(entry && targetHash === entry.hash);
|
|
210
|
+
const knownBaseline = !entry && Boolean(knownBaselines.get(name)?.has(targetHash));
|
|
211
|
+
if (force || unchangedSinceSync || knownBaseline) {
|
|
212
|
+
const temp = copySkill(sourceDir, targetDir);
|
|
213
|
+
moveWithBackup(targetDir, temp, name);
|
|
214
|
+
state.skills[name] = { hash: sourceHash, updatedAt: state.updatedAt, kitVersion: state.kitVersion };
|
|
215
|
+
delete state.conflicts[name];
|
|
216
|
+
result.updated.push(name);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
state.conflicts[name] = {
|
|
220
|
+
reason: entry ? "modified-since-last-sync" : "modified-unknown-base",
|
|
221
|
+
hash: targetHash,
|
|
222
|
+
at: state.updatedAt,
|
|
223
|
+
};
|
|
224
|
+
result.kept.push(name);
|
|
225
|
+
result.conflicts.push(name);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Skills removed from the bundle are never deleted: they may be user-owned. */
|
|
229
|
+
for (const name of Object.keys(state.skills)) {
|
|
230
|
+
if (!bundled.includes(name) && fs.existsSync(path.join(target, name))) result.removed.push(name);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
saveSkillState(target, state);
|
|
234
|
+
result.state = state;
|
|
235
|
+
if (result.added.length + result.updated.length > 0) {
|
|
236
|
+
logger?.info?.(`[dsh-desktop-kit] skills: +${result.added.length} ~${result.updated.length}, conflicts ${result.conflicts.length}`);
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function kitManifestSummary(manifest) {
|
|
242
|
+
if (!manifest?.skills) return { count: 0, bytes: 0 };
|
|
243
|
+
const values = Object.values(manifest.skills);
|
|
244
|
+
return { count: values.length, bytes: values.reduce((sum, skill) => sum + (Number(skill.bytes) || 0), 0) };
|
|
245
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-desktop-kit",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "DeepSeek Harness desktop kit 2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "DeepSeek Harness desktop kit 2.2: update-safe self-applying skills, live token lab, Skills Studio, self-scanning project memory, Russian UI, peak guard, chat move and profile transfer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"dsh",
|
|
@@ -32,8 +32,11 @@
|
|
|
32
32
|
"client.js",
|
|
33
33
|
"cordis.patch.yml",
|
|
34
34
|
"README.md",
|
|
35
|
+
"CHANGELOG.md",
|
|
35
36
|
"LICENSE",
|
|
37
|
+
"lib/**",
|
|
36
38
|
"docs/**",
|
|
39
|
+
"skills-baseline.json",
|
|
37
40
|
"plugins/*/package.json",
|
|
38
41
|
"plugins/*/*.js",
|
|
39
42
|
"plugins/*/lib/**",
|
|
@@ -47,8 +50,9 @@
|
|
|
47
50
|
],
|
|
48
51
|
"scripts": {
|
|
49
52
|
"build:client": "node tools/build-client.mjs",
|
|
53
|
+
"build:skills": "node tools/build-skill-manifest.mjs",
|
|
50
54
|
"check:client": "node tools/check-client.mjs client.js",
|
|
51
|
-
"test": "node tools/smoke-tests.mjs && node tools/memory-tests.mjs",
|
|
55
|
+
"test": "node tools/smoke-tests.mjs && node tools/memory-tests.mjs && node tools/skills-sync-tests.mjs && node tools/kit-upgrade-tests.mjs",
|
|
52
56
|
"skills:cost": "node tools/skill-cost.mjs",
|
|
53
57
|
"skills:check": "node tools/skill-cost.mjs --check"
|
|
54
58
|
},
|
|
@@ -215,18 +215,19 @@ function latestUserMessage(session) {
|
|
|
215
215
|
const cached = lastMessageCache.get(id);
|
|
216
216
|
let lastSeq = 0;
|
|
217
217
|
try {
|
|
218
|
-
lastSeq = Number(session.seq) || 0;
|
|
218
|
+
lastSeq = Number(session.seq) || Number(session.logRevision) || 0;
|
|
219
219
|
} catch {
|
|
220
220
|
lastSeq = 0;
|
|
221
221
|
}
|
|
222
|
-
if (cached && cached.seq === lastSeq) return cached.value;
|
|
222
|
+
if (lastSeq > 0 && cached && cached.seq === lastSeq) return cached.value;
|
|
223
223
|
let value = null;
|
|
224
224
|
try {
|
|
225
225
|
const nodes = session.surface?.nodes ?? [];
|
|
226
226
|
for (let index = nodes.length - 1; index >= 0; index -= 1) {
|
|
227
227
|
const event = session.eventAt(nodes[index]);
|
|
228
228
|
if (!event || event.type !== "user/message") continue;
|
|
229
|
-
|
|
229
|
+
/* DSH 0.2.1 keeps the Message under data.message; 0.2.2 stores it as data. */
|
|
230
|
+
const text = messageText(event.data?.message ?? event.data);
|
|
230
231
|
if (text.length > 0) {
|
|
231
232
|
value = { seq: Number(event.seq), text };
|
|
232
233
|
break;
|
|
@@ -235,7 +236,23 @@ function latestUserMessage(session) {
|
|
|
235
236
|
} catch {
|
|
236
237
|
value = null;
|
|
237
238
|
}
|
|
238
|
-
|
|
239
|
+
if (!value) {
|
|
240
|
+
try {
|
|
241
|
+
const events = session.snapshotEvents?.() ?? [];
|
|
242
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
243
|
+
const event = events[index];
|
|
244
|
+
if (!event || event.type !== "user/message") continue;
|
|
245
|
+
const text = messageText(event.data?.message ?? event.data);
|
|
246
|
+
if (text.length > 0) {
|
|
247
|
+
value = { seq: Number(event.seq), text };
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch {
|
|
252
|
+
value = null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (lastSeq > 0) lastMessageCache.set(id, { seq: lastSeq, value });
|
|
239
256
|
return value;
|
|
240
257
|
}
|
|
241
258
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-skill-autopilot",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Skill autopilot for DSH: reads the current user message and injects a ~80-token routing hint naming the exact skills to load, so skills apply themselves without bloating the context.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"kitVersion": "2.2.0",
|
|
4
|
+
"generatedAt": "2026-11-06T11:56:16.796Z",
|
|
5
|
+
"skills": {
|
|
6
|
+
"code-review-checklist": {
|
|
7
|
+
"hash": "e665bf72cfbfb77948a3cd6df6ebced38777f9aff543d7b9c80b2ced8cccc662",
|
|
8
|
+
"files": 1,
|
|
9
|
+
"bytes": 4483
|
|
10
|
+
},
|
|
11
|
+
"content-writing": {
|
|
12
|
+
"hash": "a21b26ff814e0337d03eeb587b200a1dc66e7de91bfea0f32840ddf8884deeee",
|
|
13
|
+
"files": 28,
|
|
14
|
+
"bytes": 271717
|
|
15
|
+
},
|
|
16
|
+
"context-economy": {
|
|
17
|
+
"hash": "bc8fa2e15327d21cf9ebd187b0df5f620d6a5b617976fa88910ae7877eb46b75",
|
|
18
|
+
"files": 4,
|
|
19
|
+
"bytes": 12766
|
|
20
|
+
},
|
|
21
|
+
"database-safety": {
|
|
22
|
+
"hash": "56f43f6bac270aefce34355e2a932192e4d17725d8faec5f91f476792b28c617",
|
|
23
|
+
"files": 1,
|
|
24
|
+
"bytes": 4448
|
|
25
|
+
},
|
|
26
|
+
"debugging-playbook": {
|
|
27
|
+
"hash": "0d70b8102f04d29774e466efd81abb448c84a6537a2ca3438f6931a2d6fe0c3a",
|
|
28
|
+
"files": 1,
|
|
29
|
+
"bytes": 4443
|
|
30
|
+
},
|
|
31
|
+
"design-critique": {
|
|
32
|
+
"hash": "bbc6ca34b0b76798d581dcd78e83a59040dfac028b5e10d8e7a9be28225410b8",
|
|
33
|
+
"files": 6,
|
|
34
|
+
"bytes": 25754
|
|
35
|
+
},
|
|
36
|
+
"dev-workflow": {
|
|
37
|
+
"hash": "832d36da6d3dc3e6e7d1feb18c38260ed8669c7702dffbd2d7a96fc9bc93d22f",
|
|
38
|
+
"files": 4,
|
|
39
|
+
"bytes": 17194
|
|
40
|
+
},
|
|
41
|
+
"git-and-ci": {
|
|
42
|
+
"hash": "0681041edb935da2c124265bbdacd3b6a0a96c7f163323960cff8c8ea67fd2f4",
|
|
43
|
+
"files": 1,
|
|
44
|
+
"bytes": 4192
|
|
45
|
+
},
|
|
46
|
+
"pg-upgrade-verify": {
|
|
47
|
+
"hash": "d734178c5dad317fc55842215bbeeba85230cdd8e7b0ea46d74eed92910b8dea",
|
|
48
|
+
"files": 1,
|
|
49
|
+
"bytes": 4496
|
|
50
|
+
},
|
|
51
|
+
"project-deep-dive": {
|
|
52
|
+
"hash": "a70355f2a40b42a6ff03e745ba42389549b4efe4f392c98033fe5073f946452e",
|
|
53
|
+
"files": 4,
|
|
54
|
+
"bytes": 26290
|
|
55
|
+
},
|
|
56
|
+
"project-memory-maintenance": {
|
|
57
|
+
"hash": "648f175093f6a86e20157156d8731b8e9602badedc79188e77160b18832f4893",
|
|
58
|
+
"files": 4,
|
|
59
|
+
"bytes": 23469
|
|
60
|
+
},
|
|
61
|
+
"release-and-deploy": {
|
|
62
|
+
"hash": "e6ed27856b00e8759d31659162290307c16e203d2b5d28fcacb574be17fc795b",
|
|
63
|
+
"files": 1,
|
|
64
|
+
"bytes": 4409
|
|
65
|
+
},
|
|
66
|
+
"requirements-and-planning": {
|
|
67
|
+
"hash": "532cbccfb2d0f7eca06343927aba100bd653145ccd9b9173ca50299194631a05",
|
|
68
|
+
"files": 4,
|
|
69
|
+
"bytes": 12964
|
|
70
|
+
},
|
|
71
|
+
"security-review": {
|
|
72
|
+
"hash": "8665b2c469de8e83aca670021d5c335fa8c9b93b8dc6bb866759e6062b1abde4",
|
|
73
|
+
"files": 1,
|
|
74
|
+
"bytes": 4257
|
|
75
|
+
},
|
|
76
|
+
"seo": {
|
|
77
|
+
"hash": "ed48ebc4c1278f857ff430f9a5ec55f638c2f9ba07cc406185cfb94be18ee80d",
|
|
78
|
+
"files": 29,
|
|
79
|
+
"bytes": 386169
|
|
80
|
+
},
|
|
81
|
+
"service-desk-conventions": {
|
|
82
|
+
"hash": "22ae6c29b7debb0154d183bc732795b67557aeaa0ac906d7b038134aea8ba39a",
|
|
83
|
+
"files": 1,
|
|
84
|
+
"bytes": 4488
|
|
85
|
+
},
|
|
86
|
+
"service-desk-db-safety": {
|
|
87
|
+
"hash": "672c08d25e0a9262aeb0eec27b6daa2a619fade6ff3269bc2b9dc27e22c83d28",
|
|
88
|
+
"files": 1,
|
|
89
|
+
"bytes": 4482
|
|
90
|
+
},
|
|
91
|
+
"service-desk-release": {
|
|
92
|
+
"hash": "1bd0c03f772bf4b79ec4e887f5d0ebd564653bafd3d0d33d70f6c934afdcce47",
|
|
93
|
+
"files": 1,
|
|
94
|
+
"bytes": 4418
|
|
95
|
+
},
|
|
96
|
+
"service-desk-testing": {
|
|
97
|
+
"hash": "eaff64154a596590684308cdccb3f55099240589b57c0f506a85e5b38ddf5ac5",
|
|
98
|
+
"files": 1,
|
|
99
|
+
"bytes": 4129
|
|
100
|
+
},
|
|
101
|
+
"service-desk-ui": {
|
|
102
|
+
"hash": "a3a0e78ab147f4837e4ca9736f4d87cf77a6638c2db7c5859629df70abdd2c1c",
|
|
103
|
+
"files": 1,
|
|
104
|
+
"bytes": 4201
|
|
105
|
+
},
|
|
106
|
+
"skills-router": {
|
|
107
|
+
"hash": "63c2dcdeb07dd4af464a30a8065465bd06b73e4caa8aa874802e5188d4d5a4ae",
|
|
108
|
+
"files": 3,
|
|
109
|
+
"bytes": 13888
|
|
110
|
+
},
|
|
111
|
+
"testing-strategy": {
|
|
112
|
+
"hash": "773fdf1e389e59841b7766862f8890a11fba4a2fce42a2022b9c4bfaae8f6ecc",
|
|
113
|
+
"files": 1,
|
|
114
|
+
"bytes": 4279
|
|
115
|
+
},
|
|
116
|
+
"ui-design-system": {
|
|
117
|
+
"hash": "19244221171995b758dbad02f2e3602859308370b50cc37d93c6f2a6d44cd6f1",
|
|
118
|
+
"files": 4,
|
|
119
|
+
"bytes": 19454
|
|
120
|
+
},
|
|
121
|
+
"web-research": {
|
|
122
|
+
"hash": "9bf4d8ba6d2a0fc39c241cdd9f5fe54835af28872473aa617941589c526387c8",
|
|
123
|
+
"files": 3,
|
|
124
|
+
"bytes": 14150
|
|
125
|
+
},
|
|
126
|
+
"wow-design": {
|
|
127
|
+
"hash": "81139f32fc759f438a36eb4c0212ed3ea17df9d8075e909580e12bc47a0848dd",
|
|
128
|
+
"files": 52,
|
|
129
|
+
"bytes": 913669
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"versions": {
|
|
4
|
+
"2.1.4": {
|
|
5
|
+
"code-review-checklist": "e665bf72cfbfb77948a3cd6df6ebced38777f9aff543d7b9c80b2ced8cccc662",
|
|
6
|
+
"content-writing": "a21b26ff814e0337d03eeb587b200a1dc66e7de91bfea0f32840ddf8884deeee",
|
|
7
|
+
"context-economy": "bc8fa2e15327d21cf9ebd187b0df5f620d6a5b617976fa88910ae7877eb46b75",
|
|
8
|
+
"database-safety": "56f43f6bac270aefce34355e2a932192e4d17725d8faec5f91f476792b28c617",
|
|
9
|
+
"debugging-playbook": "0d70b8102f04d29774e466efd81abb448c84a6537a2ca3438f6931a2d6fe0c3a",
|
|
10
|
+
"design-critique": "bbc6ca34b0b76798d581dcd78e83a59040dfac028b5e10d8e7a9be28225410b8",
|
|
11
|
+
"dev-workflow": "832d36da6d3dc3e6e7d1feb18c38260ed8669c7702dffbd2d7a96fc9bc93d22f",
|
|
12
|
+
"git-and-ci": "0681041edb935da2c124265bbdacd3b6a0a96c7f163323960cff8c8ea67fd2f4",
|
|
13
|
+
"pg-upgrade-verify": "d734178c5dad317fc55842215bbeeba85230cdd8e7b0ea46d74eed92910b8dea",
|
|
14
|
+
"project-deep-dive": "a70355f2a40b42a6ff03e745ba42389549b4efe4f392c98033fe5073f946452e",
|
|
15
|
+
"project-memory-maintenance": "648f175093f6a86e20157156d8731b8e9602badedc79188e77160b18832f4893",
|
|
16
|
+
"release-and-deploy": "e6ed27856b00e8759d31659162290307c16e203d2b5d28fcacb574be17fc795b",
|
|
17
|
+
"requirements-and-planning": "532cbccfb2d0f7eca06343927aba100bd653145ccd9b9173ca50299194631a05",
|
|
18
|
+
"security-review": "8665b2c469de8e83aca670021d5c335fa8c9b93b8dc6bb866759e6062b1abde4",
|
|
19
|
+
"seo": "ed48ebc4c1278f857ff430f9a5ec55f638c2f9ba07cc406185cfb94be18ee80d",
|
|
20
|
+
"service-desk-conventions": "22ae6c29b7debb0154d183bc732795b67557aeaa0ac906d7b038134aea8ba39a",
|
|
21
|
+
"service-desk-db-safety": "672c08d25e0a9262aeb0eec27b6daa2a619fade6ff3269bc2b9dc27e22c83d28",
|
|
22
|
+
"service-desk-release": "1bd0c03f772bf4b79ec4e887f5d0ebd564653bafd3d0d33d70f6c934afdcce47",
|
|
23
|
+
"service-desk-testing": "eaff64154a596590684308cdccb3f55099240589b57c0f506a85e5b38ddf5ac5",
|
|
24
|
+
"service-desk-ui": "a3a0e78ab147f4837e4ca9736f4d87cf77a6638c2db7c5859629df70abdd2c1c",
|
|
25
|
+
"skills-router": "63c2dcdeb07dd4af464a30a8065465bd06b73e4caa8aa874802e5188d4d5a4ae",
|
|
26
|
+
"testing-strategy": "773fdf1e389e59841b7766862f8890a11fba4a2fce42a2022b9c4bfaae8f6ecc",
|
|
27
|
+
"ui-design-system": "19244221171995b758dbad02f2e3602859308370b50cc37d93c6f2a6d44cd6f1",
|
|
28
|
+
"web-research": "9bf4d8ba6d2a0fc39c241cdd9f5fe54835af28872473aa617941589c526387c8",
|
|
29
|
+
"wow-design": "81139f32fc759f438a36eb4c0212ed3ea17df9d8075e909580e12bc47a0848dd"
|
|
30
|
+
},
|
|
31
|
+
"2.2.0": {
|
|
32
|
+
"code-review-checklist": "e665bf72cfbfb77948a3cd6df6ebced38777f9aff543d7b9c80b2ced8cccc662",
|
|
33
|
+
"content-writing": "a21b26ff814e0337d03eeb587b200a1dc66e7de91bfea0f32840ddf8884deeee",
|
|
34
|
+
"context-economy": "bc8fa2e15327d21cf9ebd187b0df5f620d6a5b617976fa88910ae7877eb46b75",
|
|
35
|
+
"database-safety": "56f43f6bac270aefce34355e2a932192e4d17725d8faec5f91f476792b28c617",
|
|
36
|
+
"debugging-playbook": "0d70b8102f04d29774e466efd81abb448c84a6537a2ca3438f6931a2d6fe0c3a",
|
|
37
|
+
"design-critique": "bbc6ca34b0b76798d581dcd78e83a59040dfac028b5e10d8e7a9be28225410b8",
|
|
38
|
+
"dev-workflow": "832d36da6d3dc3e6e7d1feb18c38260ed8669c7702dffbd2d7a96fc9bc93d22f",
|
|
39
|
+
"git-and-ci": "0681041edb935da2c124265bbdacd3b6a0a96c7f163323960cff8c8ea67fd2f4",
|
|
40
|
+
"pg-upgrade-verify": "d734178c5dad317fc55842215bbeeba85230cdd8e7b0ea46d74eed92910b8dea",
|
|
41
|
+
"project-deep-dive": "a70355f2a40b42a6ff03e745ba42389549b4efe4f392c98033fe5073f946452e",
|
|
42
|
+
"project-memory-maintenance": "648f175093f6a86e20157156d8731b8e9602badedc79188e77160b18832f4893",
|
|
43
|
+
"release-and-deploy": "e6ed27856b00e8759d31659162290307c16e203d2b5d28fcacb574be17fc795b",
|
|
44
|
+
"requirements-and-planning": "532cbccfb2d0f7eca06343927aba100bd653145ccd9b9173ca50299194631a05",
|
|
45
|
+
"security-review": "8665b2c469de8e83aca670021d5c335fa8c9b93b8dc6bb866759e6062b1abde4",
|
|
46
|
+
"seo": "ed48ebc4c1278f857ff430f9a5ec55f638c2f9ba07cc406185cfb94be18ee80d",
|
|
47
|
+
"service-desk-conventions": "22ae6c29b7debb0154d183bc732795b67557aeaa0ac906d7b038134aea8ba39a",
|
|
48
|
+
"service-desk-db-safety": "672c08d25e0a9262aeb0eec27b6daa2a619fade6ff3269bc2b9dc27e22c83d28",
|
|
49
|
+
"service-desk-release": "1bd0c03f772bf4b79ec4e887f5d0ebd564653bafd3d0d33d70f6c934afdcce47",
|
|
50
|
+
"service-desk-testing": "eaff64154a596590684308cdccb3f55099240589b57c0f506a85e5b38ddf5ac5",
|
|
51
|
+
"service-desk-ui": "a3a0e78ab147f4837e4ca9736f4d87cf77a6638c2db7c5859629df70abdd2c1c",
|
|
52
|
+
"skills-router": "63c2dcdeb07dd4af464a30a8065465bd06b73e4caa8aa874802e5188d4d5a4ae",
|
|
53
|
+
"testing-strategy": "773fdf1e389e59841b7766862f8890a11fba4a2fce42a2022b9c4bfaae8f6ecc",
|
|
54
|
+
"ui-design-system": "19244221171995b758dbad02f2e3602859308370b50cc37d93c6f2a6d44cd6f1",
|
|
55
|
+
"web-research": "9bf4d8ba6d2a0fc39c241cdd9f5fe54835af28872473aa617941589c526387c8",
|
|
56
|
+
"wow-design": "81139f32fc759f438a36eb4c0212ed3ea17df9d8075e909580e12bc47a0848dd"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|