Skip to content

Releases: PolinniZhong/dsh-skill-intelligence

v1.4.0 — Skill 触发评测(`FR-TRIG-*`)

Choose a tag to compare

@PolinniZhong PolinniZhong released this 12 Oct 07:19

1.4.0 — 2026-10-12 · Skill 触发评测(FR-TRIG-*)

这一版做一件事:把「这个 Skill 该在什么任务里被加载」变成可复测的观察。 在既有的「Skill 评测」模块里多一个「触发边界」模式:用户维护一组正向(应触发)与负向(不应触发)的提示词,在真实新会话里把它们交给 Agent,再回来记录这一次观察。插件只看一件事 —— 目标 Skill 的加载记录出现过吗,是 model-invoked 还是 user-explicit。

三条边界就是这一版的产品定义:不跑测试(不建会话、不发提示词、不启动第二个 Agent、不调模型)· 不判定(观察状态 / 调用方式 / 事件覆盖全由宿主从收据与日志推出来,浏览器提交的结论一个字节都不算)· 不聚合(没有总分、通过率、排行、趋势)。不新增路由 / 页面 / 依赖:全部动作挂在既有的第 13 条 POST /skill-trace/evaluation 上,界面只加一个模式,不新增详情模块。

发布实测(1.4.0 发布口径,2026-10-12):npm test 740 项全绿 · npm run verify 40 组 OK(第 40 组是 SKILL_TRIGGER_EVALUATION_OK)· 客户端 src/dsh/client/client.js 5906 行 · bundle dist/client.js 325620 字节(source hash c272c34814bced72) · src/core/ 35 个模块 / 14634 行(新增 skill-trigger-evaluation.mjs,零 import) · src/storage/ 8 个 / 1704 行(新增 trigger-evaluation-store.mjs) · 宿主 src/dsh/host/index.js 2135 行 / 13 条路由(路由一字未增) · 客户端 require 由十支变 十一支。

A. 触发用例与触发观察(core + storage)

  • 新增 src/core/skill-trigger-evaluation.mjs(零 import 纯函数层):触发用例身份、观察三态、覆盖三态、对照四态、限制封闭词表、两次 Run 的可比性。哈希一律由宿主算(与 V1.0 的 caseId 同一条约定)——这一层不引 node:crypto、不读时间、不掷骰子。
  • caseId 里有独立域 dsh-skill-trigger-case,且刻意不含 Skill 指纹(FR-TRIG-005,与 V1.0 相反)。这是这一版能「改完 description 还用同一组用例复测」的前提;指纹改绑到每一次 Run 上。
  • 新增 src/storage/trigger-evaluation-store.mjs:<dataRoot>/evaluation/trigger-suites/ 与 trigger-runs/(与 V1.0 的 cases/ / runs/ 分开 —— 两套身份混在一个目录里迟早被代码混用);0700 / 0600 / 原子写;禁字段清单沿用 EVALUATION_FORBIDDEN_FIELDS 那一份;认不出的三态值与限制 id 直接拒收(unknown-limitation:<id>);读取端 fail-soft。
  • 限制项是封闭词表,界面按 id 取文案;证据引用不含会话标识(trigger-evidence:sha256:<hex>,只含匹配到的加载事实)。

B. 覆盖:读取层必须如实报读数(FR-TRIG-013)

  • src/core/session-log.mjs 补了一份诚实的覆盖读数:framesDecoded / framesSkipped / frameLimitHit / linesSkipped / linesNonEvent / tornTail / oversized / unreadable。改造前这几样全是静默跳过:帧解不开就丢、超过 4096 帧就截尾、超 512MB 的文件直接当「没有日志」、撕裂的行当不存在。于是「我读不到」与「它本来就没有」分不开 —— 而这一版恰恰要靠「没读到」说话。
  • 实测(本机 330 个真实会话):complete 305 / partial 13 / unknown 12;那 13 个全部命中 4096 帧上限。也就是说,旧读取层会在 3.9% 的真实会话上静默截掉尾巴、却报成「读全了」,足以伪造一次假的「未观察到加载」。
  • complete 是有前提的合取:文件在 · 读得到 · 没被跳过 · 没命中帧上限 · 没有一个帧解码失败 · 没有非法行 · 没有撕裂尾行。一点都没读到 ⇒ unknown,不是 partial(写成 partial 会让人以为读到了一点)。活会话的事实来自内存事件流,对「到此刻为止」是完整的。
  • 不趁机改「保留最早的还是最新的 4096 帧」:本版只如实报告这件事,那是另一件事、要有自己的证据。

C. 宿主动作:观察由事实推出,不由浏览器提交(FR-TRIG-019)

  • 第 13 条路由新增五个动作:trigger-case-save / trigger-case-list / trigger-case-read / trigger-case-delete / trigger-run-capture。路由数仍是 13。
  • 请求体里写 observation: 'loaded' 不会改变落盘记录一个字节 —— 加载事实、调用方式、覆盖状态全部由宿主从收据与会话日志里取;客户端能提交的只有 caseId、要读哪个会话、以及「这是新会话」这句用户声明。守卫按字面钉住这条缝(宿主不许出现 payload.observation;客户端提交的 body 里不许出现 observation:)。
  • 显式 /name 调用是一条加载记录,但不是自然触发:invocationType: 'user-explicit' 一律降级 inconclusive。
  • sessionId 只作为读取参数,不落盘(落盘层深扫 sessionId 并拒收)。

D. description 指纹必须对未截断的原文取摘要(FR-TRIG-009)

  • 新增 source-snapshot.mjs 的 descriptionFingerprint()(带域前缀 dsh-skill-description,只把连续空白收成一个空格、不截断),经 buildSkillDefinitionView().summary.descriptionSha256 暴露;只外发哈希,不外发原文。
  • 为什么必须这样:显示用的 description 被截到 500 字,拿它做指纹会把第 500 字以后的改动全部抹掉 —— 两份不同的 description 得到同一个「指纹」,界面就会宣称「description 没变」。用截断值冒充相等是这一版最不能出的错。

E. 界面:同一个模块里的第二个模式(客户端)

  • 「Skill 评测」里加一条模式条:任务效果(原有内容,仍是默认)/ 触发边界。不新增模块、不新增页面,导航里那一格没变。
  • 触发边界那一屏:四句固定说明句(从纯函数层取,客户端不再写一份)+ 三态解释 + 用例清单与计数 + 用例详情(提示词可复制、编辑、删除)+ 「记录本次观察」(含「这是新会话」声明)+ 最近一次观察的状态 / 调用方式 / 覆盖 / 指纹 / 限制 + 逐条 Run 历史 + 两次 Run 的可比性(条件不一致时逐项列出差异并阻断归因)+ 「一次测试应该怎么跑」三步。
  • 文案一个都不自己写:三态、调用方式、覆盖、限制、字段抬头全部取自 src/core/skill-trigger-evaluation.mjs(客户端 require 由十支变十一支)。
  • 提示词超长时拒绝保存并说明原因,不静默截断(FR-TRIG-006)。

F. 反复踩过的两处

  • hook 不许排在提前 return 之后。 第一版把触发边界的 useEffect 写在 SkillDetailPage 的提前 return 之后,test/client-hook-order.test.mjs 立刻抓到(conversation.view 没有错误边界,这会整栏白屏)。已移回 hook 组。
  • 守卫红了先问「是代码错了还是守卫该改」。 两次都是前者:一是重构读取层时改了变量名,把守卫钉的那行「逐帧解码」改没了(改回源码,不动守卫);二是界面词表 —— 触发边界的说明文案里出现「已加载」,撞上 §6.7「观察不到证据就不许说任何『已…』」,改文案。真正需要更新守卫的只有两处:evaluation 那一维的接线从字面变成三目(按意图改,并顺手钉得更强)、以及客户端核心 require 10 → 11 的白名单。

G. 判定:这一版不做的

  • 不自动执行测试:不建会话、不提交提示词、不启动第二个 Agent、不调模型。
  • 不评分、不排名、不产出任何汇总口径。
  • 不用模型判断「应该触发」:期望类别只能由用户维护。
  • 不把「没有观察到」当「没有触发」;没有加载记录永远不能自动升级成失败结论。
  • 不落盘 sessionId / 完整对话 / 工具参数与结果 / 绝对路径。
  • 不新增一级页面、不重做详情 IA、不新增依赖(dependencies 仍为空)。

H. 文档与验收(已同步的八处 + 剩下的一件事)

已按 AGENTS.md §9.1 同步:AGENTS.md(头部追记 + §1 全部状态行 + §6.6 的 require 十支 → 十一支 + §3/§6.3/§9 的测试与守卫数)· README.md(工作版本声明 + V1.4 段 + 版本史)· spec/PRD.md(头部工作版本 + 新增 §5.15 的 FR-TRIG-001–020 + 三处已经过时的「Trigger Evaluation 一行代码都还没写」措辞已按事实改准)· spec/SDD.md(头部工作版本 + §0.1 的 D19 + §9.2 模块数与 storage 数 + §13/§14.1 的守卫与测试数 + 新增 §23)· docs/ARCHITECTURE.md(新增 ## V1.4 段与 Worktree numbers)· design.md(v4.6 → v4.7 + 实现入口行)· docs/PRIVACY.md(新增 ## What the Skill trigger evaluation feature stores)· package.json(1.4.0)。

剩下的一件事:真机走查(AGENTS.md §6.2:宿主半边改了必须重启 DSH)。重启后进任意 Skill 详情 →「Skill 评测」→ 切到「触发边界」,应看到四句固定说明句与「+ 新建测试用例」;要跑通一次观察需要用户自己在一个新会话里提交一条提示词(插件不会替他发),然后回到这一屏点「记录到本机」。另需看深 / 浅色与窄窗口。

v1.3.0 — Agent-native Skill Review(只读工具 skill_review)

Choose a tag to compare

@PolinniZhong PolinniZhong released this 11 Oct 01:41

1.3.0 — 2026-10-11 · Agent 可调用的只读 Skill 审查(skill_review)

这一版做一件事:把一条模型可见的只读工具交给 Agent。 它只读 —— 不加载 Skill、不执行 scripts/、不写任何文件、不调模型;不给分、不排名、不做 benchmark;三态仍只有 declared / observed / unavailable(没有 inferred)。不新增路由 / 页面 / 落盘 / 依赖,客户端与 dist/client.js 一字未改。

发布实测(1.3.0 发布口径,2026-10-11):npm test 690 项全绿 · npm run verify 39 组 OK(第 39 组是 V1.3 新增的 SKILL_REVIEW_TOOL_OK)· 客户端 src/dsh/client/client.js 5400 行(本版一字未改) · bundle dist/client.js 293246 字节(source hash 9ec62e37d7dbfa2e) · src/core/ 34 个模块 / 13973 行(新增 skill-review.mjs 1119 行、零依赖零 import) · src/storage/ 7 个 1411 行(未改) · 宿主 src/dsh/host/index.js 2002 行 / 13 条路由(路由一字未增;新增的是 ctx.inject(['tools'], …) 注册的模型可见只读工具 skill_review,以及一条随工具可见性同生共死的 context:skill-review 系统提示节)。

A. 模型可见的新工具(宿主半边)

  • 第一次改变模型可见工具集。 此前宿主只注册 13 条 HTTP 路由,模型看不见这个插件的任何东西;本版新增 ctx.inject(['tools'], …) 注册 skill_review,与那 13 条路由的可用性解耦:路由不可用不影响工具可见性。
  • 只读。 不加载 Skill、不执行 scripts/、不写任何文件、不调模型 —— 一次调用只做一次确定性投影。
  • 返回 Structured Review Packet。 render ≤ 6000 码点;超出时如实降级(写明被省略/折叠了哪些部分),不静默截断。
  • 失败一律返回信封,不向宿主抛异常;工具不可见时,界面上的相关指引随之消失。
  • 第二条引导通道(真机驱动):指引原先只放在工具 description 里 —— 真机上「建完 / 改完一个 Skill」不会主动提议审查(干净试炼里 0 次)。宿主按内核先例(@deepseek-ai/dsh-file-reference-local 的 systemPrompt.section 用法)为每个 agent 装一条 systemPrompt.section(name: 'context:skill-review',槽位 TOOL_SESSION_QUERY + 1),文本由 agent.ctx.tools.get('skill_review', agent) 是否为 undefined 决定 —— 工具不可见即渲染空串,指引与工具同生共死。加装后真机复测:子 agent 在新会话里逐字复述出该段(通道确实渲染),4 次干净试炼里 1 次主动提议、并明确等用户确认 —— 可触发,但仍不是稳定行为(不因此加任何自动 followup:自动注入与自动续跑仍是禁止项)。
  • 不给分、不排名、不做 benchmark。三态仍只有 declared / observed / unavailable(没有 inferred)。

B. 核心层新模块 src/core/skill-review.mjs(1119 行)

  • 零依赖、零 import,复用既有 buildSkillDetail / buildSkillValidation / buildSkillEvidence / buildSkillFramework / buildSkillRuntimeLogic,不重新解析 SKILL.md、不引入第二套事实。
  • 禁止键沿用 EVIDENCE_EXPORT_FORBIDDEN_KEYS(工具参数 / 工具结果 / 会话 id / 绝对路径 / 分数),并做递归断言:这些键一旦出现在包里的任何深度即失败。

C. 守卫与测试

  • 守卫 38 → 39 组:新增第 39 组 SKILL_REVIEW_TOOL_OK(钉住工具注册、只读边界、render ≤ 6000 与降级、失败返回信封)。
  • npm test 662 → 690 项:新增 test/skill-review.test.mjs(14 项)与 test/skill-review-tool.test.mjs(14 项;其中 5 项专钉第二条引导通道:节装在每个 agent 上、工具不可见时文本为空串、重复 agent/created 不装第二份、agent/disposed 恰好释放一次、节文与工具同生共死)。
  • 真机修正(unknown-skill 可达):skill_review 对不在册的 Skill 现在回 unknown-skill(此前回的是 skill-file-unreadable)。宿主原先先看验收器的 validation.reason,而「不在册」与「读不出来」都没有正文可读、都命中 skill-file-unreadable,于是 unknown-skill 在整个信封里永远不可达(真机实测:{"skillName":"definitely-not-a-skill"})。改成以 definition.reason 为第一信号、验收器只兜底,并在 test/skill-review-tool.test.mjs 里补 2 条(不在册 / 注册表整个没挂上)。
  • 不新增路由 / 页面 / 落盘 / 依赖;dist/client.js 与客户端一字未改(5400 行 / 293246 字节,source hash 9ec62e37d7dbfa2e,shasum -a 256 为 38d5ee32fbb4c897…);宿主 src/dsh/host/index.js 1844 → 2002 行(路由仍是 13 条);src/core/ 33 个 12854 行 → 34 个 13973 行;src/storage/ 7 个 1411 行不变。修改仍由当前 Agent 用 DSH 原生文件工具在对话里做 —— 这个工具只读。

v1.2.0 — Skill 证据模型(Skill Evidence Model)+ Skill 框架重构

Choose a tag to compare

@PolinniZhong PolinniZhong released this 07 Oct 02:48

1.2.0 — 2026-10-07 · Skill 证据模型(Skill Evidence Model)+ Skill 框架重构

这一版做三件事:先把「这次会话对这个 Skill 观察到了什么」整理成一组有身份、有性质、有边界的证据事实,并在详情页多一个只读的「Skill 证据」模块;再把详情页「Skill 框架」这一维从一个「按角色铺开的结构网格」重组成「阅读路径(蓝图)→ 结构地图 → 细节预览 + 声明流程 + 渐进披露」(五个区域各自独立成卡,结构地图与细节预览并排成对且两列等高),最后把「一个区域 = 一张卡」这条规则推广到其余详情模块(验收 / 证据 / 评测 / 修改对比各自成卡),并把选中态改成中性底色(亮色淡灰、暗色比卡面亮一档的浅黑)。 前者不给分、不排名、不聚合、不调模型;后者只读 SKILL.md 自己的结构、不是运行流程;两件事都不新增路由、不新增页面、不新增落盘、不新增依赖,宿主 src/dsh/host/index.js 一字未改。

发布实测(1.2.0 发布口径,2026-10-07):npm test 662 项全绿 · npm run verify 38 组 OK(第 32–38 组是 V1.2 新增的 7 条 SKILL_EVIDENCE_*;第 20 组 SKILL_FRAMEWORK_OK 在本轮框架重构中加了 2b + 2c + 2d 三段断言,组数不变;第 26 组 SKILL_VALIDATION_OK / 第 27 组 SKILL_MODIFICATION_OK / 第 29 组 SKILL_EVALUATION_OK / 第 32 组 SKILL_EVIDENCE_CARD_OK 在本轮「其余模块各自成卡」中各追加了 needle,组数仍是 38)· 客户端 src/dsh/client/client.js 5400 行 · bundle dist/client.js 293246 字节(source hash 9ec62e37d7dbfa2e)· src/core/ 33 个模块 / 12854 行(新增 skill-evidence.mjs 909 行;框架重构未动核心层)· src/storage/ 7 个 1411 行· 宿主 13 条路由(src/dsh/host/index.js 1844 行;第 14 条 POST /skill-trace/evidence-export 已撤回)· 客户端 require 由九支变十支。

A. 证据模型(src/core/skill-evidence.mjs,909 行,零 import)

  • 三态只有三个(EVIDENCE_STATUS_IDS):declared(Skill 自己声明的)/ observed(系统确实观察到的)/ unavailable(当前已验证的读取路径拿不到)。没有 inferred:缺就是缺,normalizeEvidenceStatus() 把任何认不出的值(含大小写不同)一律归一成 unavailable,绝不让「没观察到」写成「没发生」。
  • 四段证据链(EVIDENCE_CHAIN_STAGE_IDS):Definition → Load → Use → Outcome,每段带自己的 hint 与限制。它不是 V1.0 评测那套 trigger / load / use / outcome 的复用——那套属于 Run,这套属于「一条证据事实在哪一段上」。
  • 证据状态表(EVIDENCE_SUBJECT_IDS × EVIDENCE_TABLE_COLUMNS):五类对象 Skill 名称 / Skill 加载 / Skill 使用迹象 / DSH 版本 / Skill 造成结果,五列 对象 / 状态 / 当前事实 / 证据来源 / 限制。第一类永远是 declared(名字来自 SKILL.md),最后一类永远是 unavailable(因果结论永不声明)。
  • 三条边界(EVIDENCE_BOUNDARY_IDS):load-evidence / use-evidence / causal-claim,第三块的定论永远是「不声明」——结果 ≠ 这个 Skill 造成的(EVIDENCE_INEQUALITIES 三句与 V1.0 同义,但归属本模型)。
  • 漂移三态(EVIDENCE_DRIFT_IDS):current / historical / no-matching-evidence,没有「过期」这一档。判据是「手上的 Run 证据绑在哪一份 instruction fingerprint 上」,历史证据仍然有效,只是它对应的是另一份定义。
  • 限制是封闭词表(EVIDENCE_LIMITATION_CODES,9 条):界面按 id 取文案,不许自己写——「这是声明值不是运行时行为」「加载不证明内容被使用」「只读工具名与数量」「同 Turn 相关活动不等于被完整使用」「结果不是因果归属」「当前读取路径不提供 DSH 版本」「历史 schema 没这个字段」「指纹与当前定义不一致」「没有 Run 绑定到当前指纹(推不出没执行)」。
  • id 必须可重放(FR-EVIDENCE-009):evidenceHashInput() 第一行是域前缀 dsh-skill-evidence,evidenceIdOf() 的形状是 evidence:<对象>@<绑定>;同输入逐字同 id,没有 Date.now() / Math.random() / UUID(守卫先剥注释再扫源码钉住这一点)。要升级成真 sha256 只需宿主对规范输入串多算一步,模块与界面都不用动。
  • 导出(EVIDENCE_EXPORT_SCHEMA_VERSION = 1):buildEvidenceExport(model) 只含身份、条件、观察、限制与来源,逐字节稳定;EVIDENCE_EXPORT_FORBIDDEN_KEYS 明列不许出现的键(工具参数 / 工具结果等),命中即失败。

B. 界面:详情页第二维「Skill 证据」(客户端第十支 require)

  • 详情页导航从八个模块变九个,新模块 evidence-model(Skill 证据)插在 framework 之后;MODULE_CONTENT['evidence-model'] = [skillEvidence]。它复用当下这一帧已有的评测状态,不额外发请求。
  • 卡内七块:evidence-identity(Skill fingerprint / Source identity / Instruction fingerprint / Case / Run)、evidence-chain(四段 + 事实 + 来源 + 限制)、evidence-limits(三条边界,因果那行写死「不声明」)、evidence-table(五类对象 × 五列)、evidence-drift(漂移三态与它对应的说法)、evidence-evaluation(本次 Run / 历史 Run / 对照三个 tab)、以及一个「复制 JSON」按钮('data-role': 'evidence-copy';写文件那条路已撤回,见 ### G)。
  • 取走快照只有一条路:剪贴板。 点「复制 JSON」把整份快照(同一份逐字节稳定的文本)放进剪贴板,环境不支持就如实说「当前环境不支持复制。」。没有导出服务、没有写文件、没有新路由、没有落盘 —— 2026-10-06 曾短暂加过「导出 Evidence JSON」(第 14 条路由 POST /skill-trace/evidence-export + src/storage/evidence-export-writer.mjs),当天整条撤回,原因与教训见 ### G。
  • 每一行的状态都带 data-status;缺项一律 unavailable 并附限制 id,页面不出现任何分数、排名、百分比,也不出现「已执行 / 已完成 / 已加载」。

C. 没变的东西(这一版的护栏)

  • 证据模型与框架重构这两件事不新增路由 / 页面 / 落盘 / 依赖:宿主 13 条路由、1844 行一字未改;src/storage/ 7 个 1411 行未改。同一版的第四件事(桌面端导出)曾短暂破过这一条:加了第 14 条路由 POST /skill-trace/evidence-export(宿主 1844 → 1899 行)与 src/storage/evidence-export-writer.mjs(src/storage/ 7 个 1411 行 → 8 个 1520 行),当天全部撤回 —— 现状仍是 13 条路由 / 1844 行、7 个 1411 行,没有新页面、没有新导航项、没有新依赖、核心层一字未改。
  • 守卫 31 → 38 组:SKILL_EVIDENCE_MODEL_OK(三态 / 四段 / 三句不等式 / 漂移 / 边界 / 五类对象 / 五列 / 限制词表齐全)、SKILL_EVIDENCE_BINDING_OK(id 形状与可重放)、SKILL_EVIDENCE_CARD_OK(导航位置与卡内 needle)、SKILL_EVIDENCE_NO_CAUSATION_OK(归因禁令)、SKILL_EVIDENCE_EXPORT_OK(逐字节稳定 + 无禁止键)、SKILL_EVIDENCE_UNAVAILABLE_OK(空输入如实报缺)、SKILL_EVIDENCE_PRIVACY_OK(无绝对路径、无分数)。
  • 测试 643 → 657 项(新增 test/skill-evidence.test.mjs 13 项与一条渲染烟测)。
  • 触发评测顺延:仓库原先把 V1.2 一格登记为 Skill Trigger Evaluation,按用户 2026-10-06 的决定改由证据模型占 V1.2,触发评测顺延到 V1.3+(spec/PRD.md §5.13 FR-EVAL-018 与 spec/SDD.md 末尾路线行已同步)。

D. 框架重构:把「结构很多」改成「认知层级清楚」(V1.2 第二件事)

  • 只动界面:SkillFramework / FrameworkStructure / DeclaredWorkflow / ProgressiveDisclosure 四支组件与 .st-fw-* 样式。src/core/skill-framework.mjs 的解析结果、GET /definition 的数据、宿主与路由一个字都没动;界面仍是那一份确定性解析的三个子块,只是换了一种组织方式。
  • Skill Blueprint(蓝图):五站 WHY 定位 · Purpose → WHEN 触发 · Trigger → HOW 规则 · Rules → WITH 资源 · Resources → RESULT 输出 · Output,顺序与 kicker 由 const BLUEPRINT_STOPS = [ 逐字钉住。它是一条阅读路径,页面自己说「这是阅读路径,不是 Agent 的执行顺序。」每一站还配一句「它回答什么」(解决什么问题 / 什么时候使用 / 受什么约束 / 依赖哪些资源 / 最终产出什么)与真实的计数 —— 五个英文 kicker 加一个中文名字,读者不必先读懂下面那张地图。缺的那一站不消失,写成「未声明」——一个真的没有 verification 的 Skill,与一个界面忘了画 verification 的 Skill 必须长得不一样。
  • Structure Map(结构地图):八格,格子按核心层 FRAMEWORK_ROLES 生成(界面不自己维护第二份角色清单),每格写自己的 FRAMEWORK_ROLE_HINTS、一句它回答什么问题(st-fw-stop-hint)与最多三个小节标题;资源格写的是「N 个引用」(读者想知道依赖多少资源,不是它占了几节),且「声明 / 未声明」与蓝图用同一把尺子(declaredOf(entry.id))——同一屏两处口径不一致,读者只会更糊涂。选中哪一格是组件内的一个 state,不是路由、不是新页面、不新增 API。
  • Detail Preview(细节预览):只渲染选中那一格的小节(复用 FrameworkSection),默认只铺前三节(const DETAIL_SECTION_LIMIT = 3),剩下的折成按钮上的一句话 ——「还有 2 个章节」↔「收起」;带「最值得读的内容」与一句「为什么这样显示:只列这个角色在 SKILL.md 里真正命中的小节;没有的小节不在这里补出来。」没有任何小节时直说「当前没有独立结构块。」+「当前没有更多可展示的确定性内容,因此不制造额外解释。」「其它章节」不再占一格:认不出角色的章节从地图里拿出去(entries.filter((entry) => entry.id !== 'other')),改在框架末尾用一行一节的轻列表(OtherSections,data-role='other-sections')呈现标题 + 行号 + 原文开头,不丢、也不抢主视觉。旧的「把全部角色铺成一屏网格」被守卫反向钉住不许回来(className: 'st-fw-module' 出现即失败)。
  • 每个区域各自独立成卡:蓝图、结构地图、细节预览、声明流程、渐进披露、其它章节各是一张自带标题的卡(st-fw-region + st-fw-region-head),结构地图与细节预览并排成对且两列等高(st-fw-pair,≤980px 塌成一列;并排容器不带 align-items:start,细节卡自己撑满、内容区 flex:1,末句落在卡底 —— 右列跟着自己的内容伸缩时,底下那块空白会被读成「这里还没画完」);SkillFramework 外面那层「一个大边框包住几块、内部只靠一条分隔线分层」的写法已删除,.st-fw-sub / .st-fw-block 两个旧 class 一并删掉并由守卫反向钉住不许回来 —— 挤在一个边框里时,读者分不清「这是上一段的继续」还是「这是另一件事」。模块标题「Skill 框架」提到 14px,比卡标题高半级,一级 / 二级页面结构与数据一个字都没动。
  • Workflow Visualization:声明流程的步骤列表一字未改(点击仍是 flashAnchor 定位,不跳转、不打开运行图);空态补上「因此这里不制造一条运行流程图。没有声明流程,本身就是结构事实。」与一枚「不构造」标记 —— 这是运行图谱被删掉之后最容易悄悄长回来的地方。
  • Resource Pipeline:DISCLOSURE_CHAIN 六段不变,但每段从「只有名字的标签 + 箭头」改成带序号与一句说明的节点(st-fw-chain-order + st-fw-chain-label + st-fw-chain-hint:发现这个 Skill / 进入 Skill 定义 / 读到完整定义 / 确定资源基准 / SKILL.md 写到的引用 / 需要时再读)—— 说明按 chain id 从客户端的 DISCLOSURE_STAGE_HINTS 取,没写说明的 id 就不显示说明,不许现编;声明引用默认只列 6 条,多于 6 条才出现 展开全部 9 个 / 收起资源 的显式开关(rows.slice(0, 6) 由守卫钉住);分层标题只取自核心层的 tiers(正文自己声明的那一层),不自己再分一次;新增读数块 有读取证据 / 声明引用(如 0 / 23),把「声明了几条」与「有几条读取证据」并排摆出来,末尾仍逐字渲染 DISCLOSURE_NOTE(声明资源不等于已读取资源)。
  • 守卫:不新增组,第 20 组 SKILL_FRAMEWORK_OK 内新增 2b + 2c + 2d 三段(2b:蓝图五站逐字与五个 kicker、FRAMEWORK_ROLES.map((role) => {、localized('未声明', 'not declared')、framework-detail / workflow / disclosure / resource-toggle 四个 data-role、rows.slice(0, 6)、tiers.flatMap((tier) =>、st-fw-readstat,并反向钉住旧网格;2c:const DISCLOSURE_STAGE_HINTS = { 与 st-fw-chain-hint、'data-role': 'other-sections' 与 entries.filter((entry) => entry.id !== 'other')、const DETAIL_SECTION_LIMIT = 3 与 'data-role': 'detail-toggle'、declaredOf(entry.id) 同一把尺子与「N 个引用」、st-fw-stop-hint;2d:className: 'st-fw-region' 与 'st-fw-region st-fw-other'(每个区域各自成卡)、className: 'st-fw-pair' 与 st-fw-region-head(结构地图与细节预览并排成对、各带块标题、两列等高:.st-fw-pair 不带 align-items:start、.st-fw-detail 是 display:flex;flex-direction:column、.st-fw-detail-rows{flex:1;min-height:0;…}),并反向钉住 .st-fw-sub{ 与 className: 'st-fw-block' 不许回来)。守卫总数 38 组不变。
  • 测试:test/client-render-smoke.test.mjs 的框架那条改写成十一组断言(用 initialStructure 分别渲染 identity / rules / trigger 三屏,再把 OtherSections 单独渲染一次并进并集 —— 即「三屏角色 + 末尾其它章节」,等价于重构前那一屏,所以原有断言的语义一句没变),新增蓝图五句 hint、cards.length === 8 且地图里没有 other 格、「其它章节」那行不可点、st-fw-other-row 的 type === 'button' 等断言;渐进披露那条把 声明引用 2 个 · 已读取 0 个 改成 声明 2 · 读取 0、新增 0 / 2 读数与 st-fw-chain-order === ['01'…'06'] 断言;新增两条:the resource list expands on demand instead of stacking every path(9 条引用:收起只列 6 条、展开列 9 条、按钮文案与 aria-expanded 一起变)与 the structure detail shows the first few sections and counts the rest(5 节的角色默认只渲染 3 节、按钮写「还有 2 个章节」)。末尾新增第 11 组断言:st-fw-region 恰两张且 data-block 为 blueprint / map、st-fw-pair 恰一个、SkillFramework 渲染出的 st-fw-region 恰四张(其它章节没有内容时不出现)。测试 657 → 659 项。

E. 其余详情模块也各自成卡 + 选中态中性(V1.2 第三件事,用户界面反馈)

  • 「一个区域 = 一张卡」从框架推广到其余详情模块(用户 2026-10-06 的界面反馈:「基于 Skill 框架其他模块的,你也按照这种逻辑更新一下」)。四个模块从「一张长卡里靠分隔线分层」改成「一个区域一张卡」:SkillValidationPanel → 三张(结论 + 计数 + Profile + 发现 /「这次没有判定」/「这次验收查了什么、没查什么」——第三块原来折在 <details> 里,要读者点一下才看得到);SkillEvidenceCard → 六张(抬头 + 当前证据状态 / Evidence Identity / 证据链(三条边界留在这一张卡内当子区域)/ 证据状态表 / 漂移 / 本次评测);SkillEvaluationCard → 抬头卡 + 七张区域卡(① eval-case / ② eval-capture / ③ eval-runs / ④ eval-conditions / ⑤ eval-evidence / ⑥ eval-assertions / ⑦ eval-forbidden;空态是 eval-unavailable);SkillModificationPanel → 五张(改了多少 / 资源文件 / 超出修改范围 / 对比状态与边界 / 实例验收)。
  • 不拆的三个模块,以及为什么:Definition 本来就是三张独立 st-detail-card;SKILL.md 自带边框圆角、只有一张卡;「步骤证据」与「本次运行逻辑」各自只有一个区域、内部是重复行——给它们再套一层卡只会多出一个没有信息的边框。
  • 旧写法删除并反向钉住:.st-evidence-block(靠 border-top 把五块连起来)、.st-eval-section(靠 border-top + padding 连七块)、.st-mod-resources / .st-mod-out / .st-mod-state / .st-mod-instance 的 padding-top 全部删除;卡内小节标题改由新的 .st-detail-card h5 承担(承接原 `.st-eval-secti...
Read more

v1.1.0 — 详情级导航 + Agent Skills 开放标准

Choose a tag to compare

@PolinniZhong PolinniZhong released this 06 Oct 01:36

1.1.0 — 2026-10-06 · 详情级导航 + Agent Skills 开放标准

这一版只做两件事,而且做的都不是新能力:把 V1.0 那条纵向长详情页重构成「按认知维度直达」的 Skill Inspector,再把 Agent Skills 开放标准从各平台 Profile 里分出来单独判。 信息不减少、能力不删除、不新增路由 / 页面 / 落盘 / 依赖、宿主 src/dsh/host/index.js 一字未改。

发布实测(1.1.0 口径):npm test 643 项全绿(pretest 会先重建 dist/client.js)· npm run verify 31 组 OK(第 31 组是新增的 SKILL_STANDARD_ALIGNMENT_OK)· 客户端 src/dsh/client/client.js 4495 行 · bundle dist/client.js 234965 字节(source hash 71125cf37d020c7c)· src/core/ 32 个模块 / 11945 行 · src/storage/ 7 个 1411 行 · 宿主仍是 13 条路由(src/dsh/host/index.js 1844 行,未改)。

A. Skill Detail 信息架构重构(详情级导航)

  • V1.0 是纵向长页面,V1.1 是「左侧详情导航 + 右侧当前模块」:SkillDetailPage 里新增 DETAIL_MODULES(八个 id + 中文名 + hint)与 DetailNav 组件;右列由 MODULE_CONTENT[activeModule] 决定只渲染当前这一维,不再向下累积。默认进入 Skill 框架(React.useState(initialModule ?? 'framework') —— initialModule 是渲染烟测用的第五条注入缝,不传时就是真实用户看到的第一屏)。
  • 八个模块:Skill 框架(默认,结构 + 声明流程 + 渐进披露,来自 SKILL.md 的结构化解析,不是 Runtime Flow)/ Skill 验收 / Skill 评测 / 步骤证据 / 本次运行逻辑 / 本次修改对比 / Definition(事实列:身份 + 定义 + repository)/ SKILL.md。V1.0 的能力全部保留,只是换了呈现位置:Evaluation Case / Run / Comparison / Runtime Evidence 仍在「Skill 评测」卡里,Lineage / Diff / Translation / Clone / Modify 仍在 Definition 与「本次修改对比」里。
  • 「本次修改对比」没有事务时,导航与内容一起收窄:hasModification = Boolean(modifyState) && modifyState.phase !== 'idle'、availableModules = DETAIL_MODULES.filter(([id]) => id !== 'modification' || hasModification)。第一版做过的常驻空态卡 modificationEmpty 已删除 —— §9.2 的老规矩是「没有修改事务时这一块整块不出现」,点进去才是空的导航项也一样会被读成一种状态。点到这一帧不存在的维度会落回 Skill 框架。
  • 新增 2 个渲染烟测段(2e 导航 / 2f 收窄):detailFrames() 把八个维度各渲染一次取并集,所以重构前那条大烟测的断言一句没改、语义不变。DetailNav 进了 module.exports.__views(离线渲染 + 逐个调用 onClick 验证真的切维度)。

B. Agent Skills 开放标准对齐(标准合规 / 平台兼容 分离)

  • SKILL_PROFILE_IDS 由 5 个变 6 个,插入 standard(Agent Skills Open Standard),与 DSH / OpenAI / Anthropic / Microsoft 四个平台 Profile 分离:新增 skillProfileKind()(standard / platform)与 SKILL_PROFILE_KIND_LABELS(标准合规 / 平台兼容),界面按 kind 分组显示并给每条 finding 打上 data-source-kind。
  • 新增 SKILL_RULE_SOURCES(5 条静态 provenance):agentskills / dsh / openai / anthropic / microsoft,每条带双语 label 与 reference;skillRuleSourceView() 把它解析成界面上那一行「来源:… · 标准合规」。不在线拉标准文档(守卫禁 fetch( / https://agentskills / XMLHttpRequest),也不允许笼统写成「四个平台都要求」。
  • CORE-DIR-001(error):name 必须与父目录同名 —— 已知目录且一致 → pass;不一致 → error;拿不到目录 → skipped / reason: 'no-directory-name'。DSH 能加载 ≠ 标准通过:这条只在 standard 层报,profiles=common,dsh 时它不出现在结果里,profiles=common,microsoft 时是 MS-DIR-001 在报。它报错时的那句话特意写明「与 DSH 能不能加载这个 Skill 无关」。
  • CORE-LIC-001 / CORE-META-001 / CORE-TOOLS-001:对 license / metadata / allowed-tools 做形状校验(嵌套、块式映射、流式映射),三个字段缺失一律 skipped / reason: 'optional-field-absent',不是错;allowed-tools 属 experimental,severity 是 info。判「写成映射」时用 ctx.scan.entries.some((item) => item.path.startsWith('allowed-tools.')) —— 只看 entry.opens 会把正确的块式列表也判成打开状态。
  • 规则总数 32 → 36 条,每条都有 note;SKILL_VALIDATION_LIMITATIONS 新增一条同时含「标准合规」「平台兼容」,明说这是两件事。

测试与守卫

  • 新增第 31 组守卫 SKILL_STANDARD_ALIGNMENT_OK(scripts/verify-project.mjs,插在 SKILL_VALIDATION_OK 与 SKILL_MODIFICATION_OK 之间):钉住标准层 / 平台层的交集为空、并集等于 SKILL_PROFILE_IDS、standard 在标准层、逐 id 的 skillProfileKind()、CORE-DIR-001 的 profile/severity/note、MS-DIR-001 仍归 microsoft、每条规则的 source view 三字段齐全、agentskills 的中英文 label 都是 Agent Skills Open Standard、平台来源恰好 4 条、禁止 ['all','*','every','platforms'];并做行为断言(同名不报 / 不一致报 needs-fix / directoryName: null 时 skipped / 三个可选字段缺失时都是 skipped 且 CORE-TOOLS-001 severity 是 info / 形状写错才报 / 客户端 VALIDATION_PROFILE_KINDS 与核心 SKILL_PROFILE_KINDS 一致)。
  • 守卫改写:SKILL_FIRST_DETAIL_OK(默认模块 + DetailNav + 八个 id 逐字 + modules: availableModules)、SKILL_FRAMEWORK_OK(原来的「验收 → 本次修改对比 → 评测 → 框架 → 运行逻辑 → 步骤证据 → SKILL.md 纵向顺序」断言换成 MODULE_CONTENT 的映射逐项断言)、SKILL_MODIFICATION_OK(改成钉住 if (phase === 'idle') return null 且源码里不许出现 modification-empty)。
  • 新增 7 条单测(test/skill-validation.test.mjs 末尾,统一用 profileIds: ['common','standard','dsh']):CORE-DIR-001 三态、directoryName: null → skipped、标准层与 common,dsh 在同一输入上的相反结论、profiles[].kind 与 kindLabel、规则 provenance 齐全、三个可选字段缺失全 skipped、形状写错才报。
  • test/skill-validation-route.test.mjs:profileIds 默认期望改成 ['common','standard','dsh'](/skill 与 /definition 两处);原来那条「DSH 不看目录名,Microsoft 明文要求同名」重写成三段式(默认 → CORE-DIR-001 报且 MS-DIR-001 不在 / common,dsh → pass 且 CORE-DIR-001 不在 / common,microsoft → MS-DIR-001 报且 CORE-DIR-001 不在),标题改为「同一个 Skill 在标准层与平台层得到不同结论」。
  • 测试 636 → 643 项(+7),守卫 30 → 31 组(+1);样例 legacy-skill(目录名 legacy-name:与 frontmatter 的 name 不一致)在默认 Profile 下的结论由 pass 变成 needs-fix —— 这是这一版最要紧的那条语义变化,不是回归。

明确没做

  • 没加一级导航、没加 Skill Dashboard、没加独立 Analytics 页面;没加自动评分 / 排名 / Benchmark 总榜 / 聚合指标 / 自动模型裁决;没有恢复旧版「运行流程图」「运行图谱」(buildRuntimeGraph / computeRuntimeLayout 仍被反向钉住);没在线拉标准文档;没自动改 Skill;没新增路由 / 依赖 / 落盘。
  • Evaluation 的「不打分、不排名、不画趋势」原则一个字没动:V1.1 只让用户能直接点进「Skill 评测」,没往它里面加任何东西。

需求见 spec/PRD.md 的 V1.1 段;设计见 spec/SDD.md §0.1 的 D15 与 §18 / §27、docs/ARCHITECTURE.md 的版本口径段。

v1.0.0 — Skill 评测(Evaluation Case / Run / 对照 / 四段证据)

Choose a tag to compare

@PolinniZhong PolinniZhong released this 05 Oct 15:46

1.0.0 — 2026-10-05 · Skill 评测

这一版把 V0.10.0 那份一次性生成的实例验收任务固化成可重复、可对照的评测记录。 同一个 Case 可以跑很多次:每次运行记下条件(模型 / Provider / 推理档位 / 上下文窗口 / 插件版本 / 会话日志游标)与可观察到的事实(四段证据:触发 / 加载 / 使用 / 结果),然后把两次运行摆在一起逐条对照。它不是评分器:不给分、不排名、不做 benchmark、不产出任何聚合指标,也不替你跑任务 —— 插件只摆证据,判定由人给(没给就是「无法判断」,沉默不折算成「未通过」)。

发布实测(2026-10-05;V1.0 的版本号由用户定为 1.0.0):npm test 636 项全绿(52 个测试文件)· npm run verify 30 组 OK(第 30 组 SKILL_EVALUATION_OK)· 客户端 src/dsh/client/client.js 4284 行 · bundle dist/client.js 228715 字节(source hash 54bee3888d0299d8)· src/core/ 32 个模块 / 11657 行 · src/storage/ 7 个 1411 行 · 宿主 13 条路由(src/dsh/host/index.js 1844 行)。

  • Evaluation Case(src/core/skill-evaluation.mjs,565 行零依赖纯函数,客户端第 9 支 require):Case 就是「一个可重复的实验」——它的身份 caseId = sha256(六行哈希输入),六行是 域 / generator:1.0.0 / skill:<名> / fingerprint:<sha256|unavailable> / scopes:<范围> / prompt:<任务正文>;哈希在宿主算,生成器保持零 import(客户端要 require 它)。同一份输入永远得到同一个 caseId。Case 绑定的是生成它的那一版 Skill:文件再改一次,旧 Case 不是「过期」,而是另一个 Case。
  • Evaluation Run(run-capture):条件与事实全部由宿主从会话日志与收据里取,客户端只提交 judgements 与 outcome。取数层 src/core/run-conditions.mjs 是零时钟纯函数:只取四个只读元数据字段(同一条 request/header 事件里的整份工具清单与 maxTokens 一个字节都不带出来)、只数工具名与次数(不碰参数与结果)、turn/step 只作日志顺序游标。DSH 版本今天没有已验证的读取方式,如实写 unavailable(FR-EVAL-008)。
  • 四段证据与三条不等式:触发 / 加载 / 使用 / 结果,每段都写明「这一段够不着什么」,并在界面上固定陈述 加载 ≠ 使用 / 使用 ≠ 结果 / 结果 ≠ 这个 Skill 造成的。「加载」是协议级事实(正文进入了模型可见的对话),不证明模型采用了它;「使用」只报工具活动的元数据,有工具活动 ≠ 用了这条指令。
  • 对照只在一个 Case 内做:同一个 caseId 的两次运行、实验条件相同、只有被观察到的指令指纹不同 ⇒ 「改前 · 改后」(或基线 · 加上 Skill);条件差一项就不给对照并说清差在哪 —— 差异说不清是谁带来的,这一页就不说。对照列只用事实词(两次都通过 / 改前未通过 → 改后通过 / 两次都无法判断),永远不折算成分数,也不排序。
  • 落盘(新增,用户 2026-10-05 授权):<dataRoot>/evaluation/cases/<hex>.json 与 runs/<hex>/<runId>.json,目录 0700 / 文件 0600,临时文件 + 原子 rename;写入前深层扫描禁字段(sessionId / 工具参数与结果 / 绝对路径 / 任何聚合量,命中就拒绝而不是悄悄丢掉),读端 fail-soft(坏文件计数跳过)。删一个 Case 连它的运行一起删;上限 200 个 Case / 每个 Case 50 次运行,超出按最旧淘汰。这是本插件第一处把用户自己的话写进磁盘(Run 里的结果与判定),所以 docs/PRIVACY.md 单列了一节。
  • 第 13 条宿主路由 POST /skill-trace/evaluation:八个动作(case-save / case-list / case-read / case-delete / run-save / run-capture / run-list / run-read)。sessionId 必须校验,但一个字节都不落盘 —— 实验的身份是 caseId,不是会话。它不建会话、不发消息、不调模型、不自动跑。
  • 界面:详情页内多一张「Skill 评测」卡(不新增页面、不新增导航),七段:Evaluation Case / 记录这一次运行 / 已经记下来的运行 / 两次运行的条件 / 运行时证据 / 断言与对照 / 「这一版刻意不出现的东西」。判定只有三态(通过 / 未通过 / 无法判断),来源只有三级(协议事实 / 用户判定 / Agent 自报)。
  • 工程数字:测试 590 → 636 项(52 个文件);守卫 29 → 30 组(新增 SKILL_EVALUATION_OK,它钉住卡片区不许出现任何聚合口径,变异测试验证过会红);客户端 3625 → 4284 行、dist/client.js 189256 → 228715 字节(source hash faed5e9cef7db24c → 54bee3888d0299d8);src/core/ 30 → 32 个模块 / 10924 → 11657 行;src/storage/ 6 → 7 个模块 / 1117 → 1411 行;宿主 1625 → 1844 行 / 12 → 13 条路由。
  • 需求 FR-EVAL-001–018 见 spec/PRD.md §5.13;设计见 spec/SDD.md §22;三个真 bug 的修法见提交 2e9902c(观察到的指纹不再永远 unavailable)、222896e(插件版本真的读得到)、88e6e2f(指纹只写一层 sha256: 前缀)。
  • 样式表归属(2026-10-05 真机报障后补):样式表此前只由插件上下文的 ctx.effect 安装与摘除。宿主重启或 HMR 重连会把插件上下文拆掉,而屏幕上那一屏还挂着 —— 样式跟着上下文一起消失,界面就留成「有 DOM、没样式」(用户当天的报障正是这个形状:已安装 Skill 的隐藏标题变成大标题、搜索框被挤到下一行、卡片退回普通按钮;那一屏出自 V1.0 期间的某次中间构建,仓库里任何已提交 / 已发布版本都渲染不出来)。现在渲染 <section data-plugin="dsh-skill-trace"> 的组件在每次渲染前做一次幂等的 ensureStylesheet()(一张都没有才补一张),installStyles() 的归属规则与它的三条守卫原样保留;scripts/verify-project.mjs 新增两条字面守卫钉住这条缝。

v0.10.0 — Skill 实例验收

Choose a tag to compare

@PolinniZhong PolinniZhong released this 04 Oct 23:48

0.10.0 — 2026-10-05 · Skill 实例验收

这一版把「刚改的 Skill 在真实任务里到底有没有按预期工作」做成一屏可以交给 Agent 去跑的任务。 它落在详情页「本次修改对比」卡里:只有存在一次真实的本次修改时,才出现 [生成实例验收]——按这次改动的范围与当前 SKILL.md 生成一份确定性的测试 Prompt(同一份输入永远得到同一份 Prompt),连同「预期观察点」一起交给用户;用户复制 Prompt、去一个新的 DSH 会话里跑,自己看结果。

发布实测(2026-10-05):npm test 590 项全绿 · npm run verify 29 组 OK · 客户端 src/dsh/client/client.js 3625 行 · bundle dist/client.js 189256 字节(source hash faed5e9cef7db24c)· src/core/ 30 个模块 / 10924 行 · src/storage/ 6 个 1117 行 · 宿主 12 条路由(src/dsh/host/index.js 1625 行,这一版一字未改)。发布平台与 0.9.2 一致:GitHub Release(tag v0.10.0)与 npm 是同一份构建,npm 包名仍是 dsh-skill-trace。发布结果见 docs/RELEASE.md §6.0。

  • 新模块 src/core/skill-instance-test.mjs(571 行,零依赖纯函数),客户端第 8 支 require;不调模型、不读时钟、不掷骰子、不落盘,同一份输入永远得到逐字节相同的结果。任务句不照搬 description,而是写成一句真实的指派;过长的文字走 clip() 按词边界截断(此前会切出 …to avo. 这种半个单词)。
  • 两个产物物理分离:prompt.text 交给 Agent(四块:任务 / 工作目标 / 输出要求 / 注意),observations 交给用户(全是疑问句,一个字都不进 Prompt——否则等于在 Prompt 里重新教 Agent 该怎么做)。
  • 不新增路由(宿主仍是 12 条):原计划的 POST /skill-trace/instance-test 已取消(spec/PRD.md FR-INST-015);同时不新增一级/二级页面、导航与第二套 Runtime。
  • 不运行、不判定、不打分:插件只生成任务与观察项,最多说「已生成实例验收任务」;没有 成功率 / 评分 / 修改有效 这类结论。
  • 可追溯(规格 §21 的核心验收标准):结果里带一份 trace——这次生成真正在场的那几项输入(意图 / 范围 / 对比 / 描述 / 框架 / 静态验收)与主范围从哪来;详情页的「这个任务是怎么来的」把这两句念出来。用户意图只用来对齐任务形状,原文一个字节都不进 Prompt;回归约束优先取自框架里没被这次 diff 触及的声明能力(拿不到就如实进 limitations,缺框架/缺静态验收不让生成变成不可用)。
  • 工程数字:测试 561 → 590 项;守卫 28 → 29 组(新增 SKILL_INSTANCE_TEST_OK);客户端 3448 → 3625 行、dist/client.js 170324 → 189256 字节(source hash 2d202a05030a1d58 → faed5e9cef7db24c;clientSourceHash() 只哈希 src/dsh/client/,所以改 src/core/ 会动字节数、不动这个 hash);src/core/ 29 → 30 个模块 / 10353 → 10924 行;宿主 1625 行 / 12 条路由不变。
  • 需求 FR-INST-001–026 见 spec/PRD.md §5.12;设计见 spec/SDD.md §21 与 docs/ARCHITECTURE.md 的 ## V0.10.0 — Skill Instance Test。

v0.9.2 — Skill 验收 / Skill Modify / 已安装列表排序

Choose a tag to compare

@PolinniZhong PolinniZhong released this 03 Oct 02:26

0.9.2 — 2026-10-03 · Skill 验收 / Skill Modify / 已安装列表排序

这一版把三批攒着的改动一次性发出去:V0.9.0「Skill 验收」/ V0.9.1「Skill Modify」/ V0.9.2「已安装列表排序」。 主线是「Skill 声明得对不对、能不能改、按什么顺序排」。

发布平台与 0.8.0 一致: GitHub Release(tag v0.9.2)与 npm(beta 与 latest 都指向 0.9.2),同一份构建;npm 包名仍是 dsh-skill-trace,不改名。三批都没有新增一级或二级页面——一级仍是「本次 Skill」与「已安装 Skill」,详情页仍是唯一二级页;只多了一条宿主路由(POST /skill-trace/modify)与三块界面(静态验收卡、本次修改对比、已安装列表排序)。

发布实测(2026-10-03):npm test 561 项全绿 · npm run verify 28 组 OK · 宿主 12 条路由(src/dsh/host/index.js 1625 行) · 客户端 src/dsh/client/client.js 3448 行 · bundle dist/client.js 170324 字节(source hash 66dd0b76118e7b85) · src/core/ 29 个模块 10353 行 · src/storage/ 6 个 1117 行。三版都没有新增一级或二级页面,一级仍是「本次 Skill」与「已安装 Skill」,详情页仍是唯一二级页。

一、V0.9.0「Skill 验收」:这个 Skill 现在符不符合规范

把一件此前只能靠读正文判断的事,做成一屏确定性事实:src/core/skill-profiles.mjs(590 行)定义五个 Profile —— common / dsh / microsoft / openai / anthropic,默认勾选 common + dsh;src/core/skill-validation.mjs(1025 行)按规则 id 逐条判定。

  • 结论只有三态:通过 / 需要修正 / 无法判断。未知 Profile、读不到正文、规则无法判定一律进「无法判断」,不往「需要修正」推;警告(warning)单独列,也不把结论推向「需要修正」。
  • 每条发现都落在「规则 id + 标题」上(如 CORE-FM-001),不是一句模型的评语——这一层没有任何模型调用,也不产生分数、百分比或排名。
  • 不新增路由:validationFor() 由 GET /skill 与 GET /definition 两条既有路由共用,宿主仍是 11 条(v0.9.1 之后才是 12 条)。
  • 详情页最上方多一张「Skill 验收」卡,四层顺序因此变成:验收 → 框架 → 本次运行逻辑 → 步骤证据 → SKILL.md(v0.9.1 又把「本次修改对比」插到验收之后)。
  • 工程数字:守卫 25 → 26 组(第 26 条 SKILL_VALIDATION_OK);npm test 474 → 510 项(其中 36 项是这一版新增);客户端 2735 → 2910 行、dist/client.js 142672 → 150750 字节。

二、V0.9.1「Skill Modify」:让当前会话的 Agent 去改,插件不代办

这是这个插件唯一一处会碰会话 Agent 的地方,也是第二条「不写文件但不是纯读」的宿主路由(第一条是 POST /skill-trace/clone)。

  • 新增第 12 条路由 POST /skill-trace/modify,它只做两件事:把「改前」的 SKILL.md 与来源指纹存进宿主内存;用当前会话的 agent.followup() 代发一条 source.kind='skill-intelligence-modify' 的消息。插件自己不写任何文件——真正改文件的是 Agent 用 DSH 的原生工具,用户点「交给 Agent」这个动作本身就是授权。
  • 快照只在内存里的四条硬边界:不落盘、不进 receipt、不进 session log、重启即消失;键是 ${sessionId}\u0000${skillName},TTL 30 分钟、上限 32 条(src/storage/modification-snapshot-store.mjs,157 行);丢了就直说「本次修改前状态不可用」,不许造假。
  • 代发的消息里明确要求「动手之前先说明打算怎么改;拿不准就用提问工具问用户」,并要求做完后回读改动、在给出验收结论前不要说「改好了」——「提出方案 → 用户确认 → 动手」全部留在原生对话里,插件只提要求,既不代办也不解析回复。
  • 六个修改范围 id:skill-md-rules / skill-md-workflow / skill-md-description / references 可选;scripts 与 assets 在契约里 locked: true,不可勾选。范围外的正文改动会被如实报成「超范围」,某个被点名的范围读不出内容时显示无法判断(SECTION_ALIASES 只按小节标题里的 rule(s) / 规则 / 约束 与 workflow / 流程 / 工作流 / 步骤 匹配,匹配不上就是匹配不上)。
  • 八条失败路径都有人话:invalid-request / missing-intent / unknown-skill / session-not-live / skill-file-unreadable / registry-unavailable / snapshot-failed / dispatch-failed(代发失败先释放快照)。
  • 来源指纹变了只说一句话:「来源 Skill 在本次修改期间发生变化」——永远不许说「Agent 修改了来源」,因为没有直接证据。
  • 界面:详情页「Skill 演进」卡多了 [修改 Skill],打开一个对话框(意图 + 范围 + 验收目标);没有修改事务时「本次修改对比」整块不渲染(常驻的空卡会被读成一种状态);改完点「对比本次修改」才产生对比,并把新的验收结论顺手带回详情页。
  • 工程数字:守卫 26 → 27 组(第 27 条 SKILL_MODIFICATION_OK);npm test 510 → 552 项,登记时又补一条「范围外不重复报」→ 553 项;客户端 2910 → 3356 行、dist/client.js 150750 → 167506 字节。
  • 真机:2026-10-03 用一次真实的修改走通了「代发 → Agent 改文件 → 回读」这一段(目标是本机一个 PRD 类 Skill,六处改动落盘并逐条回读,HISTORY.md 与 evals/ 未被触碰)。插件侧的「对比本次修改」验收结论与 §9.2 那套走查清单还没走。

三、V0.9.2「已安装列表排序」:按「什么时候出现在本机」倒序

需求是一句大白话:刚复刻出来的那个,应该排在第一个。

  • 规则名 added-desc-then-name(INSTALLED_ORDERING_RULE):按「这个 Skill 什么时候出现在本机」倒序,读不到时间的按名称排在最后;规则名随 ordering.rule 下发,客户端只念不排。
  • 时间取的是候选根下 Skill 目录的 birthtimeMs,不是 mtime、不是 ctime、也不是文件级 —— 文件级的 mtime 会随每次编辑漂移,那量的是「最后一次改」而不是「加入本机」。三条排除理由与实测都写在 spec/SDD.md §20.1。
  • 读不到就说读不到:69 个 Skill 里有 35 个读不到目录的 birthtime(它们的根目录不在插件的候选根里,且 DSH 给插件的目录记录不携带路径)。列表头因此逐字说「另有 35 个 Skill 读不到加入时间,按名称排在最后。」——不编造时间,也不用文件时间凑数。响应里新增 ordering: {rule, addedAtKnown, addedAtUnknown},投影新增 addedAt 与 lineage,limitations 新增 added-at-unavailable / added-at-partial / lineage-unavailable 三个码。
  • 卡片元信息只说例外(FR-ORD-013):日期只到 MM-DD(跨年才带年份、不显示时:分 —— 「谁更新」由顺序回答),有血缘才多一行 复刻自 X,默认成立的调用方式与 filesystem 一个字都不写。这条改法是 2026-10-03 用户看过截图后提的:界面上那两个标签(「模型可调用」「可用 /name 调用」)在 69 个 Skill 上恒为 true、一个都不区分,那就只在严格 === false 时出字;filesystem 是默认 provider,不写。
  • 新增只读读盘两处(skillAddedAtByName() / skillRootCandidates(),都在 src/storage/skill-clone-writer.mjs,369 → 424 行),都不抛错,路由仍恰好 12 条。
  • 工程数字:守卫 27 → 28 组(第 28 条 INSTALLED_ORDERING_OK);npm test 553 → 561 项;客户端 3356 → 3448 行、dist/client.js 167506 → 170324 字节(source hash 66dd0b76118e7b85)。
  • 渲染台用真实客户端 bundle + 真实会话载荷(69 个 Skill、coverage=complete)截了图逐字核对:第一行是刚复刻的那个,列表头如实报出那 35 个,整页没有时:分、没有默认标签、没有 filesystem。
  • 宿主重启后真机原生返回 ordering(这是渲染台那张截图证明不了、现在由真机探针补上的证据):真机载荷是 73 个 Skill / 34 个有时间 / 39 个读不到——比渲染台那次多出 4 个,且全是读不到时间的那种(有时间的仍是 34 个)。拿实现里同一支 compareInstalledSkills 在真机响应上重排比对,逐位一致:有时间的 34 个在前且递减,无时间的 39 个按名称升序排在末尾。

四、真机验收与还没覆盖的一层

以上都是源码文本守卫 + 无头浏览器渲染台 + 一次真实的修改链路。真机走查(AGENTS.md §9.2 那份清单)已于 2026-10-03 由用户逐项确认通过(宿主半边改过,先重启了 DSH、再硬刷新页面)。真机探针在同一台机器上核对的事实:

  • GET /skill-trace/catalog 原生返回 ordering:{"rule":"added-desc-then-name","addedAtKnown":34,"addedAtUnknown":39},limitations:["added-at-partial"],coverage=complete;
  • 拿 src/core/installed-view.mjs 里同一支 compareInstalledSkills 在真机响应上重排比对,逐位一致;四条不变式全部成立(有时间的都在前、时间递减、无时间的 39 个按名称升序在末尾、skills.length === totalCount === 73);
  • POST /skill-trace/modify 在跑的进程里(空体返回 invalid-request,不是 404),四条已删路由(runtime-graph / receipt / context-check / backup)仍全部 404;
  • 浏览器实际拿到的客户端与本地 dist/client.js 只差一行 sourceMappingURL,双方都带构建戳 66dd0b76118e7b85,加入本机 在、模型可调用 / 可用 /name 调用 不在。

仍然没覆盖的两件事:那 39 个 Skill 读不到目录 birthtime 的根因尚未定位(界面已如实报数,需要 DSH 提供带路径的 Skill 记录才能补上时间);渲染台脚本里仍留着「宿主没重启时在本地补齐 ordering」的临时补丁,所以那张截图不能当作「运行中的宿主已返回 ordering」的证据(这一条已由上面的真机探针补上)。

发布范围与实测

  • 这一版装了什么:新增 1 条宿主路由(POST /skill-trace/modify,共 12 条)、3 个 src/core/
    模块(skill-validation.mjs / skill-modification.mjs / skill-profiles.mjs)与 1 个 src/storage/
    模块(modification-snapshot-store.mjs),外加详情页的两块新界面(静态验收卡、本次修改对比)与
    已安装列表的排序规则(added-desc-then-name)。三批一起走,版本号由用户定为 0.9.2。
  • 发布平台:GitHub Release(tag v0.9.2)与 npm(beta 与 latest 都指向 0.9.2),同一份构建。
    npm 包名仍是 dsh-skill-trace,不改名。
  • 实测数字(npm test / npm run verify / wc -l / wc -c):测试 561 项全绿 · 守卫 28 组
    全 OK · 客户端 src/dsh/client/client.js 3448 行 · bundle dist/client.js 170324 字节
    (source hash 66dd0b76118e7b85)· 宿主 src/dsh/host/index.js 1625 行 / 12 条路由 ·
    src/core/ 29 个模块 10353 行 · src/storage/ 6 个模块 1117 行 · dependencies 仍为空。
  • 实际发布结果(2026-10-03):见 docs/RELEASE.md §6.0 —— 发布提交 / tag、GitHub Release、
    npm 的 gitHead 与 shasum、净室安装、推送区间都在那张表里。

v0.8.0 — Skill 演进:复刻出来的东西,现在能倒着看回去

Choose a tag to compare

@PolinniZhong PolinniZhong released this 02 Oct 12:52

0.8.0 — 2026-10-02 · Skill 演进:复刻出来的东西,现在能倒着看回去

这一版是 minor:V0.8「Skill 演进」是主题,另外两件搭同一班车。 三件互不相干的改动一起发:

  1. V0.8「Skill 演进」(主线):0.7.0 把「复刻」做出来之后留下的那半——复刻出来的副本与它的来源
    现在可以比对了。三个 Phase 都已落地,见下面的 ### Added。
  2. 短显示名(3a1bf0a):命名体系调整,不是功能改动。产品品牌仍是 DSH Skill 智能实验室 /
    DSH Skill Intelligence
    ,技术层 Skill Trace 与 npm 包名 dsh-skill-trace 一个字没动。变的只有
    DSH 会话里那个高频入口的短名:工作栏标签(conversation.view slot 的 label)从长品牌名改为
    Skill 洞察 / Skill Insight,面板的 aria-label 同步;README 首屏与命名分层表把三层名字写清楚。
  3. 「复刻 Skill」的请求体缺陷(55f092c):0.7.0 起那个按钮一次都没成功过。这是一个功能
    可用性缺陷,不是文案改动——见下面的 ### Fixed。

发布范围:GitHub Release 与 npm 同时发布(0.5.0 与 0.6.0 只在 GitHub,0.6.1 起每一版都是两边)。
一个版本号 0.8.0 把三件一起带上,不拆成「先发一个补丁版、再发一个特性版」。npm 包名仍是
dsh-skill-trace——已发布,改名会让安装命令与 github: 锚点全部失效。

Added — V0.8「Skill 演进」:复刻出来的东西,现在能倒着看回去

0.7.0 的复刻只回答了「怎么造一个副本」。它没有回答制造者自己的问题:这个副本是从哪一版来源
复刻出来的,现在还是不是那一版?两者到底哪里不一样?
V0.8 就是这三问,一次加一条路由、一张卡、
一个面板,没有新增一级或二级页面。

判据和前面每一版一样:界面上说的每一句话,要么来自 SKILL.md 的声明,要么来自两侧文件确实读到
的事实。
这一版把它推到最容易破的地方——「比较两个 Skill」天然会引诱你去总结、去打分、去说
「这个版本更好」:

  • 只输出事实词,不输出判断词。 差异的每一行只有 新增 / 删除 / 修改 / 保持不变 / 无法比较,
    界面里不许出现「更合理 / 更完整 / 建议 / 应该 / 有问题 / 更好」这类词。守卫 25 的客户端那半
    把那段界面切片抽出来扫一遍禁用词——而且先去注释再扫,因为用户读得到的是字符串,不是注释。
  • 读不到来源时,绝不说「没有变化」。 来源不可读、指纹不可得、比较被截断,这三种情况面板
    直说「当前无法读取来源 Skill,无法完成差异比较。」并不渲染任何列表。一张空表会被读成
    「两边一样」,这是这个功能最坏的失败方式。
  • 说不清就说说不清。 来源指纹三态(变了 / 没变 / 说不清)逐字写在卡上,比较本身也有
    section-list-truncated、section-too-large-to-compare-line-by-line、diff-lines-truncated、
    resource-list-truncated 这些 limitation,逐条翻译成人话,不显示裸代码。

三个 Phase:

  • Phase 1 · 血缘:复刻成功时把「来源名 + 来源指纹 + 复刻当时的模式」记进
    src/storage/skill-lineage-store.mjs(0700/0600、原子 rename、不记绝对路径)。
    血缘不另外开路由——它随 GET /skill-trace/skill 的详情响应一起回(lineage 字段)。纯逻辑在 src/core/skill-lineage.mjs。
  • Phase 2 · 差异:新增 src/core/skill-diff.mjs 与 GET /skill-trace/diff,三层比较
    结构 / 内容 / 资源。资源那层刻意不把 SKILL.md 算进去(它属于内容层),并且如实说出
    「整包复刻没覆盖到的资源,两边都会有差异」——差异的来源是截断,不是内容变了。
  • Phase 3 · 界面:详情页左栏新增「Skill 演进」卡(只对由本插件复刻出来的 Skill 显示;不是复刻
    来的就逐字说「这个 Skill 不是由本插件复刻出来的。」),它打开一个 720px 的「Skill 差异」面板,
    三个 Tab —— 结构 / 内容 / 资源。面板自己带焦点陷阱(Tab 在首尾之间循环、Esc 关闭且
    stopPropagation)与「焦点进入 / 关闭后回到『查看差异』」两段焦点行为。读取失败的两种状态
    (路由错误 / 来源不可读)都带 role="alert",因为「读不到来源」是要被辅助技术说出来的,
    不是画一行灰字。
  • Phase 3 · 「读不到」与「没有」分开说(AGENTS.md §6.11):宿主把插件读进内存之后不会自动换代码,
    所以「客户端已经是 v0.8、宿主还是 v0.7」是插件升级的正常路径(§6.2 / 附录 B)。那时详情响应里
    根本没有 lineage 这个键,而「确实没复刻过」是 lineage: null——两者都被 detail?.lineage ?? null
    合并成了同一句话,对着一个真的复刻过的 Skill 说「不是由本插件复刻出来的」。现在用
    hasOwnProperty.call(detail, 'lineage') 分开:键不在就说「这次详情响应里没有血缘字段。宿主可能
    还没换到这一版的代码,重启 DSH 后再试。」。这条是探测运行中的老宿主时发现的,不是测试发现的
    ——§8.8 的老话:渲染台通过 ≠ 用户看到。
  • Phase 3 · 「还没问」也不是「读不到」:详情页要先去读 /skill 拿血缘、再拿 lineageId 去问
    /diff,中间那段往返里 comparison.source 还不存在。演进卡原先只拿到 source,于是那个 null
    被 diffSourceState 落进第一格——每一次打开副本详情页都会先蹦出一句「无法读取来源」,再眼看着
    它自己改口。同一个 null 有两种成因,所以卡现在也拿到 diffPhase:loading 说「正在读取来源…」
    (data-changed="pending",颜色 --st-faint,视觉上也不像一个答案)、error 说「读取来源失败」、
    只有 ready 才把宿主的观测原样说出来。这是这一版里第二处「读不到」与「没有」分不清的地方
    (第一处见上一条),而它同样不是测试发现的——是拿着用户真实的复刻(deliver-prd-custom-custom)
    走查时,顺着「卡上那句话到底对不对」这个问题读代码读出来的。

    写这条守卫时又空转了一次:断言最初写成 clientCode.includes('evolutionSourceState(source, diffPhase)'),
    而这个子串在函数签名里就有一份,把调用点换回 diffSourceState(source) 照样为真。改成整行
    调用点才红——断言必须落在调用点,不能落在函数名上(§8.10 的第四次)。

工程数字:客户端 src/dsh/client/client.js 2348 → 2735 行,宿主 src/dsh/host/index.js
1143 → 1292 行,bundle dist/client.js 129293 → 142672 字节(source hash
6ada68530c1fdcc2 → f53a7ac5965b38b0);测试 431 → 474 项(V0.8 新增 4 个测试文件,
另给 test/client-render-smoke.test.mjs 加了 8 条),静态守卫 23 → 25 组;src/core/ 24 → 26 个模块 / 7342 → 8049 行
(新增 skill-lineage.mjs 174 行、skill-diff.mjs 527 行),src/storage/ 4 → 5 个模块 /
694 → 884 行
(新增 skill-lineage-store.mjs 139 行,skill-clone-writer.mjs 297 → 348 行,
多了写血缘那一段)。§27 的圆角/分隔比仍是 3.10(上限 5.5),一个 14px 以上的圆角都没加。

守卫 25 是这一版新增的第二组,两个半都落地了。 宿主那半(Phase 2)钉住限制码与「不许把
unavailable 说成 match」;客户端那半(Phase 3)钉住「结构 / 内容 / 资源」三个 Tab 文案、
四句来源状态人话、if (!lineage) 不许去问差异路由、两处 role="alert"、界面词表必须正好是
那五个事实词。写这两半的时候连着踩了三次空转——role="alert" 只查一次会被错误态顶掉、判断词
切片的起点选在组件函数上会漏掉定义在它前面的词表、守卫把自己注释里的「更合理」当成违规。
三次都靠负例抓出来:每条断言都真的注入过一次、真的红过、红在它该红的那条上。

Changed

  • Shortened the DSH session display name to Skill 洞察 / Skill Insight; the product brand stays DSH Skill 智能实验室 / DSH Skill Intelligence.
  • Panel accessibility names and the README naming table now use the same three layers: brand, session display name, technical module names.
层 名称 说明
产品品牌 DSH Skill 智能实验室 / DSH Skill Intelligence README 首屏、仓库描述、文档抬头
DSH 会话短显示名 Skill 洞察 / Skill Insight 侧边栏 / 工作栏标签 / 面板 aria-label
npm 包名 dsh-skill-trace 不改:已发布,改名会让安装命令与 github: 锚点失效
技术层 Skill Trace 保留:/skill-trace/* 路由、命名空间、[data-plugin] 与 storage 结构

Fixed — 「复刻 Skill」从 v0.7.0 起每一次都失败

从 0.7.0 起,界面上那个「复刻 Skill」按钮一次都没有成功过。 每一次点下去,用户拿到的都是
一句 sessionId 必填。原因不在复刻逻辑里,而在一次双边的请求体约定上:

  • 宿主 handleClone 用 payload.sessionId 解析 Skill registry 与会话 cwd,这个字段是必需的;
  • 客户端 SkillCloneDialog 组请求体时压根没有放它,详情页渲染这个弹窗时也没有把 sessionId 传下去。

于是 sendJson 忠实地把校验器的原话送回了界面 —— 也就顺带违反了「所有『读不到』都必须说人话」
(§7)与「不显示裸错误码」。这条责任在宿主的 400/500 分流上:它靠正则去猜消息里有没有「必填」,
等于逼着校验消息写成字段名。

修的部分:

  • 客户端把 sessionId 放进复刻请求体,详情页把它传进弹窗。
  • 宿主新增 RequestError(带 status),把「必填 / 无效」这类裸字段名换成完整的句子;
    400/500 的分流改看这个类的 status,那条猜词正则只留给还没有改的旧错误,并注明不要再往里加词。
  • 两条回归测试。它们真的会红:去掉请求体里的 sessionId → 红;详情页忘了往下传 → 红;
    弹窗新解构一个没人传的 prop → 红。

为什么 429 项测试全绿而缺陷照样出厂:请求体有两半,而没有任何东西盯着中间那条缝。

  • test/phase18-clone-routes.test.mjs 验的是宿主,它自己把 sessionId 填进 body —— 补上了客户端漏掉的那一格。
  • 渲染烟测验的是外观,只看渲染出来的文案,从不看真正发出去的 JSON;它的 fetch 桩对什么都返回 {},
    所以 submit() → api('/clone', …) 这条路径从来没被走到过。
  • v0.7 的真机探针脚本也是直接打路由的,同样绕开了客户端。

这次的教训写进了 spec/SDD.md §3,守卫改成从宿主源码里读出它真的读哪些 payload.* 字段,
再要求客户端发出去的请求体覆盖它们 —— 单边测试补不出这条缝。

Verified — V0.8 真机验收:12 个检查点,用一份真实的复刻副本走完

宿主必须重启,客户端硬刷新即可(§6.2、附录 B)。这次两半是分开验的:

  • 重启前,GET /skill-trace/diff 答的是 {"ok":false,"error":"not found"} —— 那条路由在跑着的进程里根本不存在;
  • 重启后,同一个请求答的是真实业务消息
    {"ok":false,"code":"no-lineage","error":"这个 Skill 不是由本插件复刻出来的,没有可以比较的来源。"}。
    一句 not found 和一条业务规则是两件事,这就是「宿主到底换了代码没有」的判据(§8.11)。
  • 客户端那一半在服务端上已经是新的(plugins/??dsh-skill-trace/client.js 里含有「宿主可能还没换到这一版的代码」这句,
    证明服务端发出去的是这一版),但浏览器要硬刷新才会取新的 rev。

验收用的是一份真实的复刻,不是夹具:~/.agents/skills/deliver-prd-custom-custom(来源
~/.agents/skills/deliver-prd-custom,两侧各 5 个文件)。这台机器在此之前 ~/.dsh/skill-trace/lineage/
目录根本不存在 —— 这是第一条真的被写下来的血缘记录,catalogObservation: "observed"
(回读与 registry 观察都过了,不是「待确认」)。

拿这两份真实目录喂真实的差异引擎(buildSkillDiff,不是烟测里的夹具),结果与界面应当说的话一致:

层 新增 删除 修改 保持不变
结构 0 0 0 7
内容 0 0 0 7(每节 lines: [])
资源 0 0 0 4

comparison.status: "unchanged"、source.changed: false、limitations: []。资源那四条是 HISTORY.md /
evals/trigger-fixtures.json / references/EXAMPLE.md / references/TEMPLATE.md,SKILL.md 正确地不在资源层
(它在结构 / 内容两层里)。

「一个字没改的复刻」必须被说成「没改」。 这里有一条容易忽略的前提:指纹算的是正文
(frontmatter 之后的 .trim()),不是整份文件。复刻必须把副本 frontmatter 里的 name: 改写成目标名,
如果拿整份文件去比,每一次复刻都会永远报「来源变了」。两份 SKILL.md 只差第 2 行那一处 name:,
而记录里的 sourceSourceSha256 与当前来源指纹逐字相同。这条理由写在 src/storage/skill-clone-writer.mjs:283-284。

这一轮的两个缺陷都是真机 / 走查发现的,不是测试发现的(§8.8):老宿主没有 lineage 键时被说成
「不是复刻来的」,以及「还没问」被说成「读不到」。474 项测试在这两处全绿。

用户于 2026-10-06 确认真机走查通过(12 个检查点)。被点名确认的四件事是:卡上说「来源内容未发生变化」;
结构 / 内容两层没有任何一行显示成「修改」;资源层恰好四个文件且没有 SKILL.md;刷新后第一帧是
「正在读取来源…」而不是「无法读取来源」。完整清单在 01_重构方案/v0.8-真机验收清单.md(本地过程目录,不发布)。

发布范围与实测

  • 版本号是 minor:新增 1 条宿主路由(GET /skill-trace/diff,共 11 条)与 3 个模块
    (src/core/skill-lineage.mjs / src/storage/skill-lineage-store.mjs / src/core/skill-diff.mjs),
    外加详情页的两块新界面(「Skill 演进」卡与「Skill 差异」面板)。另外两件本身是 patch 级的,
    合在这一版里一起走。
  • 发布平台:GitHub Release(tag v0.8.0)与 npm(beta 与 latest 都指向 0.8.0),
    同一份构建。npm 包名仍是 dsh-skill-trace,不改名。
  • 实测数字(npm test / npm run verify / wc -l / wc -c):测试 474 项全绿 · 守卫 25 组
    全 OK · 客户端 src/dsh/client/client.js 2735 行 · bundle dist/client.js 142672 字节
    (source hash f53a7ac5965b38b0)· 宿主 src/dsh/host/index.js 1292 行 / 11 条路由 ·
    src/core/ 26 个模块 8049 行 · src/storage/ 5 个模块 884 行 · dependencies 仍为空。
  • 发布后的实测结果(发布提交 / tag 对象 / npm shasum / 净室安装 / 注册表传播)写在
    docs/RELEASE.md §6.0。

v0.7.1 — 品牌迁移:DSH Skill 智能实验室

Choose a tag to compare

@PolinniZhong PolinniZhong released this 02 Oct 00:24

0.7.1 — 2026-10-02 · 品牌迁移:Skill Trace → DSH Skill Intelligence(DSH Skill 智能实验室)

这是一次品牌迁移,不是功能重写。 核心功能逻辑一个字没改:Skill Runtime 分析、Framework Parser、
Translation Store、Clone 机制、Host API(仍是 10 条路由)与 Storage 结构全部保持原样;改的是产品名、
用户可见措辞与仓库元信息。能力链没变,只是换了说法:
Discovery → Understanding → Framework Analysis → Markdown Reading → Translation → Clone → Evolution。

Changed

  • Renamed product positioning from Skill Trace to DSH Skill Intelligence.
  • Updated user-facing terminology from runtime tracing to Skill understanding and evolution.
  • Introduced DSH Skill 智能实验室 brand identity.

Product Positioning

DSH Skill Intelligence helps users understand, reproduce and evolve Agent Skills.

命名分层(改什么、不改什么)

层 名称 说明
产品名 DSH Skill 智能实验室 / DSH Skill Intelligence README 首屏、DSH 工作栏标签、包描述、文档抬头
一句话 探索优秀 Agent Skill 的结构与方法,将成熟 AI 工作流转化为个人能力和企业业务能力。 所有对外介绍统一用这句
npm 包名 dsh-skill-trace 不改:已发布,改名会让安装命令与 github: 锚点全部失效
技术层 Skill Trace 保留:/skill-trace/* 路由、dsh-skill-trace 命名空间、[data-plugin="dsh-skill-trace"]、storage 结构
仓库 PolinniZhong/dsh-skill-intelligence GitHub 仓库改名,旧地址自动重定向

发布范围与实测

  • GitHub Release 与 npm 同时发布:tag 打在发布提交上,npm 的 beta 与 latest 都指向 0.7.1。
    npm 包名仍是 dsh-skill-trace,不需要 npm 迁移——路由、模块与存储结构一个字没动。
  • 只改用户理解层:src/ 的功能逻辑一行未改;改动集中在客户端文案、README 与文档抬头、
    package.json 的 description / keywords / URL,以及 GitHub 仓库元信息。
  • 实测:客户端 src/dsh/client/client.js 2332 → 2339 行,bundle dist/client.js
    129280 → 129315 字节(source hash 58ed0ec27f941c3f),测试 429 项与守卫 23 组
    都不变——数字不变正是「纯品牌迁移」的证据。
  • GitHub 仓库改名:PolinniZhong/dsh-skill-trace → PolinniZhong/dsh-skill-intelligence
    (旧地址自动重定向),description / homepage / topics 同步(skill-tracing 换成 skill-intelligence)。

v0.7.0 — Skill 理解与复用:读得懂、存得住、复刻得走

Choose a tag to compare

@PolinniZhong PolinniZhong released this 01 Oct 23:21

0.7.0 — 2026-10-01 · Skill 理解与复用:读得懂、存得住、复刻得走

一次功能版。 信息架构一个字没动:一级页面仍是「本次 Skill」「已安装 Skill」,二级页面仍是唯一的 Skill 详情,四层顺序仍是 框架 → 本次运行逻辑 → 步骤证据 → SKILL.md。这一版把产品从「观察 Skill → 理解 Skill」推进到「观察 → 理解 → 阅读 → 复刻 → 让当前 DSH Agent 继续使用」。

一、已安装 Skill 的卡片整张可点

此前「已安装 Skill」列表里的卡片是一个 <article>,没有任何进入详情的入口——详情页只对「本次 Skill」可达。现在卡片本身就是按钮:hover 轻微边框强调,click / Enter / Space 进入同一个 SkillDetailPage(没有第二套详情页),返回沿用来源感知:从「本次 Skill」进的回本次 Skill,从「已安装 Skill」进的回已安装 Skill。

卡片里没有再加一个「查看详情」按钮——两个入口指向同一个动作,其中一个必然显得多余。渲染烟测里有一条断言直接钉住这件事:这个页面的 button 总数恰好等于卡片数。

二、中文阅读版:从「临时」变成「资产」

0.6 之前译文只活在页面内存里,退出 DSH 即消失。现在三层取用:

页面内存(进程内 Map,按 sessionId)
      ↓ miss
本机译文库 <dataRoot>/translations/<sha256(key)>.json
      ↓ miss
调用宿主模型翻译 → 写回本机译文库 → 写回内存
  • 持久化键是 skillName + sourceSha256 + targetLanguage,不含 sessionId。 中文阅读版是资产,不是这次会话的产物——跨会话、跨 DSH 重启复用。
  • sourceSha256 变化即失配。 换了版本的旧译文不显示,界面退回原文并允许重新翻译;旧文件按 keepPerSkill: 2 修剪,但界面只展示与当前指纹一致的那一份。
  • 保存态必须来自一次真的写入。 /translate 的响应里多了一个 saved 布尔,界面据它说「✓ 中文阅读版已保存(本地保存)」或「中文阅读版没有保存到本机,下次打开需要重新翻译。」——发起请求、返回 200 都不等于保存成功。
  • 目录权限 0700、文件 0600、临时文件 + rename 原子落盘,写法与既有的收据 / 偏好存储一致。不写 sessionId、工具参数、工具结果、对话正文、绝对路径。

新增两条路由:GET /skill-trace/translation(只返回与当前指纹一致的那一份)与 DELETE(精确到三个字段的删除,不做模糊删除)。这两条路由刻意不接受 sessionId——要求它就等于暗示这份资产属于某次会话。

三、复刻 Skill:从一个现有 Skill 创建独立副本

详情页左栏对象区多了一个按钮,是详情页唯一的对象级动作(没有并排的 编辑 / 创建 / 优化 / 收藏 / 学习)。点开是一个 560px 的紧凑 Dialog:目标名称、保存范围(当前项目 / 我的 Skill)、复刻内容(完整 Skill / 仅 SKILL.md)、来源与当前指纹。

四条硬规则:

  1. 源 Skill 永不修改。 只读源、只写新目录。mkdir 不带 recursive,EEXIST 即冲突——不存在「先删再写」这条路,所以「不覆盖」是文件系统的性质,不是一段记得住的判断。
  2. sourceSha256 必须随请求提交,宿主重新读源再校验。 前端把读过的旧正文直接交给宿主写入是被禁止的。不一致 → 409 source-changed,界面说「来源 Skill 已经变化,请重新打开详情页再复刻。」
  3. 目标已存在时不覆盖,提示「Skill 名称已存在,请更换名称。」,由用户改名;不自动编号,也没有绕过保护的覆盖确认。
  4. 只做 read / copy / write / verify。 不执行 Skill 里的 scripts/,不跑 bash / Python,不触发 Skill,不触发 Agent,不动 workspace 里的其它文件。

副本的 frontmatter name: 会被改写成目标名。这不是可选步骤:DSH 认的是 frontmatter 而不是目录名(parseSkillFile),不改这一行,副本会以源名字注册,两个同名 Skill 在 registry 里打架;SkillRegistry.get 甚至会在加载到的名字与候选名不符时直接返回 undefined。

完成后必须 read-back 再报成功。 顺序是 write → read-back → registry 校验 → success,点击成功、Toast、200 响应都不算。成功面板给的是:写到哪个范围、文件数、目录刷新状态、来源是否被改动(重新读源比对哈希,不靠断言)、limitations、以及 调用方式 /<name> + 一个只写剪贴板的复制按钮。目录刷新是观察出来的——插件拿不到 provider 的 invalidate,只能轮询 registry;观察不到就写「Skill 已写入 Skill 目录,目录刷新状态待确认。重启 DeepSeek Harness 后一定可见。」不返回、不显示任何本地绝对路径。

新增 POST /skill-trace/clone,错误码按状态分档:400 参数 / 名字不合法、404 源不存在、409 指纹不一致或目标已存在、422 定义读不出或 bundle 不可靠、500 写入失败。每一条错误都写清发生了什么、怎么恢复,没有一句 Clone failed。

四、复刻落到哪个目录:读出来的,不是假定的

目录体系直接对齐 @deepseek-ai/dsh-skill-filesystem 的真实实现顺序:<projectRoot>/.dsh/skills(100)→ <projectRoot>/.agents/skills(200)→ customSkillDirs(300)→ <dshHome>/skills(400,skipSystem)→ <agentsHome>/skills(500)→ bundled(600)。projectRoot 是最近的含 .git 的祖先,不是 cwd。

  • 项目级写 <projectRoot>/.dsh/skills。
  • 用户级优先写用户已经在用的那个根:本机 ~/.agents/skills 里有真实的用户 Skill 而 ~/.dsh/skills 是空的,所以用户级复刻落在 ~/.agents/skills 旁边,而不是另起一个没人看的目录。两个都不存在时用 <dshHome>/skills。
  • 扁平文件形态必须识破。 Skill 可以是 <root>/<name>.md,此时 resourceBase.path 指的是 skills 根目录本身——把它当成 bundle 会把别的 Skill 一起复制走。判据是 basename(resourceBase.path) === <skill name> 且该目录下有 SKILL.md;不成立时选「完整 Skill」会被拒,并明确说「当前无法确认完整资源目录,因此无法复刻完整 Skill。可以改选「仅 SKILL.md」。」
  • 符号链接不跟进副本,被跳过并在 limitations 里记录原因。

五、为什么不用 ctx.fs 写

ctx.fs 的抽象面上没有 mkdir,而且 workspace-write 只允许写 workspaceRoot / /tmp / tmpdir() 之下——用户级 Skill 目录在它之外。所以复刻写入用的是 node:fs/promises,与仓库里既有的收据 / 偏好存储同一条路。

六、工程数字

项 值
测试 429 项全绿(原 397,+32)
静态守卫 23 组(数量不变;TRANSLATION_MEMORY_ONLY_OK 改写成 TRANSLATION_PERSISTENCE_OK)
宿主路由 7 → 10 条(新增 GET/DELETE /skill-trace/translation、POST /skill-trace/clone)
客户端源码 src/dsh/client/client.js 1970 → 2332 行
宿主源码 src/dsh/host/index.js 775 → 1106 行
bundle dist/client.js 108839 → 129280 字节
新增模块 src/storage/translation-store.mjs 208 行 · src/core/skill-clone.mjs 213 行 · src/core/skill-clone-path.mjs 70 行 · src/storage/skill-clone-writer.mjs 297 行
运行时依赖 仍然为空

七、守卫改名而不是删除

TRANSLATION_MEMORY_ONLY_OK 原本钉住「译文落盘即违规」。这一版故意推翻了那条规则,所以守卫必须跟着改写——但改的是它守什么,不是把它删掉。新的 TRANSLATION_PERSISTENCE_OK 允许写,钉住的是哪些东西不许写进去:

  • translationStoreKey 的实现体里不得出现 session;
  • persistTranslation({…}) 的头 600 字符里不得出现 sessionId;
  • 持久化模块里必须有 0o700 / 0o600 / rename( / FORBIDDEN_RECORD_FIELDS;
  • /skill-trace/translate 的响应路径上必须真的出现 saved: await persistTranslation(;
  • 而 src/core/skill-translation.mjs 一个字都不许落盘(receipt / localStorage / sessionStorage / writeFile / receiptStore 依旧禁用)。

守卫数量保持在 23 —— 删掉一条守卫等于删掉一次真实事故的教训。

八、真机验收推翻的三件事

单元测试全绿并不能证明复刻在真实 Skill 上说得对。拿真实目录里的 character-asset-kit(473 个文件 / 23 MB,自己带一个 .git)走一遍完整复刻,29 项检查里有 3 项暴露了「测试测不到、只有真数据才会出现」的缺陷:

  • .git 内部被当成了「被跳过的内容」。 walkBundle 会走进 .git,387 个条目逐个被记成 excluded-directory,skippedCount 一路传到界面——一个正常的 git 仓库里的 Skill 会显示「跳过 431 个条目」。修法是在遍历的第一行就 if (CLONE_SKIPPED_DIRECTORIES.includes(child.name)) continue:.git / node_modules 不进也不记,它们不是这个 Skill 的内容,不该出现在「跳过了多少」这句话里。selectCloneEntries 自己的 excluded-directory 判据保留,因为调用方仍可能递进来一份原始清单。
  • 没拷全的整包被说成了完整整包。 16 MB 上限砍掉了 44 个文件,而界面写的是「复刻内容:完整 Skill(42 个文件)」,bundle-truncated-by-limit 只在小字 limitations 里。现在这一行由 truncated 决定,截断时改说「完整 Skill 没有拷全(已复制 N 个文件,M 个超过单次复刻上限)」——一句「完整」在文件没拷全的时候是假话。
  • 同一段小字里在给用户看裸错误码。 limitations 之前是直接把 bundle-truncated-by-limit 这类字符串渲染出来的,违反「不许显示裸错误码」。四个复刻相关的 code 补进了 SKILL_LIMITATION_TEXT;另有一组 CLONE_LIMITATION_RESTATED(absolute-paths-withheld / catalog-refresh-observed / catalog-refresh-not-observed / source-reread-did-not-match)被过滤掉,因为上面正文已经把它们说成人话了——同一件事说两遍会把一句话读成两句话。

顺带修掉一个只在这台机器上才暴露的选择错误:resolveCloneRoot 原本只按 DSH 的排名挑存在的根,而 ~/.dsh/skills 存在但是空的、~/.agents/skills 里躺着二十多个真 Skill,于是用户级复刻落进了那个空目录——DSH 能发现它,但不在用户 Skill 待的地方。现在分两级:先挑真的有 Skill 的根(populated-dsh-user-root / populated-agents-user-root),再退到「存在但空」(existing-…),最后才用默认路径。判据由新增的 looksLikeSkillRoot() / probePopulatedRoots() 从目录内容实测出来,不是按路径名猜的。

这一节修完:测试 428 项全绿,守卫 23 组不变,真机脚本 29/29。

九、重启 DSH 之后的十七步真机验收

上面那轮是拿真实数据喂真宿主模块;这一轮是打真在跑的进程(http://127.0.0.1:3080),不 mock、不打桩。宿主缓存的是模块不是版本,所以重启前 /skill-trace/translation 与 /skill-trace/clone 都是 404——重启是这一步的前置条件,不是可选项。

源用 ~/.agents/skills/character-asset-kit(473 个文件、自带 .git),落在 user scope,跑完自己清掉。结果:

  • 三道新门都开了:GET /skill-trace/translation 200、POST /skill-trace/clone 对空体回 400(GET /clone 仍 404——它是 POST-only,这是对的)。
  • 参数校验四连全是 400 而不是 500:大写名字 / 目标名等于源名 / 非法 targetScope / 非法 sourceSha256。
  • 指纹过期 → 409,响应带 currentSha256,消息是「来源 Skill 已经变化,请重新打开详情页再复刻。」——说清了发生什么和怎么恢复。
  • 真实复刻一次成功:verified:true(来自回读,不是来自点击)、fileCount 是真实数字、pathKind 是 user-agents(两级根规则在真机上确实挑中了真有 Skill 的那个根)、invocation 是 /character-asset-kit-probe、sourceUnchanged:true、响应里没有任何绝对路径。
  • 落盘核对:副本 SKILL.md 真的在、frontmatter 的 name: 已改成目标名、正文与源逐字一致、references/ 跟过来了、.git 没被拷进来。
  • 源 473 个文件逐个哈希比对,一个都没变。
  • 同名第二次 → 409「Skill 名称已存在,请更换名称。」,原副本一个字节都没变(没有先删后写),目标只存在一次。
  • 目录发现:复刻后 catalog 里真的出现了新名字,且源的定义指纹没变;/skill-trace/skills 没被新路由打破。
  • 中文阅读版走了一遍完整往返(用 454 字节的 lark-vc,一次真实模型调用,7.3 秒):翻译前 GET → null;POST /translate → saved:true;GET 读回同一份译文;落盘目录自动创建且权限 0700、文件 0600;换个指纹读回 null(旧译文绝不当作当前译文);换语言删是 deleted:false 而 zh-CN 不动;精确三字段删 → deleted:true,删完再读 → null。
  • 落盘记录的键恰好是 schemaVersion, skillName, sourceSha256, targetLanguage, translation, chunkCount, fallbackChunks, fallbackReasons, model, truncated, createdAt, updatedAt——没有 sessionId、没有源正文、没有 args/result/messages/conversation。这是「键里不含会话」那句话在磁盘上的样子。

两个当场没过的断言,复查后都是验收脚本自己写窄了,不是产品缺陷:其一把 409 消息的匹配正则限定成「指纹 / 版本 / 不一致」,而实际文案是「来源 Skill 已经变化……」(更好的那句话);其二无条件去 stat 译文目录,而它只在第一次真实写入时才创建——DATA_ROOT 本身(~/.dsh/skill-trace)一直是 0700。两条都已按实际情况改判。

一个操作层面的教训:真机复刻留下的副本位于工作区之外的用户 Skill 目录,沙箱会拒绝删除它(EPERM),必须单独放行才能清掉——而只要它还在,它就会出现在真实的 Skill 列表里。验收脚本一定要显式清理,并且清理本身要被验证。

十、顶栏收薄:去掉底板与分隔线,高度 68px → 48px

用户看真机截图时指着「本次 Skill / 已安装 Skill」那一行说:「这个背景占用太多高度,去掉这个背景后下方数据上移,这样就能多一些垂直空间出来。」随后在同一轮里又补了四件事,其中一件是「本次 Skill 跟已安装 Skill 下方那条横线,我觉得也不需要了」。

那句话里有两件事,第二件才是重点:顶栏原来是一块自己画底色的横条(background:var(--st-layer)),底色的边界让这一行读起来像一块独立的板子;而它真正占的高度(68px)里,分段控件只用了 36px,剩下 32px 是纯空白。

改法是两件一起做:去掉 background,高度由 68px 收到 48px(36px 控件 + 上下各 6px),水平内边距按 design.md §6.1 从 18px 回到 16px。同一轮里连 border-bottom 也去掉了——顶栏现在就是一行浮在页面上的内容,既没有底板也没有分隔线。它上面是宿主的标签条、下面是页面自己的内容,两边本来就有边界,这条线是多余的。

守卫跟着改了两条、加了一条,加的那两条都是反向的:

  • §22: the top bar is 48px —— 原来钉 68px。这个数字的历史本身就是教训:它更早被钉成 72px(两份文档都没有的数字),后来才对齐到 v0.6 设计文档的 68px。
  • §22: the top bar must not paint its own background —— 守的是「那块底板不许回来」。
  • §22: the top bar must not draw its own separator —— 守的是「那条线也不许回来」。用户的要求是去掉,那么「有没有」就该由守卫说,而不是靠下次改动时的自觉。

同一轮里的另外四件事:

  • 搜索图标移到搜索框右侧、框内。 输入框改成 padding:0 34px 0 11px 把右侧让出来,图标用绝对定位放进 .st-search-icon,并加了 pointer-events:none —— 一个只做标记的图标不该抢走点击。
  • 状态行只在有话要说时出现。 已安装列表在一切正常时整格留空(连状态点一起):删掉的「可发现 N 个 Skill」和下面的卡片说的是同一件事,再报一次是把列表读成统计。但失败与不确定一句都没删——「正在读取当前环境…」「已安装 Skill 读取失败 · 宿主可能仍在运行旧版本」「当前目录无法确认 · 只显示已确认的部分」「目录可能不完整 · 已发现 N 个 Skill」全部保留。「一切正常」是唯一可以静默的情况,因为那时没有信息可加。
  • 刷新按钮保留,但只留图标。 目录是带缓存的,装完 / 删完 Skill 需要一个手动重取的入口,所以按钮不能删;删的是它自己画的底板。这里牵出一个真问题:.st-icon-button 此前一条 CSS 规则都没有——它只出现在 JSX 的 className 里,整套外观都来自宿主对 button 的默认样式。现在补齐了宽高、居中、透明底、悬停与禁用态。
  • 分段控件不再有容器底色与描边,只有选中项有底。 容器的灰底会把两个选项一起压灰,去掉之后 --st-accent-soft 的选中态才亮得起来。这里有一处不改就会立刻坏的连带:选中态原本用 --st-layer,而它映射到宿主 --dsw-alias-bg-layer-1,与页面底色是同一个颜色——容器底板一去掉,原来的选中块就彻底看不见了。

守卫红了一条,改的是代码不是守卫。 npm run verify 报 Error: client contract missing (outside the dictionary): 工作区未连接:契约钉住的那句话只活在刚被删掉的那一行里。没有删断言,而是把兜底挪到活下来的那一行 —— 顺带修掉一个潜伏的缺陷:sessionStatus 一直在没有兜底地插...

Read more