From 928c99876e8e3a66a730f759236525f7944a4a0f Mon Sep 17 00:00:00 2001 From: Turtle Date: Wed, 5 Aug 2026 21:50:44 +0800 Subject: [PATCH 1/4] feat: add optional dsh badge skill provider --- ...26-08-06-bundled-dsh-badge-skill.i18n.yaml | 6 + .../2026-08-06-bundled-dsh-badge-skill.md | 25 +++ .../2026-08-06-bundled-dsh-badge-skill.zh.md | 25 +++ apps/cli/composition.md | 3 + apps/cli/config/base.cordis.yml | 4 + apps/cli/package.json | 1 + apps/cli/tests/dsh-badge.snapshot.ts | 173 ++++++++++++++++++ apps/cli/tests/fixtures/dsh-badge/cordis.yml | 9 + .../fixtures/dsh-badge/default.cordis.yml | 6 + apps/cli/tests/fixtures/dsh-badge/snapshot.ts | 53 ++++++ docs/capability-seams.md | 4 +- docs/config-catalog.md | 1 + docs/module-graph.md | 4 + knip.json | 3 +- packages/skill/README.i18n.yaml | 4 +- packages/skill/README.md | 1 + packages/skill/README.zh.md | 1 + packages/skill/skill-badge/README.i18n.yaml | 6 + packages/skill/skill-badge/README.md | 22 +++ packages/skill/skill-badge/README.zh.md | 22 +++ .../skill/skill-badge/assets/dsh-badge.md | 31 ++++ .../skill/skill-badge/assets/dsh-badge.png | Bin 0 -> 12339 bytes packages/skill/skill-badge/package.json | 37 ++++ packages/skill/skill-badge/src/index.ts | 60 ++++++ packages/skill/skill-badge/src/invariant.ts | 30 +++ .../skill-badge/tests/skill-badge.spec.ts | 40 ++++ packages/skill/skill-badge/tsconfig.json | 14 ++ pnpm-lock.yaml | 15 ++ scripts/check-workspace-constraints.ts | 1 + scripts/gen-doc-graphs.ts | 2 +- .../verify-package-readme-model-experience.ts | 1 + tsconfig.host.json | 1 + vitest.snapshot.config.ts | 1 + 33 files changed, 601 insertions(+), 5 deletions(-) create mode 100644 .agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.i18n.yaml create mode 100644 .agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.md create mode 100644 .agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.zh.md create mode 100644 apps/cli/tests/dsh-badge.snapshot.ts create mode 100644 apps/cli/tests/fixtures/dsh-badge/cordis.yml create mode 100644 apps/cli/tests/fixtures/dsh-badge/default.cordis.yml create mode 100644 apps/cli/tests/fixtures/dsh-badge/snapshot.ts create mode 100644 packages/skill/skill-badge/README.i18n.yaml create mode 100644 packages/skill/skill-badge/README.md create mode 100644 packages/skill/skill-badge/README.zh.md create mode 100644 packages/skill/skill-badge/assets/dsh-badge.md create mode 100644 packages/skill/skill-badge/assets/dsh-badge.png create mode 100644 packages/skill/skill-badge/package.json create mode 100644 packages/skill/skill-badge/src/index.ts create mode 100644 packages/skill/skill-badge/src/invariant.ts create mode 100644 packages/skill/skill-badge/tests/skill-badge.spec.ts create mode 100644 packages/skill/skill-badge/tsconfig.json diff --git a/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.i18n.yaml b/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.i18n.yaml new file mode 100644 index 0000000000..bdc103ef46 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.md +2026-08-06-bundled-dsh-badge-skill.md: afe0b21d64a414a9e78ef55459a42c0d3817e3fd +2026-08-06-bundled-dsh-badge-skill.zh.md: de1ec989570b07987b12f0a291c84643aa5531fd diff --git a/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.md b/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.md new file mode 100644 index 0000000000..afe0b21d64 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.md @@ -0,0 +1,25 @@ +# Agent Note: Bundled dsh badge skill + +Status: implemented + +English | [中文](2026-08-06-bundled-dsh-badge-skill.zh.md) + +## Problem + +DeepSeek Harness has an official attribution badge skill, but keeping it only in a developer's personal skill directory makes it unavailable to other DSH installations and gives the shipped application no explicit opt-in point. + +## Decision + +`@deepseek-ai/dsh-skill-badge` is a native Cordis plugin that registers one immutable bundled provider on `ctx.skills`. The provider owns the `dsh-badge` summary, instruction body, and PNG resource base; `dsh-tool-skill` remains the sole owner of model-facing catalog and loader rendering. + +The shipped CLI composition declares `skill-badge` as disabled. Enabling that existing row is the explicit opt-in; disabled installations advertise no badge skill and gain no model-visible content. + +The provider uses the bundled rank after project, custom, and user filesystem sources, so a user-owned `dsh-badge` definition can override it through the ordinary registry precedence contract. Provider disposal removes the contribution through the registry-owned effect. + +## Alternatives considered + +A Codex marketplace plugin was rejected because it would install into a different runtime and would not participate in DSH's `ctx.skills` seam. Mounting `dsh-skill-local` over the packaged files was rejected because filesystem discovery, parsing, and watching add lifecycle machinery that an immutable single-skill provider does not need. + +## Consequences + +The badge instructions and source PNG are versioned with DSH and resolve through a packaged directory resource base. The provider has no configuration surface. Package tests pin provider lifecycle and the official PNG bytes, while a keyless assembled-application snapshot pins the enabled catalog and loaded skill body. diff --git a/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.zh.md b/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.zh.md new file mode 100644 index 0000000000..de1ec98957 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.zh.md @@ -0,0 +1,25 @@ +# Agent Note: 内置 dsh 徽章 skill + +Status: implemented + +[English](2026-08-06-bundled-dsh-badge-skill.md) | 中文 + +## 问题 + +DeepSeek Harness 已有官方署名徽章 skill(技能),但如果它只保存在某位开发者的个人 skill 目录中,其他 DSH 安装实例便无法使用,交付的应用也没有显式的选择加入点。 + +## 决策 + +`@deepseek-ai/dsh-skill-badge` 是一个原生 Cordis 插件,会在 `ctx.skills` 上注册一个不可变的内置提供方。该提供方负责 `dsh-badge` 的摘要、指令正文和 PNG 资源基底;`dsh-tool-skill` 仍是面向模型的目录与 loader 渲染的唯一归属方。 + +交付的 CLI(命令行界面)组合将 `skill-badge` 声明为禁用。启用这个现有配置行就是显式选择加入;禁用它的安装实例不会公开任何徽章 skill,也不会获得任何模型可见内容。 + +该提供方使用排在项目、自定义及用户文件系统来源之后的内置 rank,因此用户自有的 `dsh-badge` 定义可通过注册表的常规优先级契约覆盖它。提供方释放时,注册表拥有的 effect 会移除该贡献。 + +## 曾考虑的替代方案 + +未采用 Codex marketplace 插件,因为它会安装到不同的运行时,无法参与 DSH 的 `ctx.skills` seam。未采用使用 `dsh-skill-local` 挂载随包文件的方案,因为文件系统发现、解析和监视会引入不必要的生命周期机制,而不可变的单一 skill 提供方并不需要这些机制。 + +## 后果 + +徽章指令和源 PNG 随 DSH 一同纳入版本管理,并通过以随包目录为基础的资源基底解析。该提供方没有配置面。包测试固定提供方生命周期和官方 PNG 的字节内容;无密钥的组装应用快照则固定启用后的目录和已加载的 skill 正文。 diff --git a/apps/cli/composition.md b/apps/cli/composition.md index 28f58bcf4d..8edf39a07a 100644 --- a/apps/cli/composition.md +++ b/apps/cli/composition.md @@ -72,6 +72,8 @@ flowchart LR cfg --> plugin_dsh_base_skill plugin_dsh_base_skill_local["skill-local
@deepseek-ai/dsh-skill-local"] cfg --> plugin_dsh_base_skill_local + plugin_dsh_base_skill_badge["skill-badge
@deepseek-ai/dsh-skill-badge"] + cfg --> plugin_dsh_base_skill_badge plugin_dsh_base_tool_skill["tool-skill
@deepseek-ai/dsh-tool-skill"] cfg --> plugin_dsh_base_tool_skill plugin_dsh_base_commands["commands
@deepseek-ai/dsh-commands"] @@ -182,6 +184,7 @@ flowchart LR | `workspace-context` | `@deepseek-ai/dsh-workspace-context` | | `skill` | `@deepseek-ai/dsh-skill` | | `skill-local` | `@deepseek-ai/dsh-skill-local` | +| `skill-badge` | `@deepseek-ai/dsh-skill-badge` | | `tool-skill` | `@deepseek-ai/dsh-tool-skill` | | `commands` | `@deepseek-ai/dsh-commands` | | `goal` | `@deepseek-ai/dsh-goal` | diff --git a/apps/cli/config/base.cordis.yml b/apps/cli/config/base.cordis.yml index dddf2fc1b5..f5f39a6ae5 100644 --- a/apps/cli/config/base.cordis.yml +++ b/apps/cli/config/base.cordis.yml @@ -204,6 +204,10 @@ - id: skill-local name: '@deepseek-ai/dsh-skill-local' +- id: skill-badge + name: '@deepseek-ai/dsh-skill-badge' + disabled: true + - id: tool-skill name: '@deepseek-ai/dsh-tool-skill' diff --git a/apps/cli/package.json b/apps/cli/package.json index 4ba1da7e86..dfe35e6159 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -92,6 +92,7 @@ "@deepseek-ai/dsh-session-title-first-message-llm": "workspace:^", "@deepseek-ai/dsh-settings-local": "workspace:^", "@deepseek-ai/dsh-skill": "workspace:^", + "@deepseek-ai/dsh-skill-badge": "workspace:^", "@deepseek-ai/dsh-skill-local": "workspace:^", "@deepseek-ai/dsh-spill-local": "workspace:^", "@deepseek-ai/dsh-spill-policy": "workspace:^", diff --git a/apps/cli/tests/dsh-badge.snapshot.ts b/apps/cli/tests/dsh-badge.snapshot.ts new file mode 100644 index 0000000000..d78f4c743d --- /dev/null +++ b/apps/cli/tests/dsh-badge.snapshot.ts @@ -0,0 +1,173 @@ +import { fileURLToPath } from 'node:url' +import { describe, expect, it } from 'vitest' +import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke' +const binScript = fileURLToPath(new URL('./fixtures/dsh-badge/snapshot.ts', import.meta.url)) +const configPath = fileURLToPath(new URL('./fixtures/dsh-badge/cordis.yml', import.meta.url)) +const defaultConfigPath = fileURLToPath(new URL('./fixtures/dsh-badge/default.cordis.yml', import.meta.url)) +const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url)) +const badgeAssetsPath = fileURLToPath(new URL('../../../packages/skill/skill-badge/assets/', import.meta.url)) + +describe('dsh badge assembled snapshot', () => { + it('advertises and loads the opt-in bundled skill through the shipped app', async () => { + const disabled = await runLoaderSmoke({ + label: 'disabled dsh badge skill snapshot', + tempDirPrefix: 'headless-snapshot-dsh-badge-disabled-', + binScript, + libBinScript: binScript, + configPath: defaultConfigPath, + tsconfigPath, + }) + const enabled = await runLoaderSmoke({ + label: 'dsh badge skill snapshot', + tempDirPrefix: 'headless-snapshot-dsh-badge-', + binScript, + libBinScript: binScript, + configPath, + tsconfigPath, + }) + const disabledSnapshot = JSON.parse(disabled.stdout) as unknown + const enabledSnapshot = JSON.parse( + enabled.stdout.replaceAll(badgeAssetsPath, '{{badgeAssetsPath}}'), + ) as unknown + + expect(disabled.stderr).toBe('') + expect(enabled.stderr).toBe('') + expect(disabledSnapshot).toMatchInlineSnapshot(` + { + "result": { + "content": [ + { + "text": "Error: skill "dsh-badge" is unknown or no longer available", + "type": "text", + }, + ], + "error": { + "message": "skill "dsh-badge" is unknown or no longer available", + }, + "isError": true, + }, + } + `) + expect(enabledSnapshot).toMatchInlineSnapshot(` + { + "catalog": [ + { + "text": " + A skill is a reusable set of task-specific instructions. The following skills are available in this session: + + + - \`dsh-badge\`: Add the official “powered by dsh” badge to documents, pull requests, merge requests, and other content produced with DeepSeek Harness. Use whenever creating a pull request or merge request. Also use when the user asks for a dsh badge, powered-by-dsh attribution, or a reusable dsh badge asset or snippet. + + + If the user names a skill, or the task clearly matches a skill's description, call the \`skill\` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded. + ", + "type": "text", + }, + ], + "result": { + "content": [ + { + "text": " + + Base directory for this skill: {{badgeAssetsPath}} + Resolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed. + + + + # dsh Badge + + Add the official “powered by dsh” badge without recreating or restyling it. + + ## Assets + + - Local PNG: [\`dsh-badge.png\`](dsh-badge.png), 726×120 source image; render at 121×20 + - Shields.io image URL: \`https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white\` + - Project URL: \`https://github.com/deepseek-harness/deepseek-harness\` + + ## Markdown + + Use this linked badge in Markdown: + + \`\`\`markdown + [![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-harness/deepseek-harness) + \`\`\` + + If attribution should not be linked, use: + + \`\`\`markdown + ![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white) + \`\`\` + + ## Usage rules + + - For GitHub or GitLab Markdown, use the Shields.io URL and link it to the project URL unless the user asks for an unlinked image. + - For Feishu and other systems that import remote images unreliably, upload \`dsh-badge.png\` from this skill directory instead of generating another badge. + - Preserve the badge's 121×20 dimensions and aspect ratio. + - Place the badge at the end of the attributed document or section unless the user specifies another position. + - Do not substitute another color, logo, label, or project URL. + + + ", + "type": "text", + }, + ], + "isError": false, + "value": { + "content": "# dsh Badge + + Add the official “powered by dsh” badge without recreating or restyling it. + + ## Assets + + - Local PNG: [\`dsh-badge.png\`](dsh-badge.png), 726×120 source image; render at 121×20 + - Shields.io image URL: \`https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white\` + - Project URL: \`https://github.com/deepseek-harness/deepseek-harness\` + + ## Markdown + + Use this linked badge in Markdown: + + \`\`\`markdown + [![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-harness/deepseek-harness) + \`\`\` + + If attribution should not be linked, use: + + \`\`\`markdown + ![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white) + \`\`\` + + ## Usage rules + + - For GitHub or GitLab Markdown, use the Shields.io URL and link it to the project URL unless the user asks for an unlinked image. + - For Feishu and other systems that import remote images unreliably, upload \`dsh-badge.png\` from this skill directory instead of generating another badge. + - Preserve the badge's 121×20 dimensions and aspect ratio. + - Place the badge at the end of the attributed document or section unless the user specifies another position. + - Do not substitute another color, logo, label, or project URL. + ", + "name": "dsh-badge", + "provider": "dsh-badge", + "resourceBase": { + "kind": "directory", + "path": "{{badgeAssetsPath}}", + }, + }, + }, + "summary": { + "description": "Add the official “powered by dsh” badge to documents, pull requests, merge requests, and other content produced with DeepSeek Harness. Use whenever creating a pull request or merge request. Also use when the user asks for a dsh badge, powered-by-dsh attribution, or a reusable dsh badge asset or snippet.", + "invocation": { + "modelInvocable": true, + "userInvocable": true, + }, + "name": "dsh-badge", + "provider": "dsh-badge", + "resourceBase": { + "kind": "directory", + "path": "{{badgeAssetsPath}}", + }, + "source": "bundled", + }, + } + `) + }, LOADER_SMOKE_TEST_TIMEOUT_MS) +}) diff --git a/apps/cli/tests/fixtures/dsh-badge/cordis.yml b/apps/cli/tests/fixtures/dsh-badge/cordis.yml new file mode 100644 index 0000000000..b3bfdb1b04 --- /dev/null +++ b/apps/cli/tests/fixtures/dsh-badge/cordis.yml @@ -0,0 +1,9 @@ +- id: skill-badge + disabled: false + +- id: skill-local + config: + watch: false + +- id: telemetry-otel + disabled: true diff --git a/apps/cli/tests/fixtures/dsh-badge/default.cordis.yml b/apps/cli/tests/fixtures/dsh-badge/default.cordis.yml new file mode 100644 index 0000000000..ac3e48441a --- /dev/null +++ b/apps/cli/tests/fixtures/dsh-badge/default.cordis.yml @@ -0,0 +1,6 @@ +- id: skill-local + config: + watch: false + +- id: telemetry-otel + disabled: true diff --git a/apps/cli/tests/fixtures/dsh-badge/snapshot.ts b/apps/cli/tests/fixtures/dsh-badge/snapshot.ts new file mode 100644 index 0000000000..99379b4fe4 --- /dev/null +++ b/apps/cli/tests/fixtures/dsh-badge/snapshot.ts @@ -0,0 +1,53 @@ +import { fileURLToPath } from 'node:url' +import { Context } from 'cordis' +import { agentEvents, Inbox, type Agent } from '@deepseek-ai/dsh-agent' +import { CallId } from '@deepseek-ai/dsh-llm' +import { boot, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot' +import { SessionId } from '@deepseek-ai/dsh-session' +import type {} from '@deepseek-ai/dsh-skill' +import type {} from '@deepseek-ai/dsh-tools' + +const overlayPath = process.argv[2] +if (overlayPath === undefined) throw new Error('dsh-badge snapshot requires an overlay path') +const baseConfigPath = fileURLToPath(new URL('../../../config/base.cordis.yml', import.meta.url)) +const ctx = await boot('dsh-badge-snapshot', baseConfigPath, loadOverlayPatches('dsh-badge-snapshot', overlayPath)) + +try { + const agentId = SessionId('dsh-badge-snapshot') + const session = ctx.sessions.create(agentId, { meta: { cwd: process.cwd() } }) + const agent: Agent = { + ctx: new Context(), + id: agentId, + options: {}, + session, + inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }), + status: 'idle', + send: () => {}, + followup: () => {}, + steer: () => {}, + inject: () => { throw new Error('dsh-badge snapshot must receive the catalog at the step boundary') }, + cancel: () => {}, + runMaintenance: task => task(new AbortController().signal), + whenIdle: () => Promise.resolve(), + } + const decision = await agentEvents(ctx, agent).waterfall( + 'agent/pre-step', + [], + { turn: 1, step: 1, signal: new AbortController().signal }, + () => Promise.resolve({ kind: 'enter' as const, messages: [] }), + ) + const catalog = decision.kind === 'enter' + ? decision.messages.find(message => message.role === 'user' + && message.source.kind === 'skill-catalog')?.content + : undefined + const summary = (await ctx.skills.list()).find(skill => skill.name === 'dsh-badge') + const result = await ctx.tools.execute({ + callId: CallId('dsh-badge-snapshot'), + name: 'skill', + arguments: { name: 'dsh-badge' }, + signal: new AbortController().signal, + }) + process.stdout.write(`${JSON.stringify({ catalog, summary, result })}\n`) +} finally { + await ctx.fiber.dispose() +} diff --git a/docs/capability-seams.md b/docs/capability-seams.md index cb24cee7d5..a5f20b5349 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -89,6 +89,7 @@ flowchart LR svc_sessionProjectionCache["ctx.sessionProjectionCache
Persisted projection cache"] pkg_skill["skill"] svc_skills["ctx.skills
Skill provider registry"] + pkg_skill_badge["skill-badge"] pkg_skill_local["skill-local"] svc_agents["ctx.agents
Agent service"] pkg_acp["acp"] @@ -219,6 +220,7 @@ flowchart LR pkg_settings --> svc_settings pkg_settings_local --> svc_settings pkg_skill --> svc_skills + pkg_skill_badge --> svc_skills pkg_skill_local --> svc_skills pkg_spill --> svc_spillStore pkg_spill_local --> svc_spillStore @@ -373,7 +375,7 @@ flowchart LR | `ctx.commands` | `core` | [`commands`](../packages/ui/commands) | - | - | - | Plugins register direct human commands without sending invocations to the model. | | `ctx.sessionProjections` | `core` | [`session-projection`](../packages/session-projection/session-projection) | - | [`tool-todo`](../packages/todo/tool-todo), [`session-title`](../packages/session-title/session-title), [`host-apiproxy`](../packages/host/apiproxy) | - | Domains register state-driven fold units; the eager drive keeps per-session watermark states and api-proxy serves baselines and pushes changed values. | | `ctx.sessionProjectionCache` | `core` | [`session-projection-cache`](../packages/session-projection/session-projection-cache) | - | [`host-apiproxy`](../packages/host/apiproxy) | - | Durably checkpoints projection unit states per session (throttled + turn/end/detach mandatory points) and serves the cold-read ladder: cache row + persistence tail replay, so listings never load full logs. | -| `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`skill-local`](../packages/skill/skill-local) | [`tool-skill`](../packages/skill/tool-skill) | - | Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies. | +| `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`skill-badge`](../packages/skill/skill-badge), [`skill-local`](../packages/skill/skill-local) | [`tool-skill`](../packages/skill/tool-skill) | - | Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies. | | `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), [`cli-demo`](../packages/examples/cli-demo), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. | | `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. | | `ctx.goals` | `core` | [`goal`](../packages/goal/goal) | - | - | - | Folds revisioned objective state from the session log and keeps live continuation activation process-local. | diff --git a/docs/config-catalog.md b/docs/config-catalog.md index ffa3d5bdd4..449addad8a 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -2362,6 +2362,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co - `@deepseek-ai/dsh-session` ([`packages/core/session/src/index.ts`](../packages/core/session/src/index.ts)) - `@deepseek-ai/dsh-session-checkpoint-policy` — requires `llm` · `sessionPersistence` · `sessions` · `tools` ([`packages/session-persistence/session-checkpoint-policy/src/index.ts`](../packages/session-persistence/session-checkpoint-policy/src/index.ts)) - `@deepseek-ai/dsh-session-projection` ([`packages/session-projection/session-projection/src/index.ts`](../packages/session-projection/session-projection/src/index.ts)) +- `@deepseek-ai/dsh-skill-badge` — requires `skills` ([`packages/skill/skill-badge/src/index.ts`](../packages/skill/skill-badge/src/index.ts)) - `@deepseek-ai/dsh-storage` ([`packages/storage/storage/src/index.ts`](../packages/storage/storage/src/index.ts)) - `@deepseek-ai/dsh-subagent` ([`packages/subagent/subagent/src/index.ts`](../packages/subagent/subagent/src/index.ts)) - `@deepseek-ai/dsh-subprocess-local` ([`packages/subprocess/subprocess-local/src/index.ts`](../packages/subprocess/subprocess-local/src/index.ts)) diff --git a/docs/module-graph.md b/docs/module-graph.md index 0e2e7e0c37..7f3f68603f 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -56,6 +56,7 @@ flowchart TD end subgraph group_skill["packages/skill"] pkg_skill["skill"] + pkg_skill_badge["skill-badge"] pkg_skill_local["skill-local"] pkg_tool_skill["tool-skill"] end @@ -303,6 +304,8 @@ flowchart TD pkg_llm --> pkg_brand pkg_llm --> pkg_invariants pkg_llm --> pkg_timeout + pkg_skill_badge --> pkg_invariants + pkg_skill_badge --> pkg_skill pkg_client_connection --> pkg_host_webserver pkg_client_connection --> pkg_invariants pkg_client_hmr --> pkg_client_modules @@ -1106,6 +1109,7 @@ flowchart TD | [`typert-generator`](../packages/typert/generator) | `typert` | [`invariants`](../packages/support/invariants) | | [`typert-registry`](../packages/typert/registry) | `typert` | [`invariants`](../packages/support/invariants) | | [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`timeout`](../packages/util/timeout) | +| [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`invariants`](../packages/support/invariants), [`skill`](../packages/skill/skill) | | [`client-connection`](../packages/client/connection) | `client` | [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) | | [`client-hmr`](../packages/client/hmr) | `client` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) | | [`client-locale`](../packages/client/locale) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | diff --git a/knip.json b/knip.json index dfb8058d7c..8d10010644 100644 --- a/knip.json +++ b/knip.json @@ -622,7 +622,8 @@ "apps/cli": { "entry": [ "tests/**/*.spec.ts", - "tests/**/*.e2e.ts" + "tests/**/*.e2e.ts", + "tests/**/*.snapshot.ts" ], "project": [ "src/**/*.ts", diff --git a/packages/skill/README.i18n.yaml b/packages/skill/README.i18n.yaml index 2d424c61dd..74875f2aa3 100644 --- a/packages/skill/README.i18n.yaml +++ b/packages/skill/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/skill/README.md -README.md: d10049ac3e741350fddb42f430b70f063da1d12d -README.zh.md: 67f2da6f75edecd180ff861122c6392684ed9bdb +README.md: 533904859ad998de4f371a073fde98b68660097b +README.zh.md: 1fad581cc61a05251f671577dcb7edab37281283 diff --git a/packages/skill/README.md b/packages/skill/README.md index d10049ac3e..533904859a 100644 --- a/packages/skill/README.md +++ b/packages/skill/README.md @@ -7,6 +7,7 @@ This family discovers reusable agent instructions and exposes them to the model | Package | Role | ctx key | |---|---|---| | [`skill/`](skill/README.md) | Defines skill provider registration and lookup | `ctx.skills` | +| [`skill-badge/`](skill-badge/README.md) | Contributes the optional bundled dsh badge skill | registers on `ctx.skills` | | [`skill-local/`](skill-local/README.md) | Discovers skills from local filesystems | registers on `ctx.skills` | | [`tool-skill/`](tool-skill/README.md) | Publishes the skill catalog and model-facing loader | registers on `ctx.tools` | diff --git a/packages/skill/README.zh.md b/packages/skill/README.zh.md index 67f2da6f75..1fad581cc6 100644 --- a/packages/skill/README.zh.md +++ b/packages/skill/README.zh.md @@ -7,6 +7,7 @@ | 包 | 职责 | ctx 键 | |---|---|---| | [`skill/`](skill/README.md) | 定义 skill 提供方注册和查找 | `ctx.skills` | +| [`skill-badge/`](skill-badge/README.md) | 贡献可选的内置 dsh 徽章 skill | 注册到 `ctx.skills` | | [`skill-local/`](skill-local/README.md) | 从本地文件系统发现 skill | 注册到 `ctx.skills` | | [`tool-skill/`](tool-skill/README.md) | 发布 skill 目录和面向模型的 loader | 注册到 `ctx.tools` | diff --git a/packages/skill/skill-badge/README.i18n.yaml b/packages/skill/skill-badge/README.i18n.yaml new file mode 100644 index 0000000000..4dda53481c --- /dev/null +++ b/packages/skill/skill-badge/README.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write packages/skill/skill-badge/README.md +README.md: 49b38023a7c110bb52bb351668905c702e251216 +README.zh.md: bf7eb0d7d4c0552c07f9cdf5665f8a20df829483 diff --git a/packages/skill/skill-badge/README.md b/packages/skill/skill-badge/README.md new file mode 100644 index 0000000000..49b38023a7 --- /dev/null +++ b/packages/skill/skill-badge/README.md @@ -0,0 +1,22 @@ +# @deepseek-ai/dsh-skill-badge + +English | [中文](README.zh.md) + +Optional bundled skill provider that contributes `dsh-badge` to `ctx.skills`. The skill supplies the official “powered by dsh” Markdown snippets and the packaged PNG for systems that cannot import a remote image reliably. + +Mount the plugin to enable the provider. It has no configuration. The shipped CLI composition includes the plugin as `disabled: true`; users must explicitly enable its `skill-badge` row before the skill enters a catalog. + +The provider exposes its packaged `assets/` directory as the skill resource base. `dsh-badge.png` is the 726×120 source asset, and consumers render it at 121×20. + +## Model Experience + +Indirectly, through `@deepseek-ai/dsh-tool-skill`, which renders the catalog entry and selected skill body. + +#### KV Cache effect + +Disabled by default, the plugin changes no request. When enabled, its catalog entry and any loaded body change the provider KV prefix at their insertion points. + +## Known Limitations and Deferred Work + +- The provider contributes one fixed skill and has no runtime customization. +- Remote Markdown uses Shields.io; use the packaged PNG when the target cannot fetch remote images reliably. diff --git a/packages/skill/skill-badge/README.zh.md b/packages/skill/skill-badge/README.zh.md new file mode 100644 index 0000000000..bf7eb0d7d4 --- /dev/null +++ b/packages/skill/skill-badge/README.zh.md @@ -0,0 +1,22 @@ +# @deepseek-ai/dsh-skill-badge + +[English](README.md) | 中文 + +可选的内置 skill(技能)提供方,向 `ctx.skills` 贡献 `dsh-badge`。该 skill 提供官方「powered by dsh」Markdown 片段和随包分发的 PNG,供无法可靠导入远程图片的系统使用。 + +挂载该插件即可启用提供方。它没有配置。交付的 CLI(命令行界面)组合以 `disabled: true` 包含该插件;用户必须显式启用其 `skill-badge` 配置行,该 skill 才会进入目录。 + +该提供方将随包分发的 `assets/` 目录作为 skill 资源基底公开。`dsh-badge.png` 是尺寸为 726×120 的源图资源,消费方以 121×20 的尺寸渲染。 + +## 模型体验 + +通过 `@deepseek-ai/dsh-tool-skill` 间接影响模型;该包会渲染目录条目和所选 skill 的正文。 + +#### KV Cache 影响 + +该插件默认禁用,不会改变任何请求。启用后,其目录条目和任何已加载正文都会在各自插入点改变提供方的 KV 前缀。 + +## 已知限制与暂缓事项 + +- 该提供方只贡献一个固定 skill,不提供运行时自定义。 +- 远程 Markdown 使用 Shields.io;当目标环境无法可靠获取远程图片时,请使用随包分发的 PNG。 diff --git a/packages/skill/skill-badge/assets/dsh-badge.md b/packages/skill/skill-badge/assets/dsh-badge.md new file mode 100644 index 0000000000..9905de1ed9 --- /dev/null +++ b/packages/skill/skill-badge/assets/dsh-badge.md @@ -0,0 +1,31 @@ +# dsh Badge + +Add the official “powered by dsh” badge without recreating or restyling it. + +## Assets + +- Local PNG: [`dsh-badge.png`](dsh-badge.png), 726×120 source image; render at 121×20 +- Shields.io image URL: `https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white` +- Project URL: `https://github.com/deepseek-harness/deepseek-harness` + +## Markdown + +Use this linked badge in Markdown: + +```markdown +[![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-harness/deepseek-harness) +``` + +If attribution should not be linked, use: + +```markdown +![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white) +``` + +## Usage rules + +- For GitHub or GitLab Markdown, use the Shields.io URL and link it to the project URL unless the user asks for an unlinked image. +- For Feishu and other systems that import remote images unreliably, upload `dsh-badge.png` from this skill directory instead of generating another badge. +- Preserve the badge's 121×20 dimensions and aspect ratio. +- Place the badge at the end of the attributed document or section unless the user specifies another position. +- Do not substitute another color, logo, label, or project URL. diff --git a/packages/skill/skill-badge/assets/dsh-badge.png b/packages/skill/skill-badge/assets/dsh-badge.png new file mode 100644 index 0000000000000000000000000000000000000000..bf91ecca9790971072e946b56c01bbf99e26abd4 GIT binary patch literal 12339 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 Q1kOOnN`8?j7dH(0e_HocYybcN literal 0 HcmV?d00001 diff --git a/packages/skill/skill-badge/package.json b/packages/skill/skill-badge/package.json new file mode 100644 index 0000000000..b9dc53d9a5 --- /dev/null +++ b/packages/skill/skill-badge/package.json @@ -0,0 +1,37 @@ +{ + "name": "@deepseek-ai/dsh-skill-badge", + "description": "Bundled dsh badge skill provider for DeepSeek Harness", + "version": "0.0.1", + "private": true, + "type": "module", + "main": "lib/index.js", + "types": "lib/types/index.d.ts", + "exports": { + ".": { + "types": "./lib/types/index.d.ts", + "default": "./lib/index.js" + }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "lib/index.js", + "lib/invariant.js", + "assets", + "lib/types/**/*.d.ts" + ], + "license": "BSD-3-Clause", + "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", + "@deepseek-ai/dsh-skill": "^0.0.1", + "cordis": "^4.0.0-rc.7" + }, + "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", + "@deepseek-ai/dsh-skill": "workspace:^", + "cordis": "^4.0.0-rc.7" + } +} diff --git a/packages/skill/skill-badge/src/index.ts b/packages/skill/skill-badge/src/index.ts new file mode 100644 index 0000000000..27cfd29354 --- /dev/null +++ b/packages/skill/skill-badge/src/index.ts @@ -0,0 +1,60 @@ +/** + * Bundled `dsh-badge` skill provider. + * + * @module @deepseek-ai/dsh-skill-badge + */ + +import { readFile } from 'node:fs/promises' +import { fileURLToPath } from 'node:url' +import type { Context } from 'cordis' +import type { + SkillCandidate, + SkillDefinition, + SkillProvider, +} from '@deepseek-ai/dsh-skill' + +const PROVIDER_NAME = 'dsh-badge' +const BUNDLED_RANK = 600 +const SKILL_BODY_URL = new URL('../assets/dsh-badge.md', import.meta.url) +const RESOURCE_BASE = { + kind: 'directory', + path: fileURLToPath(new URL('../assets/', import.meta.url)), +} as const +const INVOCATION = { modelInvocable: true, userInvocable: true } as const +const DESCRIPTION = 'Add the official “powered by dsh” badge to documents, pull requests, merge requests, and other content produced with DeepSeek Harness. Use whenever creating a pull request or merge request. Also use when the user asks for a dsh badge, powered-by-dsh attribution, or a reusable dsh badge asset or snippet.' +const CANDIDATE: SkillCandidate = { + name: 'dsh-badge', + description: DESCRIPTION, + invocation: INVOCATION, + provider: PROVIDER_NAME, + source: 'bundled', + resourceBase: RESOURCE_BASE, + rank: BUNDLED_RANK, + locator: SKILL_BODY_URL, +} + +const provider: SkillProvider = { + name: PROVIDER_NAME, + list: () => Promise.resolve([CANDIDATE]), + async get(_candidate): Promise { + return { + name: CANDIDATE.name, + description: CANDIDATE.description, + invocation: CANDIDATE.invocation, + provider: CANDIDATE.provider, + source: CANDIDATE.source, + resourceBase: RESOURCE_BASE, + content: await readFile(SKILL_BODY_URL, 'utf8'), + } + }, +} + +/** Cordis plugin name. */ +export const name = 'skill-badge' +/** Service required by the bundled provider. */ +export const inject = ['skills'] + +/** Register the bundled `dsh-badge` provider on `ctx.skills`. */ +export function apply(ctx: Context): void { + ctx.skills.registerProvider(() => provider) +} diff --git a/packages/skill/skill-badge/src/invariant.ts b/packages/skill/skill-badge/src/invariant.ts new file mode 100644 index 0000000000..c087d5917f --- /dev/null +++ b/packages/skill/skill-badge/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Package-owned invariant companion for `@deepseek-ai/dsh-skill-badge`. + * @module @deepseek-ai/dsh-skill-badge/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-skill-badge' + +/** Cordis companion plugin name. */ +export const name = 'skill-badge-invariant' +/** Service required before the companion can reserve package ownership. */ +export const inject = ['invariants'] + +/** + * No runtime invariant: the package owns one immutable provider registration, + * while the skill registry owns registration uniqueness and lifecycle checks. + */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/skill/skill-badge/tests/skill-badge.spec.ts b/packages/skill/skill-badge/tests/skill-badge.spec.ts new file mode 100644 index 0000000000..e4d62f1c89 --- /dev/null +++ b/packages/skill/skill-badge/tests/skill-badge.spec.ts @@ -0,0 +1,40 @@ +import { createHash } from 'node:crypto' +import { readFile } from 'node:fs/promises' +import { fileURLToPath } from 'node:url' +import { Context } from 'cordis' +import { describe, expect, it } from 'vitest' +import SkillService from '@deepseek-ai/dsh-skill' +import * as SkillBadge from '@deepseek-ai/dsh-skill-badge' + +describe('dsh-skill-badge', () => { + it('registers and disposes the bundled badge skill', async () => { + const ctx = new Context() + await ctx.plugin(SkillService) + const fiber = await ctx.plugin(SkillBadge) + const resourcePath = fileURLToPath(new URL('../assets/', import.meta.url)) + + expect(await ctx.skills.list()).toEqual([{ + name: 'dsh-badge', + description: 'Add the official “powered by dsh” badge to documents, pull requests, merge requests, and other content produced with DeepSeek Harness. Use whenever creating a pull request or merge request. Also use when the user asks for a dsh badge, powered-by-dsh attribution, or a reusable dsh badge asset or snippet.', + invocation: { modelInvocable: true, userInvocable: true }, + provider: 'dsh-badge', + source: 'bundled', + resourceBase: { kind: 'directory', path: resourcePath }, + }]) + const loaded = await ctx.skills.get('dsh-badge') + expect(loaded?.content).toContain('Preserve the badge\'s 121×20 dimensions') + expect(loaded?.resourceBase).toEqual({ kind: 'directory', path: resourcePath }) + + await fiber.dispose() + expect(await ctx.skills.list()).toEqual([]) + }) + + it('ships the official 726×120 PNG unchanged', async () => { + const image = await readFile(new URL('../assets/dsh-badge.png', import.meta.url)) + expect(image.readUInt32BE(16)).toBe(726) + expect(image.readUInt32BE(20)).toBe(120) + expect(createHash('sha256').update(image).digest('hex')).toBe( + 'f2c4f5ec9cbe847c0c763545c4d839efa8485bc74203733d0a0e8259f233c653', + ) + }) +}) diff --git a/packages/skill/skill-badge/tsconfig.json b/packages/skill/skill-badge/tsconfig.json new file mode 100644 index 0000000000..cf6642f69e --- /dev/null +++ b/packages/skill/skill-badge/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "include": ["src"], + "references": [ + { "path": "../../../vendor/cosmokit" }, + { "path": "../../../vendor/cordis" }, + { "path": "../skill" }, + { "path": "../../support/invariants" } + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 208e43aa5f..ad6bd2250b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -365,6 +365,9 @@ importers: '@deepseek-ai/dsh-skill': specifier: workspace:^ version: link:../../packages/skill/skill + '@deepseek-ai/dsh-skill-badge': + specifier: workspace:^ + version: link:../../packages/skill/skill-badge '@deepseek-ai/dsh-skill-local': specifier: workspace:^ version: link:../../packages/skill/skill-local @@ -4846,6 +4849,18 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis + packages/skill/skill-badge: + devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-skill': + specifier: workspace:^ + version: link:../skill + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + packages/skill/skill-local: dependencies: chokidar: diff --git a/scripts/check-workspace-constraints.ts b/scripts/check-workspace-constraints.ts index 6bd613c2ea..750abbc902 100644 --- a/scripts/check-workspace-constraints.ts +++ b/scripts/check-workspace-constraints.ts @@ -105,6 +105,7 @@ const packageFileExtras: Readonly> = { '@deepseek-ai/dsh-client-ui-theme': ['lib/styles'], '@deepseek-ai/dsh-helper': ['lib/assets'], '@deepseek-ai/dsh-pty-local': ['scripts/ensure-spawn-helper.mjs'], + '@deepseek-ai/dsh-skill-badge': ['assets'], '@deepseek-ai/dsh-scripts': [ 'lib/dev/tsdown-config.js', 'lib/local-plugin-loader-hooks.js', diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index 1dd7973fd5..98ef82bb8e 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -287,7 +287,7 @@ const SERVICE_ROLES: ServiceRole[] = [ pkg: 'skill', title: 'Skill provider registry', mode: 'seam', - implementations: ['skill-local'], + implementations: ['skill-badge', 'skill-local'], consumers: ['tool-skill'], note: 'Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies.', }, diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 041972cb9f..e66e9a73d7 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -111,6 +111,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/telemetry/session-telemetry': { kind: 'none', reason: 'The seam observes the session stream and hands redacted copies outward; it registers no model surface.' }, 'packages/telemetry/session-telemetry-otel': { kind: 'none', reason: 'The backend forwards seam records into the OTel SDK pipeline and registers no model surface.' }, 'packages/skill/skill': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-skill.' }, + 'packages/skill/skill-badge': { kind: 'indirect', reason: 'The bundled provider delegates model rendering to dsh-tool-skill.' }, 'packages/skill/skill-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-skill.' }, 'packages/spill/spill': { kind: 'indirect', reason: 'The storage seam delegates model rendering to spill consumers.' }, 'packages/spill/spill-local': { kind: 'indirect', reason: 'The storage backend delegates model rendering to spill consumers.' }, diff --git a/tsconfig.host.json b/tsconfig.host.json index 4fcf71b680..8c11b48a46 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -135,6 +135,7 @@ { "path": "./packages/ui/permission" }, { "path": "./packages/core/tools" }, { "path": "./packages/skill/skill" }, + { "path": "./packages/skill/skill-badge" }, { "path": "./packages/skill/skill-local" }, { "path": "./packages/skill/tool-skill" }, { "path": "./packages/ui/tool-ask-user" }, diff --git a/vitest.snapshot.config.ts b/vitest.snapshot.config.ts index 455ecfb4d4..426ebedd8e 100644 --- a/vitest.snapshot.config.ts +++ b/vitest.snapshot.config.ts @@ -49,6 +49,7 @@ export default defineConfig({ // The assembled Web snapshot executes generated client bundles; source // mode remains the zero-build path, while lib mode requires a prior build. ...(process.env.DSH_EXAMPLE_MODE === 'lib' ? ['apps/web/tests/**/*.snapshot.ts'] : []), + 'apps/cli/tests/**/*.snapshot.ts', 'examples/*/tests/**/*.snapshot.ts', 'packages/sdk/*/tests/**/*.snapshot.ts', ], From 4dede454ed6ca776e7efa9ff5e57491348be5d26 Mon Sep 17 00:00:00 2001 From: Turtle Date: Thu, 6 Aug 2026 18:54:53 +0800 Subject: [PATCH 2/4] fix: address dsh badge review feedback --- .../feature/2026-07-05-skill-system.i18n.yaml | 4 ++-- .../implemented/feature/2026-07-05-skill-system.md | 6 ++++-- .../feature/2026-07-05-skill-system.zh.md | 6 ++++-- .../2026-08-06-bundled-dsh-badge-skill.i18n.yaml | 4 ++-- .../feature/2026-08-06-bundled-dsh-badge-skill.md | 4 ++-- .../feature/2026-08-06-bundled-dsh-badge-skill.zh.md | 6 +++--- apps/cli/tests/dsh-badge.snapshot.ts | 4 +++- apps/cli/tests/fixtures/dsh-badge/snapshot.ts | 2 +- docs/config-catalog.md | 2 +- docs/cordis-catalog/events.md | 2 +- docs/cordis-catalog/services.md | 2 +- docs/core-data-structures/skills.i18n.yaml | 4 ++-- docs/core-data-structures/skills.md | 8 +++++--- docs/core-data-structures/skills.zh.md | 8 +++++--- docs/event-producer-consumer.md | 2 +- packages/skill/skill-badge/src/index.ts | 12 ++++++------ packages/skill/skill-local/src/index.ts | 4 ++-- packages/skill/skill/src/index.ts | 3 +++ 18 files changed, 48 insertions(+), 35 deletions(-) diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml b/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml index 6bb4aac193..a98beff699 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-05-skill-system.md -2026-07-05-skill-system.md: dd2fb1d22949f55ea7cb2c9f280e7cfbfcbbb226 -2026-07-05-skill-system.zh.md: 96656a8e1dfc2ae1ce7301ba29e7739349b6aab6 +2026-07-05-skill-system.md: a998d70ec934aed4bf7ce32aa711abd47b508a1d +2026-07-05-skill-system.zh.md: 4fa7c4fd657c2f41f16b30679ec95e61a75f8a0c diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.md b/.agents/notes/implemented/feature/2026-07-05-skill-system.md index dd2fb1d229..a998d70ec9 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.md +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.md @@ -14,9 +14,11 @@ DeepSeek Harness uses the same primitive so project-specific review, plugin-auth `@deepseek-ai/dsh-skill` is the pure provider registry (`ctx.skills`), `@deepseek-ai/dsh-skill-local` is the shipped local filesystem provider, and `@deepseek-ai/dsh-tool-skill` owns the durable session catalog and model-facing loader tool. `dsh-agent-spine-demo` loads the registry, local provider, and consumer by default so TUI, headless, and ACP apps get the same behavior while embedded or remote providers contribute skills without changing the registry or consumer. Its `skills` config forwards `registry`, `local`, and `tool` branches to those owners. +Dedicated packaged providers can contribute immutable skills without filesystem discovery. The shipped CLI declares `@deepseek-ai/dsh-skill-badge` disabled by default; enabling its composition row contributes the official badge instructions through the same registry and consumer ([decision](2026-08-06-bundled-dsh-badge-skill.md)). + Provider plugins register synchronously during `apply()`. Provider membership is direct effect-owned state: registration and disposal invalidate completed catalogs synchronously, and discovery reads the current provider map on demand rather than observing registry-change events. Provider catalogs return ranked candidates from awaited `list()` calls, where remote providers perform initialization, authentication, and discovery while honoring the lookup abort signal. The registry validates each candidate, resolves same-name skills first-wins by rank, provider registration order, and provider-local order, then sorts summaries by skill name for deterministic consumers. It caches only completed catalog snapshots and retries when a provider/runtime revision changes during discovery, so an unload cannot freeze a stale, unresolvable skill into a session catalog. Runtime `ctx.skills.register(...)` remains a convenience for embedded in-process skills and uses project-over-user priority; `runtime` is reserved as the registry-owned provider name. -The local provider scans cwd-sensitive project roots, custom roots, and user roots in first-wins rank order: project `.dsh`, project `.agents`, `customSkillDirs`, user `.dsh`, then user `.agents`. The user `.dsh/skills` scan skips `.system` so a system-owned directory is not treated as normal user content. DeepSeek Harness does not ship built-in system skills; embedded or remote providers supply additional skills when configured. +The local provider scans cwd-sensitive project roots, custom roots, and user roots in first-wins rank order: project `.dsh`, project `.agents`, `customSkillDirs`, user `.dsh`, then user `.agents`. The user `.dsh/skills` scan skips `.system` so a system-owned directory is not treated as normal user content. The local provider does not synthesize built-in system skills; configured bundled roots and dedicated providers supply additional skills. Each skill is either `/SKILL.md` or `.md` with YAML frontmatter. `name` and `description` are required; `whenToUse`, `metadata`, `disable-model-invocation`, and `user-invocable` are optional. Names are kebab-case. The invocation fields project into a typed nested policy as defined by the [independent model and user invocation decision](2026-07-28-skill-invocation-policy.md); the parser rejects the old camel-case spellings. YAML frontmatter is parsed with the `yaml` package instead of `js-yaml` or a hand-written parser: `yaml` is the already-declared modern parser for this package's limited frontmatter needs, and a narrow parser would either reject valid YAML users expect to work or grow into an unreviewed YAML subset. @@ -48,7 +50,7 @@ The data structures and catalog/tool contract are documented in [skills.md](../. The agent-core spine includes one catalog contributor, one local provider, and one model-facing tool. Skill discovery is cwd-sensitive, so callers that create agents with different session cwd values can observe different project skill overrides by design. -The catalog is deterministic for a fixed root set and runtime registration revision, but disk changes are not watched; discovery is memoized until runtime registration invalidates the cache or the process restarts. +The catalog is deterministic for a fixed root set and runtime registration revision. The local provider watches configured roots and invalidates completed catalogs after relevant disk changes; runtime registration and provider disposal also invalidate them. ## Deferred diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md b/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md index 96656a8e1d..4fa7c4fd65 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md @@ -14,9 +14,11 @@ DeepSeek Harness 使用同一原语,使项目特定的评审、插件编写和 `@deepseek-ai/dsh-skill` 是纯提供方注册表(`ctx.skills`),`@deepseek-ai/dsh-skill-local` 是随附的本地文件系统提供方,`@deepseek-ai/dsh-tool-skill` 负责持久化会话目录与面向模型的 loader 工具。`dsh-agent-spine-demo` 默认加载注册表、本地提供方和消费方,使 TUI、headless 与 ACP(Agent Client Protocol)应用获得相同行为,同时嵌入式或远程提供方可在不修改注册表或消费方的前提下贡献 skill。其 `skills` 配置将 `registry`、`local` 和 `tool` 分支分别转发给对应的所有者。 +专用的随包提供方可以贡献不可变的 skill,无需文件系统发现。交付的 CLI(命令行界面)默认将 `@deepseek-ai/dsh-skill-badge` 声明为禁用;启用其组合配置行,就会通过同一个注册表和消费方贡献官方徽章指令(见[决策](2026-08-06-bundled-dsh-badge-skill.md))。 + 提供方插件在 `apply()` 期间同步注册。提供方成员资格是由直接 effect 持有的状态:注册与 dispose(资源释放)同步地使已完成的目录失效,发现操作按需读取当前提供方映射而非监听注册表变更事件。提供方目录从等待的 `list()` 调用返回排序后的候选项,远程提供方在此过程中执行初始化、认证和发现,同时遵守查找的 abort 信号。注册表校验每个候选项,按排名、提供方注册顺序和提供方内部顺序以先到先得方式解决同名 skill 冲突,然后按 skill 名称排序摘要以保证消费方获得确定性结果。它仅缓存已完成的目录快照,并在发现过程中提供方/运行时修订版本发生变化时重试,因此卸载操作不会将一个陈旧且不可解析的 skill 冻结到会话目录中。运行时 `ctx.skills.register(...)` 仍作为嵌入式进程内 skill 的便捷方式保留,使用 project 优先于 user 的优先级;`runtime` 保留为注册表拥有的提供方名称。 -本地提供方按先到先得的排名顺序扫描 cwd 敏感的项目根目录、自定义根目录和用户根目录:项目 `.dsh`、项目 `.agents`、`customSkillDirs`、用户 `.dsh`,然后是用户 `.agents`。用户 `.dsh/skills` 扫描跳过 `.system`,以免系统拥有的目录被当作普通用户内容处理。DeepSeek Harness 不随附内置系统 skill;嵌入式或远程提供方在配置后提供额外 skill。 +本地提供方按先到先得的排名顺序扫描 cwd 敏感的项目根目录、自定义根目录和用户根目录:项目 `.dsh`、项目 `.agents`、`customSkillDirs`、用户 `.dsh`,然后是用户 `.agents`。用户 `.dsh/skills` 扫描跳过 `.system`,以免系统拥有的目录被当作普通用户内容处理。本地提供方不会合成内置系统 skill;已配置的 bundled 根目录和专用提供方会提供额外 skill。 每个 skill 是 `/SKILL.md` 或带 YAML frontmatter 的 `.md`。`name` 和 `description` 为必填;`whenToUse`、`metadata`、`disable-model-invocation` 和 `user-invocable` 为可选。名称采用 kebab-case。调用字段会投影到类型化的嵌套策略中,具体由[模型与用户独立调用决策](2026-07-28-skill-invocation-policy.md)定义;解析器会拒绝旧的驼峰拼写。YAML frontmatter 使用 `yaml` 包(package)解析,而非 `js-yaml` 或手写解析器:`yaml` 是本包有限 frontmatter 需求已声明的现代解析器,窄解析器要么拒绝用户预期可用的合法 YAML,要么膨胀为一个未经评审的 YAML 子集。 @@ -48,7 +50,7 @@ DeepSeek Harness 使用同一原语,使项目特定的评审、插件编写和 agent-core 主干包含一个目录贡献者、一个本地提供方和一个面向模型的工具。Skill 发现是 cwd 敏感的,因此以不同会话 cwd 值创建 agent 的调用方可以按设计观察到不同的项目 skill 覆盖。 -目录对于固定的根目录集合和运行时注册修订版本是确定性的,但不监视磁盘变化;发现结果被缓存,直到运行时注册使缓存失效或进程重启。 +目录对于固定的根目录集合和运行时注册修订版本是确定性的。本地提供方会监视已配置的根目录,并在发生相关磁盘变化后使已完成的目录失效;运行时注册和提供方释放也会使其失效。 ## 延后 diff --git a/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.i18n.yaml b/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.i18n.yaml index bdc103ef46..222ed1ebbe 100644 --- a/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.md -2026-08-06-bundled-dsh-badge-skill.md: afe0b21d64a414a9e78ef55459a42c0d3817e3fd -2026-08-06-bundled-dsh-badge-skill.zh.md: de1ec989570b07987b12f0a291c84643aa5531fd +2026-08-06-bundled-dsh-badge-skill.md: 512f67ca347ca311a1f80fef932f6af8c91b0fe9 +2026-08-06-bundled-dsh-badge-skill.zh.md: 88fcadf66944cb91419441be3916ae04968be663 diff --git a/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.md b/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.md index afe0b21d64..512f67ca34 100644 --- a/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.md +++ b/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.md @@ -6,7 +6,7 @@ English | [中文](2026-08-06-bundled-dsh-badge-skill.zh.md) ## Problem -DeepSeek Harness has an official attribution badge skill, but keeping it only in a developer's personal skill directory makes it unavailable to other DSH installations and gives the shipped application no explicit opt-in point. +The [Cordis tutorial](../../../../docs/cordis-tutorial/index.md) uses an official “powered by dsh” badge across its pages, but the shipped CLI has no reusable instructions or explicit opt-in provider for applying the same attribution elsewhere. ## Decision @@ -18,7 +18,7 @@ The provider uses the bundled rank after project, custom, and user filesystem so ## Alternatives considered -A Codex marketplace plugin was rejected because it would install into a different runtime and would not participate in DSH's `ctx.skills` seam. Mounting `dsh-skill-local` over the packaged files was rejected because filesystem discovery, parsing, and watching add lifecycle machinery that an immutable single-skill provider does not need. +**Mount packaged files through `dsh-skill-local`.** Rejected because filesystem discovery, parsing, and watching add lifecycle machinery that an immutable single-skill provider does not need. ## Consequences diff --git a/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.zh.md b/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.zh.md index de1ec98957..88fcadf669 100644 --- a/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.zh.md +++ b/.agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.zh.md @@ -6,19 +6,19 @@ Status: implemented ## 问题 -DeepSeek Harness 已有官方署名徽章 skill(技能),但如果它只保存在某位开发者的个人 skill 目录中,其他 DSH 安装实例便无法使用,交付的应用也没有显式的选择加入点。 +[Cordis 教程](../../../../docs/cordis-tutorial/index.md)的各个页面都使用官方「powered by dsh」徽章,但交付的 CLI(命令行界面)既没有用于在其他位置应用同样署名的可复用指令,也没有可显式选择加入的提供方。 ## 决策 `@deepseek-ai/dsh-skill-badge` 是一个原生 Cordis 插件,会在 `ctx.skills` 上注册一个不可变的内置提供方。该提供方负责 `dsh-badge` 的摘要、指令正文和 PNG 资源基底;`dsh-tool-skill` 仍是面向模型的目录与 loader 渲染的唯一归属方。 -交付的 CLI(命令行界面)组合将 `skill-badge` 声明为禁用。启用这个现有配置行就是显式选择加入;禁用它的安装实例不会公开任何徽章 skill,也不会获得任何模型可见内容。 +交付的 CLI 组合将 `skill-badge` 声明为禁用。启用这个现有配置行就是显式选择加入;禁用它的安装实例不会公开任何徽章 skill(技能),也不会获得任何模型可见内容。 该提供方使用排在项目、自定义及用户文件系统来源之后的内置 rank,因此用户自有的 `dsh-badge` 定义可通过注册表的常规优先级契约覆盖它。提供方释放时,注册表拥有的 effect 会移除该贡献。 ## 曾考虑的替代方案 -未采用 Codex marketplace 插件,因为它会安装到不同的运行时,无法参与 DSH 的 `ctx.skills` seam。未采用使用 `dsh-skill-local` 挂载随包文件的方案,因为文件系统发现、解析和监视会引入不必要的生命周期机制,而不可变的单一 skill 提供方并不需要这些机制。 +**通过 `dsh-skill-local` 挂载随包文件。** 否决,因为文件系统发现、解析和监视会引入生命周期机制,而不可变的单一 skill 提供方并不需要这些机制。 ## 后果 diff --git a/apps/cli/tests/dsh-badge.snapshot.ts b/apps/cli/tests/dsh-badge.snapshot.ts index d78f4c743d..abd66e6f79 100644 --- a/apps/cli/tests/dsh-badge.snapshot.ts +++ b/apps/cli/tests/dsh-badge.snapshot.ts @@ -34,6 +34,7 @@ describe('dsh badge assembled snapshot', () => { expect(enabled.stderr).toBe('') expect(disabledSnapshot).toMatchInlineSnapshot(` { + "catalog": null, "result": { "content": [ { @@ -46,6 +47,7 @@ describe('dsh badge assembled snapshot', () => { }, "isError": true, }, + "summary": null, } `) expect(enabledSnapshot).toMatchInlineSnapshot(` @@ -169,5 +171,5 @@ describe('dsh badge assembled snapshot', () => { }, } `) - }, LOADER_SMOKE_TEST_TIMEOUT_MS) + }, LOADER_SMOKE_TEST_TIMEOUT_MS * 2) }) diff --git a/apps/cli/tests/fixtures/dsh-badge/snapshot.ts b/apps/cli/tests/fixtures/dsh-badge/snapshot.ts index 99379b4fe4..0bf4a92c8b 100644 --- a/apps/cli/tests/fixtures/dsh-badge/snapshot.ts +++ b/apps/cli/tests/fixtures/dsh-badge/snapshot.ts @@ -47,7 +47,7 @@ try { arguments: { name: 'dsh-badge' }, signal: new AbortController().signal, }) - process.stdout.write(`${JSON.stringify({ catalog, summary, result })}\n`) + process.stdout.write(`${JSON.stringify({ catalog: catalog ?? null, summary: summary ?? null, result })}\n`) } finally { await ctx.fiber.dispose() } diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 449addad8a..0cfc956221 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -1372,7 +1372,7 @@ export interface Config { } ``` -Source: [`packages/skill/skill/src/index.ts:170`](../packages/skill/skill/src/index.ts) +Source: [`packages/skill/skill/src/index.ts:173`](../packages/skill/skill/src/index.ts) ## `@deepseek-ai/dsh-skill-local` diff --git a/docs/cordis-catalog/events.md b/docs/cordis-catalog/events.md index 5044b0e5ce..cd54572d91 100644 --- a/docs/cordis-catalog/events.md +++ b/docs/cordis-catalog/events.md @@ -670,7 +670,7 @@ A skill provider, runtime contribution, or provider-backed catalog may have chan 'skills/change'(): void ``` -Source: [`packages/skill/skill/src/index.ts:188`](../../packages/skill/skill/src/index.ts) +Source: [`packages/skill/skill/src/index.ts:191`](../../packages/skill/skill/src/index.ts) ## `subagent/*` diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md index b3d81d61b9..2df5bbeb0a 100644 --- a/docs/cordis-catalog/services.md +++ b/docs/cordis-catalog/services.md @@ -1897,7 +1897,7 @@ async get(name: string, options: SkillLookupOptions = {}): Promise/skills` | | 600 | `bundled` | `Config.bundledSkillDir` when configured | -The project root is the nearest ancestor containing `.git`; without one, the current cwd is used. When `ctx.fs` is available, the git-root walk probes `.git` through the filesystem service so remote or sandboxed workspaces do not fall back to the host filesystem boundary. The user DSH root skips its `.system` child. The local provider does not ship built-in system skills; deployments supply built-ins through another provider. +The project root is the nearest ancestor containing `.git`; without one, the current cwd is used. When `ctx.fs` is available, the git-root walk probes `.git` through the filesystem service so remote or sandboxed workspaces do not fall back to the host filesystem boundary. The user DSH root skips its `.system` child. The local provider does not synthesize built-in system skills; deployments supply packaged skills through configured bundled roots or dedicated providers. + +`dsh-skill-badge` registers one immutable `bundled` candidate at `BUNDLED_SKILL_RANK` and exposes its packaged asset directory through `resourceBase`. The shipped CLI declares the plugin disabled, so enabling its composition row is an explicit opt-in. Chokidar watches existing roots for direct bundle/flat-entry additions and removals plus direct skill-entry changes. A missing root is followed one absent path segment at a time from its nearest existing ancestor until Chokidar can attach. Resource files below a bundle are not catalog changes. Model-facing `write` and `edit` observations synchronously invalidate the provider when their target is catalog-relevant, while the host watcher covers IDE, Git, shell, and external-process mutations. Watcher failures make the current observation incomplete without hiding readable candidates from direct loads; project-scoped watchers use a configured bounded LRU. diff --git a/docs/core-data-structures/skills.zh.md b/docs/core-data-structures/skills.zh.md index 7d69b84f7b..3f8c034ec2 100644 --- a/docs/core-data-structures/skills.zh.md +++ b/docs/core-data-structures/skills.zh.md @@ -2,9 +2,9 @@ [English](skills.md) | 中文 -[skill(技能)能力族](../../packages/skill) 拆分为三个包:注册表([dsh-skill](../../packages/skill/skill),`ctx.skills`)合并各提供方的目录;本地提供方([dsh-skill-local](../../packages/skill/skill-local))扫描并监视项目、自定义和用户目录;消费方([dsh-tool-skill](../../packages/skill/tool-skill))拥有初始目录和替换目录,以及面向模型的 `skill` 工具。skill 是可选的指令而非会话事件,因此其词汇定义在此处而非 [core.md](core.md)。 +[skill(技能)能力族](../../packages/skill) 包含注册表([dsh-skill](../../packages/skill/skill),`ctx.skills`)、本地提供方([dsh-skill-local](../../packages/skill/skill-local))、可选的随包徽章提供方([dsh-skill-badge](../../packages/skill/skill-badge))和消费方([dsh-tool-skill](../../packages/skill/tool-skill))。注册表合并各提供方的目录;提供方贡献本地或随包 skill;消费方拥有初始目录和替换目录,以及面向模型的 `skill` 工具。skill 是可选的指令而非会话事件,因此其词汇定义在此处而非 [core.md](core.md)。 -源码:[`packages/skill/skill/src/index.ts`](../../packages/skill/skill/src/index.ts)、[`packages/skill/skill-local/src/index.ts`](../../packages/skill/skill-local/src/index.ts) 与 [`packages/skill/tool-skill/src/index.ts`](../../packages/skill/tool-skill/src/index.ts)。 +源码:[`packages/skill/skill/src/index.ts`](../../packages/skill/skill/src/index.ts)、[`packages/skill/skill-local/src/index.ts`](../../packages/skill/skill-local/src/index.ts)、[`packages/skill/skill-badge/src/index.ts`](../../packages/skill/skill-badge/src/index.ts) 与 [`packages/skill/tool-skill/src/index.ts`](../../packages/skill/tool-skill/src/index.ts)。 ## 提供方注册表 @@ -72,7 +72,9 @@ interface SkillProviderControl { | 500 | `user-agents` | `/skills` | | 600 | `bundled` | 配置了 `Config.bundledSkillDir` 时使用该目录 | -项目根目录为包含 `.git` 的最近祖先目录;找不到时使用当前 cwd。当 `ctx.fs` 可用时,git-root 向上查找通过文件系统服务探测 `.git`,使远程或沙箱工作区不会回退到宿主文件系统边界。用户 DSH 根目录会跳过其 `.system` 子目录。本地提供方不附带内置系统 skill;部署方通过另一个提供方提供内置 skill。 +项目根目录为包含 `.git` 的最近祖先目录;找不到时使用当前 cwd。当 `ctx.fs` 可用时,git-root 向上查找通过文件系统服务探测 `.git`,使远程或沙箱工作区不会回退到宿主文件系统边界。用户 DSH 根目录会跳过其 `.system` 子目录。本地提供方不会合成内置系统 skill;部署方通过已配置的 bundled 根目录或专用提供方提供随包 skill。 + +`dsh-skill-badge` 在 `BUNDLED_SKILL_RANK` 注册一个不可变的 `bundled` 候选项,并通过 `resourceBase` 公开其随包资产目录。交付的 CLI(命令行界面)将该插件声明为禁用,因此启用其组合配置行即为显式选择加入。 Chokidar 会监视现有根目录中直属 bundle 和平铺条目的添加与移除,以及直属 skill 条目的变更。缺失的根目录会从最近的现有祖先开始,逐个跟踪缺失路径段,直至 Chokidar 可以附加。bundle 下的资源文件变更不属于目录变更。面向模型的 `write` 和 `edit` 观测会在目标路径相关时同步使提供方目录失效,而宿主 watcher 覆盖 IDE、Git、shell 和外部进程产生的变更。watcher 失败会使当前观测不完整,但不会在直接加载时隐藏可读候选项;项目作用域 watcher 使用按配置设限的 LRU。 diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 2377ca0d69..bdf52f0bc5 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -36,7 +36,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `session/flush` | `parallel` | [`packages/core/session/src/index.ts:104`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry) | | `settings/document-updated` | `emit` | [`packages/settings/settings/src/index.ts:150`](../packages/settings/settings/src/index.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | `apiproxy` | | `settings/updated` | `emit` | [`packages/settings/settings/src/index.ts:137`](../packages/settings/settings/src/index.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | [`settings`](../packages/settings/settings) | -| `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:188`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | - | +| `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:191`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | - | | `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:160`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`jsonrpc`](../packages/ui/jsonrpc), [`subagent`](../packages/subagent/subagent) | | `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:134`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) | | `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:140`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) | diff --git a/packages/skill/skill-badge/src/index.ts b/packages/skill/skill-badge/src/index.ts index 27cfd29354..9cff2070fb 100644 --- a/packages/skill/skill-badge/src/index.ts +++ b/packages/skill/skill-badge/src/index.ts @@ -7,14 +7,14 @@ import { readFile } from 'node:fs/promises' import { fileURLToPath } from 'node:url' import type { Context } from 'cordis' -import type { - SkillCandidate, - SkillDefinition, - SkillProvider, +import { + BUNDLED_SKILL_RANK, + type SkillCandidate, + type SkillDefinition, + type SkillProvider, } from '@deepseek-ai/dsh-skill' const PROVIDER_NAME = 'dsh-badge' -const BUNDLED_RANK = 600 const SKILL_BODY_URL = new URL('../assets/dsh-badge.md', import.meta.url) const RESOURCE_BASE = { kind: 'directory', @@ -29,7 +29,7 @@ const CANDIDATE: SkillCandidate = { provider: PROVIDER_NAME, source: 'bundled', resourceBase: RESOURCE_BASE, - rank: BUNDLED_RANK, + rank: BUNDLED_SKILL_RANK, locator: SKILL_BODY_URL, } diff --git a/packages/skill/skill-local/src/index.ts b/packages/skill/skill-local/src/index.ts index d6df41237e..71ed3be21d 100644 --- a/packages/skill/skill-local/src/index.ts +++ b/packages/skill/skill-local/src/index.ts @@ -21,6 +21,7 @@ import { parse as parseYaml } from 'yaml' import type { FileSystem, FsDirEntry, FsTarget } from '@deepseek-ai/dsh-fs' import { resolveDshHome } from '@deepseek-ai/dsh-paths' import { + BUNDLED_SKILL_RANK, isSkillName, type SkillCandidate, type SkillDefinition, @@ -40,7 +41,6 @@ const USER_AGENTS_RANK = 500 const DEFAULT_WATCH_STABILITY_THRESHOLD_MS = 200 const DEFAULT_WATCH_POLL_INTERVAL_MS = 100 const DEFAULT_WATCH_MAX_PROJECTS = 128 -const BUNDLED_RANK = 600 export const name = 'skill-local' export const inject = ['skills'] @@ -256,7 +256,7 @@ export class LocalSkillProvider implements SkillProvider { ) } if (this.bundledSkillDir !== undefined) { - roots.push({ path: this.bundledSkillDir, source: 'bundled', rank: BUNDLED_RANK, trustedHost: true }) + roots.push({ path: this.bundledSkillDir, source: 'bundled', rank: BUNDLED_SKILL_RANK, trustedHost: true }) } return roots } diff --git a/packages/skill/skill/src/index.ts b/packages/skill/skill/src/index.ts index 32f7112542..139b72bc8a 100644 --- a/packages/skill/skill/src/index.ts +++ b/packages/skill/skill/src/index.ts @@ -19,6 +19,9 @@ const MAX_COLLECT_ATTEMPTS = 2 const RUNTIME_PROVIDER = 'runtime' const RUNTIME_RANK = 250 +/** Standard precedence rank for packaged skill providers and local bundled roots. */ +export const BUNDLED_SKILL_RANK = 600 + /** * Return whether a string is a valid kebab-case skill name. * @param name - candidate skill name to validate. From b9e7db5abfe67a2b9cfd2732a57ebd8264b2ad36 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sat, 8 Aug 2026 15:39:19 +0800 Subject: [PATCH 3/4] fix(skill-badge): align with current base contracts --- apps/cli/tests/dsh-badge.snapshot.ts | 9 +++++---- packages/skill/skill-badge/assets/dsh-badge.md | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/cli/tests/dsh-badge.snapshot.ts b/apps/cli/tests/dsh-badge.snapshot.ts index abd66e6f79..00d5278f82 100644 --- a/apps/cli/tests/dsh-badge.snapshot.ts +++ b/apps/cli/tests/dsh-badge.snapshot.ts @@ -62,6 +62,7 @@ describe('dsh badge assembled snapshot', () => { If the user names a skill, or the task clearly matches a skill's description, call the \`skill\` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded. + A user may also invoke a skill directly; its block then appears in this conversation. Follow it, and do not call the \`skill\` tool again for that skill. ", "type": "text", }, @@ -84,14 +85,14 @@ describe('dsh badge assembled snapshot', () => { - Local PNG: [\`dsh-badge.png\`](dsh-badge.png), 726×120 source image; render at 121×20 - Shields.io image URL: \`https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white\` - - Project URL: \`https://github.com/deepseek-harness/deepseek-harness\` + - Project URL: \`https://github.com/deepseek-ai/deepseek-harness-sdk\` ## Markdown Use this linked badge in Markdown: \`\`\`markdown - [![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-harness/deepseek-harness) + [![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-ai/deepseek-harness-sdk) \`\`\` If attribution should not be linked, use: @@ -123,14 +124,14 @@ describe('dsh badge assembled snapshot', () => { - Local PNG: [\`dsh-badge.png\`](dsh-badge.png), 726×120 source image; render at 121×20 - Shields.io image URL: \`https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white\` - - Project URL: \`https://github.com/deepseek-harness/deepseek-harness\` + - Project URL: \`https://github.com/deepseek-ai/deepseek-harness-sdk\` ## Markdown Use this linked badge in Markdown: \`\`\`markdown - [![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-harness/deepseek-harness) + [![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-ai/deepseek-harness-sdk) \`\`\` If attribution should not be linked, use: diff --git a/packages/skill/skill-badge/assets/dsh-badge.md b/packages/skill/skill-badge/assets/dsh-badge.md index 9905de1ed9..5a789fac2f 100644 --- a/packages/skill/skill-badge/assets/dsh-badge.md +++ b/packages/skill/skill-badge/assets/dsh-badge.md @@ -6,14 +6,14 @@ Add the official “powered by dsh” badge without recreating or restyling it. - Local PNG: [`dsh-badge.png`](dsh-badge.png), 726×120 source image; render at 121×20 - Shields.io image URL: `https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white` -- Project URL: `https://github.com/deepseek-harness/deepseek-harness` +- Project URL: `https://github.com/deepseek-ai/deepseek-harness-sdk` ## Markdown Use this linked badge in Markdown: ```markdown -[![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-harness/deepseek-harness) +[![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-ai/deepseek-harness-sdk) ``` If attribution should not be linked, use: From 8dcbabc3a45d78565aba5650162d8e0febd9be85 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sat, 8 Aug 2026 16:04:24 +0800 Subject: [PATCH 4/4] docs: refresh module graph --- docs/module-graph.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/module-graph.md b/docs/module-graph.md index 8abb5b3393..2d5300f312 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -320,8 +320,6 @@ flowchart TD pkg_llm --> pkg_brand pkg_llm --> pkg_invariants pkg_llm --> pkg_timeout - pkg_skill_badge --> pkg_invariants - pkg_skill_badge --> pkg_skill pkg_client_connection --> pkg_host_webserver pkg_client_connection --> pkg_invariants pkg_client_hmr --> pkg_client_modules @@ -430,6 +428,8 @@ flowchart TD pkg_fs --> pkg_invariants pkg_fs --> pkg_llm pkg_fs --> pkg_sandbox + pkg_skill_badge --> pkg_invariants + pkg_skill_badge --> pkg_skill pkg_compact --> pkg_invariants pkg_compact --> pkg_llm pkg_compact --> pkg_session @@ -1182,7 +1182,6 @@ flowchart TD | [`typert-generator`](../packages/typert/generator) | `typert` | [`invariants`](../packages/support/invariants) | | [`typert-registry`](../packages/typert/registry) | `typert` | [`invariants`](../packages/support/invariants) | | [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`timeout`](../packages/util/timeout) | -| [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`invariants`](../packages/support/invariants), [`skill`](../packages/skill/skill) | | [`client-connection`](../packages/client/connection) | `client` | [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) | | [`client-hmr`](../packages/client/hmr) | `client` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) | | [`client-runtime`](../packages/client/runtime) | `client` | [`invariants`](../packages/support/invariants), [`type-meta`](../packages/typert/type-meta), [`typert-registry`](../packages/typert/registry) | @@ -1215,6 +1214,7 @@ flowchart TD | [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`type-meta`](../packages/typert/type-meta) | | [`bash`](../packages/bash/bash) | `bash` | [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`subprocess`](../packages/subprocess/subprocess) | | [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | +| [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`invariants`](../packages/support/invariants), [`skill`](../packages/skill/skill) | | [`compact`](../packages/compact/compact) | `compact` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`web-fetch-local`](../packages/web/web-fetch-local) | `web` | [`invariants`](../packages/support/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) | | [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) |