@yottameta/yotta-skills 0.20.2 → 0.22.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/CHANGELOG.md +13 -0
- package/README.md +14 -1
- package/README.zh-CN.md +14 -1
- package/SKILL.md +41 -5
- package/bin/yotta-skills.js +214 -2
- package/lib/m1-features.js +112 -0
- package/lib/provider.js +1 -0
- package/lib/usage-journal.js +201 -0
- package/package.json +1 -1
- package/references/faq.md +12 -0
- package/references/install-flow.md +7 -0
- package/references/provider-protocol.md +107 -3
- package/references/tutorial.md +21 -0
- package/scripts/yotta-skills-mcp.py +26 -2
- package/skill-manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## v0.22.0 (2026-09-30)
|
|
2
|
+
- 关联 Agent Plugin 包新增 DeepSeek Harness(DSH)profile bundle 适配层:插件仓根 `package.json` 增加 `dsh.bundle.patch`,并由构建器生成 `cordis.patch.yml`。
|
|
3
|
+
- DSH 安装后同时注册包内 `yotta-skills` 技能与 `mcp__yotta-skills__*` 工具;复用包内 stdio MCP server,不新增运行时依赖。
|
|
4
|
+
- Agent Plugins 1.0 安装方式与 CLI / MCP 工具行为保持不变;本版为插件分发形态升级。
|
|
5
|
+
|
|
6
|
+
## v0.21.0 (2026-09-30)
|
|
7
|
+
- 新增 M1 记忆裁决器开源调用口:`yotta-skills decide-memory` 与 MCP `decide_memory`,调用本地扩展提供方 `m1.adjudicate`,输出每个技能的 `promote / hold / demote` 只读建议、分数与信号明细。
|
|
8
|
+
- 新增本地使用记录:`usage status|enable|disable|mark|reset`;默认关闭,`usage enable` 后 `--route` 才记录结构化 route_hits 与组合对,显式 `usage mark` 记录 used / named / accepted。记录只含 slug、时间、信号类型、playbook / confidence 与组合对,不含需求原文、记忆正文、路径或身份信息。
|
|
9
|
+
- `decide-memory` 默认只读;`--dry-run` 显式只读;`--promote` 只写 `~/.yottaskills/memory-adjudication.json` 建议文件,生成私密 `PREF` 记忆候选,不自动写元忆、不删除任何内容。`--explain` 输出每个信号的得分明细。
|
|
10
|
+
- provider 子进程环境透传授权库相关变量(`YOTTA_LICENSE_HOME` / `YOTTA_LICENSE_KEYS_DIR` / `YOTTA_LICENSE_BASE_URL` / `YOTTA_LICENSE_SERVER_ID`),私有 M1 provider 可用 `LicenseGate.assertCan('m1.adjudicate')` 做授权门;未授权 / 超时 / 非法输出一律 fail-open,退出码保持 0。
|
|
11
|
+
- 评分算法位于本地私有目录 `license/m1/`,不进任何发布件;开源侧只做特征快照、白名单校验与展示。协议见 `references/provider-protocol.md`。
|
|
12
|
+
- 元忆 0.19.0 不变;静态 playbook / 安装 / hook / scanPolicy 契约零变更。
|
|
13
|
+
|
|
1
14
|
## v0.20.2 (2026-09-29)
|
|
2
15
|
- 家族安装的 npm 拉包新增镜像回退:默认源返回 404(国内镜像未同步该版本 tarball)时自动改用官方源 `https://registry.npmjs.org/` 重试一次;安装输出显示回退行,安装证据新增 `npm_registry_fallback` 字段。
|
|
3
16
|
- 两次都失败时给出可直接复制的修复提示(`npm_config_registry` / `YOTTA_SKILLS_NPM_FLAGS`);已显式指定 registry 或设置 `YOTTA_SKILLS_NO_FALLBACK=1` 时不回退。
|
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ Installing the whole YottaMeta family used to mean running <code>npx</code> for
|
|
|
27
27
|
|
|
28
28
|
- **List** - see the whole family: slug, Chinese name, package, version and a one-line description.
|
|
29
29
|
- **Orchestration routing** - <code>--route</code> turns a task summary into a candidate combination, call order, per-skill roles, confidence, evidence, installed/missing status, and an install command; it only suggests installation and never installs automatically. An optional local provider may add or re-order installed skills within the allow-list (no behavior change when unconfigured; see <code>references/provider-protocol.md</code>).
|
|
30
|
+
- **M1 memory adjudication** - <code>decide-memory</code> returns read-only <code>promote / hold / demote</code> suggestions, scores and signal details. Scoring is performed by a user-configured local provider (capability <code>m1.adjudicate</code>). It is read-only by default; <code>--promote</code> writes only a local recommendation file and never writes memory or deletes anything. <code>usage</code> is off by default and records only structured signals after the user enables it.
|
|
30
31
|
- **Install** — install everything (or a subset) into an agent's default user-level directory or any directory.
|
|
31
32
|
- **Update** — incremental update: add missing skills, upgrade version-skewed ones.
|
|
32
33
|
- **Update check / auto-update** — <code>update --check</code> compares installed skill versions against the npm registry and reports; <code>update --check --scheduled</code> is the weekly background entry with a local cache and jitter, so it stays offline until due; <code>update --auto</code> upgrades the installed YottaMeta family to the latest (non-family skills are never auto-updated).
|
|
@@ -34,7 +35,7 @@ Installing the whole YottaMeta family used to mean running <code>npx</code> for
|
|
|
34
35
|
- **Runtime hook adapter** — manifests declare six lifecycle requirements; <code>hook capabilities / evaluate / bind / unbind</code> probe host support, apply deterministic decisions, record evidence, and never overstate audit-only hosts as enforced.
|
|
35
36
|
- **Idempotent** — a skill already at the manifest version is skipped; re-running is safe.
|
|
36
37
|
- **Pre-install gate** — family installs read the package manifest, bootstrap yotta-verify (元信) when it is missing, then scan each package. <code>DO NOT INSTALL</code> blocks; caution/review continue with visible risk. Old versions are snapshotted before replacement.
|
|
37
|
-
- **Inventory / re-index** - scan the skill directories on this machine and keep a local registry (<code>~/.yottaskills/registry.json</code>); set <code>YOTTA_SKILLS_REGISTRY_FILE</code> to isolate the registry per agent; self-contained, no other skills required. Newly installed skills are discovered automatically: <code>install</code> / <code>update</code> re-index the registry afterwards, and <code>--reindex</code> re-scans on demand. An optional <code>yotta-skills</code> MCP (on-demand, not resident) exposes <code>list_installed_skills</code> / <code>describe_skill</code> / <code>reindex</code> / <code>route_request</code>; see <code>SKILL.md</code> for the config.
|
|
38
|
+
- **Inventory / re-index** - scan the skill directories on this machine and keep a local registry (<code>~/.yottaskills/registry.json</code>); set <code>YOTTA_SKILLS_REGISTRY_FILE</code> to isolate the registry per agent; self-contained, no other skills required. Newly installed skills are discovered automatically: <code>install</code> / <code>update</code> re-index the registry afterwards, and <code>--reindex</code> re-scans on demand. An optional <code>yotta-skills</code> MCP (on-demand, not resident) exposes <code>list_installed_skills</code> / <code>describe_skill</code> / <code>reindex</code> / <code>route_request</code> / <code>decide_memory</code>; see <code>SKILL.md</code> for the config.
|
|
38
39
|
- **No silent writes** - 元阁 only suggests. Installing skills, adding a client <code>mcpServers</code> entry, and writing an agent's global memory each require explicit user confirmation beforehand; declining never blocks the plain CLI workflow.
|
|
39
40
|
|
|
40
41
|
Boundaries: it only downloads, places, gates and summarizes — it does **not** develop skill content, does **not** bundle any skill body, does **not** use <code>-g</code> global installs, and does **not** silently write host configuration or agent global memory. Besides the target directory, it keeps its registry, snapshots, install evidence and update-check cache under <code>~/.yottaskills</code>.
|
|
@@ -83,6 +84,14 @@ npx -y @yottameta/yotta-skills --dry-run
|
|
|
83
84
|
# Route a task to a combination, call order, and missing-skill install suggestion
|
|
84
85
|
npx -y @yottameta/yotta-skills --route "Review this code carefully before release"
|
|
85
86
|
|
|
87
|
+
# Inspect / enable local structured usage signals (off by default)
|
|
88
|
+
npx -y @yottameta/yotta-skills usage status
|
|
89
|
+
npx -y @yottameta/yotta-skills usage enable
|
|
90
|
+
|
|
91
|
+
# Read-only M1 memory adjudication; --promote writes only a local recommendation file
|
|
92
|
+
npx -y @yottameta/yotta-skills decide-memory --explain
|
|
93
|
+
npx -y @yottameta/yotta-skills decide-memory --promote --json
|
|
94
|
+
|
|
86
95
|
# Inventory installed skills on this machine (self-contained scan, no other skills needed)
|
|
87
96
|
npx -y @yottameta/yotta-skills --inventory
|
|
88
97
|
|
|
@@ -111,6 +120,10 @@ Requirements: Node.js 18+, npm, and system <code>tar</code> (built into Windows
|
|
|
111
120
|
| `--inventory` | Inventory installed skills: scan skill directories and update the local registry (self-contained); `--json` for JSON, `--project` adds project-level dirs |
|
|
112
121
|
| `--reindex` | Re-index the registry: re-scan skill directories and merge changes incrementally (on demand; `install` / `update` re-index automatically; `--rescan` is a synonym); `--json` for JSON |
|
|
113
122
|
| `--route <task-summary>` | Static orchestration routing: return a combination, call order, per-skill roles, confidence, evidence, installed/missing status, and an install suggestion; also lists other installed (non-YottaMeta) skills as candidates matched mechanically against their frontmatter description, tagged "not scanned", read-only and never auto-invoked; an optional local provider may add or re-order installed skills within the allow-list (see <code>references/provider-protocol.md</code>); `--json` for JSON plus a `dynamic` status block, `--project` adds project-level dirs |
|
|
123
|
+
| `usage status` / `usage enable` / `usage disable` | Inspect / enable / disable local structured usage signals; off by default and limited to slug, time, signal type, playbook / confidence and skill pairs |
|
|
124
|
+
| `usage mark --skill <slug> --signal used\|named\|accepted` | Record one explicit usage signal; no prior enable required |
|
|
125
|
+
| `usage reset --yes` | Clear the local usage record |
|
|
126
|
+
| `decide-memory [--dry-run\|--promote] [--explain]` | Read-only M1 memory adjudication; `--promote` writes only a local recommendation file and never writes memory or deletes anything; `--json` for stable JSON |
|
|
114
127
|
| `--no-reindex` | Do not re-index the registry automatically after `install` / `update` |
|
|
115
128
|
| `--dry-run` | Preview the install / update list; no network, no changes |
|
|
116
129
|
| `--pin` | Lock the exact manifest versions (default) |
|
package/README.zh-CN.md
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
- **看清单**--全家技能一览:slug / 中文名 / 包名 / 版本 / 一句话说明。
|
|
32
32
|
- **编排路由**--`--route` 按需求摘要输出候选组合、调用顺序、技能角色、置信度、依据、已装/缺失状态与安装命令;非元阁家族已装技能按 frontmatter description 机械匹配作并列候选(标注来源与未扫描状态,只读不自动调用);只建议安装,不自动安装。可选本地扩展提供方可在已装白名单内增补 / 重排(未配置时行为不变),协议见 `references/provider-protocol.md`。
|
|
33
|
+
- **M1 记忆裁决**--`decide-memory` 输出技能 `promote / hold / demote` 只读建议、分数与信号明细;评分由用户显式配置的本地 provider(capability `m1.adjudicate`)完成。默认只读,`--promote` 只写本地建议文件,不写元忆、不删除内容。`usage` 默认关闭,显式开启后才记录结构化使用信号(不含需求原文)。
|
|
33
34
|
- **安装**——装全家(或指定技能)到智能体默认用户级目录或任意目录。
|
|
34
35
|
- **更新**——增量更新:补齐缺失技能、升级版本不一致的技能。
|
|
35
36
|
- **更新检查 / 自动更新**——<code>update --check</code> 只读比对已装技能版本与 npm 注册表并报告;<code>update --check --scheduled</code> 是后台周检入口,用本地缓存和随机抖动做到未到期不联网;<code>update --auto</code> 检查后自动把已装元阁家族升到最新(非元阁家族技能绝不自动更新)。
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
- **运行时 hook 适配层**——技能 manifest 只声明六个生命周期要求;<code>hook capabilities / evaluate / bind / unbind</code> 负责宿主能力探测、确定性决策、结构化证据和降级标注,不把 audit 能力夸大为强制。
|
|
38
39
|
- **幂等**——已在清单版本的技能跳过;重复运行安全。
|
|
39
40
|
- **装前门禁**——家族安装先读取包内 manifest,元信(yotta-verify)缺失时自动自举,再逐个扫描。`DO NOT INSTALL` 阻断;`CAUTION` / `REVIEW` 继续但显示风险。旧版本会先做快照再替换。
|
|
40
|
-
- **盘点 / re-index**--扫描本机各智能体技能目录,维护本地注册表(<code>~/.yottaskills/registry.json</code>);可用 <code>YOTTA_SKILLS_REGISTRY_FILE</code> 为不同 agent 指定独立注册表;自包含,不需要任何其他技能。新装技能自动被发现:<code>install</code> / <code>update</code> 完成后自动重扫注册表,<code>--reindex</code> 可随时手动重扫(如会话开工)。可选 <code>yotta-skills</code> MCP(按需加载、不常驻)提供 <code>list_installed_skills</code> / <code>describe_skill</code> / <code>reindex</code> / <code>route_request</code>
|
|
41
|
+
- **盘点 / re-index**--扫描本机各智能体技能目录,维护本地注册表(<code>~/.yottaskills/registry.json</code>);可用 <code>YOTTA_SKILLS_REGISTRY_FILE</code> 为不同 agent 指定独立注册表;自包含,不需要任何其他技能。新装技能自动被发现:<code>install</code> / <code>update</code> 完成后自动重扫注册表,<code>--reindex</code> 可随时手动重扫(如会话开工)。可选 <code>yotta-skills</code> MCP(按需加载、不常驻)提供 <code>list_installed_skills</code> / <code>describe_skill</code> / <code>reindex</code> / <code>route_request</code> / <code>decide_memory</code> 五工具,配置见 <code>SKILL.md</code>。
|
|
41
42
|
|
|
42
43
|
边界:只做「下载 + 落位 + 门禁 + 汇总」——**不**开发技能内容、**不**内置任何技能本体、**不**用 <code>-g</code>
|
|
43
44
|
全局安装;除目标目录外,会在 <code>~/.yottaskills</code> 下保留注册表、快照、安装证据与更新检查缓存。
|
|
@@ -86,6 +87,14 @@ npx -y @yottameta/yotta-skills --dry-run
|
|
|
86
87
|
# 按需求摘要给出组合、顺序与缺失技能安装建议
|
|
87
88
|
npx -y @yottameta/yotta-skills --route "检查代码质量,别糊弄"
|
|
88
89
|
|
|
90
|
+
# 查看 / 开启本地使用记录(默认关闭)
|
|
91
|
+
npx -y @yottameta/yotta-skills usage status
|
|
92
|
+
npx -y @yottameta/yotta-skills usage enable
|
|
93
|
+
|
|
94
|
+
# M1 记忆裁决只读建议;--promote 只写本地建议文件
|
|
95
|
+
npx -y @yottameta/yotta-skills decide-memory --explain
|
|
96
|
+
npx -y @yottameta/yotta-skills decide-memory --promote --json
|
|
97
|
+
|
|
89
98
|
# 盘点本机已装技能(自包含扫描,不依赖任何元技能)
|
|
90
99
|
npx -y @yottameta/yotta-skills --inventory
|
|
91
100
|
|
|
@@ -114,6 +123,10 @@ npx -y @yottameta/yotta-skills --reindex
|
|
|
114
123
|
| `--inventory` | 盘点已装技能:扫描技能目录并更新本地注册表(自包含);`--json` 输出 JSON、`--project` 附扫项目级目录 |
|
|
115
124
|
| `--reindex` | 重扫注册表:扫描技能目录并增量合并变化(install / update 完成后自动重扫,也可随时手动运行;`--rescan` 同义);`--json` 输出 JSON |
|
|
116
125
|
| `--route <需求摘要>` | 静态编排路由:输出组合、调用顺序、技能角色、置信度、依据、已装/缺失状态与安装建议;可选本地扩展提供方在已装白名单内增补 / 重排(未配置时行为不变,协议见 `references/provider-protocol.md`);`--json` 输出 JSON 与 `dynamic` 状态块、`--project` 附扫项目级目录 |
|
|
126
|
+
| `usage status` / `usage enable` / `usage disable` | 查看 / 开启 / 关闭本地结构化使用记录;默认关闭,只记录 slug、时间、信号类型、playbook / confidence 与组合对 |
|
|
127
|
+
| `usage mark --skill <slug> --signal used\|named\|accepted` | 记录一次显式使用信号;不要求先 enable |
|
|
128
|
+
| `usage reset --yes` | 清空本地使用记录 |
|
|
129
|
+
| `decide-memory [--dry-run\|--promote] [--explain]` | M1 记忆裁决只读建议;`--promote` 只写本地建议文件,不写元忆、不删除内容;`--json` 输出稳定 JSON |
|
|
117
130
|
| `--no-reindex` | 安装 / 更新后不自动重扫注册表 |
|
|
118
131
|
| `--dry-run` | 预览将执行的安装 / 更新清单;不联网、不改动 |
|
|
119
132
|
| `--pin` | 锁死清单精确版本(默认) |
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yotta-skills
|
|
3
|
-
version: 0.
|
|
4
|
-
description: 元阁 -- 元阁全家技能的总编排策划 + 编排路由 + 一键安装器 + 技能盘点 + 运行时 hook 适配。路由层:--route / route_request 按需求摘要给出候选组合、调用顺序、角色、置信度、依据、已装/缺失状态与安装命令,只建议不自动安装;非元阁家族已装技能按 frontmatter description
|
|
3
|
+
version: 0.22.0
|
|
4
|
+
description: 元阁 -- 元阁全家技能的总编排策划 + 编排路由 + 一键安装器 + 技能盘点 + 运行时 hook 适配。路由层:--route / route_request 按需求摘要给出候选组合、调用顺序、角色、置信度、依据、已装/缺失状态与安装命令,只建议不自动安装;非元阁家族已装技能按 frontmatter description 机械匹配作并列候选(标注来源与未扫描状态,只读不自动调用);M1 记忆裁决层:usage enable/mark 本地结构化记录 + decide-memory / MCP decide_memory 输出 promote / hold / demote 只读建议(需授权 provider;只建议不删除、不自动写元忆);策划层:按场景给出「该组合哪几个元技能、组合强在哪、怎么组合使用(安装与调用均由用户确认后执行)」;安装层:一条命令把 YottaMeta 已发布的全部 yotta-* 技能装进指定智能体或目录(默认 --pin 锁死清单精确版本);盘点层:--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/decide_memory,不常驻,未加载降级 CLI)。支持 --list 清单 / --route 路由 / usage 使用记录 / decide-memory 记忆裁决 / install / update / update --check(只读检查)/ update --check --scheduled(后台周检)/ update --auto(家族自动更新)/ hook 适配 / --inventory / --reindex / --dry-run 预览 / --pin(默认)/ --range。触发:需要批量安装或更新元阁全家技能、按场景组合多个元技能、路由或判断该用哪些技能、判断哪些技能值得长期记忆、查看或记录技能使用信号、盘点或查看本机已装技能、重扫技能注册表、给某个智能体或目录一次性铺齐 yotta-* 技能、评估宿主 hook 能力、预览安装清单、锁版本安装、或用户说 元阁/装全家/一次装齐/yotta-skills/install-all/更新全家/检查更新/自动更新/hook 适配/该用哪个技能/路由技能/记忆裁决/技能该不该记住/盘点技能/查看已装技能 等。边界(Do NOT trigger):只做「组合策划 + 静态路由建议 + M1 记忆裁决只读建议 + 清单 + 下载 + 落位 + 汇总 + 盘点 + re-index + hook 适配」,不含技能本体、不做技能内容开发、不 -g 污染全局、不自动安装缺失技能、不静默写宿主配置或全局记忆、不自动删除技能或记忆;家族安装先自举或调用元信装前门禁,DO NOT INSTALL 阻断,非元阁家族包不自动安装。
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
7
|
zh_name: 元阁
|
|
@@ -44,6 +44,7 @@ metadata:
|
|
|
44
44
|
|
|
45
45
|
- **路由层(该用哪几个)**:`--route "<需求摘要>"` / MCP `route_request` 查本地注册表与静态编排 playbook,输出候选组合、调用顺序、每个技能角色、置信度、依据、已装/缺失状态与安装命令;非元阁家族已装技能按 frontmatter description 机械匹配作并列候选(标注来源与未扫描状态,只读不自动调用);只建议安装,不自动安装。
|
|
46
46
|
- **动态扩展口(v0.20.0,capability `o1.route`)**:可选调用用户显式配置的本地扩展提供方(provider),在已装注册表白名单内对路由结果增补 / 重排;静态 playbook 结果先算必算、永不缺席,未配置或调用失败时文本输出与历史一致,`--json` 仅多一个 `dynamic` 状态块。协议与配置见 `references/provider-protocol.md`。
|
|
47
|
+
- **M1 记忆裁决层(哪些值得长期记住)**:`decide-memory` / MCP `decide_memory` 调用可选本地 provider(capability `m1.adjudicate`),输出 `promote / hold / demote` 只读建议、分数与信号明细;默认不写元忆、不删除技能。`usage` 默认关闭,用户显式开启后才记录结构化使用信号。协议与边界见 `references/provider-protocol.md`。
|
|
47
48
|
- **策划层(怎么用)**:接到需求,先按「编排策划」定位命中哪个组合——哪些元技能搭配起来最强、适合什么场景、AI 该给出什么安装与调用建议(**安装与调用均由用户确认后执行**)。
|
|
48
49
|
- **安装层(怎么装)**:一条 `npx -y @yottameta/yotta-skills` 把组合/全家装进指定智能体或目录——`--list` 看清单、`install` 装、`update` 增量更新、`doctor` 只读自检、`rollback` 恢复快照、`--dry-run` 预览、`--pin` 锁版本。
|
|
49
50
|
|
|
@@ -173,6 +174,11 @@ openclaw skills update @yottameta/yotta-skills
|
|
|
173
174
|
| `--inventory` | 盘点本机已装技能:扫描技能目录生成/更新注册表(自包含,不依赖元技能);`--json` 输出 JSON、`--project` 附扫项目级目录 |
|
|
174
175
|
| `--reindex` | 重扫注册表:扫描技能目录并增量合并变化(install / update 完成后 CLI 自动重扫;也可在会话开工等时机手动运行);`--json` 输出 JSON、`--project` 附扫项目级目录;`--rescan` 同义 |
|
|
175
176
|
| `--route <需求摘要>` | 静态编排路由:输出组合、调用顺序、技能角色、置信度、依据、已装/缺失状态与安装建议;`--json` 输出 JSON、`--project` 附扫项目级目录 |
|
|
177
|
+
| `usage status` | 查看本地使用记录开关与计数;默认关闭,不创建文件 |
|
|
178
|
+
| `usage enable` / `usage disable` | 开启 / 关闭 `--route` 的结构化使用记录;不记录需求原文 |
|
|
179
|
+
| `usage mark --skill <slug> --signal used\|named\|accepted` | 记录一次显式使用信号;不要求先 enable |
|
|
180
|
+
| `usage reset --yes` | 清空本地使用记录 |
|
|
181
|
+
| `decide-memory [--dry-run\|--promote] [--explain] [--json]` | M1 记忆裁决只读建议;`--promote` 只写本地建议文件,不写元忆、不删除内容 |
|
|
176
182
|
| `--no-reindex` | 安装 / 更新后不自动重扫注册表 |
|
|
177
183
|
| `--dry-run` | 预览将执行的安装 / 更新清单;不联网、不改动 |
|
|
178
184
|
| `--pin` | 锁死清单精确版本(默认 range:跟随同 major 最新 patch) |
|
|
@@ -298,11 +304,41 @@ npx -y @yottameta/yotta-skills --route "检查代码质量,别糊弄" --json
|
|
|
298
304
|
- **无明确匹配**:低置信度回退到「入口 + 安装」组合,先澄清需求再继续。
|
|
299
305
|
- **边界**:路由是建议,不保证完全正确;关键动作由用户确认,数据不出本机。
|
|
300
306
|
|
|
307
|
+
## M1 记忆裁决与使用记录
|
|
308
|
+
|
|
309
|
+
`decide-memory` 判断哪些已装技能值得进入长期记忆,输出 `promote / hold / demote` 建议、分数与信号明细。
|
|
310
|
+
评分由用户显式配置的本地 provider(capability `m1.adjudicate`)完成;未配置 / 未授权 / 超时 / 非法输出时
|
|
311
|
+
只返回状态,不阻断其他能力,退出码保持 0。
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
# 默认只读:只看建议,不写任何文件
|
|
315
|
+
npx -y @yottameta/yotta-skills decide-memory
|
|
316
|
+
|
|
317
|
+
# 显式只读 + 信号明细
|
|
318
|
+
npx -y @yottameta/yotta-skills decide-memory --dry-run --explain
|
|
319
|
+
|
|
320
|
+
# 只写本地建议文件(不写元忆、不删除内容)
|
|
321
|
+
npx -y @yottameta/yotta-skills decide-memory --promote --json
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
使用记录默认关闭:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
npx -y @yottameta/yotta-skills usage status
|
|
328
|
+
npx -y @yottameta/yotta-skills usage enable
|
|
329
|
+
npx -y @yottameta/yotta-skills usage mark --skill yotta-memory --signal used
|
|
330
|
+
npx -y @yottameta/yotta-skills usage disable
|
|
331
|
+
npx -y @yottameta/yotta-skills usage reset --yes
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
边界:记录只含技能 slug、时间、信号类型、playbook / confidence 与组合对;不含需求原文、记忆正文、路径或身份信息。
|
|
335
|
+
`--promote` 只写 `~/.yottaskills/memory-adjudication.json`,生成私密 `PREF` 记忆候选,由用户或 AI 再显式调用元忆写入。
|
|
336
|
+
|
|
301
337
|
### MCP:按需加载(可选)
|
|
302
338
|
|
|
303
339
|
本技能自带一个 MCP server:`yotta-skills`(`scripts/yotta-skills-mcp.py`,零依赖、数据不出本机),
|
|
304
340
|
基于 MCP 最新协议 2026-07-28(无状态时代;向后兼容 2025-11-25 及更早握手客户端)。
|
|
305
|
-
提供 `list_installed_skills`(盘点)/ `describe_skill`(单技能详情)/ `reindex`(强制重扫)/ `route_request
|
|
341
|
+
提供 `list_installed_skills`(盘点)/ `describe_skill`(单技能详情)/ `reindex`(强制重扫)/ `route_request`(静态编排路由)/ `decide_memory`(M1 记忆裁决只读建议)五个工具。
|
|
306
342
|
|
|
307
343
|
**按需加载,不走常驻**:本技能与 MCP 均为按需触发。默认以 CLI 为主
|
|
308
344
|
(`npx -y @yottameta/yotta-skills --inventory`);需要让 AI 通过工具直接调用时,再按下面配置启用。
|
|
@@ -327,9 +363,9 @@ npx -y @yottameta/yotta-skills --route "检查代码质量,别糊弄" --json
|
|
|
327
363
|
> `<技能目录>` = 本技能实际安装目录,**不要写死盘符路径**;Windows 用 `python`,Linux/macOS 用 `python3`。
|
|
328
364
|
> 单 agent 环境可不设 `YOTTA_SKILLS_REGISTRY_FILE`;同一宿主内多 agent 共用 MCP 时,应分别配置独立路径,避免注册表状态互相覆盖。
|
|
329
365
|
3. **提醒用户**:改 `mcpServers` 后多数客户端需**重启 / 重载一次** MCP server 才生效;加载后应看到
|
|
330
|
-
`list_installed_skills` / `describe_skill` / `reindex` / `route_request`
|
|
366
|
+
`list_installed_skills` / `describe_skill` / `reindex` / `route_request` / `decide_memory` 五个工具。
|
|
331
367
|
4. **降级兜底(重要)**:若客户端未暴露 MCP 工具 / 用户拒绝改配置 / 无法改配置 / server 未加载,**自动降级 CLI**
|
|
332
|
-
|
|
368
|
+
(同一套扫描、路由与 M1 核心、结果一致):`npx -y @yottameta/yotta-skills --inventory / --reindex / --route ... / decide-memory ...`。
|
|
333
369
|
|
|
334
370
|
## 版本策略
|
|
335
371
|
|
package/bin/yotta-skills.js
CHANGED
|
@@ -37,10 +37,12 @@ const updateCheckLib = require('../lib/update-check');
|
|
|
37
37
|
const hookAdapterLib = require('../lib/hook-adapter');
|
|
38
38
|
const scanPolicyLib = require('../lib/scan-policy');
|
|
39
39
|
const npmPackLib = require('../lib/npm-pack');
|
|
40
|
+
const usageJournalLib = require('../lib/usage-journal');
|
|
41
|
+
const m1FeaturesLib = require('../lib/m1-features');
|
|
40
42
|
const { createInstaller, isSafeTarEntry } = require('../lib/install-pipeline');
|
|
41
43
|
|
|
42
44
|
const PKG_ROOT = path.join(__dirname, '..');
|
|
43
|
-
let VERSION = '0.
|
|
45
|
+
let VERSION = '0.22.0';
|
|
44
46
|
try { VERSION = require(path.join(PKG_ROOT, 'package.json')).version; } catch (_) { /* keep fallback */ }
|
|
45
47
|
|
|
46
48
|
function loadManifest() {
|
|
@@ -153,6 +155,7 @@ function parseArgs(argv) {
|
|
|
153
155
|
inventory: false, reindex: false, noReindex: false, json: false, project: false, route: null,
|
|
154
156
|
check: false, auto: false, scheduled: false, registry: null, slug: null,
|
|
155
157
|
host: null, event: null, manifest: null, context: null,
|
|
158
|
+
signal: null, yes: false, explain: false, promote: false,
|
|
156
159
|
};
|
|
157
160
|
const positionals = [];
|
|
158
161
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -197,12 +200,18 @@ function parseArgs(argv) {
|
|
|
197
200
|
else if (a === '--event') opts.event = take('--event').toLowerCase();
|
|
198
201
|
else if (a === '--manifest') opts.manifest = take('--manifest');
|
|
199
202
|
else if (a === '--context') opts.context = take('--context');
|
|
203
|
+
else if (a === '--skill') opts.skill = take('--skill').toLowerCase();
|
|
204
|
+
else if (a === '--signal') opts.signal = take('--signal').toLowerCase();
|
|
205
|
+
else if (a === '--yes') opts.yes = true;
|
|
206
|
+
else if (a === '--explain') opts.explain = true;
|
|
207
|
+
else if (a === '--promote') opts.promote = true;
|
|
200
208
|
else if (a.startsWith('-')) die('未知参数: ' + a, 2, '可用 --help 查看支持的选项。');
|
|
201
209
|
else positionals.push(a);
|
|
202
210
|
}
|
|
203
211
|
// 命令解析:install / update / doctor / rollback,其余位置参数 = 技能 slug(可多个)
|
|
204
212
|
for (const p of positionals) {
|
|
205
|
-
if (p === 'install' || p === 'update' || p === 'doctor' || p === 'rollback' || p === 'hook'
|
|
213
|
+
if (p === 'install' || p === 'update' || p === 'doctor' || p === 'rollback' || p === 'hook'
|
|
214
|
+
|| p === 'usage' || p === 'decide-memory') {
|
|
206
215
|
if (opts.command && opts.command !== p) die('命令冲突:' + opts.command + ' 与 ' + p);
|
|
207
216
|
opts.command = p;
|
|
208
217
|
} else {
|
|
@@ -1101,6 +1110,9 @@ function printHelp() {
|
|
|
1101
1110
|
out(' yotta-skills --inventory 盘点本机已装技能(自研扫描,不依赖任何元技能)');
|
|
1102
1111
|
out(' yotta-skills --reindex 重扫注册表(手动触发;install / update 完成后 CLI 自动重扫)');
|
|
1103
1112
|
out(' yotta-skills --route "<需求摘要>" 给出场景组合、调用顺序、缺失技能安装建议');
|
|
1113
|
+
out(' yotta-skills usage status 查看本地使用记录开关与计数');
|
|
1114
|
+
out(' yotta-skills usage enable|disable 开启 / 关闭 --route 的结构化使用记录');
|
|
1115
|
+
out(' yotta-skills usage mark --skill <slug> --signal used|named|accepted');
|
|
1104
1116
|
out(' yotta-skills hook capabilities 查看宿主六事件能力矩阵');
|
|
1105
1117
|
out(' yotta-skills hook evaluate --event <event> --manifest <file> 评估 hook 声明并留证');
|
|
1106
1118
|
out(' yotta-skills hook bind --manifest <file> 注册 hook 声明(幂等)');
|
|
@@ -1127,6 +1139,11 @@ function printHelp() {
|
|
|
1127
1139
|
out(' --event <event> hook 六事件之一(before_start / before_tool / before_install / before_publish / after_milestone / before_send)');
|
|
1128
1140
|
out(' --manifest <file> hook evaluate / bind 使用的 skill-manifest.json 路径');
|
|
1129
1141
|
out(' --context <json> hook evaluate 的检查结果 JSON(checks / wrapperRegistered / evidence)');
|
|
1142
|
+
out(' --skill <slug> usage mark 的技能 slug');
|
|
1143
|
+
out(' --signal <name> usage mark 的信号:used / named / accepted');
|
|
1144
|
+
out(' --yes usage reset 确认清空');
|
|
1145
|
+
out(' --explain decide-memory 文本报告追加信号明细');
|
|
1146
|
+
out(' --promote decide-memory 只写本地建议文件(不写元忆)');
|
|
1130
1147
|
out(' --project inventory / reindex 时附加扫描当前项目 .agents/skills / .codex/skills');
|
|
1131
1148
|
out(' --no-reindex 安装 / 更新后不自动重扫注册表');
|
|
1132
1149
|
out(' -h, --help 帮助');
|
|
@@ -1302,6 +1319,15 @@ function runRoute(opts) {
|
|
|
1302
1319
|
const yottaSlugs = new Set([...defaultYottaSlugs(), ...MANIFEST.map((s) => s.slug)]);
|
|
1303
1320
|
const result = routeRequest(opts.route, { registry, yottaSlugs });
|
|
1304
1321
|
result.dynamic = applyDynamicRoute(result, registry, opts);
|
|
1322
|
+
try {
|
|
1323
|
+
usageJournalLib.recordRoute({
|
|
1324
|
+
playbook: result.playbook.id,
|
|
1325
|
+
confidence: result.confidence,
|
|
1326
|
+
skills: result.skills.map((skill) => skill.slug),
|
|
1327
|
+
});
|
|
1328
|
+
} catch (_) {
|
|
1329
|
+
// 使用记录失败不阻断路由
|
|
1330
|
+
}
|
|
1305
1331
|
if (opts.json) {
|
|
1306
1332
|
out(JSON.stringify(result, null, 2));
|
|
1307
1333
|
return;
|
|
@@ -1347,6 +1373,184 @@ function runRoute(opts) {
|
|
|
1347
1373
|
out('说明: ' + result.disclaimer);
|
|
1348
1374
|
}
|
|
1349
1375
|
|
|
1376
|
+
function runUsage(opts) {
|
|
1377
|
+
const action = opts.rest[0] || 'status';
|
|
1378
|
+
if (action === 'status') {
|
|
1379
|
+
const state = usageJournalLib.readUsage();
|
|
1380
|
+
if (opts.json) {
|
|
1381
|
+
out(JSON.stringify(state, null, 2));
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
out('元阁本地使用记录:' + (state.enabled ? '已开启' : '已关闭'));
|
|
1385
|
+
out('文件: ' + usageJournalLib.usageFilePath());
|
|
1386
|
+
const skills = Object.keys(state.skills || {}).sort();
|
|
1387
|
+
if (!skills.length) {
|
|
1388
|
+
out('暂无记录。');
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
for (const slug of skills) {
|
|
1392
|
+
const item = state.skills[slug];
|
|
1393
|
+
out(' ' + slug + ' used=' + item.used + ' named=' + item.named
|
|
1394
|
+
+ ' accepted=' + item.accepted + ' route_hits=' + item.route_hits
|
|
1395
|
+
+ ' pairs=' + Object.keys(item.pairs || {}).length);
|
|
1396
|
+
}
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
if (action === 'enable' || action === 'disable') {
|
|
1400
|
+
const state = usageJournalLib.setEnabled(action === 'enable');
|
|
1401
|
+
if (opts.json) {
|
|
1402
|
+
out(JSON.stringify(state, null, 2));
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
out('元阁本地使用记录已' + (state.enabled ? '开启' : '关闭') + '。');
|
|
1406
|
+
out('文件: ' + usageJournalLib.usageFilePath());
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
if (action === 'mark') {
|
|
1410
|
+
if (!opts.skill) die('usage mark 缺少 --skill', 2, '例如 --skill yotta-memory。');
|
|
1411
|
+
if (!opts.signal) die('usage mark 缺少 --signal', 2, '可用 used / named / accepted。');
|
|
1412
|
+
let state;
|
|
1413
|
+
try {
|
|
1414
|
+
state = usageJournalLib.markUsage(opts.skill, opts.signal);
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
die(error.message, 2, '请检查 --skill 与 --signal。');
|
|
1417
|
+
}
|
|
1418
|
+
if (opts.json) {
|
|
1419
|
+
out(JSON.stringify(state, null, 2));
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
out('已记录:' + opts.skill + ' / ' + opts.signal);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
if (action === 'reset') {
|
|
1426
|
+
if (!opts.yes) die('usage reset 需要 --yes 确认', 2, '该操作会清空本地使用记录。');
|
|
1427
|
+
const file = usageJournalLib.resetUsage();
|
|
1428
|
+
if (opts.json) {
|
|
1429
|
+
out(JSON.stringify({ reset: true, file }, null, 2));
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
out('已清空本地使用记录:' + file);
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
die('未知 usage 子命令: ' + action, 2, '支持 status / enable / disable / mark / reset。');
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
function m1StatusText(block) {
|
|
1439
|
+
if (!block) return '';
|
|
1440
|
+
if (block.status === 'active') {
|
|
1441
|
+
return '已应用(提供方 ' + (block.provider_id || '-') + ';建议 '
|
|
1442
|
+
+ block.decisions.length + ' 条)';
|
|
1443
|
+
}
|
|
1444
|
+
if (block.status === 'license_required') return '需授权(该能力需要授权后使用)';
|
|
1445
|
+
if (block.status === 'timeout') return '未生效(提供方超时)';
|
|
1446
|
+
if (block.status === 'invalid_output') return '未生效(提供方输出无效)';
|
|
1447
|
+
if (block.status === 'error') return '未生效(提供方异常)';
|
|
1448
|
+
return block.status;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function runDecideMemory(opts) {
|
|
1452
|
+
if (opts.dryRun && opts.promote) {
|
|
1453
|
+
die('--dry-run 与 --promote 不能同时使用', 2, '默认就是只读;需要写建议文件时只加 --promote。');
|
|
1454
|
+
}
|
|
1455
|
+
const { registry } = reindexRegistry(opts);
|
|
1456
|
+
const usage = usageJournalLib.readUsage();
|
|
1457
|
+
const snapshot = m1FeaturesLib.buildFeatureSnapshot(registry, usage);
|
|
1458
|
+
const provider = require('../lib/provider');
|
|
1459
|
+
let run;
|
|
1460
|
+
try {
|
|
1461
|
+
run = provider.runCapability('m1.adjudicate', snapshot);
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
run = { status: 'error', provider_id: '', note: 'M1 装载失败:' + error.message };
|
|
1464
|
+
}
|
|
1465
|
+
const block = {
|
|
1466
|
+
status: run.status || 'error',
|
|
1467
|
+
provider_id: run.provider_id || '',
|
|
1468
|
+
applied: false,
|
|
1469
|
+
mode: opts.promote ? 'recommendation' : 'dry-run',
|
|
1470
|
+
decisions: [],
|
|
1471
|
+
summary: { promote: 0, hold: 0, demote: 0 },
|
|
1472
|
+
dropped: [],
|
|
1473
|
+
note: run.note || run.message || '',
|
|
1474
|
+
};
|
|
1475
|
+
if (run.status === 'active' && run.data && typeof run.data === 'object') {
|
|
1476
|
+
const allowed = new Set(Object.keys(registry.skills || {}));
|
|
1477
|
+
const verdicts = new Set(['promote', 'hold', 'demote']);
|
|
1478
|
+
const requested = Array.isArray(run.data.decisions) ? run.data.decisions : [];
|
|
1479
|
+
for (const raw of requested) {
|
|
1480
|
+
const slug = raw && typeof raw === 'object' ? String(raw.slug || '') : '';
|
|
1481
|
+
const score = raw && Number(raw.score);
|
|
1482
|
+
const verdict = raw && String(raw.verdict || '');
|
|
1483
|
+
if (!slug || !allowed.has(slug) || !verdicts.has(verdict) || !Number.isFinite(score) || score < 0 || score > 100) {
|
|
1484
|
+
if (slug) block.dropped.push(slug);
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
const reasons = Array.isArray(raw.reasons)
|
|
1488
|
+
? raw.reasons.filter((item) => typeof item === 'string').slice(0, 20)
|
|
1489
|
+
: [];
|
|
1490
|
+
block.decisions.push({
|
|
1491
|
+
slug,
|
|
1492
|
+
verdict,
|
|
1493
|
+
score: Math.round(score),
|
|
1494
|
+
reasons,
|
|
1495
|
+
signals: raw.signals && typeof raw.signals === 'object' && !Array.isArray(raw.signals)
|
|
1496
|
+
? raw.signals
|
|
1497
|
+
: {},
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
block.decisions.sort((left, right) => right.score - left.score || left.slug.localeCompare(right.slug));
|
|
1501
|
+
for (const decision of block.decisions) {
|
|
1502
|
+
if (block.summary[decision.verdict] !== undefined) block.summary[decision.verdict] += 1;
|
|
1503
|
+
}
|
|
1504
|
+
block.applied = block.decisions.length > 0;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
let reportFile = null;
|
|
1508
|
+
if (opts.promote && block.status === 'active') {
|
|
1509
|
+
const report = {
|
|
1510
|
+
schema: 1,
|
|
1511
|
+
generated_at: snapshot.generated_at,
|
|
1512
|
+
provider_id: block.provider_id,
|
|
1513
|
+
mode: 'recommendation',
|
|
1514
|
+
decisions: block.decisions,
|
|
1515
|
+
memory_candidates: m1FeaturesLib.buildMemoryCandidates(block.decisions, registry),
|
|
1516
|
+
note: '只写本地建议文件;不写元忆、不删除任何内容。',
|
|
1517
|
+
};
|
|
1518
|
+
try {
|
|
1519
|
+
reportFile = m1FeaturesLib.writeAdjudication(report);
|
|
1520
|
+
block.report_file = reportFile;
|
|
1521
|
+
} catch (error) {
|
|
1522
|
+
die('M1 建议文件写入失败: ' + error.message, 1, '请检查 ~/.yottaskills 目录权限。');
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
if (opts.json) {
|
|
1527
|
+
out(JSON.stringify({ schema: 1, generated_at: snapshot.generated_at, m1: block }, null, 2));
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
out('元阁记忆裁决(M1)');
|
|
1531
|
+
out('状态: ' + m1StatusText(block));
|
|
1532
|
+
if (block.status === 'active') {
|
|
1533
|
+
out('建议汇总: promote ' + block.summary.promote + ' / hold ' + block.summary.hold
|
|
1534
|
+
+ ' / demote ' + block.summary.demote + ';丢弃 ' + block.dropped.length);
|
|
1535
|
+
for (const decision of block.decisions) {
|
|
1536
|
+
out(' [' + decision.verdict + '] ' + decision.slug + ' ' + decision.score
|
|
1537
|
+
+ (decision.reasons.length ? ' - ' + decision.reasons.join(';') : ''));
|
|
1538
|
+
if (opts.explain) {
|
|
1539
|
+
const signals = decision.signals || {};
|
|
1540
|
+
out(' 信号: used=' + (signals.used || 0) + ' named=' + (signals.named || 0)
|
|
1541
|
+
+ ' accepted=' + (signals.accepted || 0) + ' route_hits=' + (signals.route_hits || 0)
|
|
1542
|
+
+ ' distinct_pairs=' + (signals.distinct_pairs || 0)
|
|
1543
|
+
+ ' description_quality=' + (signals.description_quality || 0)
|
|
1544
|
+
+ ' recency=' + (signals.recency || 0));
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
if (reportFile) out('建议文件: ' + reportFile);
|
|
1548
|
+
} else if (block.note) {
|
|
1549
|
+
out('说明: ' + block.note);
|
|
1550
|
+
}
|
|
1551
|
+
out('边界: 只建议不删除;不自动写元忆;数据不出本机。');
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1350
1554
|
/** 装技能后自动 re-index(--no-reindex 关闭):把本次落位结果反映进注册表。best-effort:失败不阻断安装。 */
|
|
1351
1555
|
function maybeAutoReindex(opts, dest) {
|
|
1352
1556
|
if (opts.noReindex || opts.dryRun || !dest) return;
|
|
@@ -1465,6 +1669,14 @@ function main() {
|
|
|
1465
1669
|
if (opts.route && !opts.command) { runRoute(opts); return; }
|
|
1466
1670
|
|
|
1467
1671
|
const command = opts.command || 'install';
|
|
1672
|
+
if (command === 'usage') {
|
|
1673
|
+
runUsage(opts);
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
if (command === 'decide-memory') {
|
|
1677
|
+
runDecideMemory(opts);
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1468
1680
|
if (command === 'hook') {
|
|
1469
1681
|
runHook(opts);
|
|
1470
1682
|
return;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* M1 记忆裁决器:开源侧只做确定性特征快照与建议文件。
|
|
5
|
+
*
|
|
6
|
+
* 评分算法不在本文件,由私有 provider(capability m1.adjudicate)返回。
|
|
7
|
+
* 快照只含技能元数据与聚合计数,不含需求原文、记忆正文、路径、用户名或主机名。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const usageJournal = require('./usage-journal');
|
|
13
|
+
|
|
14
|
+
const SCHEMA = 1;
|
|
15
|
+
const REPORT_FILE = 'memory-adjudication.json';
|
|
16
|
+
|
|
17
|
+
function nowIso(options) {
|
|
18
|
+
const value = options && options.now;
|
|
19
|
+
return typeof value === 'string' && value ? value : new Date().toISOString();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function skillSignal(usage, slug) {
|
|
23
|
+
const source = usage && usage.skills && usage.skills[slug] ? usage.skills[slug] : {};
|
|
24
|
+
const pairs = source.pairs && typeof source.pairs === 'object' && !Array.isArray(source.pairs)
|
|
25
|
+
? source.pairs
|
|
26
|
+
: {};
|
|
27
|
+
return {
|
|
28
|
+
used: Number(source.used) || 0,
|
|
29
|
+
named: Number(source.named) || 0,
|
|
30
|
+
accepted: Number(source.accepted) || 0,
|
|
31
|
+
route_hits: Number(source.route_hits) || 0,
|
|
32
|
+
distinct_pairs: Object.keys(pairs).length,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function buildFeatureSnapshot(registry, usage, options) {
|
|
37
|
+
const skills = Object.values((registry && registry.skills) || {})
|
|
38
|
+
.filter((record) => record && record.slug)
|
|
39
|
+
.map((record) => {
|
|
40
|
+
const slug = String(record.slug);
|
|
41
|
+
return {
|
|
42
|
+
slug,
|
|
43
|
+
version: String(record.version || ''),
|
|
44
|
+
status: String(record.status || 'known'),
|
|
45
|
+
description: String(record.description || ''),
|
|
46
|
+
first_seen: String(record.first_seen || ''),
|
|
47
|
+
last_seen: String(record.last_seen || ''),
|
|
48
|
+
last_signal_at: String((usage && usage.skills && usage.skills[slug] && usage.skills[slug].last_signal_at) || ''),
|
|
49
|
+
pinned: record.pinned === true,
|
|
50
|
+
signals: skillSignal(usage, slug),
|
|
51
|
+
};
|
|
52
|
+
})
|
|
53
|
+
.sort((left, right) => left.slug.localeCompare(right.slug));
|
|
54
|
+
return {
|
|
55
|
+
schema: SCHEMA,
|
|
56
|
+
generated_at: nowIso(options),
|
|
57
|
+
skills,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function reportPath(options) {
|
|
62
|
+
const opts = options || {};
|
|
63
|
+
if (opts.file) return path.resolve(opts.file);
|
|
64
|
+
const fromEnv = String(process.env.YOTTA_SKILLS_M1_REPORT_FILE || '').trim();
|
|
65
|
+
if (fromEnv) return path.resolve(fromEnv);
|
|
66
|
+
return path.join(path.dirname(usageJournal.usageFilePath(opts)), REPORT_FILE);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildMemoryCandidates(decisions, registry) {
|
|
70
|
+
const records = (registry && registry.skills) || {};
|
|
71
|
+
const candidates = [];
|
|
72
|
+
for (const decision of Array.isArray(decisions) ? decisions : []) {
|
|
73
|
+
if (!decision || decision.verdict !== 'promote') continue;
|
|
74
|
+
const record = records[decision.slug] || {};
|
|
75
|
+
const description = String(record.description || '').trim();
|
|
76
|
+
const summary = description
|
|
77
|
+
? description.slice(0, 180)
|
|
78
|
+
: '暂无描述';
|
|
79
|
+
candidates.push({
|
|
80
|
+
type: 'PREF',
|
|
81
|
+
subject: '技能索引:' + decision.slug,
|
|
82
|
+
statement: '本机长期保留技能 ' + decision.slug + ':' + summary
|
|
83
|
+
+ ';M1 评分:' + decision.score + '。',
|
|
84
|
+
source: 'yotta-skills m1',
|
|
85
|
+
weight: 0.8,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return candidates;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function writeAdjudication(report, options) {
|
|
92
|
+
const file = reportPath(options);
|
|
93
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
94
|
+
const tmp = file + '.tmp-' + process.pid + '-' + Date.now();
|
|
95
|
+
fs.writeFileSync(tmp, JSON.stringify(report, null, 2) + '\n', 'utf8');
|
|
96
|
+
try {
|
|
97
|
+
fs.renameSync(tmp, file);
|
|
98
|
+
} catch (_) {
|
|
99
|
+
fs.rmSync(file, { force: true });
|
|
100
|
+
fs.renameSync(tmp, file);
|
|
101
|
+
}
|
|
102
|
+
return file;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
SCHEMA,
|
|
107
|
+
REPORT_FILE,
|
|
108
|
+
buildFeatureSnapshot,
|
|
109
|
+
reportPath,
|
|
110
|
+
buildMemoryCandidates,
|
|
111
|
+
writeAdjudication,
|
|
112
|
+
};
|
package/lib/provider.js
CHANGED
|
@@ -94,6 +94,7 @@ function minimalEnv() {
|
|
|
94
94
|
const keep = [
|
|
95
95
|
'PATH', 'Path', 'PATHEXT', 'SystemRoot', 'windir', 'COMSPEC', 'ComSpec',
|
|
96
96
|
'TEMP', 'TMP', 'HOME', 'USERPROFILE', 'LANG', 'LC_ALL',
|
|
97
|
+
'YOTTA_LICENSE_HOME', 'YOTTA_LICENSE_KEYS_DIR', 'YOTTA_LICENSE_BASE_URL', 'YOTTA_LICENSE_SERVER_ID',
|
|
97
98
|
];
|
|
98
99
|
const env = {};
|
|
99
100
|
for (const key of keep) {
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 元阁本地使用记录(M1 特征来源之一)。
|
|
5
|
+
*
|
|
6
|
+
* 边界:
|
|
7
|
+
* - 只记录技能 slug、信号类型、路由 playbook / confidence 与组合对。
|
|
8
|
+
* - 不记录需求原文、记忆正文、文件内容、用户名、主机名或路径。
|
|
9
|
+
* - 自动路由记录默认关闭;显式 mark 不要求先 enable。
|
|
10
|
+
* - 文件默认 ~/.yottaskills/usage.json,YOTTA_SKILLS_USAGE_FILE 可覆盖。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const SCHEMA = 1;
|
|
18
|
+
const SIGNALS = ['used', 'named', 'accepted'];
|
|
19
|
+
const MAX_COUNT = 999999;
|
|
20
|
+
const SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
21
|
+
|
|
22
|
+
function defaultState() {
|
|
23
|
+
return {
|
|
24
|
+
schema: SCHEMA,
|
|
25
|
+
enabled: false,
|
|
26
|
+
updated_at: '',
|
|
27
|
+
skills: {},
|
|
28
|
+
last_route: null,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function usageFilePath(options) {
|
|
33
|
+
const opts = options || {};
|
|
34
|
+
if (opts.file) return path.resolve(opts.file);
|
|
35
|
+
const fromEnv = String(process.env.YOTTA_SKILLS_USAGE_FILE || '').trim();
|
|
36
|
+
if (fromEnv) return path.resolve(fromEnv);
|
|
37
|
+
return path.join(opts.homeDir || os.homedir(), '.yottaskills', 'usage.json');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function bump(value) {
|
|
41
|
+
const n = Number.isFinite(value) ? Math.floor(value) : 0;
|
|
42
|
+
return Math.min(MAX_COUNT, Math.max(0, n) + 1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeSkill(raw) {
|
|
46
|
+
const skill = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
|
|
47
|
+
const pairs = {};
|
|
48
|
+
if (skill.pairs && typeof skill.pairs === 'object' && !Array.isArray(skill.pairs)) {
|
|
49
|
+
for (const key of Object.keys(skill.pairs)) {
|
|
50
|
+
if (!SLUG_RE.test(key)) continue;
|
|
51
|
+
const n = Number(skill.pairs[key]);
|
|
52
|
+
if (Number.isFinite(n) && n > 0) pairs[key] = Math.min(MAX_COUNT, Math.floor(n));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
used: Math.min(MAX_COUNT, Math.max(0, Math.floor(Number(skill.used) || 0))),
|
|
57
|
+
named: Math.min(MAX_COUNT, Math.max(0, Math.floor(Number(skill.named) || 0))),
|
|
58
|
+
accepted: Math.min(MAX_COUNT, Math.max(0, Math.floor(Number(skill.accepted) || 0))),
|
|
59
|
+
route_hits: Math.min(MAX_COUNT, Math.max(0, Math.floor(Number(skill.route_hits) || 0))),
|
|
60
|
+
last_signal_at: typeof skill.last_signal_at === 'string' ? skill.last_signal_at : '',
|
|
61
|
+
pairs,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeState(raw) {
|
|
66
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw) || raw.schema !== SCHEMA) {
|
|
67
|
+
return defaultState();
|
|
68
|
+
}
|
|
69
|
+
const state = defaultState();
|
|
70
|
+
state.enabled = raw.enabled === true;
|
|
71
|
+
state.updated_at = typeof raw.updated_at === 'string' ? raw.updated_at : '';
|
|
72
|
+
if (raw.skills && typeof raw.skills === 'object' && !Array.isArray(raw.skills)) {
|
|
73
|
+
for (const slug of Object.keys(raw.skills)) {
|
|
74
|
+
if (!SLUG_RE.test(slug)) continue;
|
|
75
|
+
state.skills[slug] = normalizeSkill(raw.skills[slug]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (raw.last_route && typeof raw.last_route === 'object' && !Array.isArray(raw.last_route)) {
|
|
79
|
+
const skills = Array.isArray(raw.last_route.skills)
|
|
80
|
+
? raw.last_route.skills.filter((slug) => typeof slug === 'string' && SLUG_RE.test(slug))
|
|
81
|
+
: [];
|
|
82
|
+
state.last_route = {
|
|
83
|
+
at: typeof raw.last_route.at === 'string' ? raw.last_route.at : '',
|
|
84
|
+
playbook: typeof raw.last_route.playbook === 'string' ? raw.last_route.playbook : '',
|
|
85
|
+
confidence: typeof raw.last_route.confidence === 'string' ? raw.last_route.confidence : '',
|
|
86
|
+
skills,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return state;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readUsage(options) {
|
|
93
|
+
const file = usageFilePath(options);
|
|
94
|
+
try {
|
|
95
|
+
return normalizeState(JSON.parse(fs.readFileSync(file, 'utf8')));
|
|
96
|
+
} catch (_) {
|
|
97
|
+
return defaultState();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function writeUsage(state, options) {
|
|
102
|
+
const file = usageFilePath(options);
|
|
103
|
+
const next = normalizeState(Object.assign(defaultState(), state));
|
|
104
|
+
next.updated_at = new Date().toISOString();
|
|
105
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
106
|
+
const tmp = file + '.tmp-' + process.pid + '-' + Date.now();
|
|
107
|
+
fs.writeFileSync(tmp, JSON.stringify(next, null, 2) + '\n', 'utf8');
|
|
108
|
+
try {
|
|
109
|
+
fs.renameSync(tmp, file);
|
|
110
|
+
} catch (_) {
|
|
111
|
+
fs.rmSync(file, { force: true });
|
|
112
|
+
fs.renameSync(tmp, file);
|
|
113
|
+
}
|
|
114
|
+
return file;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function setEnabled(enabled, options) {
|
|
118
|
+
const state = readUsage(options);
|
|
119
|
+
state.enabled = enabled === true;
|
|
120
|
+
writeUsage(state, options);
|
|
121
|
+
return state;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function ensureSkill(state, slug) {
|
|
125
|
+
if (!state.skills[slug]) {
|
|
126
|
+
state.skills[slug] = normalizeSkill({});
|
|
127
|
+
}
|
|
128
|
+
return state.skills[slug];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function markUsage(skill, signal, options) {
|
|
132
|
+
const slug = String(skill || '').trim().toLowerCase();
|
|
133
|
+
if (!SLUG_RE.test(slug)) throw new Error('非法 skill slug:' + String(skill || ''));
|
|
134
|
+
if (SIGNALS.indexOf(signal) === -1) throw new Error('非法 signal:' + String(signal || ''));
|
|
135
|
+
const state = readUsage(options);
|
|
136
|
+
const record = ensureSkill(state, slug);
|
|
137
|
+
record[signal] = bump(record[signal]);
|
|
138
|
+
record.last_signal_at = new Date().toISOString();
|
|
139
|
+
writeUsage(state, options);
|
|
140
|
+
return state;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function uniqueSkills(skills) {
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
const out = [];
|
|
146
|
+
for (const item of Array.isArray(skills) ? skills : []) {
|
|
147
|
+
const slug = String(item || '').trim().toLowerCase();
|
|
148
|
+
if (!SLUG_RE.test(slug) || seen.has(slug)) continue;
|
|
149
|
+
seen.add(slug);
|
|
150
|
+
out.push(slug);
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function recordRoute(input, options) {
|
|
156
|
+
const state = readUsage(options);
|
|
157
|
+
if (!state.enabled) return { recorded: false, reason: 'disabled' };
|
|
158
|
+
const data = input && typeof input === 'object' ? input : {};
|
|
159
|
+
const skills = uniqueSkills(data.skills);
|
|
160
|
+
if (!skills.length) return { recorded: false, reason: 'empty' };
|
|
161
|
+
for (const slug of skills) {
|
|
162
|
+
const record = ensureSkill(state, slug);
|
|
163
|
+
record.route_hits = bump(record.route_hits);
|
|
164
|
+
}
|
|
165
|
+
for (let i = 0; i < skills.length; i++) {
|
|
166
|
+
for (let j = i + 1; j < skills.length; j++) {
|
|
167
|
+
const left = ensureSkill(state, skills[i]);
|
|
168
|
+
const right = skills[j];
|
|
169
|
+
left.pairs[right] = bump(left.pairs[right]);
|
|
170
|
+
const rightRecord = ensureSkill(state, right);
|
|
171
|
+
rightRecord.pairs[skills[i]] = bump(rightRecord.pairs[skills[i]]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
state.last_route = {
|
|
175
|
+
at: new Date().toISOString(),
|
|
176
|
+
playbook: typeof data.playbook === 'string' ? data.playbook : '',
|
|
177
|
+
confidence: typeof data.confidence === 'string' ? data.confidence : '',
|
|
178
|
+
skills,
|
|
179
|
+
};
|
|
180
|
+
writeUsage(state, options);
|
|
181
|
+
return { recorded: true, skills };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function resetUsage(options) {
|
|
185
|
+
const file = usageFilePath(options);
|
|
186
|
+
fs.rmSync(file, { force: true });
|
|
187
|
+
return file;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
SCHEMA,
|
|
192
|
+
SIGNALS,
|
|
193
|
+
usageFilePath,
|
|
194
|
+
defaultState,
|
|
195
|
+
readUsage,
|
|
196
|
+
writeUsage,
|
|
197
|
+
setEnabled,
|
|
198
|
+
markUsage,
|
|
199
|
+
recordRoute,
|
|
200
|
+
resetUsage,
|
|
201
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yottameta/yotta-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "YuanGe (元阁) - orchestration routing, inventory, and one-command installer for the YottaMeta skill family (zero dependencies, Node.js only).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
package/references/faq.md
CHANGED
|
@@ -113,3 +113,15 @@ npx -y @yottameta/yotta-skills install <slug> --dir /path/to/skills
|
|
|
113
113
|
不会。元阁只给建议:`--route` 输出缺失技能与安装命令,安装动作由你执行或确认后才发生;
|
|
114
114
|
写客户端 `mcpServers` 配置、把编排护栏写进全局记忆(如 `AGENTS.md` / `CLAUDE.md`)都需要**事先明确同意**,
|
|
115
115
|
并会先展示目标文件与完整文本。你拒绝写入时,元阁不写任何文件,直接以 CLI 方式继续工作。
|
|
116
|
+
|
|
117
|
+
## 15. `decide-memory` 会替我删除技能或写记忆吗?
|
|
118
|
+
|
|
119
|
+
不会。`decide-memory` 默认只读,输出 `promote / hold / demote` 建议、分数与信号明细;
|
|
120
|
+
`--promote` 只写本地 `~/.yottaskills/memory-adjudication.json` 建议文件,并生成私密 `PREF` 记忆候选。
|
|
121
|
+
元忆写入由你或 AI 再显式调用元忆完成;技能本体与记忆都不会被自动删除。
|
|
122
|
+
|
|
123
|
+
## 16. `usage` 会记录我的需求原文吗?
|
|
124
|
+
|
|
125
|
+
不会。使用记录默认关闭;执行 `usage enable` 后才记录 `--route` 的结构化 route_hits 与组合对。
|
|
126
|
+
记录只含技能 slug、时间、信号类型、playbook / confidence 与组合对,不含需求原文、记忆正文、路径或身份信息。
|
|
127
|
+
`usage disable` 停止记录,`usage reset --yes` 清空记录。
|
|
@@ -109,6 +109,13 @@
|
|
|
109
109
|
- 评估证据写入 `~/.yottaskills/hook-log.jsonl`,绑定记录写入 `~/.yottaskills/hook-bindings.json`。
|
|
110
110
|
- `native-audit` 不等于强制;缺少能力或证据时必须显示 `explicit-unverified`。
|
|
111
111
|
|
|
112
|
+
## M1 记忆裁决
|
|
113
|
+
|
|
114
|
+
- `decide-memory` 默认只读,调用可选本地 provider(capability `m1.adjudicate`)输出 `promote / hold / demote` 建议。
|
|
115
|
+
- `--promote` 只写 `~/.yottaskills/memory-adjudication.json` 建议文件;不写元忆、不删除技能。
|
|
116
|
+
- `usage` 默认关闭;显式 `usage enable` 后才记录结构化使用信号,不记录需求原文。
|
|
117
|
+
- 未配置 / 未授权 / 超时 / 非法输出时 fail-open,退出码保持 0。
|
|
118
|
+
|
|
112
119
|
## 快照与回滚
|
|
113
120
|
|
|
114
121
|
- 旧版本快照:`~/.yottaskills/snapshots/<slug>/<timestamp>-<version>-<随机后缀>/`;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
# 扩展提供方协议 v1(元阁 · capability `o1.route`)
|
|
1
|
+
# 扩展提供方协议 v1(元阁 · capability `o1.route` / `m1.adjudicate`)
|
|
2
2
|
|
|
3
|
-
元阁可以可选地调用一个由用户显式配置的**本地扩展提供方(provider
|
|
4
|
-
|
|
3
|
+
元阁可以可选地调用一个由用户显式配置的**本地扩展提供方(provider)**:
|
|
4
|
+
|
|
5
|
+
- `o1.route`:在已装技能白名单内对静态路由结果做增补 / 重排;未配置时 `--route` 行为与输出与之前完全一致。
|
|
6
|
+
- `m1.adjudicate`:对已装技能做 M1 记忆裁决,返回 `promote / hold / demote` 只读建议;未配置时 `decide-memory` 返回 `not_installed`,不写任何文件。
|
|
5
7
|
|
|
6
8
|
## 1. 配置
|
|
7
9
|
|
|
@@ -99,3 +101,105 @@ CLI 文本输出只在状态非 `not_installed` 时多一行「动态路由: ...
|
|
|
99
101
|
- 元阁不替 provider 联网;provider 自身行为由它自己的包声明。
|
|
100
102
|
- 删除 `provider.json` 即回到纯静态路由,无残留依赖。
|
|
101
103
|
- provider 输出只当数据使用:白名单外的 slug、非法结构一律丢弃,不作为指令执行。
|
|
104
|
+
|
|
105
|
+
## 6. capability `m1.adjudicate`
|
|
106
|
+
|
|
107
|
+
### 6.1 触发
|
|
108
|
+
|
|
109
|
+
只在用户显式执行 `yotta-skills decide-memory`(CLI)或 MCP `decide_memory` 时触发。
|
|
110
|
+
安装 / 更新 / 盘点 / re-index / doctor 等路径不执行 M1 provider。
|
|
111
|
+
|
|
112
|
+
### 6.2 请求
|
|
113
|
+
|
|
114
|
+
元阁先把注册表与本地使用记录转成确定性特征快照,再发给 provider:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"schema": 1,
|
|
119
|
+
"capability": "m1.adjudicate",
|
|
120
|
+
"request_id": "<uuid>",
|
|
121
|
+
"payload": {
|
|
122
|
+
"schema": 1,
|
|
123
|
+
"generated_at": "2026-09-30T00:00:00Z",
|
|
124
|
+
"skills": [
|
|
125
|
+
{
|
|
126
|
+
"slug": "yotta-memory",
|
|
127
|
+
"version": "0.19.0",
|
|
128
|
+
"status": "known",
|
|
129
|
+
"description": "文件式智能体记忆...",
|
|
130
|
+
"first_seen": "2026-08-23T00:00:00Z",
|
|
131
|
+
"last_seen": "2026-09-30T00:00:00Z",
|
|
132
|
+
"last_signal_at": "2026-09-30T00:00:00Z",
|
|
133
|
+
"pinned": false,
|
|
134
|
+
"signals": {
|
|
135
|
+
"used": 2,
|
|
136
|
+
"named": 0,
|
|
137
|
+
"accepted": 1,
|
|
138
|
+
"route_hits": 5,
|
|
139
|
+
"distinct_pairs": 3
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
payload 不含需求原文、记忆正文、凭据、主机名、用户名、来源目录或任意文件路径。
|
|
148
|
+
|
|
149
|
+
### 6.3 响应
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"ok": true,
|
|
154
|
+
"capability": "m1.adjudicate",
|
|
155
|
+
"data": {
|
|
156
|
+
"policy": {
|
|
157
|
+
"version": "m1-mvp-1",
|
|
158
|
+
"promote_threshold": 60,
|
|
159
|
+
"hold_threshold": 30,
|
|
160
|
+
"cooling_days": 7
|
|
161
|
+
},
|
|
162
|
+
"decisions": [
|
|
163
|
+
{
|
|
164
|
+
"slug": "yotta-memory",
|
|
165
|
+
"verdict": "promote",
|
|
166
|
+
"score": 78,
|
|
167
|
+
"reasons": ["used x2:+24", "组合出现 x3:+9"],
|
|
168
|
+
"signals": {
|
|
169
|
+
"used": 2,
|
|
170
|
+
"named": 0,
|
|
171
|
+
"accepted": 1,
|
|
172
|
+
"route_hits": 5,
|
|
173
|
+
"distinct_pairs": 3,
|
|
174
|
+
"description_quality": 12,
|
|
175
|
+
"recency": 10
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
元阁只接受:slug 在本次注册表白名单内、verdict 为 `promote / hold / demote`、score 为 0-100。
|
|
184
|
+
未知 slug / 非法 verdict / 非法 score 一律丢弃并记入 `dropped`。
|
|
185
|
+
|
|
186
|
+
### 6.4 授权与回落
|
|
187
|
+
|
|
188
|
+
| 状态 | 触发 | 元阁行为 |
|
|
189
|
+
| --- | --- | --- |
|
|
190
|
+
| `not_installed` | 无配置 / 无匹配 capability | `decide-memory` 返回 `not_installed`,不写文件 |
|
|
191
|
+
| `active` | provider 返回合法 decisions | 展示建议;`--promote` 可写本地建议文件 |
|
|
192
|
+
| `license_required` | provider 明确返回该 code | 显示需授权;不阻断其他能力 |
|
|
193
|
+
| `timeout` / `invalid_output` / `error` | 超时 / 非法 JSON / 执行失败 | 显示状态;不写文件、不阻断 |
|
|
194
|
+
|
|
195
|
+
provider 内部应使用路线 B 授权门(capability `m1.adjudicate`)。未授权时返回:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{ "ok": false, "code": "license_required", "message": "该能力需要授权后使用" }
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 6.5 建议文件与元忆边界
|
|
202
|
+
|
|
203
|
+
`--promote` 只写 `~/.yottaskills/memory-adjudication.json`,包含 decisions 与 `memory_candidates`。
|
|
204
|
+
`memory_candidates` 默认使用私密 `PREF`,不自动写元忆;用户或 AI 需要再显式调用元忆写入。
|
|
205
|
+
MCP `decide_memory` 始终只读,不写建议文件、不写元忆。
|
package/references/tutorial.md
CHANGED
|
@@ -152,6 +152,27 @@ npx -y @yottameta/yotta-skills --route "检查代码质量,别糊弄"
|
|
|
152
152
|
- `--json` 输出机器可读结果(含新增 / 更新 / 消失),适合脚本与钩子。
|
|
153
153
|
- 如果本机还装了非元阁家族技能,`--route` 会额外列出「其他已装技能候选」:只按 frontmatter description 与需求文本做本地机械匹配,标注来源、得分、命中词项与扫描状态(默认未扫描),不读取全文指令、不自动调用;使用/安装前请先做装前安全扫描。
|
|
154
154
|
|
|
155
|
+
## M1 记忆裁决(decide-memory)
|
|
156
|
+
|
|
157
|
+
当你想判断「哪些已装技能值得长期记住」,可以运行:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npx -y @yottameta/yotta-skills decide-memory --explain
|
|
161
|
+
npx -y @yottameta/yotta-skills decide-memory --promote --json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`decide-memory` 默认只读,输出每个技能的 `promote / hold / demote` 建议、分数与信号明细;
|
|
165
|
+
`--promote` 只写 `~/.yottaskills/memory-adjudication.json` 建议文件,不写元忆、不删除内容。
|
|
166
|
+
评分由用户显式配置的本地 provider(capability `m1.adjudicate`)完成;未配置 / 未授权 / 超时 / 非法输出时只返回状态,不阻断其他能力。
|
|
167
|
+
|
|
168
|
+
使用记录默认关闭,只有显式开启后才记录结构化信号:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npx -y @yottameta/yotta-skills usage status
|
|
172
|
+
npx -y @yottameta/yotta-skills usage enable
|
|
173
|
+
npx -y @yottameta/yotta-skills usage mark --skill yotta-memory --signal used
|
|
174
|
+
```
|
|
175
|
+
|
|
155
176
|
## 运行时 hook 适配(hook)
|
|
156
177
|
|
|
157
178
|
技能 manifest 只声明六个统一事件的要求;元阁负责能力探测、确定性评估、证据留痕和降级标注:
|
|
@@ -7,6 +7,7 @@ stdio MCP server(JSON-RPC 2.0,换行分隔),把元阁技能扫描核心
|
|
|
7
7
|
describe_skill 查看单个技能详情(slug / 版本 / 功能 / 来源)
|
|
8
8
|
reindex 强制重新扫描并更新注册表
|
|
9
9
|
route_request 按需求摘要给出静态编排路由建议
|
|
10
|
+
decide_memory M1 记忆裁决只读建议(不写元忆、不删除)
|
|
10
11
|
|
|
11
12
|
自包含原则:扫描由元阁自带核心(bin/yotta-skills.js --inventory / --reindex)完成,
|
|
12
13
|
不依赖任何元技能;数据只写本机注册表,默认 ~/.yottaskills/registry.json,
|
|
@@ -26,7 +27,7 @@ import subprocess
|
|
|
26
27
|
import sys
|
|
27
28
|
from pathlib import Path
|
|
28
29
|
|
|
29
|
-
VERSION = "0.
|
|
30
|
+
VERSION = "0.22.0"
|
|
30
31
|
TOOL_NAME = "yotta-skills"
|
|
31
32
|
CN_NAME = "元阁"
|
|
32
33
|
MCP_PROTOCOL_MODERN = "2026-07-28"
|
|
@@ -163,11 +164,24 @@ def _tool_route(arguments):
|
|
|
163
164
|
"isError": False}
|
|
164
165
|
|
|
165
166
|
|
|
167
|
+
def _tool_decide_memory(arguments):
|
|
168
|
+
if arguments.get("dry_run") is False:
|
|
169
|
+
return _tool_error("decide_memory 只读:MCP 不写建议文件,也不写元忆")
|
|
170
|
+
stdout = _run_cli(["decide-memory", "--json"])
|
|
171
|
+
try:
|
|
172
|
+
payload = json.loads(stdout)
|
|
173
|
+
except json.JSONDecodeError as e:
|
|
174
|
+
return _tool_error("decide_memory 输出解析失败:%s" % e)
|
|
175
|
+
return {"content": [{"type": "text", "text": json.dumps(payload, ensure_ascii=False, indent=2)}],
|
|
176
|
+
"isError": False}
|
|
177
|
+
|
|
178
|
+
|
|
166
179
|
TOOL_HANDLERS = {
|
|
167
180
|
"list_installed_skills": _tool_list,
|
|
168
181
|
"describe_skill": _tool_describe,
|
|
169
182
|
"reindex": _tool_reindex,
|
|
170
183
|
"route_request": _tool_route,
|
|
184
|
+
"decide_memory": _tool_decide_memory,
|
|
171
185
|
}
|
|
172
186
|
|
|
173
187
|
|
|
@@ -201,6 +215,16 @@ def mcp_tools():
|
|
|
201
215
|
{"request": {"type": "string", "description": "用户需求摘要"}},
|
|
202
216
|
["request"],
|
|
203
217
|
),
|
|
218
|
+
_tool_spec(
|
|
219
|
+
"decide_memory",
|
|
220
|
+
"M1 记忆裁决只读建议:返回技能评分、promote / hold / demote 建议与信号明细。"
|
|
221
|
+
"只读,不写元忆、不删除技能、不写建议文件;需要写建议文件时使用 CLI --promote。数据不出本机。",
|
|
222
|
+
{
|
|
223
|
+
"dry_run": {"type": "boolean", "description": "固定为 true;MCP 只读"},
|
|
224
|
+
"explain": {"type": "boolean", "description": "是否要求解释信号明细(JSON 始终含 signals)"},
|
|
225
|
+
},
|
|
226
|
+
[],
|
|
227
|
+
),
|
|
204
228
|
]
|
|
205
229
|
|
|
206
230
|
|
|
@@ -261,7 +285,7 @@ def handle_message(msg):
|
|
|
261
285
|
"result": _modern_ok({
|
|
262
286
|
"supportedVersions": [MCP_PROTOCOL_MODERN],
|
|
263
287
|
"capabilities": {"tools": {}},
|
|
264
|
-
"instructions": "元阁 MCP(基于 MCP 最新协议 2026-07-28,向后兼容 2025-11-25
|
|
288
|
+
"instructions": "元阁 MCP(基于 MCP 最新协议 2026-07-28,向后兼容 2025-11-25 及更早握手):本机技能盘点、静态编排路由与 M1 记忆裁决只读建议 list_installed_skills/describe_skill/reindex/route_request/decide_memory;数据不出本机。",
|
|
265
289
|
}, (3600000, "public")),
|
|
266
290
|
}
|
|
267
291
|
if method == "tools/list":
|