@yottameta/yotta-skills-plugin 0.7.2 → 0.8.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.
- package/.agents/plugins/marketplace.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/yotta-skills/SKILL.md +16 -9
- package/skills/yotta-skills/bin/yotta-skills.js +126 -70
- package/skills/yotta-skills/references/faq.md +6 -2
- package/skills/yotta-skills/references/install-flow.md +46 -7
- package/skills/yotta-skills/references/skill-list.md +1 -1
- package/skills/yotta-skills/references/tutorial.md +10 -6
- package/skills/yotta-skills/scripts/yotta-skills-mcp.py +1 -1
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"authentication": "ON_INSTALL"
|
|
13
13
|
},
|
|
14
14
|
"category": "Developer Tools",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.8.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.
|
|
15
|
+
"version": "0.8.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
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.
|
|
4
|
+
"version": "0.8.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.
|
|
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
|
|
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 阻断,非元阁家族包不自动安装。
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
7
|
zh_name: 元阁
|
|
@@ -132,9 +132,9 @@ npx -y @yottameta/yotta-skills --reindex
|
|
|
132
132
|
| `--dry-run` | 预览将执行的安装 / 更新清单;不联网、不改动 |
|
|
133
133
|
| `--pin` | 锁死清单精确版本(默认 range:跟随同 major 最新 patch) |
|
|
134
134
|
| `--force` | 已是最新也重新安装 |
|
|
135
|
-
| `--skip-scan` |
|
|
135
|
+
| `--skip-scan` | 人工应急路径:跳过元信门禁并标记 `explicit-unverified`;不得用于 `update --auto` |
|
|
136
136
|
| `--npm <path>` | 指定 npm 可执行文件 |
|
|
137
|
-
| `--python <path>` | 指定 python
|
|
137
|
+
| `--python <path>` | 指定 python 可执行文件(元信 scan 用) |
|
|
138
138
|
| `--verify <path>` | 指定 yotta_verify.py 路径 |
|
|
139
139
|
| `-h, --help` / `-v, --version` | 帮助 / 版本 |
|
|
140
140
|
|
|
@@ -235,12 +235,19 @@ npx -y @yottameta/yotta-skills --route "检查代码质量,别糊弄" --json
|
|
|
235
235
|
- 是否「已是最新」由目标目录 `<slug>/SKILL.md` 的 frontmatter `version` 与清单比对,
|
|
236
236
|
一致即跳过(幂等)。
|
|
237
237
|
|
|
238
|
-
##
|
|
238
|
+
## 元信装前门禁
|
|
239
|
+
|
|
240
|
+
家族安装默认执行装前扫描。若本机没有元信(yotta-verify),元阁先按 `skills.json`
|
|
241
|
+
安装元信自身,再扫描待装技能。安装前会先读取包内 manifest、校验身份,再进入元信门禁:
|
|
242
|
+
|
|
243
|
+
- `SAFE TO INSTALL`:继续安装;
|
|
244
|
+
- `INSTALL WITH CAUTION` / `REVIEW REQUIRED`:继续安装,但显示风险并写入证据;
|
|
245
|
+
- `DO NOT INSTALL` 或扫描失败:阻断,不替换旧版本。
|
|
239
246
|
|
|
240
|
-
安装时若检测到元信(yotta-verify)引擎,会对每个待装技能先跑一次装前摘要并打印 verdict
|
|
241
|
-
与计数——仅提示、不拦截;verdict 为 DO NOT INSTALL 时会额外提示人工复核。
|
|
242
247
|
引擎查找顺序:`--verify` 指定路径 → 环境变量 `YOTTA_SKILLS_VERIFY` → 目标目录下已装的
|
|
243
|
-
`yotta-verify/scripts/yotta_verify.py
|
|
248
|
+
`yotta-verify/scripts/yotta_verify.py`。`--skip-scan` 只保留为人工应急路径,使用时输出
|
|
249
|
+
`explicit-unverified` 并写入 `~/.yottaskills/install-log.jsonl`;`update --auto` 不会使用
|
|
250
|
+
该开关。
|
|
244
251
|
|
|
245
252
|
## 支持智能体
|
|
246
253
|
|
|
@@ -254,7 +261,7 @@ npx -y @yottameta/yotta-skills --route "检查代码质量,别糊弄" --json
|
|
|
254
261
|
|---|---|
|
|
255
262
|
| `YOTTA_SKILLS_NPM` | 指定 npm 可执行文件(同 `--npm`) |
|
|
256
263
|
| `YOTTA_SKILLS_NPM_FLAGS` | 追加传给 `npm pack` 的参数(按空白拆分,如 `--registry=...`) |
|
|
257
|
-
| `YOTTA_SKILLS_PYTHON` | 指定 python
|
|
264
|
+
| `YOTTA_SKILLS_PYTHON` | 指定 python 可执行文件(元信 scan 用,同 `--python`) |
|
|
258
265
|
| `YOTTA_SKILLS_VERIFY` | 指定 yotta_verify.py 路径(同 `--verify`) |
|
|
259
266
|
| `YOTTA_SKILLS_MANIFEST` | 指定技能清单 JSON 路径(默认随包 skills.json) |
|
|
260
267
|
|
|
@@ -24,6 +24,9 @@ const os = require('os');
|
|
|
24
24
|
const { spawnSync } = require('child_process');
|
|
25
25
|
const http = require('http');
|
|
26
26
|
const https = require('https');
|
|
27
|
+
const evidenceLib = require('../lib/install-evidence');
|
|
28
|
+
const gateLib = require('../lib/verify-gate');
|
|
29
|
+
const { createInstaller, isSafeTarEntry } = require('../lib/install-pipeline');
|
|
27
30
|
|
|
28
31
|
const PKG_ROOT = path.join(__dirname, '..');
|
|
29
32
|
let VERSION = '0.2.1';
|
|
@@ -343,19 +346,27 @@ async function runUpdateAuto(opts, dest) {
|
|
|
343
346
|
}
|
|
344
347
|
out('');
|
|
345
348
|
out('检测到 ' + r.updates + ' 个家族技能可更新,开始自动更新(安装管线,含装前安全扫描):');
|
|
346
|
-
var ok = 0, failed2 = 0;
|
|
349
|
+
var ok = 0, failed2 = 0, autoExitCode = 0;
|
|
347
350
|
for (var i = 0; i < r.rows.length; i++) {
|
|
348
351
|
var row = r.rows[i];
|
|
349
352
|
if (!row.hasUpdate) continue;
|
|
350
|
-
var res = installOne(
|
|
353
|
+
var res = installOne(
|
|
354
|
+
Object.assign({}, row.family, { version: row.latest }),
|
|
355
|
+
dest,
|
|
356
|
+
Object.assign({}, opts, { force: true, pin: true, skipScan: false }),
|
|
357
|
+
);
|
|
351
358
|
if (res.status === 'ok') ok++;
|
|
352
359
|
else if (res.status === 'skip') ok++;
|
|
353
|
-
else
|
|
360
|
+
else {
|
|
361
|
+
failed2++;
|
|
362
|
+
autoExitCode = Math.max(autoExitCode, res.exitCode || 1);
|
|
363
|
+
out(' ✘ ' + row.slug + ' 失败: ' + res.note);
|
|
364
|
+
}
|
|
354
365
|
}
|
|
355
366
|
out('');
|
|
356
367
|
out('自动更新汇总: 成功 ' + ok + ' / 失败 ' + failed2);
|
|
357
|
-
maybeAutoReindex(opts, dest);
|
|
358
|
-
return { code: failed2 > 0 ?
|
|
368
|
+
if (failed2 === 0) maybeAutoReindex(opts, dest);
|
|
369
|
+
return { code: failed2 > 0 ? autoExitCode : 0 };
|
|
359
370
|
}
|
|
360
371
|
|
|
361
372
|
function shouldSkip(name, isFile) {
|
|
@@ -399,17 +410,12 @@ function detectProjectDir() {
|
|
|
399
410
|
return null;
|
|
400
411
|
}
|
|
401
412
|
|
|
402
|
-
// ── 元信 scan
|
|
413
|
+
// ── 元信 scan 与安装门禁 ──────────────────────────────────────────────────
|
|
403
414
|
function findVerifyEngine(dest, opts) {
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
if (process.env.YOTTA_SKILLS_VERIFY) cands.push(path.resolve(process.env.YOTTA_SKILLS_VERIFY));
|
|
407
|
-
if (dest) cands.push(path.join(dest, 'yotta-verify', 'scripts', 'yotta_verify.py'));
|
|
408
|
-
for (const c of cands) {
|
|
409
|
-
try { if (fs.statSync(c).isFile()) return c; } catch (_) { /* next */ }
|
|
410
|
-
}
|
|
411
|
-
return null;
|
|
415
|
+
const registry = require('../lib/skills-scan').readRegistry();
|
|
416
|
+
return gateLib.findVerifier({ dest, opts, registry });
|
|
412
417
|
}
|
|
418
|
+
|
|
413
419
|
function findPython(opts) {
|
|
414
420
|
const cands = [];
|
|
415
421
|
if (opts.python) cands.push(opts.python);
|
|
@@ -424,19 +430,29 @@ function findPython(opts) {
|
|
|
424
430
|
}
|
|
425
431
|
return null;
|
|
426
432
|
}
|
|
433
|
+
|
|
427
434
|
function runScan(engine, skillDir) {
|
|
428
435
|
const python = findPython({});
|
|
429
|
-
if (!python) return { ok: false,
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
436
|
+
if (!python) return { ok: false, error: '未找到 Python(元信 scan 需要 Python 3.8+)' };
|
|
437
|
+
return gateLib.runVerifier(engine, skillDir, { python, spawnSync });
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function scanTarget(engine, skillDir) {
|
|
441
|
+
const scan = runScan(engine, skillDir);
|
|
442
|
+
if (scan.ok) {
|
|
443
|
+
const counts = scan.counts || {};
|
|
444
|
+
const line = 'critical ' + (counts.critical || 0) + ' / high ' + (counts.high || 0) +
|
|
445
|
+
' / medium ' + (counts.medium || 0) + ' / low ' + (counts.low || 0) +
|
|
446
|
+
' / info ' + (counts.info || 0);
|
|
447
|
+
out(' 元信 scan: ' + scan.verdict + '(' + line + ')');
|
|
448
|
+
if (scan.verdict === gateLib.BLOCK) out(' ⚠ 元信 verdict 为 DO NOT INSTALL,已阻断安装。');
|
|
449
|
+
else if (scan.verdict === gateLib.CAUTION || scan.verdict === gateLib.REVIEW) {
|
|
450
|
+
out(' ⚠ 元信 verdict 为 ' + scan.verdict + ',继续安装并保留风险证据。');
|
|
451
|
+
}
|
|
452
|
+
} else {
|
|
453
|
+
out(' 元信 scan: ' + scan.error);
|
|
439
454
|
}
|
|
455
|
+
return scan;
|
|
440
456
|
}
|
|
441
457
|
|
|
442
458
|
// ── 安装 ───────────────────────────────────────────────────────────────────
|
|
@@ -468,6 +484,13 @@ function runNpmPack(skill, opts, packDir) {
|
|
|
468
484
|
}
|
|
469
485
|
|
|
470
486
|
function extractTarball(tarball, extractDir) {
|
|
487
|
+
const listed = spawnSync(tarBin(), ['-tzf', tarball], { encoding: 'utf8', timeout: 120000, maxBuffer: 16 * 1024 * 1024 });
|
|
488
|
+
if (listed.status !== 0) {
|
|
489
|
+
return { error: (listed.stderr || listed.stdout || 'tar 列表读取失败').trim().split('\n').pop() };
|
|
490
|
+
}
|
|
491
|
+
const entries = String(listed.stdout || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
492
|
+
const unsafe = entries.find((entry) => !isSafeTarEntry(entry));
|
|
493
|
+
if (unsafe) return { error: '压缩包包含不安全路径: ' + unsafe };
|
|
471
494
|
const r = spawnSync(tarBin(), ['-xzf', tarball, '-C', extractDir], { encoding: 'utf8', timeout: 120000 });
|
|
472
495
|
if (r.status !== 0) return { error: (r.stderr || r.stdout || 'tar 解压失败').trim().split('\n').pop() };
|
|
473
496
|
const pkgDir = path.join(extractDir, 'package');
|
|
@@ -477,72 +500,94 @@ function extractTarball(tarball, extractDir) {
|
|
|
477
500
|
|
|
478
501
|
const COPY_SKIP = new Set(['package.json', 'bin', 'node_modules', '.git', '__pycache__']);
|
|
479
502
|
|
|
480
|
-
function
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
if (
|
|
484
|
-
return {
|
|
503
|
+
function ensureGate(context) {
|
|
504
|
+
const { skill, extracted, dest, opts } = context;
|
|
505
|
+
const current = findVerifyEngine(dest, opts);
|
|
506
|
+
if (current) {
|
|
507
|
+
return {
|
|
508
|
+
ok: true,
|
|
509
|
+
engine: current,
|
|
510
|
+
mode: opts.bootstrap ? 'trusted-bootstrap' : 'installed',
|
|
511
|
+
};
|
|
485
512
|
}
|
|
513
|
+
if (skill.slug === 'yotta-verify') {
|
|
514
|
+
const engine = path.join(extracted.pkgDir, 'scripts', 'yotta_verify.py');
|
|
515
|
+
if (!fs.existsSync(engine)) return { ok: false, error: '元信包内缺少 scripts/yotta_verify.py' };
|
|
516
|
+
return { ok: true, engine, mode: 'trusted-bootstrap' };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const verifier = findSkill('yotta-verify');
|
|
520
|
+
if (!verifier) return { ok: false, error: 'skills.json 缺少 yotta-verify' };
|
|
486
521
|
let tmp;
|
|
487
522
|
try {
|
|
488
|
-
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'yotta-
|
|
523
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'yotta-verify-bootstrap-'));
|
|
489
524
|
const packDir = path.join(tmp, 'pack');
|
|
490
525
|
fs.mkdirSync(packDir, { recursive: true });
|
|
491
|
-
const packed = runNpmPack(
|
|
492
|
-
if (packed.error) {
|
|
493
|
-
if (packed.detail) out(' ' + packed.detail.split(/\r?\n/).filter(Boolean).slice(-12).join('\n '));
|
|
494
|
-
return { skill, status: 'fail', version: null, note: packed.error };
|
|
495
|
-
}
|
|
526
|
+
const packed = runNpmPack(verifier, opts, packDir);
|
|
527
|
+
if (packed.error) return { ok: false, error: '元信自举下载失败: ' + packed.error };
|
|
496
528
|
const extractDir = path.join(tmp, 'extract');
|
|
497
529
|
fs.mkdirSync(extractDir, { recursive: true });
|
|
498
|
-
const
|
|
499
|
-
if (
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
fs.rmSync(target, { recursive: true, force: true });
|
|
518
|
-
fs.mkdirSync(target, { recursive: true });
|
|
519
|
-
copyDir(extracted.pkgDir, target, COPY_SKIP);
|
|
520
|
-
return { skill, status: 'ok', version: packed.resolved || skill.version, note: null };
|
|
521
|
-
} catch (e) {
|
|
522
|
-
return { skill, status: 'fail', version: null, note: e.message };
|
|
530
|
+
const extractedVerifier = extractTarball(packed.tarball, extractDir);
|
|
531
|
+
if (extractedVerifier.error) return { ok: false, error: '元信自举解包失败: ' + extractedVerifier.error };
|
|
532
|
+
const engine = path.join(extractedVerifier.pkgDir, 'scripts', 'yotta_verify.py');
|
|
533
|
+
if (!fs.existsSync(engine)) return { ok: false, error: '元信包内缺少 scripts/yotta_verify.py' };
|
|
534
|
+
|
|
535
|
+
out(' 元信未安装,使用可信源包自举并先自扫。');
|
|
536
|
+
const result = installOne(verifier, dest, {
|
|
537
|
+
...opts,
|
|
538
|
+
force: true,
|
|
539
|
+
skipScan: false,
|
|
540
|
+
bootstrap: true,
|
|
541
|
+
verify: engine,
|
|
542
|
+
});
|
|
543
|
+
if (result.status !== 'ok') return { ok: false, error: '元信自举失败: ' + result.note };
|
|
544
|
+
const installedEngine = path.join(dest, 'yotta-verify', 'scripts', 'yotta_verify.py');
|
|
545
|
+
if (!fs.existsSync(installedEngine)) return { ok: false, error: '元信自举后未找到安装引擎' };
|
|
546
|
+
return { ok: true, engine: installedEngine, mode: 'trusted-bootstrap' };
|
|
523
547
|
} finally {
|
|
524
548
|
if (tmp) fs.rmSync(tmp, { recursive: true, force: true });
|
|
525
549
|
}
|
|
526
550
|
}
|
|
527
551
|
|
|
552
|
+
const installOne = createInstaller({
|
|
553
|
+
runNpmPack,
|
|
554
|
+
extractTarball,
|
|
555
|
+
copyDir: (src, dst) => copyDir(src, dst, COPY_SKIP),
|
|
556
|
+
readInstalledVersion,
|
|
557
|
+
ensureGate,
|
|
558
|
+
scanTarget,
|
|
559
|
+
appendEvidence: evidenceLib.appendEvidence,
|
|
560
|
+
});
|
|
561
|
+
|
|
528
562
|
function runInstall(opts, dest) {
|
|
529
563
|
const skills = selectSkills(opts);
|
|
530
564
|
out('yotta-skills(元阁)v' + VERSION + ' —— 安装 ' + skills.length + ' 个技能 -> ' + dest);
|
|
531
565
|
out('版本策略: ' + (opts.pin ? 'pin(锁死清单精确版本)' : 'range(' + skillRange(skills[0]) + ',跟随最新 patch;--pin 锁死)'));
|
|
532
566
|
const results = [];
|
|
533
567
|
let failed = 0;
|
|
568
|
+
let exitCode = 0;
|
|
534
569
|
for (const s of skills) {
|
|
535
570
|
const r = installOne(s, dest, opts);
|
|
536
571
|
results.push(r);
|
|
537
|
-
if (r.status === 'ok')
|
|
572
|
+
if (r.status === 'ok') {
|
|
573
|
+
if (r.gate && r.gate.mode === 'explicit-unverified') {
|
|
574
|
+
out(' ⚠ ' + s.slug.padEnd(22) + '未执行装前扫描(explicit-unverified)');
|
|
575
|
+
}
|
|
576
|
+
out(' ✔ ' + s.slug.padEnd(22) + s.name + ' -> ' + (r.version || '?'));
|
|
577
|
+
}
|
|
538
578
|
else if (r.status === 'skip') out(' - ' + s.slug.padEnd(22) + s.name + ' (' + r.note + ',v' + r.version + ')');
|
|
539
|
-
else {
|
|
579
|
+
else {
|
|
580
|
+
failed++;
|
|
581
|
+
exitCode = Math.max(exitCode, r.exitCode || 1);
|
|
582
|
+
out(' ✘ ' + s.slug.padEnd(22) + s.name + ' 失败: ' + r.note);
|
|
583
|
+
}
|
|
540
584
|
}
|
|
541
585
|
const ok = results.filter(r => r.status === 'ok').length;
|
|
542
586
|
const skip = results.filter(r => r.status === 'skip').length;
|
|
543
587
|
out('');
|
|
544
588
|
out('汇总: 成功 ' + ok + ' / 跳过(已是最新) ' + skip + ' / 失败 ' + failed + '(共 ' + results.length + ')');
|
|
545
|
-
if (failed > 0) process.exitCode = 1;
|
|
589
|
+
if (failed > 0) process.exitCode = exitCode || 1;
|
|
590
|
+
return { failed, exitCode };
|
|
546
591
|
}
|
|
547
592
|
|
|
548
593
|
function runUpdate(opts, dest) {
|
|
@@ -550,6 +595,7 @@ function runUpdate(opts, dest) {
|
|
|
550
595
|
out('yotta-skills(元阁)v' + VERSION + ' —— 增量更新 -> ' + dest);
|
|
551
596
|
const results = [];
|
|
552
597
|
let failed = 0;
|
|
598
|
+
let exitCode = 0;
|
|
553
599
|
for (const s of skills) {
|
|
554
600
|
const existing = readInstalledVersion(path.join(dest, s.slug));
|
|
555
601
|
if (existing === s.version) {
|
|
@@ -559,15 +605,25 @@ function runUpdate(opts, dest) {
|
|
|
559
605
|
}
|
|
560
606
|
const r = installOne(s, dest, opts);
|
|
561
607
|
results.push(r);
|
|
562
|
-
if (r.status === 'ok')
|
|
608
|
+
if (r.status === 'ok') {
|
|
609
|
+
if (r.gate && r.gate.mode === 'explicit-unverified') {
|
|
610
|
+
out(' ⚠ ' + s.slug.padEnd(22) + '未执行装前扫描(explicit-unverified)');
|
|
611
|
+
}
|
|
612
|
+
out(' ✔ ' + s.slug.padEnd(22) + s.name + ' -> ' + (r.version || '?') + (existing ? '(原 v' + existing + ')' : '(新装)'));
|
|
613
|
+
}
|
|
563
614
|
else if (r.status === 'skip') out(' - ' + s.slug.padEnd(22) + s.name + ' (' + r.note + ')');
|
|
564
|
-
else {
|
|
615
|
+
else {
|
|
616
|
+
failed++;
|
|
617
|
+
exitCode = Math.max(exitCode, r.exitCode || 1);
|
|
618
|
+
out(' ✘ ' + s.slug.padEnd(22) + s.name + ' 失败: ' + r.note);
|
|
619
|
+
}
|
|
565
620
|
}
|
|
566
621
|
const ok = results.filter(r => r.status === 'ok').length;
|
|
567
622
|
const skip = results.filter(r => r.status === 'skip').length;
|
|
568
623
|
out('');
|
|
569
624
|
out('汇总: 更新 ' + ok + ' / 已是最新 ' + skip + ' / 失败 ' + failed);
|
|
570
|
-
if (failed > 0) process.exitCode = 1;
|
|
625
|
+
if (failed > 0) process.exitCode = exitCode || 1;
|
|
626
|
+
return { failed, exitCode };
|
|
571
627
|
}
|
|
572
628
|
|
|
573
629
|
// ── 展示 ───────────────────────────────────────────────────────────────────
|
|
@@ -777,12 +833,12 @@ function main() {
|
|
|
777
833
|
process.exitCode = r.code;
|
|
778
834
|
});
|
|
779
835
|
} else {
|
|
780
|
-
runUpdate(opts, dest);
|
|
781
|
-
maybeAutoReindex(opts, dest);
|
|
836
|
+
const result = runUpdate(opts, dest);
|
|
837
|
+
if (!result.failed) maybeAutoReindex(opts, dest);
|
|
782
838
|
}
|
|
783
839
|
} else {
|
|
784
|
-
runInstall(opts, dest);
|
|
785
|
-
maybeAutoReindex(opts, dest);
|
|
840
|
+
const result = runInstall(opts, dest);
|
|
841
|
+
if (!result.failed) maybeAutoReindex(opts, dest);
|
|
786
842
|
}
|
|
787
843
|
}
|
|
788
844
|
|
|
@@ -69,11 +69,15 @@ npx -y @yottameta/yotta-skills install yotta-memory yotta-workflow --agent codex
|
|
|
69
69
|
|
|
70
70
|
## 10. 安装前安全扫描怎么用?
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
家族安装默认执行元信门禁:没有元信时先自动自举,再扫描待装包。`DO NOT INSTALL`
|
|
73
|
+
和扫描失败会阻断;`INSTALL WITH CAUTION` / `REVIEW REQUIRED` 会继续但显示风险。
|
|
74
|
+
可用 `--verify <path>` 指定引擎。`--skip-scan` 只是人工应急路径,会标记
|
|
75
|
+
`explicit-unverified`,`update --auto` 不使用它。
|
|
73
76
|
|
|
74
77
|
## 11. 安装失败后如何恢复?
|
|
75
78
|
|
|
76
|
-
|
|
79
|
+
查看汇总中的失败原因。旧版本快照保存在 `~/.yottaskills/snapshots/`,当前版本失败时
|
|
80
|
+
新版本不会替换旧版本。先修复目录权限或网络,再单独安装失败技能:
|
|
77
81
|
|
|
78
82
|
```bash
|
|
79
83
|
npx -y @yottameta/yotta-skills install <slug> --dir /path/to/skills
|
|
@@ -10,10 +10,13 @@
|
|
|
10
10
|
1. 读清单(`skills.json`,默认随包;`YOTTA_SKILLS_MANIFEST` 可覆盖);
|
|
11
11
|
2. `npm pack <pkg>@<spec> --pack-destination <临时目录>`;
|
|
12
12
|
3. 系统 `tar -xzf` 解压到临时目录(产物应有 `SKILL.md`);
|
|
13
|
-
4.
|
|
14
|
-
5.
|
|
15
|
-
6.
|
|
16
|
-
7.
|
|
13
|
+
4. 读取包内 `skill-manifest.json`;没有 manifest 时使用 `skills.json` 的家族默认契约;
|
|
14
|
+
5. 校验 slug / package / version / 权限 / 生命周期脚本路径;
|
|
15
|
+
6. 元信装前扫描:已有元信则直接扫描;没有则自动安装元信自身,再扫描;
|
|
16
|
+
7. 通过后创建旧版本快照,把新版本复制到同盘暂存目录,再原子切换到目标目录;
|
|
17
|
+
8. setup / doctor 阶段执行内置检查;P0-2.1 不执行自定义生命周期脚本;
|
|
18
|
+
9. 汇总报告:✔ 成功 / - 跳过(已是最新)/ ✘ 失败;
|
|
19
|
+
10. 全部成功后自动 re-index 本地技能注册表(`~/.yottaskills/registry.json`):重扫技能根目录并增量合并,
|
|
17
20
|
新装 / 更新的技能随即进入注册表(`--no-reindex` 可关闭;`--dry-run` 不触发)。
|
|
18
21
|
|
|
19
22
|
## 复制跳过规则
|
|
@@ -24,6 +27,17 @@
|
|
|
24
27
|
因此装进技能目录的是「技能本体」(SKILL.md / references / scripts 等),不含 npm
|
|
25
28
|
安装器自身、测试夹具与 Python 字节码缓存。
|
|
26
29
|
|
|
30
|
+
## manifest 与家族默认契约
|
|
31
|
+
|
|
32
|
+
每个技能包可以自带根目录 `skill-manifest.json`。有 manifest 时:
|
|
33
|
+
|
|
34
|
+
- 校验 `slug` / `package` / `version` 与包目录、`package.json`、`SKILL.md` 一致;
|
|
35
|
+
- 校验 `install.idempotent` 为 `true`;
|
|
36
|
+
- 校验 setup / doctor / rollback 路径为包内相对路径,不能包含绝对路径或 `..`。
|
|
37
|
+
|
|
38
|
+
没有 manifest 时使用家族默认契约:来源 `yottameta`、幂等安装、自动应用模式 `route`,
|
|
39
|
+
不声明 MCP、hook 或自定义生命周期脚本。
|
|
40
|
+
|
|
27
41
|
## re-index(装技能后自动重扫注册表)
|
|
28
42
|
|
|
29
43
|
- `install` / `update` 完成后,CLI 自动调用 `--reindex`(同一套 `lib/skills-scan.js` 扫描核心):
|
|
@@ -51,14 +65,37 @@
|
|
|
51
65
|
其它 → 直接执行;
|
|
52
66
|
- `YOTTA_SKILLS_NPM_FLAGS` 追加到 `npm pack` 参数。
|
|
53
67
|
|
|
54
|
-
##
|
|
68
|
+
## 元信装前门禁
|
|
55
69
|
|
|
56
70
|
- 引擎查找:`--verify` → `YOTTA_SKILLS_VERIFY` → `<dest>/yotta-verify/scripts/yotta_verify.py`;
|
|
57
71
|
- python 查找:`--python` → `YOTTA_SKILLS_PYTHON` → `python3` / `python` / `py`(win32);
|
|
58
72
|
- 执行:`python -B <engine> scan <解压目录> --json`(`-B` 禁止写 `__pycache__`,防止污染
|
|
59
73
|
引擎所在目录);
|
|
60
|
-
-
|
|
61
|
-
|
|
74
|
+
- 元信缺失时:先按 `skills.json` 安装 `yotta-verify`,并记录 `gate_mode=trusted-bootstrap`
|
|
75
|
+
与 `bootstrap_scan=self`,随后用元信扫描其余家族包;
|
|
76
|
+
- verdict 处置:`SAFE TO INSTALL` 继续;`INSTALL WITH CAUTION` / `REVIEW REQUIRED`
|
|
77
|
+
继续但显示风险并留证;`DO NOT INSTALL` 和扫描失败阻断,不替换目标;
|
|
78
|
+
- `--skip-scan` 只保留为人工应急路径,使用时输出 `explicit-unverified` 并写入证据;
|
|
79
|
+
- `update --auto` 始终执行装前门禁,不接受 `--skip-scan`。
|
|
80
|
+
|
|
81
|
+
## 快照与回滚
|
|
82
|
+
|
|
83
|
+
- 旧版本快照:`~/.yottaskills/snapshots/<slug>/<timestamp>-<version>-<随机后缀>/`;
|
|
84
|
+
- 新版本先落在 `<dest>/.yottaskills-staging/<slug>-<随机后缀>/`;
|
|
85
|
+
- 旧目标先重命名为同盘备份,再把暂存目录切到目标;切换失败时恢复备份;
|
|
86
|
+
- 证据写入失败会触发回滚,不把安装标记为成功;
|
|
87
|
+
- Windows 上对 `EPERM` / `EACCES` / `EBUSY` 做短重试,避免杀毒或索引服务造成的瞬时锁。
|
|
88
|
+
|
|
89
|
+
## 安装证据
|
|
90
|
+
|
|
91
|
+
每次安装决策写入:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
~/.yottaskills/install-log.jsonl
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
记录包含时间、事件、技能、包名、版本、`gate_mode`、verdict、decision
|
|
98
|
+
和快照路径;不记录技能内容或用户数据,只写本机。
|
|
62
99
|
|
|
63
100
|
## 退出码
|
|
64
101
|
|
|
@@ -68,6 +105,8 @@
|
|
|
68
105
|
| 1 | 安装 / 更新存在失败项 |
|
|
69
106
|
| 2 | 用法错误(未知参数 / 未知技能 / 未收录智能体) |
|
|
70
107
|
| 4 | 未指定目标且当前目录未检测到项目级技能目录 |
|
|
108
|
+
| 5 | 装前 gate 阻断或元信不可用 |
|
|
109
|
+
| 6 | manifest / 身份校验失败 |
|
|
71
110
|
|
|
72
111
|
## 目标目录解析
|
|
73
112
|
|
|
@@ -8,7 +8,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
10
|
| `yotta-workflow` | 元序 | `@yottameta/yotta-workflow` | 0.4.0 | 跨会话/跨项目通用工作流协议(状态就近存) |
|
|
11
|
-
| `yotta-memory` | 元忆 | `@yottameta/yotta-memory` | 0.
|
|
11
|
+
| `yotta-memory` | 元忆 | `@yottameta/yotta-memory` | 0.12.0 | 有权限边界的文件式智能体记忆(FACT/PREF/BOUND/COMMIT) |
|
|
12
12
|
| `yotta-learn` | 元习 | `@yottameta/yotta-learn` | 0.2.0 | 学习沉淀 CLI:错误/纠正/洞见沉淀为 .learnings/ 条目 |
|
|
13
13
|
| `yotta-security-audit` | 元安 | `@yottameta/yotta-security-audit` | 0.2.2 | 安全扫描引擎:13 类检测器 + 系统安全基线 |
|
|
14
14
|
| `yotta-vetter` | 元审 | `@yottameta/yotta-vetter` | 0.2.3 | 安全审查协议:四阶段 review + SAFE TO INSTALL 判定 |
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
- 学会 `--list` / `install` / `update` / `--dry-run` / `--pin` / `--force` 的用法;
|
|
10
10
|
- 理解版本策略(range 与 --pin)与幂等行为;
|
|
11
|
-
-
|
|
11
|
+
- 了解元信装前门禁、元信自举与 `--skip-scan` 的人工应急边界;
|
|
12
12
|
- 前置:Node.js 18+、npm、系统 tar(Windows 10+ 自带);网络可访问 npm 注册表
|
|
13
13
|
(国内可配置镜像或代理)。
|
|
14
14
|
|
|
@@ -76,22 +76,26 @@ dry-run 只打印将要执行的清单(含目标与版本策略),不联网
|
|
|
76
76
|
npx -y @yottameta/yotta-skills install --agent codex --pin
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
## 8.
|
|
79
|
+
## 8. 强制重装与元信门禁
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
82
|
npx -y @yottameta/yotta-skills install --agent codex --force # 已是最新也重装
|
|
83
|
-
npx -y @yottameta/yotta-skills install --agent codex --skip-scan #
|
|
83
|
+
npx -y @yottameta/yotta-skills install --agent codex --skip-scan # 人工应急:显式未验证
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
家族安装默认执行元信门禁:没有元信时,元阁先按可信源安装元信自身,再扫描待装技能。
|
|
87
|
+
`DO NOT INSTALL` 会阻断安装;`INSTALL WITH CAUTION` / `REVIEW REQUIRED` 会继续但显示风险。
|
|
88
|
+
旧版本会先快照到 `~/.yottaskills/snapshots/`,安装决策写入
|
|
89
|
+
`~/.yottaskills/install-log.jsonl`。`--skip-scan` 只用于人工应急,会输出
|
|
90
|
+
`explicit-unverified` 并留证,`update --auto` 不会使用它。
|
|
88
91
|
|
|
89
92
|
## 9. 验证安装结果
|
|
90
93
|
|
|
91
94
|
- 看安装汇总:成功 N / 跳过 N / 失败 N;
|
|
92
95
|
- 抽查技能目录:`ls ~/.codex/skills/yotta-memory/SKILL.md`;
|
|
93
96
|
- 复核版本:每个技能 SKILL.md 的 frontmatter `version` 应与 `--list` 一致(这也是幂等
|
|
94
|
-
|
|
97
|
+
判断的依据);
|
|
98
|
+
- 需要安全复核时,查看 `~/.yottaskills/install-log.jsonl` 中的 verdict 与 `gate_mode`。
|
|
95
99
|
|
|
96
100
|
## 10. 常见问题
|
|
97
101
|
|