Repository navigation
Releases: PolinniZhong/dsh-skill-intelligence
Release list
v1.4.0 — Skill 触发评测(`FR-TRIG-*`)
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 个真实会话):
complete305 /partial13 /unknown12;那 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那一维的接线从字面变成三目(按意图改,并顺手钉得更强)、以及客户端核心require10 → 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)
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 test662 → 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 hash9ec62e37d7dbfa2e,shasum -a 256为38d5ee32fbb4c897…);宿主src/dsh/host/index.js1844 → 2002 行(路由仍是 13 条);src/core/33 个 12854 行 → 34 个 13973 行;src/storage/7 个 1411 行不变。修改仍由当前 Agent 用 DSH 原生文件工具在对话里做 —— 这个工具只读。
v1.2.0 — Skill 证据模型(Skill Evidence Model)+ Skill 框架重构
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.mjs13 项与一条渲染烟测)。 - 触发评测顺延:仓库原先把
V1.2一格登记为Skill Trigger Evaluation,按用户 2026-10-06 的决定改由证据模型占 V1.2,触发评测顺延到V1.3+(spec/PRD.md§5.13FR-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...
v1.1.0 — 详情级导航 + Agent Skills 开放标准
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-001severity 是 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 / 对照 / 四段证据)
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.js189256 → 228715 字节(source hashfaed5e9cef7db24c→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 实例验收
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.mdFR-INST-015);同时不新增一级/二级页面、导航与第二套 Runtime。 - 不运行、不判定、不打分:插件只生成任务与观察项,最多说「已生成实例验收任务」;没有
成功率/评分/修改有效这类结论。 - 可追溯(规格 §21 的核心验收标准):结果里带一份
trace——这次生成真正在场的那几项输入(意图 / 范围 / 对比 / 描述 / 框架 / 静态验收)与主范围从哪来;详情页的「这个任务是怎么来的」把这两句念出来。用户意图只用来对齐任务形状,原文一个字节都不进 Prompt;回归约束优先取自框架里没被这次 diff 触及的声明能力(拿不到就如实进limitations,缺框架/缺静态验收不让生成变成不可用)。 - 工程数字:测试 561 → 590 项;守卫 28 → 29 组(新增
SKILL_INSTANCE_TEST_OK);客户端 3448 → 3625 行、dist/client.js170324 → 189256 字节(source hash2d202a05030a1d58→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 / 已安装列表排序
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 test474 → 510 项(其中 36 项是这一版新增);客户端 2735 → 2910 行、dist/client.js142672 → 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 test510 → 552 项,登记时又补一条「范围外不重复报」→ 553 项;客户端 2910 → 3356 行、dist/client.js150750 → 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 test553 → 561 项;客户端 3356 → 3448 行、dist/client.js167506 → 170324 字节(source hash66dd0b76118e7b85)。 - 渲染台用真实客户端 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.js3448 行 · bundledist/client.js170324 字节
(source hash66dd0b76118e7b85)· 宿主src/dsh/host/index.js1625 行 / 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 演进:复刻出来的东西,现在能倒着看回去
0.8.0 — 2026-10-02 · Skill 演进:复刻出来的东西,现在能倒着看回去
这一版是 minor:V0.8「Skill 演进」是主题,另外两件搭同一班车。 三件互不相干的改动一起发:
- V0.8「Skill 演进」(主线):
0.7.0把「复刻」做出来之后留下的那半——复刻出来的副本与它的来源
现在可以比对了。三个 Phase 都已落地,见下面的### Added。 - 短显示名(
3a1bf0a):命名体系调整,不是功能改动。产品品牌仍是 DSH Skill 智能实验室 /
DSH Skill Intelligence,技术层 Skill Trace 与 npm 包名dsh-skill-trace一个字没动。变的只有
DSH 会话里那个高频入口的短名:工作栏标签(conversation.viewslot 的label)从长品牌名改为
Skill 洞察 / Skill Insight,面板的aria-label同步;README 首屏与命名分层表把三层名字写清楚。 - 「复刻 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.js2735 行 · bundledist/client.js142672 字节
(source hashf53a7ac5965b38b0)· 宿主src/dsh/host/index.js1292 行 / 11 条路由 ·
src/core/26 个模块 8049 行 ·src/storage/5 个模块 884 行 ·dependencies仍为空。 - 发布后的实测结果(发布提交 / tag 对象 / npm shasum / 净室安装 / 注册表传播)写在
docs/RELEASE.md§6.0。
v0.7.1 — 品牌迁移:DSH Skill 智能实验室
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.js2332 → 2339 行,bundledist/client.js
129280 → 129315 字节(source hash58ed0ec27f941c3f),测试 429 项与守卫 23 组
都不变——数字不变正是「纯品牌迁移」的证据。 - GitHub 仓库改名:
PolinniZhong/dsh-skill-trace→PolinniZhong/dsh-skill-intelligence
(旧地址自动重定向),description / homepage / topics 同步(skill-tracing换成skill-intelligence)。
v0.7.0 — Skill 理解与复用:读得懂、存得住、复刻得走
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)、来源与当前指纹。
四条硬规则:
- 源 Skill 永不修改。 只读源、只写新目录。
mkdir不带recursive,EEXIST 即冲突——不存在「先删再写」这条路,所以「不覆盖」是文件系统的性质,不是一段记得住的判断。 sourceSha256必须随请求提交,宿主重新读源再校验。 前端把读过的旧正文直接交给宿主写入是被禁止的。不一致 →409 source-changed,界面说「来源 Skill 已经变化,请重新打开详情页再复刻。」- 目标已存在时不覆盖,提示「Skill 名称已存在,请更换名称。」,由用户改名;不自动编号,也没有绕过保护的覆盖确认。
- 只做
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/translation200、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 一直在没有兜底地插...