@yottameta/yotta-skills-plugin 0.8.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,7 +12,7 @@
12
12
  "authentication": "ON_INSTALL"
13
13
  },
14
14
  "category": "Developer Tools",
15
- "version": "0.8.0",
15
+ "version": "0.19.0",
16
16
  "description": "YuanGe (元阁) - orchestration routing, inventory, and one-command installer for the YottaMeta skill family (zero dependencies, Node.js only).",
17
17
  "interface": {
18
18
  "displayName": "元阁 yotta-skills",
@@ -12,7 +12,7 @@
12
12
  "authentication": "ON_INSTALL"
13
13
  },
14
14
  "category": "Developer Tools",
15
- "version": "0.8.0",
15
+ "version": "0.19.0",
16
16
  "description": "YuanGe (元阁) - orchestration routing, inventory, and one-command installer for the YottaMeta skill family (zero dependencies, Node.js only).",
17
17
  "interface": {
18
18
  "displayName": "元阁 yotta-skills",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-skills-plugin",
3
- "version": "0.8.0",
3
+ "version": "0.19.0",
4
4
  "description": "YuanGe (元阁) — orchestration and routing for the YottaMeta skill family, packaged as a YottaMeta Agent Plugin.",
5
5
  "license": "MIT",
6
6
  "keywords": [
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "yotta-skills",
4
- "version": "0.8.0",
4
+ "version": "0.19.0",
5
5
  "description": "YuanGe (元阁) - orchestration routing, inventory, and one-command installer for the YottaMeta skill family (zero dependencies, Node.js only).",
6
6
  "author": {
7
7
  "name": "YottaMeta",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: yotta-skills
3
- version: 0.8.0
4
- description: 元阁 -- 元阁全家技能的总编排策划 + 编排路由 + 一键安装器 + 技能盘点。路由层:--route / route_request 按需求摘要给出候选组合、调用顺序、角色、置信度、依据、已装/缺失状态与安装命令,只建议不自动安装;非元阁家族已装技能按 frontmatter description 机械匹配作并列候选(标注来源与未扫描状态,只读不自动调用);策划层:按场景给出「该组合哪几个元技能、组合强在哪、怎么自动装+自动用」;安装层:一条命令把 YottaMeta 已发布的全部 yotta-* 技能装进指定智能体或目录;盘点层:--inventory / --reindex 扫描本机已装技能生成/更新注册表,新装技能自动被发现(install/update 后自动 re-index,会话开工可先跑 --reindex 与 update --check(联网只读检查更新);自包含零依赖,不依赖任何元技能);MCP 按需加载(可选:list_installed_skills/describe_skill/reindex/route_request,不常驻,未加载降级 CLI)。支持 --list 清单 / --route 路由 / install / update / update --check(只读检查)/ update --auto(家族自动更新)/ --inventory / --reindex / --dry-run 预览 / --pin 锁版本。触发:需要批量安装或更新元阁全家技能、按场景组合多个元技能、路由或判断该用哪些技能、盘点或查看本机已装技能、重扫技能注册表、给某个智能体或目录一次性铺齐 yotta-* 技能、预览安装清单、锁版本安装、或用户说 元阁/装全家/一次装齐/yotta-skills/install-all/更新全家/检查更新/自动更新/该用哪个技能/路由技能/盘点技能/查看已装技能 等。边界(Do NOT trigger):只做「组合策划 + 静态路由建议 + 清单 + 下载 + 落位 + 汇总 + 盘点 + re-index」,不含技能本体、不做技能内容开发、不 -g 污染全局、不自动安装缺失技能;家族安装先自举或调用元信装前门禁,DO NOT INSTALL 阻断,非元阁家族包不自动安装。
3
+ version: 0.19.0
4
+ description: 元阁 -- 元阁全家技能的总编排策划 + 编排路由 + 一键安装器 + 技能盘点 + 运行时 hook 适配。路由层:--route / route_request 按需求摘要给出候选组合、调用顺序、角色、置信度、依据、已装/缺失状态与安装命令,只建议不自动安装;非元阁家族已装技能按 frontmatter description 机械匹配作并列候选(标注来源与未扫描状态,只读不自动调用);策划层:按场景给出「该组合哪几个元技能、组合强在哪、怎么自动装+自动用」;安装层:一条命令把 YottaMeta 已发布的全部 yotta-* 技能装进指定智能体或目录;盘点层:--inventory / --reindex 扫描本机已装技能生成/更新注册表,新装技能自动被发现(install/update 后自动 re-index,会话开工只建议跑本地 --reindex;更新检查走手动 --check 或后台 --check --scheduled);运行时适配层:hook capabilities / evaluate / bind / unbind 按宿主能力矩阵执行六个统一事件并留证降级,元信 before_install 已接入安装管线;自包含零依赖,不依赖任何元技能;MCP 按需加载(可选:list_installed_skills/describe_skill/reindex/route_request,不常驻,未加载降级 CLI)。支持 --list 清单 / --route 路由 / install / update / update --check(只读检查)/ update --check --scheduled(后台周检)/ update --auto(家族自动更新)/ hook 适配 / --inventory / --reindex / --dry-run 预览 / --pin 锁版本。触发:需要批量安装或更新元阁全家技能、按场景组合多个元技能、路由或判断该用哪些技能、盘点或查看本机已装技能、重扫技能注册表、给某个智能体或目录一次性铺齐 yotta-* 技能、评估宿主 hook 能力、预览安装清单、锁版本安装、或用户说 元阁/装全家/一次装齐/yotta-skills/install-all/更新全家/检查更新/自动更新/hook 适配/该用哪个技能/路由技能/盘点技能/查看已装技能 等。边界(Do NOT trigger):只做「组合策划 + 静态路由建议 + 清单 + 下载 + 落位 + 汇总 + 盘点 + re-index + hook 适配」,不含技能本体、不做技能内容开发、不 -g 污染全局、不自动安装缺失技能;家族安装先自举或调用元信装前门禁,DO NOT INSTALL 阻断,非元阁家族包不自动安装。
5
5
  license: MIT
6
6
  metadata:
7
7
  zh_name: 元阁
@@ -16,8 +16,8 @@ metadata:
16
16
  **写入内容(逐字)**:
17
17
 
18
18
  > **元阁编排**:单技能是零件,组合才是系统。会话开工先跑一次 `yotta-skills --reindex`
19
- > (重扫本机技能注册表,新装技能自动被看见);会话开工再跑一次 `yotta-skills update --check`
20
- > (联网只读检查本地已装技能是否有更新;有更新列清单,确认后 `update`,或 `update --auto` 仅自动更新自家 yotta-* 家族)。接到需求,先查「元阁编排策划」命中哪个组合
19
+ > (重扫本机技能注册表,新装技能自动被看见);更新检查走手动 `yotta-skills update --check`,
20
+ > 或后台周检 `yotta-skills update --check --scheduled`(未到期不联网,到期单次检查并写缓存)。接到需求,先查「元阁编排策划」命中哪个组合
21
21
  > (输出呈现标准 / 长生命周期 / 交付质量门 / 装前安全门 / 造技能发版 / 安全事件响应 / 入口安装),
22
22
  > 再按需自动安装并应用。完整决策表见技能 `yotta-skills/references/orchestration.md`。
23
23
 
@@ -32,7 +32,7 @@ metadata:
32
32
 
33
33
  - **路由层(该用哪几个)**:`--route "<需求摘要>"` / MCP `route_request` 查本地注册表与静态编排 playbook,输出候选组合、调用顺序、每个技能角色、置信度、依据、已装/缺失状态与安装命令;非元阁家族已装技能按 frontmatter description 机械匹配作并列候选(标注来源与未扫描状态,只读不自动调用);只建议安装,不自动安装。
34
34
  - **策划层(怎么用)**:接到需求,先按「编排策划」定位命中哪个组合——哪些元技能搭配起来最强、适合什么场景、AI 该怎么自动装并自动用起来。
35
- - **安装层(怎么装)**:一条 `npx -y @yottameta/yotta-skills` 把组合/全家装进指定智能体或目录——`--list` 看清单、`install` 装、`update` 增量更新、`update --check` 只读检查更新、`update --auto` 家族自动更新、`--dry-run` 预览、`--pin` 锁版本。
35
+ - **安装层(怎么装)**:一条 `npx -y @yottameta/yotta-skills` 把组合/全家装进指定智能体或目录——`--list` 看清单、`install` 装、`update` 增量更新、`doctor` 只读自检、`rollback` 恢复快照、`--dry-run` 预览、`--pin` 锁版本。
36
36
  - **盘点层(已装了什么)**:`--inventory` / `--reindex` 扫描本机各智能体技能目录,生成/更新本地注册表;装技能后自动 re-index,新装技能自动被发现——自包含扫描,不依赖任何元技能。
37
37
 
38
38
  每个技能仍走各自独立的 npm 包(版本源唯一);本包只做「清单 + 下载 + 落位 + 汇总」,**不内置任何技能本体**。
@@ -101,9 +101,19 @@ npx -y @yottameta/yotta-skills update --agent codex
101
101
  # 只读检查有没有更新(联网对 npm 最新,不改动;0=全部最新 / 3=有更新 / 1=查失败)
102
102
  npx -y @yottameta/yotta-skills update --check --agent codex
103
103
 
104
+ # 后台周检入口(未到期不联网;到期只检查一次并写 ~/.yottaskills/update-check.json)
105
+ npx -y @yottameta/yotta-skills update --check --scheduled --agent codex
106
+
104
107
  # 检查到家族更新后自动更新(仅 yotta-* 自家家族,含装前安全扫描)
105
108
  npx -y @yottameta/yotta-skills update --auto --agent codex
106
109
 
110
+ # 只读检查已装技能(不修改目录;可加 --json 输出机器可读结果)
111
+ npx -y @yottameta/yotta-skills doctor --agent codex
112
+
113
+ # 查看可用快照 / 回滚最近一次安装或更新
114
+ npx -y @yottameta/yotta-skills rollback --list --agent codex
115
+ npx -y @yottameta/yotta-skills rollback --slug yotta-memory --agent codex
116
+
107
117
  # 预览将安装清单(不联网、不改动)
108
118
  npx -y @yottameta/yotta-skills --dry-run
109
119
 
@@ -124,7 +134,13 @@ npx -y @yottameta/yotta-skills --reindex
124
134
  | `install <skill>... [--agent <name> \| --dir <path>]` | 只装指定的一个或多个技能 |
125
135
  | `update [--agent <name> \| --dir <path>]` | 增量更新:补齐缺失技能、升级版本不一致的技能 |
126
136
  | `update --check [--agent <name> \| --dir <path>]` | 只读检查更新:联网对 npm 最新,不改动;退出码 0=全部最新 / 3=有更新 / 1=查失败 |
137
+ | `update --check --scheduled [--agent <name> \| --dir <path>]` | 后台周检入口:未到期不联网;到期只检查一次并写本地缓存;文本失败静默,`--json` 保留诊断;始终退出 0 |
127
138
  | `update --auto [--agent <name> \| --dir <path>]` | 检查到家族更新后自动更新(仅 yotta-* 自家家族,含装前安全扫描) |
139
+ | `doctor [--agent <name> \| --dir <path>] [--slug <slug>]` | 只读自检:SKILL / 版本 / manifest / 注册表 / 自定义 doctor;`--json` 输出稳定字段 |
140
+ | `rollback [--agent <name> \| --dir <path>] [--slug <slug>]` | 校验并恢复最近一次快照;`--list` 只列快照;`--json` 输出机器可读结果 |
141
+ | `hook capabilities --host <name>` | 查看宿主六个统一事件的能力等级;未知宿主默认 `unsupported` |
142
+ | `hook evaluate --host <name> --event <event> --manifest <file> --context <json>` | 评估 manifest hook 声明,输出 allow / block / warn / unverified,并写结构化证据 |
143
+ | `hook bind --host <name> --manifest <file>` | 幂等注册 hook 声明;`hook unbind <id>` 反注册 |
128
144
  | `--inventory` | 盘点本机已装技能:扫描技能目录生成/更新注册表(自包含,不依赖元技能);`--json` 输出 JSON、`--project` 附扫项目级目录 |
129
145
  | `--reindex` | 重扫注册表:扫描技能目录并增量合并变化(会话开工 / 装技能后自动调用);`--json` 输出 JSON、`--project` 附扫项目级目录;`--rescan` 同义 |
130
146
  | `--route <需求摘要>` | 静态编排路由:输出组合、调用顺序、技能角色、置信度、依据、已装/缺失状态与安装建议;`--json` 输出 JSON、`--project` 附扫项目级目录 |
@@ -136,10 +152,26 @@ npx -y @yottameta/yotta-skills --reindex
136
152
  | `--npm <path>` | 指定 npm 可执行文件 |
137
153
  | `--python <path>` | 指定 python 可执行文件(元信 scan 用) |
138
154
  | `--verify <path>` | 指定 yotta_verify.py 路径 |
155
+ | `--slug <slug>` | doctor / rollback 时只处理指定技能 |
139
156
  | `-h, --help` / `-v, --version` | 帮助 / 版本 |
140
157
 
141
158
  不带命令直接给技能名时,等价于 `install <skill>`。
142
159
 
160
+ ## 运行时 hook 适配层
161
+
162
+ 技能 manifest 只声明六个统一事件的要求,元阁负责能力探测、确定性评估、证据留痕和降级标注:
163
+
164
+ ```bash
165
+ npx -y @yottameta/yotta-skills hook capabilities --host codex
166
+ npx -y @yottameta/yotta-skills hook evaluate --host codex --event before_send --manifest ./skill-manifest.json --context '{"checks":{}}'
167
+ npx -y @yottameta/yotta-skills hook bind --host codex --manifest ./skill-manifest.json
168
+ npx -y @yottameta/yotta-skills hook unbind <binding-id>
169
+ ```
170
+
171
+ 证据写入 `~/.yottaskills/hook-log.jsonl`,绑定注册表写入
172
+ `~/.yottaskills/hook-bindings.json`。`native-audit` 只表示可审计和一次纠偏,不表示动作前强制;
173
+ `unsupported` 必须显示 `explicit-unverified`。本层只做本地确定性判断,不联网、不执行下载。
174
+
143
175
  ## 技能盘点(--inventory)
144
176
 
145
177
  元阁自带技能扫描核心(零依赖,不依赖任何元技能):扫描各智能体技能目录,解析
@@ -175,14 +207,49 @@ npx -y @yottameta/yotta-skills --inventory --dir ~/my-skills --project
175
207
  (含元信装前安全扫描)自动更新。**非元阁家族(非 yotta-*)技能绝不自动更新**(默认保守:`--check` 列清单,
176
208
  用户确认后再 `update`)。
177
209
 
210
+ `--check --scheduled` 是后台周检入口:默认 7 天加 0 到 24 小时随机抖动,未到期不联网;
211
+ 到期只检查一次并把结果写入 `~/.yottaskills/update-check.json`。文本模式网络失败静默,
212
+ `--json` 可读取 `error` / `cache` 诊断字段。它不作为会话开工默认动作。
213
+
178
214
  ```bash
179
215
  # 只读检查(0=全部最新 / 3=有更新 / 1=查失败)
180
216
  npx -y @yottameta/yotta-skills update --check --agent codex
181
217
 
218
+ # 后台周检入口(未到期不联网;到期单次检查并写缓存)
219
+ npx -y @yottameta/yotta-skills update --check --scheduled --agent codex
220
+
182
221
  # 检查到家族更新后自动更新(仅自家家族)
183
222
  npx -y @yottameta/yotta-skills update --auto --agent codex
184
223
  ```
185
224
 
225
+ ## doctor 与 rollback
226
+
227
+ `doctor` 是只读自检,不会修改技能目录或注册表。它检查目标技能目录、`SKILL.md`、
228
+ frontmatter 版本、manifest 身份、本地注册表记录,以及包内声明的自定义 doctor 脚本。
229
+ `--json` 返回稳定字段,适合脚本和智能体消费。
230
+
231
+ ```bash
232
+ npx -y @yottameta/yotta-skills doctor --agent codex --slug yotta-memory
233
+ npx -y @yottameta/yotta-skills doctor --dir ~/my-skills --json
234
+ ```
235
+
236
+ `rollback` 只恢复已经存在的本机快照。恢复前会校验快照(新快照校验 SHA-256 元数据,
237
+ 旧快照按结构校验);恢复过程中先在同盘暂存,再替换目标,失败时保留当前目录。
238
+ 快照默认保留,不会被回滚操作删除。
239
+
240
+ ```bash
241
+ # 查看当前有哪些快照(--slug 可只看一个技能)
242
+ npx -y @yottameta/yotta-skills rollback --list --agent codex
243
+
244
+ # 回滚最近一次安装或更新;也可用 --slug 指定技能
245
+ npx -y @yottameta/yotta-skills rollback --slug yotta-memory --agent codex
246
+ ```
247
+
248
+ 包内 manifest 可声明 `install.setup`、`install.doctor`、`install.rollback`,
249
+ 值必须是包内相对路径。安装时先执行 setup,再执行内置 doctor 和自定义 doctor;
250
+ 任一步失败都会恢复旧版本并留下安装证据。元阁只执行元阁家族包内的生命周期脚本,
251
+ 不使用 shell,也不接受绝对路径或 `..` 路径。
252
+
186
253
  ## 编排路由(--route)
187
254
 
188
255
  把需求摘要交给静态编排 playbook 和本地注册表,得到一组可执行建议:
@@ -9,7 +9,10 @@
9
9
  * npx -y @yottameta/yotta-skills install <skill> --dir <path> # 装单个技能
10
10
  * npx -y @yottameta/yotta-skills update --agent <name> # 增量更新已装技能(补齐缺失/版本不一致)
11
11
  * npx -y @yottameta/yotta-skills update --check # 只读检查更新(联网对 npm 最新,不改动;退出码 0/3/1)
12
+ * npx -y @yottameta/yotta-skills update --check --scheduled # 后台周检(未到期不联网,到期单次检查并写缓存)
12
13
  * npx -y @yottameta/yotta-skills update --auto # 检查到家族更新后自动更新(仅 yotta-* 家族,含装前扫描)
14
+ * npx -y @yottameta/yotta-skills hook capabilities # 查看宿主六事件能力矩阵
15
+ * npx -y @yottameta/yotta-skills hook evaluate --event before_send --manifest <file> # 评估并留证
13
16
  * npx -y @yottameta/yotta-skills --dry-run # 预览将安装清单(不联网、不改动)
14
17
  *
15
18
  * 版本策略:清单锁定 `major.x`(不锁死 patch,维护性更新随最新);--pin 锁死精确版本。
@@ -26,10 +29,15 @@ const http = require('http');
26
29
  const https = require('https');
27
30
  const evidenceLib = require('../lib/install-evidence');
28
31
  const gateLib = require('../lib/verify-gate');
32
+ const healthLib = require('../lib/install-health');
33
+ const lifecycleLib = require('../lib/install-lifecycle');
34
+ const snapshotLib = require('../lib/install-snapshot');
35
+ const updateCheckLib = require('../lib/update-check');
36
+ const hookAdapterLib = require('../lib/hook-adapter');
29
37
  const { createInstaller, isSafeTarEntry } = require('../lib/install-pipeline');
30
38
 
31
39
  const PKG_ROOT = path.join(__dirname, '..');
32
- let VERSION = '0.2.1';
40
+ let VERSION = '0.19.0';
33
41
  try { VERSION = require(path.join(PKG_ROOT, 'package.json')).version; } catch (_) { /* keep fallback */ }
34
42
 
35
43
  function loadManifest() {
@@ -140,7 +148,8 @@ function parseArgs(argv) {
140
148
  help: false, version: false, agent: null, dir: null, npm: null,
141
149
  python: null, verify: null, command: null, skill: null, rest: [],
142
150
  inventory: false, reindex: false, noReindex: false, json: false, project: false, route: null,
143
- check: false, auto: false, registry: null,
151
+ check: false, auto: false, scheduled: false, registry: null, slug: null,
152
+ host: null, event: null, manifest: null, context: null,
144
153
  };
145
154
  const positionals = [];
146
155
  for (let i = 0; i < argv.length; i++) {
@@ -169,15 +178,27 @@ function parseArgs(argv) {
169
178
  else if (a === '--npm') opts.npm = take('--npm');
170
179
  else if (a === '--python') opts.python = take('--python');
171
180
  else if (a === '--verify') opts.verify = take('--verify');
181
+ else if (a === '--slug') {
182
+ const value = take('--slug').toLowerCase();
183
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
184
+ die('--slug 格式非法', 2, '只允许小写字母、数字和连字符,例如 yotta-memory。');
185
+ }
186
+ opts.slug = value;
187
+ }
172
188
  else if (a === '--check') opts.check = true;
173
189
  else if (a === '--auto') opts.auto = true;
190
+ else if (a === '--scheduled') opts.scheduled = true;
174
191
  else if (a === '--registry') opts.registry = take('--registry');
192
+ else if (a === '--host') opts.host = take('--host').toLowerCase();
193
+ else if (a === '--event') opts.event = take('--event').toLowerCase();
194
+ else if (a === '--manifest') opts.manifest = take('--manifest');
195
+ else if (a === '--context') opts.context = take('--context');
175
196
  else if (a.startsWith('-')) die('未知参数: ' + a, 2, '可用 --help 查看支持的选项。');
176
197
  else positionals.push(a);
177
198
  }
178
- // 命令解析:install / update,其余位置参数 = 技能 slug(可多个)
199
+ // 命令解析:install / update / doctor / rollback,其余位置参数 = 技能 slug(可多个)
179
200
  for (const p of positionals) {
180
- if (p === 'install' || p === 'update') {
201
+ if (p === 'install' || p === 'update' || p === 'doctor' || p === 'rollback' || p === 'hook') {
181
202
  if (opts.command && opts.command !== p) die('命令冲突:' + opts.command + ' 与 ' + p);
182
203
  opts.command = p;
183
204
  } else {
@@ -187,6 +208,9 @@ function parseArgs(argv) {
187
208
  // 直接给 slug 且无命令 → 视为 install 单个/多个
188
209
  if (!opts.command && opts.rest.length > 0) opts.command = 'install';
189
210
  opts.skills = opts.rest.map(s => s.toLowerCase());
211
+ if (opts.scheduled && (opts.command !== 'update' || !opts.check || opts.auto)) {
212
+ die('--scheduled 只能与 update --check 一起使用', 2, '请使用 update --check --scheduled;自动更新不使用后台调度入口。');
213
+ }
190
214
  return opts;
191
215
  }
192
216
 
@@ -287,15 +311,69 @@ async function runUpdateCheck(opts, dest) {
287
311
  var lines = [];
288
312
  var quiet = !!opts.json;
289
313
  function say(s) { if (!quiet) lines.push(s); }
290
- say('yotta-skills(元阁)v' + VERSION + ' —— 检查更新(只读,不改动) -> ' + dest);
291
- say('版本源: ' + (opts.registry || process.env.YOTTA_SKILLS_REGISTRY || 'https://registry.npmjs.org/'));
292
- say('');
314
+ var scheduled = !!opts.scheduled;
315
+ var cache = updateCheckLib.readCache();
316
+ var target = cache.targets[updateCheckLib.targetKey(dest)] || null;
317
+ var due = updateCheckLib.isDue(target, Date.now());
318
+ if (!scheduled) {
319
+ say('yotta-skills(元阁)v' + VERSION + ' —— 检查更新(只读,不改动) -> ' + dest);
320
+ say('版本源: ' + (opts.registry || process.env.YOTTA_SKILLS_REGISTRY || 'https://registry.npmjs.org/'));
321
+ say('');
322
+ }
323
+ if (scheduled && !due) {
324
+ var cachedRows = (target && target.last_result && target.last_result.rows) || [];
325
+ var cachedPayload = {
326
+ dest: dest,
327
+ scheduled: true,
328
+ due: false,
329
+ checked: false,
330
+ updatable: cachedRows.filter(function (x) { return x.hasUpdate; }),
331
+ updates: (target && target.last_result && target.last_result.updates) || 0,
332
+ latest: (target && target.last_result && target.last_result.latest) || 0,
333
+ failed: 0,
334
+ nonFamily: (target && target.last_result && target.last_result.nonFamily) || 0,
335
+ cache: target,
336
+ error: null,
337
+ };
338
+ if (opts.json) out(JSON.stringify(cachedPayload, null, 2));
339
+ return {
340
+ code: 0,
341
+ scheduled: true,
342
+ due: false,
343
+ checked: false,
344
+ rows: cachedRows,
345
+ updates: cachedPayload.updates,
346
+ latest: cachedPayload.latest,
347
+ failed: 0,
348
+ nonFamily: cachedPayload.nonFamily,
349
+ };
350
+ }
293
351
  var installed = scanInstalledSlugs(dest);
294
352
  if (installed.length === 0) {
295
- say('(' + dest + ' 下未发现已装技能目录)');
296
- return { code: 0, rows: [], updates: 0, latest: 0, failed: 0, nonFamily: 0 };
353
+ var emptyResult = { updates: 0, latest: 0, failed: 0, nonFamily: 0, rows: [], errors: [] };
354
+ var emptyCacheRecord = null;
355
+ try {
356
+ emptyCacheRecord = updateCheckLib.recordCheck(os.homedir(), dest, { result: emptyResult, error: null });
357
+ } catch (_) { /* 缓存是后台优化的 best-effort,不阻断检查结果 */ }
358
+ if (!scheduled) {
359
+ say('(' + dest + ' 下未发现已装技能目录)');
360
+ for (var emptyIndex = 0; emptyIndex < lines.length; emptyIndex++) out(lines[emptyIndex]);
361
+ }
362
+ return {
363
+ code: 0,
364
+ scheduled: scheduled,
365
+ due: true,
366
+ checked: true,
367
+ rows: [],
368
+ updates: 0,
369
+ latest: 0,
370
+ failed: 0,
371
+ nonFamily: 0,
372
+ cache: emptyCacheRecord,
373
+ };
297
374
  }
298
375
  var rows = [];
376
+ var failures = [];
299
377
  var updates = 0, latest = 0, failed = 0, nonFamily = 0;
300
378
  for (var i = 0; i < installed.length; i++) {
301
379
  var it = installed[i];
@@ -308,6 +386,7 @@ async function runUpdateCheck(opts, dest) {
308
386
  var res = await fetchRegistryLatest(fam.pkg, opts);
309
387
  if (!res.ok) {
310
388
  failed++;
389
+ failures.push({ slug: it.slug, error: res.error });
311
390
  say(' ✘ ' + it.slug.padEnd(22) + '检查失败: ' + res.error);
312
391
  continue;
313
392
  }
@@ -326,11 +405,61 @@ async function runUpdateCheck(opts, dest) {
326
405
  }
327
406
  rows.push({ slug: it.slug, installed: cur, latest: current, pkg: fam.pkg, hasUpdate: !isUp, family: fam });
328
407
  }
329
- say('');
330
- say('汇总: 有更新 ' + updates + ' / 已最新 ' + latest + ' / 检查失败 ' + failed + ' / 非家族跳过 ' + nonFamily);
331
- for (var j = 0; j < lines.length; j++) out(lines[j]);
408
+ var result = {
409
+ updates: updates,
410
+ latest: latest,
411
+ failed: failed,
412
+ nonFamily: nonFamily,
413
+ rows: rows,
414
+ errors: failures,
415
+ };
416
+ var lastError = failures.length > 0
417
+ ? failures.map(function (x) { return x.slug + ': ' + x.error; }).join('; ')
418
+ : null;
419
+ var cacheRecord = null;
420
+ try {
421
+ cacheRecord = updateCheckLib.recordCheck(os.homedir(), dest, { result: result, error: lastError });
422
+ } catch (_) { /* 缓存是后台优化的 best-effort,不阻断检查结果 */ }
332
423
  var code = (failed > 0) ? 1 : (updates > 0 ? 3 : 0);
333
- return { code: code, rows: rows, updates: updates, latest: latest, failed: failed, nonFamily: nonFamily };
424
+ var updatable = rows.filter(function (x) { return x.hasUpdate; });
425
+ if (scheduled) {
426
+ if (updates > 0) {
427
+ for (var j = 0; j < updatable.length; j++) {
428
+ out(' ' + updatable[j].slug + ':本地 v' + (updatable[j].installed || '未知') + ' -> 最新 v' + updatable[j].latest);
429
+ }
430
+ }
431
+ if (opts.json) {
432
+ out(JSON.stringify({
433
+ dest: dest,
434
+ scheduled: true,
435
+ due: true,
436
+ checked: true,
437
+ updatable: updatable,
438
+ updates: updates,
439
+ latest: latest,
440
+ failed: failed,
441
+ nonFamily: nonFamily,
442
+ cache: cacheRecord,
443
+ error: lastError,
444
+ }, null, 2));
445
+ }
446
+ return { code: 0, scheduled: true, due: true, checked: true, rows: rows, updates: updates, latest: latest, failed: failed, nonFamily: nonFamily };
447
+ }
448
+ say('汇总: 有更新 ' + updates + ' / 已最新 ' + latest + ' / 检查失败 ' + failed + ' / 非家族跳过 ' + nonFamily);
449
+ for (var lineIndex = 0; lineIndex < lines.length; lineIndex++) out(lines[lineIndex]);
450
+ if (opts.json) {
451
+ out(JSON.stringify({
452
+ dest: dest,
453
+ updatable: updatable,
454
+ updates: updates,
455
+ latest: latest,
456
+ failed: failed,
457
+ nonFamily: nonFamily,
458
+ cache: cacheRecord,
459
+ error: lastError,
460
+ }, null, 2));
461
+ }
462
+ return { code: code, scheduled: false, due: true, checked: true, rows: rows, updates: updates, latest: latest, failed: failed, nonFamily: nonFamily };
334
463
  }
335
464
 
336
465
  async function runUpdateAuto(opts, dest) {
@@ -626,6 +755,296 @@ function runUpdate(opts, dest) {
626
755
  return { failed, exitCode };
627
756
  }
628
757
 
758
+ // ── doctor / rollback ─────────────────────────────────────────────────────
759
+ function readInstalledManifest(target) {
760
+ const file = path.join(target, 'skill-manifest.json');
761
+ try {
762
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
763
+ } catch (_) {
764
+ return null;
765
+ }
766
+ }
767
+
768
+ function runCustomDoctor(skillDir, dest) {
769
+ const manifest = readInstalledManifest(skillDir);
770
+ if (!manifest || !manifest.install || !manifest.install.doctor) {
771
+ return { ok: true, skipped: true, error: null, result: null };
772
+ }
773
+ if (manifest.trust !== 'yottameta') {
774
+ return {
775
+ ok: false,
776
+ skipped: false,
777
+ error: 'manifest trust 不是 yottameta,拒绝执行自定义 doctor',
778
+ result: null,
779
+ };
780
+ }
781
+ return lifecycleLib.runPhase(skillDir, manifest, 'doctor', {
782
+ skillDir,
783
+ packageDir: skillDir,
784
+ dest,
785
+ });
786
+ }
787
+
788
+ function doctorTargets(opts, dest) {
789
+ if (opts.slug) {
790
+ const skill = familySkillFor(opts.slug);
791
+ return skill ? [skill] : [];
792
+ }
793
+ if (opts.skills.length) {
794
+ return opts.skills.map((slug) => familySkillFor(slug)).filter(Boolean);
795
+ }
796
+ const found = [];
797
+ let entries;
798
+ try { entries = fs.readdirSync(dest, { withFileTypes: true }); } catch (_) { return found; }
799
+ for (const entry of entries) {
800
+ if (!entry.isDirectory() || !/^yotta-/.test(entry.name)) continue;
801
+ const skill = familySkillFor(entry.name);
802
+ if (skill) found.push(skill);
803
+ }
804
+ return found;
805
+ }
806
+
807
+ function doctorExitCode(payload) {
808
+ if (payload.ok) return 0;
809
+ const manifestFailure = payload.results.some((result) =>
810
+ (result.checks || []).some((check) =>
811
+ (check.id === 'manifest' || check.id === 'manifest_trust') && !check.ok));
812
+ return manifestFailure ? 6 : 1;
813
+ }
814
+
815
+ function runDoctor(opts, dest) {
816
+ const scan = require('../lib/skills-scan');
817
+ const registry = scan.readRegistry();
818
+ const targets = doctorTargets(opts, dest);
819
+ const results = [];
820
+ const errors = [];
821
+ const warnings = [];
822
+ const fixes = [];
823
+
824
+ if (targets.length === 0) {
825
+ errors.push(opts.slug ? '未找到技能: ' + opts.slug : '目标目录下没有可检查的元阁家族技能');
826
+ }
827
+
828
+ for (const skill of targets) {
829
+ const target = path.join(dest, skill.slug);
830
+ const result = healthLib.checkInstalledSkill({
831
+ slug: skill.slug,
832
+ target,
833
+ expectedVersion: skill.version || null,
834
+ expectedPackage: skill.pkg || null,
835
+ registry,
836
+ });
837
+ const custom = runCustomDoctor(target, dest);
838
+ result.custom_doctor = {
839
+ ok: !!custom.ok,
840
+ skipped: !!custom.skipped,
841
+ error: custom.error || null,
842
+ result: custom.result || null,
843
+ };
844
+ if (!custom.ok) {
845
+ result.ok = false;
846
+ result.errors.push('自定义 doctor 失败: ' + (custom.error || '未知错误'));
847
+ result.checks.push({
848
+ id: 'custom_doctor',
849
+ ok: false,
850
+ severity: 'error',
851
+ message: '自定义 doctor 失败: ' + (custom.error || '未知错误'),
852
+ hint: '修复技能包内的 doctor 脚本后重试',
853
+ });
854
+ } else if (!custom.skipped) {
855
+ result.checks.push({
856
+ id: 'custom_doctor',
857
+ ok: true,
858
+ severity: 'info',
859
+ message: '自定义 doctor 通过',
860
+ hint: null,
861
+ });
862
+ }
863
+ results.push(result);
864
+ errors.push.apply(errors, result.errors);
865
+ warnings.push.apply(warnings, result.warnings);
866
+ fixes.push.apply(fixes, result.fixes);
867
+ }
868
+
869
+ const payload = {
870
+ ok: errors.length === 0,
871
+ dir: dest,
872
+ checked: results.length,
873
+ results,
874
+ errors,
875
+ warnings,
876
+ fixes: Array.from(new Set(fixes)),
877
+ };
878
+ const code = doctorExitCode(payload);
879
+ if (opts.json) {
880
+ out(JSON.stringify(payload, null, 2));
881
+ } else {
882
+ out('yotta-skills(元阁)v' + VERSION + ' —— doctor');
883
+ out('目标: ' + dest);
884
+ for (const result of results) {
885
+ out('');
886
+ out((result.ok ? '✔ ' : '✘ ') + result.slug + ' v' + (result.version || '未知'));
887
+ for (const check of result.checks) {
888
+ out(' ' + (check.ok ? '✔' : '✘') + ' ' + check.message + (check.hint ? '(修复: ' + check.hint + ')' : ''));
889
+ }
890
+ }
891
+ for (const item of warnings) out('[警告] ' + item);
892
+ for (const item of fixes) out('[建议] ' + item);
893
+ if (!payload.ok) out('doctor 未通过:请按上面的修复建议处理后重试。');
894
+ }
895
+ process.exitCode = code;
896
+ return payload;
897
+ }
898
+
899
+ function latestRollbackTarget(home) {
900
+ const file = path.join(home, '.yottaskills', 'install-log.jsonl');
901
+ let lines;
902
+ try { lines = fs.readFileSync(file, 'utf8').split(/\r?\n/).filter(Boolean); } catch (_) { return null; }
903
+ for (let i = lines.length - 1; i >= 0; i--) {
904
+ let entry;
905
+ try { entry = JSON.parse(lines[i]); } catch (_) { continue; }
906
+ if (!entry.skill || !entry.snapshot) continue;
907
+ if (!fs.existsSync(entry.snapshot)) continue;
908
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(entry.skill)) continue;
909
+ const expectedRoot = path.resolve(snapshotLib.snapshotRoot(home, entry.skill)) + path.sep;
910
+ if (!path.resolve(entry.snapshot).startsWith(expectedRoot)) continue;
911
+ const validation = snapshotLib.validateSnapshot(entry.snapshot);
912
+ if (validation.ok) return { slug: entry.skill, snapshot: entry.snapshot };
913
+ }
914
+ return null;
915
+ }
916
+
917
+ function runRollback(opts, dest) {
918
+ const home = os.homedir();
919
+ const requestedSlug = opts.slug || (opts.skills.length ? opts.skills[0] : null);
920
+
921
+ if (opts.list) {
922
+ const rows = snapshotLib.listSnapshots(home, requestedSlug);
923
+ const payload = {
924
+ ok: true,
925
+ dir: dest || null,
926
+ slug: requestedSlug || null,
927
+ count: rows.filter((row) => row.valid).length,
928
+ snapshots: rows,
929
+ };
930
+ if (opts.json) out(JSON.stringify(payload, null, 2));
931
+ else {
932
+ out('yotta-skills(元阁)v' + VERSION + ' —— rollback 快照');
933
+ for (const row of rows) {
934
+ out(' ' + (row.valid ? '✔' : '✘') + ' ' + row.slug + ' v' + (row.version || '未知') + ' ' + row.path + (row.reason ? '(' + row.reason + ')' : ''));
935
+ }
936
+ out('可用快照: ' + payload.count + ' / ' + rows.length);
937
+ }
938
+ return payload;
939
+ }
940
+
941
+ let slug = requestedSlug;
942
+ let selected = null;
943
+ if (slug) {
944
+ const rows = snapshotLib.listSnapshots(home, slug);
945
+ selected = rows.find((row) => row.valid) || rows[0] || null;
946
+ } else {
947
+ const latest = latestRollbackTarget(home);
948
+ if (latest) {
949
+ slug = latest.slug;
950
+ selected = snapshotLib.listSnapshots(home, slug).find((row) => row.path === latest.snapshot && row.valid) || null;
951
+ }
952
+ }
953
+
954
+ if (!slug) {
955
+ const payload = { ok: false, dir: dest, slug: null, errors: ['安装记录中没有可回滚的技能快照'] };
956
+ if (opts.json) out(JSON.stringify(payload, null, 2));
957
+ else out('回滚失败:安装记录中没有可回滚的技能快照。');
958
+ process.exitCode = 1;
959
+ return payload;
960
+ }
961
+ if (!selected || !selected.valid) {
962
+ const payload = {
963
+ ok: false,
964
+ dir: dest,
965
+ slug,
966
+ errors: [selected && selected.reason ? '选中的快照不可用: ' + selected.reason : '没有可用快照'],
967
+ };
968
+ if (opts.json) out(JSON.stringify(payload, null, 2));
969
+ else out('回滚失败:' + payload.errors[0]);
970
+ process.exitCode = 1;
971
+ return payload;
972
+ }
973
+
974
+ const target = path.join(dest, slug);
975
+ const restored = snapshotLib.restoreSnapshot(selected.path, target);
976
+ if (!restored.ok) {
977
+ const payload = { ok: false, dir: dest, slug, snapshot: selected.path, errors: [restored.error] };
978
+ if (opts.json) out(JSON.stringify(payload, null, 2));
979
+ else out('回滚失败:' + restored.error);
980
+ process.exitCode = 1;
981
+ return payload;
982
+ }
983
+
984
+ const family = familySkillFor(slug);
985
+ const scan = require('../lib/skills-scan');
986
+ const doctor = healthLib.checkInstalledSkill({
987
+ slug,
988
+ target,
989
+ expectedVersion: restored.version && restored.version !== 'unknown' ? restored.version : null,
990
+ expectedPackage: family ? family.pkg : null,
991
+ registry: scan.readRegistry(),
992
+ });
993
+ const custom = runCustomDoctor(target, dest);
994
+ if (!custom.ok) {
995
+ doctor.ok = false;
996
+ doctor.errors.push('自定义 doctor 失败: ' + (custom.error || '未知错误'));
997
+ }
998
+
999
+ const payload = {
1000
+ ok: doctor.ok,
1001
+ dir: dest,
1002
+ slug,
1003
+ snapshot: selected.path,
1004
+ version: restored.version,
1005
+ doctor,
1006
+ errors: doctor.errors,
1007
+ warnings: doctor.warnings,
1008
+ fixes: doctor.fixes,
1009
+ reindexed: false,
1010
+ };
1011
+ try {
1012
+ evidenceLib.appendEvidence({
1013
+ event: 'rollback',
1014
+ skill: slug,
1015
+ package: family ? family.pkg : null,
1016
+ version: restored.version,
1017
+ decision: doctor.ok ? 'ok' : 'fail',
1018
+ snapshot: selected.path,
1019
+ }, { homeDir: home });
1020
+ } catch (error) {
1021
+ payload.ok = false;
1022
+ payload.errors.push('回滚证据写入失败: ' + error.message);
1023
+ }
1024
+ if (payload.ok && !opts.noReindex) {
1025
+ try {
1026
+ reindexRegistry({ ...opts, dir: dest });
1027
+ payload.reindexed = true;
1028
+ } catch (error) {
1029
+ payload.warnings.push('回滚完成,但注册表重扫失败: ' + error.message);
1030
+ }
1031
+ }
1032
+
1033
+ if (opts.json) {
1034
+ out(JSON.stringify(payload, null, 2));
1035
+ } else {
1036
+ out('yotta-skills(元阁)v' + VERSION + ' —— rollback');
1037
+ out('技能: ' + slug);
1038
+ out('快照: ' + selected.path);
1039
+ out('结果: ' + (payload.ok ? '✔ 已恢复 v' + (restored.version || '未知') : '✘ 恢复后 doctor 未通过'));
1040
+ if (payload.errors.length) out('错误: ' + payload.errors.join('; '));
1041
+ if (payload.warnings.length) out('警告: ' + payload.warnings.join('; '));
1042
+ if (payload.reindexed) out('已重扫本地技能注册表。');
1043
+ }
1044
+ process.exitCode = payload.ok ? 0 : 1;
1045
+ return payload;
1046
+ }
1047
+
629
1048
  // ── 展示 ───────────────────────────────────────────────────────────────────
630
1049
  function printList(opts) {
631
1050
  const skills = opts.skills.length ? selectSkills(opts) : MANIFEST;
@@ -648,10 +1067,18 @@ function printHelp() {
648
1067
  out(' yotta-skills install --dir <path> 装全家到指定目录');
649
1068
  out(' yotta-skills install <skill> --dir <path> 装单个技能(可多个)');
650
1069
  out(' yotta-skills update --agent <name> 增量更新已装技能(补齐缺失 / 版本不一致)');
1070
+ out(' yotta-skills update --check 只读联网检查更新(退出码 0/3/1)');
1071
+ out(' yotta-skills update --check --scheduled 后台周检(未到期不联网;到期单次检查并写缓存)');
1072
+ out(' yotta-skills doctor --dir <path> 只读自检技能目录(可加 --slug / --json)');
1073
+ out(' yotta-skills rollback --dir <path> 回滚最近一次技能安装或更新(--list 查看快照)');
651
1074
  out(' yotta-skills --dry-run 预览将安装清单(不联网、不改动)');
652
1075
  out(' yotta-skills --inventory 盘点本机已装技能(自研扫描,不依赖任何元技能)');
653
1076
  out(' yotta-skills --reindex 重扫注册表(会话开工 / 装技能后自动调用;增量合并)');
654
1077
  out(' yotta-skills --route "<需求摘要>" 给出场景组合、调用顺序、缺失技能安装建议');
1078
+ out(' yotta-skills hook capabilities 查看宿主六事件能力矩阵');
1079
+ out(' yotta-skills hook evaluate --event <event> --manifest <file> 评估 hook 声明并留证');
1080
+ out(' yotta-skills hook bind --manifest <file> 注册 hook 声明(幂等)');
1081
+ out(' yotta-skills hook unbind <binding-id> 反注册 hook 声明');
655
1082
  out('');
656
1083
  out('选项:');
657
1084
  out(' --agent <name> 智能体键名(--list 可查看;未知智能体请用 --dir)');
@@ -662,11 +1089,17 @@ function printHelp() {
662
1089
  out(' --npm <path> 指定 npm 可执行文件(默认 npm / npm.cmd)');
663
1090
  out(' --python <path> 指定 python 可执行文件(元信 scan 用)');
664
1091
  out(' --verify <path> 指定 yotta_verify.py 路径(默认找目标目录已装的元信)');
665
- out(' --json inventory / reindex / route 时输出 JSON');
1092
+ out(' --json inventory / reindex / route / doctor / rollback 时输出 JSON');
1093
+ out(' --slug <slug> doctor / rollback 时只处理指定技能');
666
1094
  out(' --route <需求> 静态编排路由(输出组合 / 顺序 / 依据 / 缺失技能建议)');
667
1095
  out(' --check update 时仅只读检查更新(联网对 npm 最新版本,不改动;退出码 0/3/1)');
1096
+ out(' --scheduled 与 update --check 合用:后台周检入口(未到期不联网;到期单次检查并写缓存)');
668
1097
  out(' --auto update 时检查到家族更新后自动更新(仅 yotta-* 家族,含装前扫描)');
669
1098
  out(' --registry <url> npm registry 地址(默认 https://registry.npmjs.org/;YOTTA_SKILLS_REGISTRY 覆盖)');
1099
+ out(' --host <name> hook 适配宿主名(默认 generic;当前已实测 codex)');
1100
+ out(' --event <event> hook 六事件之一(before_start / before_tool / before_install / before_publish / after_milestone / before_send)');
1101
+ out(' --manifest <file> hook evaluate / bind 使用的 skill-manifest.json 路径');
1102
+ out(' --context <json> hook evaluate 的检查结果 JSON(checks / wrapperRegistered / evidence)');
670
1103
  out(' --project inventory / reindex 时附加扫描当前项目 .agents/skills / .codex/skills');
671
1104
  out(' --no-reindex 安装 / 更新后不自动重扫注册表');
672
1105
  out(' -h, --help 帮助');
@@ -791,20 +1224,121 @@ function maybeAutoReindex(opts, dest) {
791
1224
  }
792
1225
  }
793
1226
 
1227
+ // ── 运行时 hook 适配层(P0-3) ───────────────────────────────────────────────
1228
+ function readJsonFile(file, label) {
1229
+ try {
1230
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
1231
+ } catch (error) {
1232
+ die(label + ' 读取失败: ' + error.message, 2, '请确认文件存在且为合法 JSON。');
1233
+ }
1234
+ }
1235
+
1236
+ function runHook(opts) {
1237
+ const action = opts.rest[0] || 'capabilities';
1238
+ const host = opts.host || 'generic';
1239
+ const adapter = hookAdapterLib.createHookAdapter({ host });
1240
+
1241
+ if (action === 'capabilities') {
1242
+ const payload = { host, capabilities: adapter.capabilities() };
1243
+ if (opts.json) out(JSON.stringify(payload, null, 2));
1244
+ else {
1245
+ out('yotta-skills(元阁)v' + VERSION + ' —— hook capabilities');
1246
+ out('宿主: ' + host);
1247
+ hookAdapterLib.EVENTS.forEach((event) => out(' ' + event.padEnd(18) + payload.capabilities[event]));
1248
+ }
1249
+ return;
1250
+ }
1251
+
1252
+ if (action === 'evaluate') {
1253
+ if (!opts.event) die('hook evaluate 缺少 --event', 2, '请提供六个统一事件之一。');
1254
+ if (!opts.manifest) die('hook evaluate 缺少 --manifest', 2, '请提供 skill-manifest.json 路径。');
1255
+ const manifest = readJsonFile(opts.manifest, 'manifest');
1256
+ let context = {};
1257
+ if (opts.context) {
1258
+ try {
1259
+ context = JSON.parse(opts.context);
1260
+ } catch (error) {
1261
+ die('--context 不是合法 JSON: ' + error.message, 2, '请传入 JSON 对象。');
1262
+ }
1263
+ }
1264
+ if (!context || typeof context !== 'object' || Array.isArray(context)) {
1265
+ die('--context 必须是 JSON 对象', 2);
1266
+ }
1267
+ const result = adapter.evaluate(opts.event, manifest, context);
1268
+ const entries = result.results.length > 0
1269
+ ? result.results
1270
+ : [{ event: opts.event, skill: manifest.slug, action: null, decision: result.decision, evidence: {}, capability: adapter.capabilities()[opts.event] || 'unsupported', reason: result.user_message }];
1271
+ for (const entry of entries) {
1272
+ hookAdapterLib.appendHookEvidence({
1273
+ event: entry.event,
1274
+ skill: entry.skill,
1275
+ action: entry.action,
1276
+ result: entry.decision,
1277
+ capability: entry.capability,
1278
+ evidence: entry.evidence,
1279
+ reason: entry.reason,
1280
+ }, { homeDir: os.homedir() });
1281
+ }
1282
+ if (opts.json) out(JSON.stringify({ host, event: opts.event, ...result }, null, 2));
1283
+ else {
1284
+ out('yotta-skills(元阁)v' + VERSION + ' —— hook evaluate');
1285
+ out('宿主: ' + host + ' / 事件: ' + opts.event);
1286
+ out('决策: ' + result.decision + ' / verified: ' + result.verified);
1287
+ out(result.user_message);
1288
+ for (const entry of result.results) out(' ' + entry.action + ': ' + entry.decision + '(' + entry.capability + ')');
1289
+ }
1290
+ process.exitCode = result.decision === 'block' ? 3 : 0;
1291
+ return;
1292
+ }
1293
+
1294
+ if (action === 'bind') {
1295
+ if (!opts.manifest) die('hook bind 缺少 --manifest', 2, '请提供 skill-manifest.json 路径。');
1296
+ const manifest = readJsonFile(opts.manifest, 'manifest');
1297
+ const bindings = adapter.bind(manifest, opts.event || null);
1298
+ if (opts.json) out(JSON.stringify({ host, bindings }, null, 2));
1299
+ else {
1300
+ out('yotta-skills(元阁)v' + VERSION + ' —— hook bind');
1301
+ out('宿主: ' + host + ' / 绑定 ' + bindings.length + ' 项');
1302
+ for (const binding of bindings) out(' ' + binding.id + ' ' + binding.event + ' ' + binding.action + ' ' + binding.capability);
1303
+ }
1304
+ return;
1305
+ }
1306
+
1307
+ if (action === 'unbind') {
1308
+ const id = opts.rest[1];
1309
+ if (!id) die('hook unbind 缺少 binding id', 2, '先用 hook bind / list 获取 id。');
1310
+ const removed = adapter.unbind(id);
1311
+ if (opts.json) out(JSON.stringify({ host, id, removed }, null, 2));
1312
+ else out(removed ? '已解除绑定: ' + id : '未找到绑定: ' + id);
1313
+ process.exitCode = removed ? 0 : 1;
1314
+ return;
1315
+ }
1316
+
1317
+ die('未知 hook 子命令: ' + action, 2, '支持 capabilities / evaluate / bind / unbind。');
1318
+ }
1319
+
794
1320
  // ── main ───────────────────────────────────────────────────────────────────
795
1321
  function main() {
796
1322
  const opts = parseArgs(process.argv.slice(2));
797
1323
  if (opts.help) { printHelp(); return; }
798
1324
  if (opts.version) { out('yotta-skills v' + VERSION); return; }
799
- if (opts.list) { printList(opts); return; }
1325
+ if (opts.list && !opts.command) { printList(opts); return; }
800
1326
  if (opts.inventory && !opts.command) { runInventory(opts); return; }
801
1327
  if (opts.reindex && !opts.command) { runReindex(opts); return; }
802
1328
  if (opts.route && !opts.command) { runRoute(opts); return; }
803
1329
 
804
1330
  const command = opts.command || 'install';
1331
+ if (command === 'hook') {
1332
+ runHook(opts);
1333
+ return;
1334
+ }
1335
+ if (command === 'rollback' && opts.list) {
1336
+ runRollback(opts, null);
1337
+ return;
1338
+ }
805
1339
  let dest = resolveTargetDir(opts);
806
1340
  if (!dest && !opts.dryRun) dest = detectProjectDir();
807
- if (!dest && command === 'install' && !opts.dryRun) {
1341
+ if (!dest && (command === 'install' || command === 'doctor' || command === 'rollback') && !opts.dryRun) {
808
1342
  die('未指定目标:请用 --agent <name> 或 --dir <path>(当前目录未检测到项目级技能目录)。', 4, '未收录智能体也可用 --dir 指定其技能目录。');
809
1343
  }
810
1344
 
@@ -823,13 +1357,14 @@ function main() {
823
1357
  return;
824
1358
  }
825
1359
 
826
- if (command === 'update') {
1360
+ if (command === 'doctor') {
1361
+ runDoctor(opts, dest);
1362
+ } else if (command === 'rollback') {
1363
+ runRollback(opts, dest);
1364
+ } else if (command === 'update') {
827
1365
  if (opts.check || opts.auto) {
828
1366
  var runFn = opts.auto ? runUpdateAuto : runUpdateCheck;
829
1367
  runFn(opts, dest).then(function (r) {
830
- if (opts.json && !opts.auto) {
831
- out(JSON.stringify({ dest: dest, updatable: r.rows.filter(function (x) { return x.hasUpdate; }), updates: r.updates, latest: r.latest, failed: r.failed, nonFamily: r.nonFamily }, null, 2));
832
- }
833
1368
  process.exitCode = r.code;
834
1369
  });
835
1370
  } else {
@@ -4,7 +4,7 @@
4
4
 
5
5
  - **安装目标**:未指定目标怎么办 · 支持哪些智能体 · 装完为什么看不到
6
6
  - **网络与源**:国内访问慢 · 镜像延迟 · 网络失败
7
- - **更新**:`update --check` 退出码 · 自动更新边界 · 幂等
7
+ - **更新**:`update --check` 退出码 · 后台周检缓存 · 自动更新边界 · 幂等
8
8
  - **版本**:`--pin` 与默认策略 · 清单漂移 · 单技能安装
9
9
  - **安全**:装前扫描 · 目标目录校验 · 失败恢复
10
10
 
@@ -43,6 +43,13 @@ YOTTA_SKILLS_NPM_FLAGS="--registry=https://registry.npmmirror.com" npx -y @yotta
43
43
 
44
44
  该命令只读,不修改文件。
45
45
 
46
+ ## 4.1 `update --check --scheduled` 什么时候用?
47
+
48
+ 它是后台周检入口:默认 7 天加 0 到 24 小时随机抖动,未到期不联网;到期只检查一次,
49
+ 结果写入 `~/.yottaskills/update-check.json`。文本模式网络失败静默并退出 0,
50
+ `--json` 可读取 `error` / `cache` 诊断字段。会话开工只建议做本地 `--reindex`,
51
+ 不默认联网更新检查。
52
+
46
53
  ## 5. `update --auto` 会更新所有技能吗?
47
54
 
48
55
  只自动更新元阁家族内技能(`yotta-*` 且在清单中)。其他已装技能只提示,不自动改动。
@@ -76,8 +83,16 @@ npx -y @yottameta/yotta-skills install yotta-memory yotta-workflow --agent codex
76
83
 
77
84
  ## 11. 安装失败后如何恢复?
78
85
 
79
- 查看汇总中的失败原因。旧版本快照保存在 `~/.yottaskills/snapshots/`,当前版本失败时
80
- 新版本不会替换旧版本。先修复目录权限或网络,再单独安装失败技能:
86
+ 查看汇总中的失败原因。setup / doctor 失败时元阁会自动恢复旧版本;旧版本快照仍保存在
87
+ `~/.yottaskills/snapshots/`。可以先用 `doctor` 自检,再用 `rollback` 恢复最近快照:
88
+
89
+ ```bash
90
+ npx -y @yottameta/yotta-skills doctor --dir /path/to/skills --slug <slug>
91
+ npx -y @yottameta/yotta-skills rollback --list --dir /path/to/skills
92
+ npx -y @yottameta/yotta-skills rollback --slug <slug> --dir /path/to/skills
93
+ ```
94
+
95
+ 如果只是安装源或网络问题,再单独安装失败技能:
81
96
 
82
97
  ```bash
83
98
  npx -y @yottameta/yotta-skills install <slug> --dir /path/to/skills
@@ -86,3 +101,7 @@ npx -y @yottameta/yotta-skills install <slug> --dir /path/to/skills
86
101
  ## 12. 装完为什么智能体里看不到?
87
102
 
88
103
  确认安装目录与智能体实际读取目录一致;安装后通常需要重启会话或重新加载技能列表。
104
+
105
+ ## 13. `hook evaluate` 会真的拦截动作吗?
106
+
107
+ 不一定。适配层先探测宿主能力:`native-block` 才能声明动作前阻断;`native-audit` 只能审计并触发一次纠偏,结果会标记 `explicit-unverified`;`wrapper-only` 仅在 wrapper 注册后保证;未知宿主全部按 `unsupported` 处理。当前 Codex 已实测能力见 `hook capabilities --host codex`。
@@ -14,7 +14,7 @@
14
14
  5. 校验 slug / package / version / 权限 / 生命周期脚本路径;
15
15
  6. 元信装前扫描:已有元信则直接扫描;没有则自动安装元信自身,再扫描;
16
16
  7. 通过后创建旧版本快照,把新版本复制到同盘暂存目录,再原子切换到目标目录;
17
- 8. setup / doctor 阶段执行内置检查;P0-2.1 不执行自定义生命周期脚本;
17
+ 8. 依次执行包内 setup、内置 doctor、包内自定义 doctor;任一步失败自动恢复旧版本;
18
18
  9. 汇总报告:✔ 成功 / - 跳过(已是最新)/ ✘ 失败;
19
19
  10. 全部成功后自动 re-index 本地技能注册表(`~/.yottaskills/registry.json`):重扫技能根目录并增量合并,
20
20
  新装 / 更新的技能随即进入注册表(`--no-reindex` 可关闭;`--dry-run` 不触发)。
@@ -38,6 +38,16 @@
38
38
  没有 manifest 时使用家族默认契约:来源 `yottameta`、幂等安装、自动应用模式 `route`,
39
39
  不声明 MCP、hook 或自定义生命周期脚本。
40
40
 
41
+ ## 自定义生命周期脚本
42
+
43
+ 包内 `skill-manifest.json` 可声明 `install.setup` / `install.doctor` /
44
+ `install.rollback`,值必须是包内相对路径,不能是绝对路径或包含 `..`。
45
+
46
+ 元阁用当前 Node.js 直接执行脚本,不经过 shell。脚本接收 `--skill-dir` /
47
+ `--package-dir` / `--dest`,rollback 另接收 `--snapshot`,并始终追加 `--json`;
48
+ 脚本必须输出一个 JSON 对象,至少包含 `ok`。setup 或 doctor 失败时,先恢复旧版本,
49
+ 再在声明了 rollback 时调用它。生命周期结果、回滚结果和快照路径都会写入安装证据。
50
+
41
51
  ## re-index(装技能后自动重扫注册表)
42
52
 
43
53
  - `install` / `update` 完成后,CLI 自动调用 `--reindex`(同一套 `lib/skills-scan.js` 扫描核心):
@@ -78,14 +88,53 @@
78
88
  - `--skip-scan` 只保留为人工应急路径,使用时输出 `explicit-unverified` 并写入证据;
79
89
  - `update --auto` 始终执行装前门禁,不接受 `--skip-scan`。
80
90
 
91
+ ## 更新检查缓存
92
+
93
+ - 手动 `update --check`:每次联网,只读,保持退出码 0 / 3 / 1。
94
+ - 后台周检 `update --check --scheduled`:默认 7 天加 0 到 24 小时随机抖动;未到期直接返回,
95
+ 到期只检查一次;文本失败静默,`--json` 保留诊断。
96
+ - 缓存文件:`~/.yottaskills/update-check.json`;按目标技能目录隔离记录,
97
+ 字段包含 `last_checked`、`next_check`、`last_result`、`last_error`。
98
+ - 本缓存只记录版本检查结果与网络错误,不记录技能内容或用户数据。
99
+
100
+ ## 运行时 hook 适配
101
+
102
+ - `hook capabilities --host <name>`:查看六个统一事件的四档宿主能力;未知宿主默认 `unsupported`。
103
+ - `hook evaluate --host <name> --event <event> --manifest <file> --context <json>`:按 manifest 顺序评估声明,输出 `allow` / `block` / `warn` / `unverified`。
104
+ - `hook bind --manifest <file>` / `hook unbind <id>`:幂等注册或反注册声明,不直接改写宿主配置。
105
+ - 评估证据写入 `~/.yottaskills/hook-log.jsonl`,绑定记录写入 `~/.yottaskills/hook-bindings.json`。
106
+ - `native-audit` 不等于强制;缺少能力或证据时必须显示 `explicit-unverified`。
107
+
81
108
  ## 快照与回滚
82
109
 
83
110
  - 旧版本快照:`~/.yottaskills/snapshots/<slug>/<timestamp>-<version>-<随机后缀>/`;
111
+ - 新快照同时写入 `<快照目录>.meta.json`,包含 SHA-256 摘要、版本、文件数和来源;
112
+ - 无元数据的旧快照仍可按 `SKILL.md` 做结构校验;
84
113
  - 新版本先落在 `<dest>/.yottaskills-staging/<slug>-<随机后缀>/`;
85
114
  - 旧目标先重命名为同盘备份,再把暂存目录切到目标;切换失败时恢复备份;
86
115
  - 证据写入失败会触发回滚,不把安装标记为成功;
87
116
  - Windows 上对 `EPERM` / `EACCES` / `EBUSY` 做短重试,避免杀毒或索引服务造成的瞬时锁。
88
117
 
118
+ 回滚命令:
119
+
120
+ ```bash
121
+ yotta-skills rollback --list --dir <skills-dir>
122
+ yotta-skills rollback --slug <slug> --dir <skills-dir>
123
+ ```
124
+
125
+ 恢复前校验快照摘要;恢复时先在目标目录同盘暂存,再替换当前目录。恢复成功后执行
126
+ 内置 doctor 和自定义 doctor,并保留原快照供再次恢复。
127
+
128
+ ## doctor
129
+
130
+ ```bash
131
+ yotta-skills doctor --dir <skills-dir> --slug <slug>
132
+ yotta-skills doctor --dir <skills-dir> --json
133
+ ```
134
+
135
+ doctor 只读检查目录、`SKILL.md`、版本、manifest 身份、注册表记录和自定义 doctor,
136
+ 不会修改目标目录或注册表。注册表版本不一致属于 warning,可用 `--reindex` 修复。
137
+
89
138
  ## 安装证据
90
139
 
91
140
  每次安装决策写入:
@@ -7,26 +7,26 @@
7
7
  |---|---|---|---|---|
8
8
  | `yotta-anti-shallow` | 元谨 | `@yottameta/yotta-anti-shallow` | 1.4.0 | 防 AI 敷衍规则引擎(深入分析/根因追溯时激活) |
9
9
  | `yotta-code-quality` | 元质 | `@yottameta/yotta-code-quality` | 0.4.0 | 结对式代码质量评审(十二类腐化风险 + 0-100 健康分) |
10
- | `yotta-workflow` | 元序 | `@yottameta/yotta-workflow` | 0.4.0 | 跨会话/跨项目通用工作流协议(状态就近存) |
11
- | `yotta-memory` | 元忆 | `@yottameta/yotta-memory` | 0.12.0 | 有权限边界的文件式智能体记忆(FACT/PREF/BOUND/COMMIT) |
10
+ | `yotta-workflow` | 元序 | `@yottameta/yotta-workflow` | 0.4.1 | 跨会话/跨项目通用工作流协议(状态就近存) |
11
+ | `yotta-memory` | 元忆 | `@yottameta/yotta-memory` | 0.13.0 | 有权限边界的文件式智能体记忆(FACT/PREF/BOUND/COMMIT) |
12
12
  | `yotta-learn` | 元习 | `@yottameta/yotta-learn` | 0.2.0 | 学习沉淀 CLI:错误/纠正/洞见沉淀为 .learnings/ 条目 |
13
- | `yotta-security-audit` | 元安 | `@yottameta/yotta-security-audit` | 0.2.2 | 安全扫描引擎:13 类检测器 + 系统安全基线 |
13
+ | `yotta-security-audit` | 元安 | `@yottameta/yotta-security-audit` | 0.2.3 | 安全扫描引擎:13 类检测器 + 系统安全基线 |
14
14
  | `yotta-vetter` | 元审 | `@yottameta/yotta-vetter` | 0.2.3 | 安全审查协议:四阶段 review + SAFE TO INSTALL 判定 |
15
- | `yotta-recon` | 元析 | `@yottameta/yotta-recon` | 0.1.5 | 跨智能体网络侦察:零依赖端口/服务/版本指纹探测 |
16
- | `yotta-guardian` | 元盾 | `@yottameta/yotta-guardian` | 0.1.2 | 跨智能体危险调用拦截护栏:确定性规则 + 可插拔意图验证 |
15
+ | `yotta-recon` | 元析 | `@yottameta/yotta-recon` | 0.1.6 | 跨智能体网络侦察:零依赖端口/服务/版本指纹探测 |
16
+ | `yotta-guardian` | 元盾 | `@yottameta/yotta-guardian` | 0.1.3 | 跨智能体危险调用拦截护栏:确定性规则 + 可插拔意图验证 |
17
17
  | `yotta-humanize` | 元真 | `@yottameta/yotta-humanize` | 0.2.0 | 去 AI 味中文写作编辑:检测器引擎 |
18
18
  | `yotta-logs` | 元史 | `@yottameta/yotta-logs` | 0.3.0 | 跨智能体历史会话 / 记忆日志检索 |
19
19
  | `yotta-security-testing` | 元测 | `@yottameta/yotta-security-testing` | 0.3.0 | 有纪律的 AI 安全测试方法论 + Scope Guard 五道防线 |
20
20
  | `yotta-agent-hardening` | 元安全 | `@yottameta/yotta-agent-hardening` | 0.2.4 | 防御向 AI 智能体自身安全加固(配置面静态扫描三域) |
21
- | `yotta-skill-creator` | 元造 | `@yottameta/yotta-skill-creator` | 0.1.0 | 工坊「造」:端到端造技能脚手架 |
22
- | `yotta-publish-guard` | 元守 | `@yottameta/yotta-publish-guard` | 0.3.0 | 工坊「守」:发布前守门(版本四件对齐 + 三通道查重) |
23
- | `yotta-logwatch` | 元察 | `@yottameta/yotta-logwatch` | 0.2.7 | 安全日志分析检测引擎(攻击链识别/告警聚合) |
21
+ | `yotta-skill-creator` | 元造 | `@yottameta/yotta-skill-creator` | 0.1.1 | 工坊「造」:端到端造技能脚手架 |
22
+ | `yotta-publish-guard` | 元守 | `@yottameta/yotta-publish-guard` | 0.4.1 | 工坊「守」:发布前守门(版本四件对齐 + 三通道查重) |
23
+ | `yotta-logwatch` | 元察 | `@yottameta/yotta-logwatch` | 0.2.8 | 安全日志分析检测引擎(攻击链识别/告警聚合) |
24
24
  | `yotta-intel` | 元情 | `@yottameta/yotta-intel` | 0.2.0 | 威胁情报 IOC 提取与规范化引擎(STIX-lite) |
25
25
  | `yotta-secret` | 元钥 | `@yottameta/yotta-secret` | 0.2.0 | 密钥 / 凭据泄露源头扫描引擎 |
26
26
  | `yotta-chain` | 元链 | `@yottameta/yotta-chain` | 0.1.2 | 供应链依赖校验引擎 |
27
- | `yotta-triage` | 元鉴 | `@yottameta/yotta-triage` | 0.1.1 | 恶意样本静态初筛引擎(哈希/熵/字符串/PE-ELF) |
27
+ | `yotta-triage` | 元鉴 | `@yottameta/yotta-triage` | 0.1.2 | 恶意样本静态初筛引擎(哈希/熵/字符串/PE-ELF) |
28
28
  | `yotta-prompt` | 元引 | `@yottameta/yotta-prompt` | 0.2.0 | 意图澄清 + 生态入口(常驻注入,map 串联元阁全家) |
29
- | `yotta-verify` | 元信 | `@yottameta/yotta-verify` | 0.2.2 | 装前安全扫描器 + audited 徽章(prompt injection + 危险模式) |
29
+ | `yotta-verify` | 元信 | `@yottameta/yotta-verify` | 0.3.0 | 装前安全扫描器 + audited 徽章(prompt injection + 危险模式) |
30
30
 
31
31
  ## 家族分布
32
32
 
@@ -89,6 +89,15 @@ npx -y @yottameta/yotta-skills install --agent codex --skip-scan # 人工应急
89
89
  `~/.yottaskills/install-log.jsonl`。`--skip-scan` 只用于人工应急,会输出
90
90
  `explicit-unverified` 并留证,`update --auto` 不会使用它。
91
91
 
92
+ 安装阶段还会按包内 manifest 执行 setup / doctor;setup 或 doctor 失败时自动恢复旧版本。
93
+ 可以用 `doctor` 只读自检,用 `rollback` 校验并恢复最近快照:
94
+
95
+ ```bash
96
+ npx -y @yottameta/yotta-skills doctor --agent codex --slug yotta-memory
97
+ npx -y @yottameta/yotta-skills rollback --list --agent codex
98
+ npx -y @yottameta/yotta-skills rollback --slug yotta-memory --agent codex
99
+ ```
100
+
92
101
  ## 9. 验证安装结果
93
102
 
94
103
  - 看安装汇总:成功 N / 跳过 N / 失败 N;
@@ -108,6 +117,11 @@ npx -y @yottameta/yotta-skills install --agent codex --skip-scan # 人工应急
108
117
  - **目标未指定**:当前目录没有项目级技能目录时报错(退出码 4),加 `--agent` 或
109
118
  `--dir`。
110
119
  - **已有旧版本**:默认按 range 覆盖升级到最新 patch;`--pin` 则锁定清单版本。
120
+ - **doctor 报注册表版本不一致**:运行 `yotta-skills --reindex` 重扫注册表;doctor 本身不会写文件。
121
+ - **需要撤销最近安装 / 更新**:先用 `rollback --list` 查看快照,再执行
122
+ `rollback --slug <slug>`;恢复前会自动校验快照。
123
+ - **后台更新检查没有输出**:`update --check --scheduled` 在未到期、已最新或网络失败时
124
+ 文本模式保持静默;需要诊断时加 `--json`。
111
125
 
112
126
  ## 11. 盘点已装技能与 re-index(新装技能自动被发现)
113
127
 
@@ -122,6 +136,9 @@ npx -y @yottameta/yotta-skills --reindex
122
136
  - `install` / `update` 完成后会自动重扫注册表(`~/.yottaskills/registry.json`),新装 / 更新的
123
137
  技能随即出现在 `--inventory` / `--reindex` 里;`--no-reindex` 可关闭自动重扫。
124
138
  - 建议每会话开工先跑一次 `--reindex`(快速增量,只合并变化),让后装的技能自动被看见。
139
+ - 更新检查不再作为会话开工默认动作:手动检查用 `update --check`;后台周检用
140
+ `update --check --scheduled`,未到期不联网,到期只检查一次并写
141
+ `~/.yottaskills/update-check.json`。
125
142
 
126
143
  ## 编排路由(--route)
127
144
 
@@ -134,3 +151,16 @@ npx -y @yottameta/yotta-skills --route "检查代码质量,别糊弄"
134
151
  输出会告诉你命中哪个组合、按什么顺序调用、每个技能负责什么、哪些已装、哪些缺失,以及缺失技能的安装命令。元阁只给建议,不会自动安装;安装前请先做装前安全扫描,并由用户确认。
135
152
  - `--json` 输出机器可读结果(含新增 / 更新 / 消失),适合脚本与钩子。
136
153
  - 如果本机还装了非元阁家族技能,`--route` 会额外列出「其他已装技能候选」:只按 frontmatter description 与需求文本做本地机械匹配,标注来源、得分、命中词项与扫描状态(默认未扫描),不读取全文指令、不自动调用;使用/安装前请先做装前安全扫描。
154
+
155
+ ## 运行时 hook 适配(hook)
156
+
157
+ 技能 manifest 只声明六个统一事件的要求;元阁负责能力探测、确定性评估、证据留痕和降级标注:
158
+
159
+ ```bash
160
+ npx -y @yottameta/yotta-skills hook capabilities --host codex --json
161
+ npx -y @yottameta/yotta-skills hook evaluate --host codex --event before_send --manifest ./skill-manifest.json --context '{"checks":{}}' --json
162
+ npx -y @yottameta/yotta-skills hook bind --host codex --manifest ./skill-manifest.json
163
+ ```
164
+
165
+ `native-block` 才会显示动作前阻断;`native-audit` 只能审计并触发一次纠偏,结果标
166
+ `explicit-unverified`;未知宿主全部按 `unsupported` 处理。本层不联网、不下载、不改宿主配置文件。
@@ -30,15 +30,23 @@ npx -y @yottameta/yotta-skills --agent codex
30
30
  npx -y @yottameta/yotta-skills update --check --agent codex
31
31
  ```
32
32
 
33
- 2. 根据输出确认可更新清单。
33
+ 2. 如果由后台调度执行,改用周检入口:
34
34
 
35
- 3. 执行增量更新:
35
+ ```bash
36
+ npx -y @yottameta/yotta-skills update --check --scheduled --agent codex
37
+ ```
38
+
39
+ 未到期时不联网;到期只检查一次并把结果写入 `~/.yottaskills/update-check.json`。
40
+
41
+ 3. 根据输出确认可更新清单。
42
+
43
+ 4. 执行增量更新:
36
44
 
37
45
  ```bash
38
46
  npx -y @yottameta/yotta-skills update --agent codex
39
47
  ```
40
48
 
41
- 4. 复核:已最新的技能应显示跳过,落后的技能显示更新;失败项单独重装。
49
+ 5. 复核:已最新的技能应显示跳过,落后的技能显示更新;失败项单独重装。
42
50
 
43
51
  ## 走查 3:按路由安装单个技能
44
52
 
@@ -24,7 +24,7 @@ import subprocess
24
24
  import sys
25
25
  from pathlib import Path
26
26
 
27
- VERSION = "0.8.0"
27
+ VERSION = "0.19.0"
28
28
  TOOL_NAME = "yotta-skills"
29
29
  CN_NAME = "元阁"
30
30
  MCP_PROTOCOL_MODERN = "2026-07-28"