dsh-skill-hub 0.3.25 → 0.3.27

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/CONTRIBUTING.md CHANGED
@@ -39,7 +39,7 @@ src/client/locales/ dictionaries by view (common/skills/market/sources/detai
39
39
  ```bash
40
40
  npm install
41
41
  npm run typecheck # tsc --noEmit
42
- npm test # vitest (294 tests across 16 suites)
42
+ npm test # vitest (295 tests across 16 suites)
43
43
  npm run build # tsc declarations + tsdown bundles (lib/index.js + lib/client.js)
44
44
  ```
45
45
 
package/README.md CHANGED
@@ -24,7 +24,7 @@ dsh plugin --profile web add dsh-skill-hub
24
24
  # restart dsh web → Settings → 技能 → Market → scan → import
25
25
  ```
26
26
 
27
- Requires `Node ^22.19 || >=24` + dsh web `>=0.1.7-alpha.1 <0.3`.
27
+ Requires `Node ^22.19 || >=24` + dsh web `>=0.1.7-alpha.1 <0.3`. dsh `0.2.2` and later require `Node >=24.9`.
28
28
 
29
29
  Two surfaces — the hub panel lives at **Settings → 技能**, and this plugin's own configuration card sits on the plugin's page in the **Plugins manager** (sidebar → 插件 → dsh-skill-hub). The card is registered by the plugin itself into the `plugins.bundle.config` slot — dsh has no auto-generated config page — and covers the master switch, announce-to-agent, dot colors, usage display and the stats window/interval.
30
30
 
@@ -108,7 +108,7 @@ Host uses only `ctx.skills.snapshot/get`, `ctx.webServer.register`, `ctx.systemP
108
108
 
109
109
  ```bash
110
110
  npm run typecheck # tsc --noEmit
111
- npm test # 294 tests, 16 suites
111
+ npm test # 295 tests, 16 suites
112
112
  npm run build # tsc + tsdown → lib/index.js + lib/client.js
113
113
  ```
114
114
 
package/README.zh.md CHANGED
@@ -24,7 +24,7 @@ dsh plugin --profile web add dsh-skill-hub
24
24
  # 重启 dsh web → 设置 → 技能 → 市场 → 扫描 → 导入
25
25
  ```
26
26
 
27
- 要求 `Node ^22.19 || >=24` + dsh web `>=0.1.7-alpha.1 <0.3`。
27
+ 要求 `Node ^22.19 || >=24` + dsh web `>=0.1.7-alpha.1 <0.3`。dsh `0.2.2` 起要求 `Node >=24.9`。
28
28
 
29
29
  两个界面 —— 管理面板在 **设置 → 技能**;本插件自身的配置卡片在 **插件管理页** 的插件自有页面上(侧边栏「插件」→ dsh-skill-hub)。该卡片由本插件自己注册进 `plugins.bundle.config` 槽位(dsh 没有自动生成的配置页),覆盖总开关、向 Agent 公告、圆点颜色、用量显示与统计窗口/间隔。
30
30
 
@@ -108,7 +108,7 @@ GitHub 仓库 ──扫描/导入──▶ ~/.dsh/skills
108
108
 
109
109
  ```bash
110
110
  npm run typecheck # tsc --noEmit
111
- npm test # 294 tests, 16 suites
111
+ npm test # 295 tests, 16 suites
112
112
  npm run build # tsc + tsdown → lib/index.js + lib/client.js
113
113
  ```
114
114
 
package/lib/index.js CHANGED
@@ -937,6 +937,17 @@ var SkillHubStore = class {
937
937
  * Upsert one skill into a source record. When the repo has no record yet a
938
938
  * new one is created (root + commit snapshot from the caller).
939
939
  */
940
+ /**
941
+ * Record one imported skill in its repo-level source row.
942
+ *
943
+ * A repo owns exactly one source row, hence one pinned ref, so a later import
944
+ * must re-pin it: keeping the first import's `ref` / `commitSha` makes
945
+ * `sources/check` compare against a stale sha and `sources/sync` re-download
946
+ * the old ref over content that was imported from a tag — the "pinning a tag
947
+ * means nothing" failure of issue #15, reached through the bookkeeping
948
+ * instead of the download. An absent `ref` means the import ran on the
949
+ * default branch, which clears the pin rather than silently keeping it.
950
+ */
940
951
  async addSourceSkill(repo, root, commitSha, ref, skillName) {
941
952
  await this.ensureLoaded();
942
953
  const existing = this.sourcesByRepo.get(repo);
@@ -949,10 +960,14 @@ var SkillHubStore = class {
949
960
  });
950
961
  else {
951
962
  const skills = existing.skills.includes(skillName) ? existing.skills : [...existing.skills, skillName].sort((a, b) => a.localeCompare(b));
952
- this.sourcesByRepo.set(repo, {
963
+ const next = {
953
964
  ...existing,
954
965
  skills
955
- });
966
+ };
967
+ if (ref !== void 0 && ref !== "") next.ref = ref;
968
+ else delete next.ref;
969
+ if (commitSha !== "") next.commitSha = commitSha;
970
+ this.sourcesByRepo.set(repo, next);
956
971
  }
957
972
  await this.persist();
958
973
  }
@@ -83,6 +83,17 @@ export declare class SkillHubStore {
83
83
  * Upsert one skill into a source record. When the repo has no record yet a
84
84
  * new one is created (root + commit snapshot from the caller).
85
85
  */
86
+ /**
87
+ * Record one imported skill in its repo-level source row.
88
+ *
89
+ * A repo owns exactly one source row, hence one pinned ref, so a later import
90
+ * must re-pin it: keeping the first import's `ref` / `commitSha` makes
91
+ * `sources/check` compare against a stale sha and `sources/sync` re-download
92
+ * the old ref over content that was imported from a tag — the "pinning a tag
93
+ * means nothing" failure of issue #15, reached through the bookkeeping
94
+ * instead of the download. An absent `ref` means the import ran on the
95
+ * default branch, which clears the pin rather than silently keeping it.
96
+ */
86
97
  addSourceSkill(repo: string, root: string, commitSha: string, ref: string | undefined, skillName: string): Promise<void>;
87
98
  /** Replace a source's skill list (used after sync/confirm-delete). */
88
99
  setSourceSkills(repo: string, skills: readonly string[]): Promise<SourceRecord | undefined>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-skill-hub",
3
- "version": "0.3.25",
3
+ "version": "0.3.27",
4
4
  "description": "In-GUI skill hub for DeepSeek Harness (dsh): browse the full local skill catalog from the official ctx.skills registry (every root + third-party providers), toggle skills on/off, inspect bodies, surface frontmatter diagnostics, and scaffold new skills — plus a codex-style skill market (built-in catalog, upstream update checks, one-click update-all) with tracked source sync. The full manager beyond the read-only dsh-skill-manager browser.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -68,7 +68,9 @@
68
68
  "0.2.0-rc.1",
69
69
  "0.2.0-rc.2",
70
70
  "0.2.1-alpha.2",
71
- "0.2.1-alpha.5"
71
+ "0.2.1-alpha.5",
72
+ "0.2.1-rc.2",
73
+ "0.2.2-alpha.2"
72
74
  ]
73
75
  },
74
76
  "capability": {
@@ -88,7 +90,7 @@
88
90
  "js-yaml": "^4.1.0"
89
91
  },
90
92
  "peerDependencies": {
91
- "@deepseek-ai/cordis": "^4.0.1 || ^4.0.2 || ^4.0.5-alpha.1 || ^4.0.5-alpha.2",
93
+ "@deepseek-ai/cordis": "^4.0.1 || ^4.0.2 || ^4.0.5-alpha.1 || ^4.0.5-alpha.2 || ^4.0.6-alpha.1",
92
94
  "@deepseek-ai/dsh-client-connection": ">=0.1.7-alpha.1 <0.3.0-0",
93
95
  "@deepseek-ai/dsh-client-locale": ">=0.1.7-alpha.1 <0.3.0-0",
94
96
  "@deepseek-ai/dsh-client-runtime": ">=0.1.7-alpha.1 <0.3.0-0",
@@ -102,7 +104,7 @@
102
104
  "@deepseek-ai/dsh-settings": ">=0.1.7-alpha.1 <0.3.0-0",
103
105
  "@deepseek-ai/dsh-skill": ">=0.1.7-alpha.1 <0.3.0-0",
104
106
  "@deepseek-ai/dsh-system-prompt": ">=0.1.7-alpha.1 <0.3.0-0",
105
- "@deepseek-ai/schemastery": "^3.18.3 || ^3.18.5-alpha.1 || ^3.18.5-alpha.2",
107
+ "@deepseek-ai/schemastery": "^3.18.3 || ^3.18.5-alpha.1 || ^3.18.5-alpha.2 || ^3.18.6-alpha.1",
106
108
  "react": "^18.2.0",
107
109
  "react-dom": "^18.2.0"
108
110
  },
@@ -115,22 +117,22 @@
115
117
  }
116
118
  },
117
119
  "devDependencies": {
118
- "@deepseek-ai/cordis": "^4.0.5-alpha.2",
119
- "@deepseek-ai/dsh-client-connection": "^0.2.1-alpha.5",
120
- "@deepseek-ai/dsh-client-locale": "^0.2.1-alpha.5",
121
- "@deepseek-ai/dsh-client-store": "^0.2.1-alpha.5",
122
- "@deepseek-ai/dsh-client-ui-input-trigger": "^0.2.1-alpha.5",
123
- "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.2.1-alpha.5",
124
- "@deepseek-ai/dsh-client-ui-settings": "^0.2.1-alpha.5",
125
- "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.2.1-alpha.5",
126
- "@deepseek-ai/dsh-client-ui-slots": "^0.2.1-alpha.5",
127
- "@deepseek-ai/dsh-host-webserver": "^0.2.1-alpha.5",
128
- "@deepseek-ai/dsh-session": "^0.2.1-alpha.5",
129
- "@deepseek-ai/dsh-session-query": "^0.2.1-alpha.5",
130
- "@deepseek-ai/dsh-settings": "^0.2.1-alpha.5",
131
- "@deepseek-ai/dsh-skill": "^0.2.1-alpha.5",
132
- "@deepseek-ai/dsh-system-prompt": "^0.2.1-alpha.5",
133
- "@deepseek-ai/schemastery": "^3.18.5-alpha.2",
120
+ "@deepseek-ai/cordis": "^4.0.6-alpha.1",
121
+ "@deepseek-ai/dsh-client-connection": "^0.2.2-alpha.2",
122
+ "@deepseek-ai/dsh-client-locale": "^0.2.2-alpha.2",
123
+ "@deepseek-ai/dsh-client-store": "^0.2.2-alpha.2",
124
+ "@deepseek-ai/dsh-client-ui-input-trigger": "^0.2.2-alpha.2",
125
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.2.2-alpha.2",
126
+ "@deepseek-ai/dsh-client-ui-settings": "^0.2.2-alpha.2",
127
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.2.2-alpha.2",
128
+ "@deepseek-ai/dsh-client-ui-slots": "^0.2.2-alpha.2",
129
+ "@deepseek-ai/dsh-host-webserver": "^0.2.2-alpha.2",
130
+ "@deepseek-ai/dsh-session": "^0.2.2-alpha.2",
131
+ "@deepseek-ai/dsh-session-query": "^0.2.2-alpha.2",
132
+ "@deepseek-ai/dsh-settings": "^0.2.2-alpha.2",
133
+ "@deepseek-ai/dsh-skill": "^0.2.2-alpha.2",
134
+ "@deepseek-ai/dsh-system-prompt": "^0.2.2-alpha.2",
135
+ "@deepseek-ai/schemastery": "^3.18.6-alpha.1",
134
136
  "@types/js-yaml": "^4.0.9",
135
137
  "@types/node": "^22.20.0",
136
138
  "@types/react": "~18.3.1",
@@ -282,6 +282,17 @@ export class SkillHubStore {
282
282
  * Upsert one skill into a source record. When the repo has no record yet a
283
283
  * new one is created (root + commit snapshot from the caller).
284
284
  */
285
+ /**
286
+ * Record one imported skill in its repo-level source row.
287
+ *
288
+ * A repo owns exactly one source row, hence one pinned ref, so a later import
289
+ * must re-pin it: keeping the first import's `ref` / `commitSha` makes
290
+ * `sources/check` compare against a stale sha and `sources/sync` re-download
291
+ * the old ref over content that was imported from a tag — the "pinning a tag
292
+ * means nothing" failure of issue #15, reached through the bookkeeping
293
+ * instead of the download. An absent `ref` means the import ran on the
294
+ * default branch, which clears the pin rather than silently keeping it.
295
+ */
285
296
  async addSourceSkill(repo: string, root: string, commitSha: string, ref: string | undefined, skillName: string): Promise<void> {
286
297
  await this.ensureLoaded()
287
298
  const existing = this.sourcesByRepo.get(repo)
@@ -295,7 +306,11 @@ export class SkillHubStore {
295
306
  })
296
307
  } else {
297
308
  const skills = existing.skills.includes(skillName) ? existing.skills : [...existing.skills, skillName].sort((a, b) => a.localeCompare(b))
298
- this.sourcesByRepo.set(repo, { ...existing, skills })
309
+ const next: SourceRecord = { ...existing, skills }
310
+ if (ref !== undefined && ref !== '') next.ref = ref
311
+ else delete next.ref
312
+ if (commitSha !== '') next.commitSha = commitSha
313
+ this.sourcesByRepo.set(repo, next)
299
314
  }
300
315
  await this.persist()
301
316
  }
package/src/store.test.ts CHANGED
@@ -125,6 +125,31 @@ describe('SkillHubStore', () => {
125
125
  expect(again[0].manifest).toEqual({ 'skills/docx/SKILL.md': 120 })
126
126
  })
127
127
 
128
+ // issue #15 follow-up: one repo owns one source row, hence one pinned ref, so
129
+ // a later import must re-pin it. Keeping the first import's ref/sha made
130
+ // `check` compare a stale sha and `sync` re-download the old ref over
131
+ // tag-pinned content — the same "pinning a tag means nothing" failure.
132
+ it('re-pins the source to the ref and sha of the latest import', async () => {
133
+ await store.addSourceSkill('a/b', 'skills', 'sha-main', 'main', 'one')
134
+ expect(await store.getSource('a/b')).toMatchObject({ ref: 'main', commitSha: 'sha-main' })
135
+
136
+ await store.addSourceSkill('a/b', 'skills', 'sha-tag', 'v1.2.0', 'two')
137
+ expect(await store.getSource('a/b')).toMatchObject({ ref: 'v1.2.0', commitSha: 'sha-tag', skills: ['one', 'two'] })
138
+
139
+ // An import from the default branch clears the pin instead of keeping it.
140
+ await store.addSourceSkill('a/b', 'skills', 'sha-default', undefined, 'three')
141
+ const unpinned = await store.getSource('a/b')
142
+ expect(unpinned?.ref).toBeUndefined()
143
+ expect(unpinned?.commitSha).toBe('sha-default')
144
+ expect(unpinned?.skills).toEqual(['one', 'three', 'two'])
145
+
146
+ // The cleared pin survives the sidecar round trip.
147
+ const reloaded = new SkillHubStore(file)
148
+ const persisted = await reloaded.getSource('a/b')
149
+ expect(persisted?.ref).toBeUndefined()
150
+ expect(persisted?.commitSha).toBe('sha-default')
151
+ })
152
+
128
153
  it('drops only the given skill directory from the baseline', async () => {
129
154
  await store.addSourceSkill('a/b', 'skills', 'sha-1', undefined, 'docx')
130
155
  await store.addSourceSkill('a/b', 'skills', 'sha-1', undefined, 'pdf')