Parcourir la source

skill-core-guide v1.0.0: Fmode Harness 平台母技能标准指南

liuyuyang il y a 2 jours
commit
95e6481573

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+node_modules/
+*.log
+.sts-probe.json
+.DS_Store

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Fmode (未来飞马) · Yuyang001
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 252 - 0
README.md

@@ -0,0 +1,252 @@
+# skill-core-guide · Fmode Harness 平台母技能标准指南
+
+> 一份**可独立阅读**的 Fmode 技能开发规范。读它不需要先读任何别的文档。
+> 它是 Fmode 技能生态的「宪法」——定义平台真值、包结构、分发渠道、质检标准。
+
+[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
+[![npm](https://img.shields.io/badge/npm-skill--core--guide-blue.svg)](https://www.npmjs.com/package/skill-core-guide)
+[![ESM](https://img.shields.io/badge/module-ESM--only-orange.svg)](#四端可用性)
+
+---
+
+## 这是什么
+
+`skill-core-guide` 是 Fmode Harness 平台的**母技能**。它同时是:
+
+1. **一份规范文档** —— [`SKILL.md`](SKILL.md) 是完整的平台开发标准,可独立阅读
+2. **一套可执行工具** —— CLI + SDK 帮你脚手架、质检、发布新技能
+3. **一个真值源** —— 平台端点实测状态表,代码可 import,避免硬编码与「文档说能跑」的假象
+
+**解决的问题**:Fmode 技能生态过去没有统一标准 —— 端点状态靠口口相传、
+包结构各写各的、发布流程记在脑子里、质检靠人工、凭据配置踩坑反复。
+本技能把这一切固化成**可执行、可验证、可复用**的规范。
+
+---
+
+## 三分钟上手
+
+```bash
+# 创建一个新技能(脚手架)
+npx --yes skill-core-guide@latest init my-thing --name skill-my-thing
+
+# 检查你的技能(六项自动质检)
+npx --yes skill-core-guide@latest check .
+
+# 生成四渠道发布计划
+npx --yes skill-core-guide@latest publish .
+
+# 查看平台端点真值表(哪些真的能调)
+npx --yes skill-core-guide@latest endpoints
+
+# 检查/初始化 Fmode 凭据
+npx --yes skill-core-guide@latest bootstrap
+```
+
+---
+
+## 核心内容
+
+| 章节 | 内容 | 位置 |
+|------|------|------|
+| **平台基础设施规范** | 基址、`.fmode/` 机制、**端点真值表(实测状态)** | [SKILL.md §1](SKILL.md#一fmode-harness-平台基础设施规范) |
+| **技能分类体系** | 系统层 / 服务层 / 应用层 + 命名规则 | [SKILL.md §2](SKILL.md#二技能分类体系) | [`inventory.md`](inventory.md) |
+| **ESM-first 打包标准** | 包结构、package.json 约束、**四端等价性** | [SKILL.md §3](SKILL.md#三esm-first-多端可用打包标准) |
+| **多渠道分发** | Gogs / GitHub / npm / skillhub.cn | [SKILL.md §4](SKILL.md#四多渠道分发机制) |
+| **自动质检与看板** | 六项检查、`skip≠pass` 语义、看板 manifest | [SKILL.md §5](SKILL.md#五自动质检与看板机制) |
+| **一键凭证供给** | 5 级凭据链、诚实自举 | [SKILL.md §6](SKILL.md#六一键凭证供给机制) |
+| **新技能开发 SOP** | 11 步完整流程 + 开发纪律 | [SKILL.md §7](SKILL.md#七新技能开发-sop) |
+| **事故复盘** | 规范里每条 ⚠️ 背后的真实事故 | [SKILL.md §8](SKILL.md#八事故复盘本规范为什么这么写) |
+
+---
+
+## 六项自动质检
+
+```bash
+skill-core check .
+```
+
+| # | 检查项 | 验证方式 |
+|---|--------|----------|
+| 1 | 功能完整性 | 运行 `scripts.test` / `test/*.mjs` |
+| 2 | Fmode API 联通 | 探测 `api.fmode.cn` 与 `server.fmode.cn` |
+| 3 | 基础 SOP 跑通 | `skillhub publish --dry-run` + frontmatter 校验 |
+| 4 | 看板后台就绪 | `skill-package-manifest.json` 存在且合法 |
+| 5 | Loop 迭代能力 | `npm view`(轻量)/ `npx --yes`(`--deep`) |
+| 6 | 多端可用性 | 实测 ESM `import` + CLI `--help` |
+
+**结果语义**:`pass`(有证据通过)/ `fail`(明确失败,exit 1)/ `skip`(无法验证,**不算通过**,exit 3)。
+
+> 🎯 `skip ≠ pass` 是本平台最贵的一课:端点 404 却被当成成功,
+> 「能跑通」只是历史遗留配置的假象。详见 [SKILL.md §8.1](SKILL.md#81-伪自举事故--skip--pass-的由来)。
+
+---
+
+## 平台端点真值表(实测 2026-09-22)
+
+| 端点 | 状态 | 用途 |
+|------|------|------|
+| `api.fmode.cn/v1/chat/completions` | ✅ live | LLM 调用(统一出口) |
+| `api.fmode.cn/v1/images/generations` | ✅ live | 图像生成 |
+| `server.fmode.cn/api/listen/transcribe` | ✅ live | 录音转写 |
+| `server.fmode.cn/api/fmode/voc-skill/install-prompt` | ✅ live | 凭据自举(唯一通道) |
+| `server.fmode.cn/api/apig/deploy/huaweicloud` | ✅ live | 项目隔离 OBS STS |
+| `server.fmode.cn/api/storage/upload` | 🕓 planned | 未上线,走 obsutil 直传 |
+| `server.fmode.cn/api/storage/credentials` | ✗ deprecated | 从未上线(伪自举事故源) |
+| `server.fmode.cn/api/image/generate` | 🕓 planned | 未上线,用 `/v1/images/generations` |
+| `server.fmode.cn/api/vision/analyze` | 🕓 planned | 未上线,用多模态 chat |
+| `server.fmode.cn/api/fmode/verifycode` | 🕓 planned | 未上线,用 sessionToken 路径 |
+
+> ⚠️ **`planned` / `deprecated` 端点调用前必须探测并回落**,禁止当作已上线。
+> 完整说明:`npx --yes skill-core-guide@latest endpoints`
+
+---
+
+## 四端可用性
+
+| 端 | 入口 | 用法 | 状态 |
+|----|------|------|------|
+| **CLI** | `bin/skill-core.mjs` | `npx --yes skill-core-guide@latest <cmd>` | ✅ |
+| **SDK** | `lib/index.mjs` | `import { ... } from 'skill-core-guide'` | ✅ |
+| **Browser** | `browser/index.mjs` | `<script type="module">`(无 Node 依赖) | ✅ |
+| **Server** | — | `require('skill-core-guide')` | ❌ ESM only |
+
+```javascript
+// SDK
+import { PLATFORM, ENDPOINTS, runChecks, bootstrap, INVENTORY } from 'skill-core-guide';
+
+// 浏览器
+import { PLATFORM, checkApiConnectivity } from 'skill-core-guide/browser';
+```
+
+---
+
+## 各工具安装
+
+### Claude Code
+```bash
+npx --yes skill-core-guide@latest workspace
+```
+
+### 任意 Agent(读 README 自行安装)
+```bash
+git clone https://github.com/fmodecn/skill-core-guide.git
+cp -r skill-core-guide/skills/skill-core-guide <你的工具技能目录>/skill-core-guide
+```
+
+### Codex / Gemini CLI
+把 `skills/skill-core-guide/SKILL.md` 的内容并入 `AGENTS.md`(Codex)
+或 `~/.gemini/commands/`(Gemini CLI)。
+
+### npm / skillhub
+```bash
+npm install skill-core-guide
+skillhub install fmode-skill-core-guide
+```
+
+---
+
+## 技能清单
+
+完整清单(17 个技能,含标签、版本、渠道、状态)见 **[`inventory.md`](inventory.md)**。
+
+```bash
+npx --yes skill-core-guide@latest inventory
+```
+
+| 分层 | 技能 |
+|------|------|
+| **系统层**(7) | `skill-heterarchy` · `skill-multi-branch` · `skill-bypass-permission` · `skill-task-progress` · `plugin-wecom-fix` · `skill-agent-clone` · `skill-core-guide` |
+| **服务层**(6) | `skill-storage` · `skill-image` · `skill-vision` · `skill-listen` · `fmode-ffmpeg` · `fmode-qiwei` |
+| **应用层**(3) | `skill-study-report` · `skill-present` · `fmode-product-lab` |
+
+---
+
+## 凭据(零密钥入库)
+
+Fmode 标准 5 级凭据链,命中即用,全失败显式报错(**绝不伪装成功**):
+
+```
+第0级  FMODE_SESSION_TOKEN / ~/.fmode/config.json 的 sessionToken → 自举换 API token
+第1级  环境变量 FMODE_API_TOKEN
+第2级  ~/.fmode/config.json → fmodeApiToken
+第3级  <cwd>/.fmode/config.json → fmodeApiToken
+第4级  ~/.claude/settings.json → env.ANTHROPIC_AUTH_TOKEN
+```
+
+检查:`npx --yes skill-core-guide@latest bootstrap`
+
+> ⚠️ 任务书描述的「手机号+验证码」开户路径依赖 `/api/fmode/verifycode`,
+> 该端点**当前实测 404(未上线)**。本技能不伪造短信流程,
+> 真实可用路径是 sessionToken 自举。详见 [SKILL.md §6](SKILL.md#六一键凭证供给机制)。
+
+---
+
+## CLI 命令
+
+```
+skill-core init [dir]        从脚手架创建新技能
+skill-core check [dir]       运行六项自动质检
+skill-core verify [dir]      静态校验(不执行代码)
+skill-core publish [dir]     生成四渠道发布计划
+skill-core inventory         输出技能清单
+skill-core bootstrap         一键凭证供给
+skill-core spec              输出平台规范摘要
+skill-core endpoints         输出端点真值表
+```
+
+---
+
+## 仓库结构
+
+```
+skill-core-guide/
+├── SKILL.md                      # ★ 完整规范文档(可独立阅读)
+├── README.md                     # 本文件
+├── inventory.md                  # 完整技能清单
+├── package.json                  # ESM, zero-dependency
+├── lib/
+│   ├── index.mjs                 # ESM 入口(平台常量 + 校验器 + 分发计划)
+│   ├── platform.mjs              # ★ 端点真值表(single source of truth)
+│   ├── check.mjs                 # ★ 六项质检引擎
+│   ├── bootstrap.mjs             # ★ 诚实凭证供给
+│   └── inventory.mjs             # 技能清单真值
+├── bin/
+│   └── skill-core.mjs            # CLI
+├── browser/
+│   └── index.mjs                 # 浏览器 bundle(无 Node 依赖)
+├── templates/
+│   └── skill-starter/            # ★ 新技能脚手架(可跑通)
+├── test/
+│   └── smoke.mjs                 # 冒烟测试
+├── LICENSE                       # MIT
+└── skill-package-manifest.json   # 看板数据源
+```
+
+---
+
+## 开发
+
+```bash
+npm test                              # 冒烟测试
+node bin/skill-core.mjs check . --offline   # 自检(离线)
+node bin/skill-core.mjs verify .            # 静态校验
+```
+
+---
+
+## Changelog
+
+### 1.0.0
+- 首版:平台端点真值表(实测 2026-09-22)、技能三层清单(17 个)、
+  ESM-first 四端标准、四渠道分发、六项自动质检、诚实凭证供给、脚手架模板
+- **端点真值修正**:任务书描述的 `/api/storage/upload`、`/api/image/generate`、
+  `/api/vision/analyze`、`/api/fmode/verifycode` 实测均为 **404(planned)**,
+  已在真值表中标注并给出替代路径
+- **真实端点补充**:`/v1/images/generations`(live)、`/api/apig/deploy/huaweicloud`(live)、
+  `/api/fmode/voc-skill/install-prompt`(live,凭据自举唯一通道)
+
+---
+
+## License
+
+MIT © 2026 Fmode (未来飞马)

+ 926 - 0
SKILL.md

@@ -0,0 +1,926 @@
+---
+slug: fmode-skill-core-guide
+displayName: skill-core-guide
+version: 1.0.0
+summary: Fmode Harness 平台母技能标准指南 —— 平台端点真值表、ESM-first 四端标准、四渠道分发、六项自动质检、一键凭证供给、新技能脚手架。
+license: MIT
+tags: [fmode, harness, skill, standard, spec, esm, scaffold, meta]
+---
+
+# skill-core-guide · Fmode Harness 平台母技能标准指南
+
+> 一份**可独立阅读**的技能开发规范。读它不需要先读任何别的文档。
+> 它是 Fmode 技能生态的「宪法」——定义平台真值、包结构、分发渠道、质检标准。
+
+[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
+
+**技能名**:`skill-core-guide` | **npm**:`skill-core-guide` | **skillhub**:`fmode-skill-core-guide`
+
+---
+
+## 目录
+
+- [零、给 Agent 的 60 秒速览](#零给-agent-的-60-秒速览)
+- [一、Fmode Harness 平台基础设施规范](#一fmode-harness-平台基础设施规范)
+- [二、技能分类体系](#二技能分类体系)
+- [三、ESM-first 多端可用打包标准](#三esm-first-多端可用打包标准)
+- [四、多渠道分发机制](#四多渠道分发机制)
+- [五、自动质检与看板机制](#五自动质检与看板机制)
+- [六、一键凭证供给机制](#六一键凭证供给机制)
+- [七、新技能开发 SOP](#七新技能开发-sop)
+- [八、事故复盘:本规范为什么这么写](#八事故复盘本规范为什么这么写)
+- [九、附录](#九附录)
+
+---
+
+## 零、给 Agent 的 60 秒速览
+
+你要开发一个新技能?按这个顺序做,不要跳步:
+
+```bash
+# 1. 脚手架
+npx --yes skill-core-guide@latest init my-skill --name skill-my-skill
+cd skill-my-skill
+
+# 2. 写三处:技能契约 / SDK 接口 / CLI 入口
+#    skills/skill-my-skill/SKILL.md   ← 何时触发、怎么用
+#    lib/index.mjs                    ← export 公共接口
+#    bin/my-skill.mjs                 ← CLI 命令
+
+# 3. 跑通
+npm test
+
+# 4. 六项质检(必须全绿或显式解释每个 skip)
+npx --yes skill-core-guide@latest check .
+
+# 5. 四渠道发布
+npx --yes skill-core-guide@latest publish . --apply
+```
+
+**三条铁律**(违反其中任何一条,技能不算交付):
+
+1. **零密钥入库** —— 凭据只从环境变量/用户目录解析,仓库里永远不出现真实密钥。
+2. **不伪造成功** —— 拿不到结果就显式报错并给出修复指引,绝不假装跑通。
+3. **端点先探测再调用** —— 标注 `planned` 的端点调用前必须探测,404 时显式回落。
+
+---
+
+## 一、Fmode Harness 平台基础设施规范
+
+### 1.1 平台基址
+
+| 用途 | 基址 | 鉴权方式 |
+|------|------|----------|
+| **API 网关**(LLM / 图像) | `https://api.fmode.cn` | `Authorization: Bearer sk-...` |
+| **业务网关**(转写 / 凭据自举 / deploy STS) | `https://server.fmode.cn` | `x-parse-session-token: r:...` 或 Bearer |
+| **CDN**(OBS → fmode.cn 回源) | `https://fmode.cn` | 公开读 |
+| **OBS 直链**(CDN 未生效时的降级通道) | `https://fmode-s3.obs.cn-north-4.myhuaweicloud.com` | 公开读 / AK-SK 写 |
+| **主 Gogs**(内网日常迭代) | `https://git.fmode.cn/fmode/` | URL 携带凭据 |
+| **GitHub 镜像**(公开发布) | `https://github.com/fmodecn/` | SSH key / PAT |
+| **npm** | `https://registry.npmjs.org` | `~/.npmrc` token |
+| **skillhub.cn** | `https://api.skillhub.cn` | `sk-ent-...`(团队 fmode / `org-m8z913un`) |
+
+### 1.2 `.fmode/` 目录机制
+
+```
+~/.fmode/
+├── config.json          # 全局配置(600 权限)
+│                        #   非敏感:profiles / projects / obsBucket / storageProjectId
+│                        #   敏感:sessionToken / fmodeApiToken / githubToken
+├── credentials/         # 敏感凭据文件(700 目录 / 600 文件)
+│   └── academic-identity.txt
+└── projects/            # 项目认知文件映射
+```
+
+**项目级覆盖**:`<cwd>/.fmode/config.json` 优先级高于用户级(用于项目专属配置)。
+
+**环境变量覆盖**:
+- `FMODE_HOME` —— 覆盖 `~/.fmode` 根目录(测试/多身份隔离用)
+- `FMODE_API_TOKEN` —— 直接指定 API token
+- `FMODE_SESSION_TOKEN` —— 指定 sessionToken(触发自举)
+- `FMODE_API_BASE` —— 覆盖业务网关基址
+
+> ⚠️ **BOM 陷阱**:用户手工保存的 `config.json` 常带 UTF-8 BOM(`EF BB BF`),
+> `JSON.parse` 会直接抛错。**解析前必须剥 BOM**:
+> `JSON.parse(raw.replace(/^/, ''))`。这是多个技能踩过的真实坑。
+
+### 1.3 统一 API 接入
+
+所有技能通过 Fmode 网关调用 LLM / 存储 / 图像 / 转写 / 视觉,**不在客户端直连第三方**。
+
+#### 端点真值表(实测于 2026-09-22)
+
+状态语义:
+- **`live`** —— 实测返回 200 或 401(401 = 端点存在、需鉴权),可直接使用
+- **`planned`** —— 实测 404,设计文档存在但服务端未上线;**调用前必须探测并回落**
+- **`deprecated`** —— 曾经被文档描述、实测 404 且已确认不再维护;**不要使用**
+
+| # | 端点 | 方法 | 状态 | 鉴权 | 用途 |
+|---|------|------|------|------|------|
+| 1 | `api.fmode.cn/v1/chat/completions` | POST | ✅ **live** | Bearer `sk-` | LLM 对话补全(OpenAI 兼容)——所有技能的统一模型出口 |
+| 2 | `api.fmode.cn/v1/images/generations` | POST | ✅ **live** | Bearer `sk-` | 图像生成(fmode-image) |
+| 3 | `server.fmode.cn/api/listen/transcribe` | POST | ✅ **live** | Bearer `sk-` | 录音转写(讯飞 LFASR) |
+| 4 | `server.fmode.cn/api/fmode/voc-skill/install-prompt` | POST | ✅ **live** | `x-parse-session-token` | **凭据自举唯一通道**:sessionToken → API token |
+| 5 | `server.fmode.cn/api/apig/deploy/huaweicloud` | POST | ✅ **live** | Bearer sessionToken | 签发项目隔离 OBS STS |
+| 6 | `server.fmode.cn/api/storage/upload` | POST | 🕓 planned | Bearer `sk-` | 对象存储上传(未上线,走 obsutil 直传) |
+| 7 | `server.fmode.cn/api/storage/credentials` | POST | ✗ **deprecated** | Bearer sessionToken | 从未上线(恒 404),见 §8.1 事故复盘 |
+| 8 | `server.fmode.cn/api/image/generate` | POST | 🕓 planned | Bearer `sk-` | 网关侧图像生成(未上线,用 #2 代替) |
+| 9 | `server.fmode.cn/api/vision/analyze` | POST | 🕓 planned | Bearer `sk-` | 网关侧视觉识别(未上线,用 #1 多模态代替) |
+| 10 | `server.fmode.cn/api/fmode/verifycode` | POST | 🕓 planned | 无 | 手机号验证码(未上线,见 §6) |
+
+**统计**:5 live / 4 planned / 1 deprecated。
+
+> 📌 **真值源**:上表由 `lib/platform.mjs` 的 `ENDPOINTS` 常量驱动。
+> 代码里请**引用 `ENDPOINTS.llmChat.url` 而不是硬编码 URL**——
+> 平台迁移时只改一处。可用 `skill-core endpoints` 随时打印最新真值表。
+
+#### 调用示例
+
+```javascript
+// LLM 调用
+const res = await fetch('https://api.fmode.cn/v1/chat/completions', {
+  method: 'POST',
+  headers: {
+    'Content-Type': 'application/json',
+    Authorization: `Bearer ${token}`,
+  },
+  body: JSON.stringify({ model: 'glm-5.3-flash', messages: [...] }),
+});
+
+// 图像生成
+await fetch('https://api.fmode.cn/v1/images/generations', {
+  method: 'POST',
+  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
+  body: JSON.stringify({ model: '...', prompt: '...' }),
+});
+
+// 录音转写
+await fetch('https://server.fmode.cn/api/listen/transcribe', {
+  method: 'POST',
+  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
+  body: JSON.stringify({ url: 'https://.../meeting.mp3', ... }),
+});
+```
+
+#### 视觉识别:宿主优先策略
+
+`skill-vision` 定义的**成本最优**策略,所有视觉类技能应遵循:
+
+```
+1. 探测宿主 Agent 配置的模型是否支持视觉(Claude Code / Codex settings)
+   → 支持则直接用宿主模型读图(零额外成本、零网络往返)
+2. 否则回落 Fmode API 的 /v1/chat/completions(多模态 messages),模型 glm-5.3-flash
+```
+
+#### 存储:4 级诚实凭据链
+
+`skill-storage` 的凭据链是平台存储接入的权威参考:
+
+```
+第1级  环境变量 OBS_AK / OBS_SK(可选 OBS_ENDPOINT / OBS_BUCKET)
+第2级  obsutil config 文件(OBSUTIL_CONFIG 或 ~/.obsutilconfig)
+第3级  sessionToken + storageProjectId
+        → POST /api/apig/deploy/huaweicloud  → 项目隔离 STS
+          (obsPath = obs://nova-cloud/dev/<projectId>/,key 强制限定前缀)
+          STS 仅内存持有,用完即删
+第4级  项目级 ./.fmode/config.json(obsBucket / obsEndpoint / cdnDomain)
+```
+
+**全失败时必须打印初始化向导并退出码 2,绝不伪装成功。**
+
+---
+
+## 二、技能分类体系
+
+技能按**职责边界**分三层。分层决定了它的发布渠道、依赖关系与质检重点。
+
+### 2.1 系统层 / Infrastructure
+
+平台基础设施与 Agent 运行时治理。**不依赖任何业务场景**,是其他技能的地基。
+
+| 技能 | 作用 | 平台 |
+|------|------|------|
+| `skill-heterarchy` | 内异层认知协同范式(单主体内多心智分化) | Gogs/GitHub/npm/skillhub |
+| `skill-multi-branch` | 沟通/执行分层编排(Hermes → CC/Codex) | GitHub |
+| `skill-bypass-permission` | YOLO 模式权限自检(免确认自主执行) | GitHub |
+| `skill-task-progress` | 4 态任务进度跟踪(ack/running/done/failed) | GitHub |
+| `plugin-wecom-fix` | 企微通道自检修复(plugin 形态) | Hermes plugin |
+| `skill-agent-clone` | 数字生命克隆(配置/SOUL/记忆/会话) | GitHub |
+| **`skill-core-guide`** | **平台母技能标准指南(本技能)** | Gogs/GitHub/npm/skillhub |
+
+### 2.2 服务层 / Platform Services
+
+Fmode 基础服务的客户端封装。**一个技能封装一个平台能力**,接口稳定、无业务假设。
+
+| 技能 | 作用 | npm 包 | 平台 |
+|------|------|--------|------|
+| `skill-storage` | OBS 对象存储与公开分享 | — | GitHub |
+| `skill-image` | Fmode API 图像生成(白底 PNG,¥0.3-0.5/张) | `fmode-image@0.2.0` | GitHub/npm |
+| `skill-vision` | 宿主多模态优先 + Fmode API 回落视觉识别 | `fmode-vision@0.1.1` | npm |
+| `skill-listen` | 讯飞 LFASR × Fmode 网关录音转写 | `fmode-listen@0.1.2` | npm |
+| `fmode-ffmpeg` | FFmpeg 音视频处理封装 | `fmode-ffmpeg@0.1.1` | npm |
+| `fmode-qiwei` | 企微网关 SDK | `fmode-qiwei@0.5.2` | npm |
+
+### 2.3 应用层 / Business Applications
+
+面向具体业务场景的端到端技能。**可以依赖服务层**,但不应被服务层依赖。
+
+| 技能 | 作用 | 平台 |
+|------|------|------|
+| `skill-study-report` | 学习复盘报告(多 Agent 采集 + PPT 级 HTML) | GitHub |
+| `skill-present` | 课程课件/报告 HTML 演讲系统(含 43 条 Claude 规则) | Gogs |
+| `fmode-product-lab` | 新品研发(VOC + KANO + 市场 + 定位分析) | npm |
+
+> 📊 **完整清单(含标签、版本、渠道、状态)见 [`inventory.md`](inventory.md)**,
+> 机器可读真值在 `lib/inventory.mjs`。用 `skill-core inventory` 随时查看。
+
+### 2.4 命名规则
+
+| 形态 | 规则 | 示例 |
+|------|------|------|
+| 仓库名 / Hermes 技能名 | `skill-<kebab-case>` | `skill-my-thing` |
+| npm 包名(部分历史技能) | `fmode-<kebab-case>` | `fmode-image` |
+| skillhub slug | `fmode-skill-<kebab-case>` | `fmode-skill-my-thing` |
+| CLI 命令名 | 去前缀的 kebab-case | `my-thing` |
+
+正则:`/^(skill|fmode)-[a-z0-9]+(-[a-z0-9]+)*$/`
+
+---
+
+## 三、ESM-first 多端可用打包标准
+
+### 3.1 包结构模板
+
+```
+<skill-name>/
+├── package.json                  # type: module; exports: "." → lib/index.mjs
+├── lib/
+│   ├── index.mjs                 # ESM 入口,export 所有公共接口
+│   ├── platform.mjs              # (可选)平台常量与端点真值表
+│   ├── check.mjs                 # (可选)质检引擎
+│   └── bootstrap.mjs             # (可选)凭证供给
+├── bin/
+│   └── <name>.mjs                # CLI 入口(#!/usr/bin/env node)
+├── browser/
+│   └── index.mjs                 # 浏览器 bundle(无 Node 依赖)
+├── skills/
+│   └── <skill-name>/
+│       └── SKILL.md              # 技能定义文档(Agent 读这个)
+├── templates/                    # (可选)脚手架模板
+├── test/
+│   └── smoke.mjs                 # 冒烟测试
+├── README.md                     # GitHub/Gogs 首页
+├── LICENSE                       # MIT
+└── skill-package-manifest.json   # 元数据清单(看板数据源)
+```
+
+### 3.2 package.json 关键字段
+
+```json
+{
+  "name": "skill-my-thing",
+  "version": "1.0.0",
+  "description": "一句话描述",
+  "type": "module",
+  "main": "./lib/index.mjs",
+  "exports": {
+    ".": {
+      "import": "./lib/index.mjs",
+      "default": "./lib/index.mjs"
+    }
+  },
+  "bin": {
+    "my-thing": "./bin/my-thing.mjs"
+  },
+  "files": [
+    "lib/",
+    "bin/",
+    "browser/",
+    "skills/",
+    "README.md",
+    "LICENSE",
+    "skill-package-manifest.json"
+  ],
+  "engines": { "node": ">=18" },
+  "scripts": { "test": "node test/smoke.mjs" },
+  "license": "MIT",
+  "dependencies": {}
+}
+```
+
+**硬性约束**(`validatePackageJson()` 会逐条检查):
+
+| 字段 | 要求 | 原因 |
+|------|------|------|
+| `type` | 必须 `"module"` | ESM only |
+| `main` | 必须 `"./lib/index.mjs"` | 统一入口 |
+| `exports["."]` | 必须同时有 `import` 与 `default` | 多端解析一致 |
+| `bin` | 对象形式,指向 `.mjs` | CLI 端 |
+| `files` | 白名单必须覆盖 `lib/ bin/ skills/ README.md LICENSE manifest` | 避免发布缺文件 |
+| `license` | 必须(平台统一 MIT) | 合规 |
+| `require` | **禁止出现** | 不提供 CJS 入口 |
+| `dependencies` | 强烈建议为空 | 母技能/服务技能应零依赖 |
+
+> 💡 **零依赖原则**:服务层技能应尽量零依赖。平台已有 `fetch`(Node ≥18 内置)、
+> `AbortSignal.timeout`、`node:test`,绝大多数需求不需要第三方包。
+> 零依赖 = 安装快 + 供应链风险低 + 不会因上游破坏性升级而挂掉。
+
+### 3.3 四端等价性原则
+
+| 端 | 入口 | 用法 | 状态 |
+|----|------|------|------|
+| **CLI** | `bin/<name>.mjs` | `npx --yes <skill>@latest <command>` | ✅ 必须可用 |
+| **SDK** | `lib/index.mjs` | `import { ... } from '<skill>'` | ✅ 必须可用 |
+| **Browser** | `browser/index.mjs` | `<script type="module" src="...">` | ✅ 按需提供 |
+| **Server** | — | `require('<skill>')` | ❌ **不可用(ESM only,团队共识)** |
+
+#### Browser 端的硬约束
+
+`browser/index.mjs` **禁止 import 任何 `node:` 内置模块**。只能使用 Web 标准 API:
+
+| 可用 | 不可用 |
+|------|--------|
+| `fetch` / `Request` / `Response` | `node:fs` / `node:path` / `node:os` |
+| `URL` / `URLSearchParams` | `node:child_process` |
+| `crypto.subtle` | `node:crypto` |
+| `TextEncoder` / `TextDecoder` | `node:buffer` |
+| `AbortSignal.timeout` | `node:process` |
+
+**为什么**:浏览器 bundle 要能被 `<script type="module">` 直接加载,或在 Vite/webpack
+中消费。引入 `node:` 会导致构建失败或运行时报错。
+
+**怎么测**:`checkMultiRuntime` 会自动扫描 `browser/index.mjs` 的 `node:` import 并报告。
+
+#### Server 端为何不支持 CJS
+
+团队共识:**ESM only**。理由:
+- Node 22+ 已原生支持 ESM 与顶层 await,CJS 无技术必要性
+- 双入口(CJS + ESM)会导致「双包危害」(dual package hazard):同一模块被加载两份,
+  单例状态分裂,`instanceof` 失效
+- 维护成本翻倍,收益为零
+
+需要 CJS 场景请用动态 `import()`:
+```javascript
+const { greet } = await import('skill-my-thing');
+```
+
+### 3.4 ESM 编码纪律
+
+```javascript
+// ✅ 正确:顶层 import
+import fs from 'node:fs';
+import path from 'node:path';
+
+// ❌ 错误:ESM 里没有 require
+const fs = require('node:fs');   // ReferenceError
+
+// ❌ 错误:ESM 里没有 __dirname / __filename
+console.log(__dirname);          // ReferenceError
+
+// ✅ 正确:用 import.meta.url 推导
+import { fileURLToPath } from 'node:url';
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+// ✅ 正确:JSON 导入用 readFile + parse(不要依赖 import assertions)
+const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));
+```
+
+> ⚠️ **常见坑**:`export { x } from './y.mjs'` 只转发、**不产生本地绑定**。
+> 如果本文件内还要用 `x`,必须额外 `import { x } from './y.mjs'`。
+
+### 3.5 SKILL.md 的三种 frontmatter
+
+同一个技能在不同渠道需要不同格式的 frontmatter。
+
+#### ① Hermes 本地技能格式(`skills/<name>/SKILL.md`)
+
+```yaml
+---
+name: skill-xxx
+description: "简短描述(写清触发场景)"
+version: 1.0.0
+author: Yuyang001 (FmodeAgent)
+license: MIT
+tags: [tag1, tag2]
+---
+```
+
+必需:`name` / `description` / `version`
+
+#### ② skillhub.cn 格式(仓库根 `SKILL.md`)
+
+```yaml
+---
+slug: fmode-skill-xxx
+displayName: skill-xxx
+version: 1.0.0
+summary: 简短描述
+license: MIT
+tags: [tag1, tag2]
+---
+```
+
+必需:`slug` / `displayName` / `version` / `summary` / `license`
+
+#### ③ GitHub/Gogs README 格式(`README.md`,无 frontmatter)
+
+```markdown
+# skill-xxx · 中文标题
+
+> 一句话定位
+
+[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
+
+## 这是什么
+## 安装
+## 用法
+## 凭据
+## License
+```
+
+> 📌 **注意**:YAML frontmatter **必须从文件第一行开始**。在 `---` 之前写任何注释或
+> 空行都会导致解析失败。本仓库的 `templates/skill-starter/skills/.../SKILL.md`
+> 就把说明写在了 frontmatter **之后**的 HTML 注释里。
+
+---
+
+## 四、多渠道分发机制
+
+### 4.1 四渠道定位
+
+| 渠道 | 定位 | 同步方向 | 凭据位置 |
+|------|------|----------|----------|
+| **Gogs**(`git.fmode.cn/fmode/`) | **主仓**,内网日常迭代 | 源头 | URL 携带(内网) |
+| **GitHub**(`github.com/fmodecn/`) | **公开镜像**,对外发布 | Gogs → GitHub | `~/.fmode/config.json` 的 `githubToken` |
+| **npm**(`registry.npmjs.org`) | SDK/CLI 分发 | 从仓库发布 | `~/.npmrc`(账号 `fmode001`) |
+| **skillhub.cn** | 社区分发(团队 `fmode` / `org-m8z913un`) | 从目录发布 | `sk-ent-...` |
+
+**工作流**:日常迭代在 Gogs 进行 → 版本稳定后推 GitHub → 同时发 npm 与 skillhub。
+
+### 4.2 发布清单
+
+```bash
+# ---------- Gogs(主仓)----------
+git remote add origin https://fmode:<PASSWORD>@git.fmode.cn/fmode/<skill>.git
+git push origin master
+
+# ---------- GitHub(镜像)----------
+git remote add github git@github.com:fmodecn/<skill>.git
+GIT_SSH_COMMAND="ssh -i ~/.ssh/id_ed25519_fmodecn -o StrictHostKeyChecking=no" \
+  git push github master
+
+# ---------- npm ----------
+npm publish --access public
+
+# ---------- skillhub.cn ----------
+curl -fsSL https://skillhub.cn/install/install.sh | bash -s -- --cli-only
+skillhub login --key <API_KEY> --host https://api.skillhub.cn
+skillhub publish <dir> --changelog "<msg>"
+```
+
+> 💡 一条命令生成完整计划:`skill-core publish .`(加 `--apply` 实际执行)。
+
+### 4.3 建仓注意事项(实测)
+
+| 渠道 | 建仓方式 | 实测结论 |
+|------|----------|----------|
+| Gogs | **Web UI 手动建**,或已登录会话 | ⚠️ `git.fmode.cn/api/v1/*` 用 basic auth 返回 **401**,匿名 API 不可用;但 `git push` 到不存在的仓库**不会自动建仓**。建仓需走 Web UI。 |
+| GitHub | REST API(PAT)或 Web UI | ✅ `GET /user` 用 `githubToken` 返回 200(实测账号 `ryanemax`)。建仓:`POST /orgs/fmodecn/repos` |
+| npm | 首次 `npm publish` 自动创建 | ✅ `npm whoami` = `fmode001` |
+| skillhub | 首次 `skillhub publish` 自动创建 | ✅ 需先 `skillhub login` |
+
+**GitHub 建仓示例**:
+
+```bash
+GH_TOKEN=$(python3 -c "import json;print(json.load(open('$HOME/.fmode/config.json'))['githubToken'])")
+curl -X POST -H "Authorization: Bearer $GH_TOKEN" \
+     -H "Accept: application/vnd.github+json" \
+     https://api.github.com/orgs/fmodecn/repos \
+     -d '{"name":"skill-my-thing","description":"...","private":false,"license_template":"mit"}'
+```
+
+### 4.4 发布前检查清单
+
+- [ ] `npm test` 通过
+- [ ] `skill-core check .` 六项无失败
+- [ ] 仓库内**无任何真实密钥**(`git grep -iE "sk-[a-z0-9]{20}|sk-ent-|ghp_|github_pat_"`)
+- [ ] `package.json` 的 `files` 白名单覆盖 `skills/`
+- [ ] 根 `SKILL.md` 的 frontmatter 是 skillhub 格式
+- [ ] `README.md` 有各工具安装说明
+- [ ] `LICENSE` 存在(MIT)
+- [ ] `skill-package-manifest.json` 存在且 `version` 与 `package.json` 一致
+
+---
+
+## 五、自动质检与看板机制
+
+### 5.1 六项检查
+
+技能开发完成后**必须**跑六项质检。用 `skill-core check .` 一键执行。
+
+| # | 检查项 | id | 验证方式 | 失败即 |
+|---|--------|----|----------|--------|
+| 1 | **功能完整性** | `functional` | 运行 `scripts.test` 或 `test/*.mjs` | 技能跑不起来 |
+| 2 | **Fmode API 联通** | `apiConnectivity` | 探测 `api.fmode.cn` 与 `server.fmode.cn`(401=端点存在) | 上线后必然挂 |
+| 3 | **基础 SOP 跑通** | `sop` | `skillhub publish --dry-run` + frontmatter 校验 | 发布失败 |
+| 4 | **看板后台就绪** | `dashboard` | `skill-package-manifest.json` 存在且结构合法 | 看板索引不到 |
+| 5 | **Loop 迭代能力** | `loop` | `npm view <name>`(轻量)/ `npx --yes <name>@latest`(`--deep`) | 用户装不上 |
+| 6 | **多端可用性** | `multiRuntime` | 实测 ESM `import` + CLI `--help`,扫描 browser 的 `node:` 依赖 | 某端不可用 |
+
+### 5.2 结果语义(关键设计)
+
+质检结果有三种状态,**语义严格区分**:
+
+| 状态 | 含义 | 对结论的影响 |
+|------|------|--------------|
+| ✅ `pass` | 有可复核证据证明通过 | — |
+| ❌ `fail` | 明确失败 | **必须修复**,exit code 1 |
+| ⏭️ `skip` | 无法验证(离线 / 工具缺失 / 端点未上线 / 嵌套调用) | **不算通过**,exit code 3(除非 `--allow-skip`) |
+
+> 🎯 **为什么 skip 不等于 pass**:平台最贵的一课是「伪自举」——
+> 端点 404 但代码当成成功,结果「能跑通」只是历史遗留配置的假象。
+> 所以**拿不到证据就是 skip**,且 skip 会让 CI 失败,逼迫开发者显式处理。
+
+每项检查都会输出 **evidence**(可复核的证据:命令、退出码、HTTP 码、输出片段)。
+没有 evidence 的 pass 是不允许的。
+
+### 5.3 常用命令
+
+```bash
+skill-core check .                              # 全量六项
+skill-core check . --offline                    # 跳过网络项(离线开发)
+skill-core check . --only functional,multiRuntime   # 只跑指定项
+skill-core check . --deep                       # 真正 npx 拉取验证 Loop
+skill-core check . --json                       # 机器可读(CI 集成)
+skill-core check . --allow-skip                 # 有 skip 也返回 0(慎用)
+```
+
+### 5.4 看板机制
+
+**数据源**:`skill-package-manifest.json`(每仓库根目录一个)。
+
+```json
+{
+  "name": "skill-my-thing",
+  "version": "1.0.0",
+  "description": "一句话描述",
+  "skills": [{ "name": "skill-my-thing", "path": "skills/skill-my-thing/SKILL.md" }],
+  "install": "npx --yes skill-my-thing@latest workspace",
+  "tier": "service",
+  "platforms": ["gogs", "github", "npm", "skillhub"]
+}
+```
+
+看板通过扫描各仓库的 manifest 汇总技能状态。**manifest 缺失 = 看板上看不到这个技能**,
+所以它是「看板后台就绪」检查项的核心。
+
+配套的运行时上报能力见 `skill-task-progress`(四态上报 `ack→running→done/failed` +
+30s 心跳 + 交付物入库)。
+
+### 5.5 Loop 迭代能力
+
+**定义**:技能必须能通过 `npx --yes <skill>@latest` 被拉到并运行,形成
+「开发 → 发布 → 用户拉取 → 反馈 → 再开发」的闭环。
+
+**为什么重要**:如果用户装不上,再好的功能也是零。
+
+**验证**:
+- 轻量(默认):`npm view <name> version` —— 确认包在 registry 上可解析
+- 深度(`--deep`):真正执行 `npx --yes <name>@latest --help`,确认 bin 入口可运行
+
+**常见失败原因**:bin 入口缺 shebang、bin 文件未包含在 `files` 白名单、
+`bin` 字段指向了不存在的文件。
+
+---
+
+## 六、一键凭证供给机制
+
+### 6.1 诚实声明(先读这段)
+
+任务书描述的「手机号 + 验证码 → 自动创建 `~/.fmode/`」路径依赖端点:
+
+```
+POST https://server.fmode.cn/api/fmode/verifycode
+```
+
+**该端点实测返回 404,服务端尚未上线**(真值表状态 `planned`)。
+因此本技能的 `lib/bootstrap.mjs` **不会伪造短信流程假装成功** ——
+它会探测端点,未上线时明确返回 `{ ok: false, planned: true }` 并给出可执行的替代路径。
+
+### 6.2 当前真实可用的自举路径
+
+**sessionToken → API token 自举**(生产实测,与 `skill-listen` / `skill-vision` 同源):
+
+```
+用户浏览器登录 FMODE Studio
+    ↓  取得 sessionToken(形如 r:xxxxx)
+写入 FMODE_SESSION_TOKEN 环境变量 或 ~/.fmode/config.json 的 "sessionToken"
+    ↓
+POST https://server.fmode.cn/api/fmode/voc-skill/install-prompt
+     header: x-parse-session-token: <sessionToken>
+     body:   { channel: "claude-code", scope: "user" }
+    ↓
+从 body.data.prompt 文本中提取  /sk-(?!ant-)[A-Za-z0-9_-]{8,}/
+    ↓
+fmode API token(sk- 开头)—— 仅内存持有,不落盘、不进日志
+```
+
+> ⚠️ 服务端**唯一**以 session 鉴权并返回 token 本体的端点是 `voc-skill/install-prompt`。
+> token 内嵌在返回的 prompt 文本中,必须用正则提取。
+
+### 6.3 标准 5 级凭据解析链
+
+**所有技能必须实现这条链**(命中即用,逐级回落):
+
+| 级 | 来源 | 说明 |
+|----|------|------|
+| **0** | `FMODE_SESSION_TOKEN` / `~/.fmode/config.json` 的 `sessionToken` | 自举换 API token(仅内存持有) |
+| **1** | 环境变量 `FMODE_API_TOKEN` | 最直接的显式配置 |
+| **2** | `~/.fmode/config.json` → `fmodeApiToken` / `newapiToken` | FMODE Studio 保存写这里 |
+| **3** | `<cwd>/.fmode/config.json` → `fmodeApiToken` / `newapiToken` | 项目级覆盖 |
+| **4** | `~/.claude/settings.json`(含 `.local` / 项目级)→ `env.ANTHROPIC_AUTH_TOKEN` | **fmode 的 newapi SK 默认就是 Claude Code 的 token** |
+
+**第 4 级为什么重要**:用户按 Claude Code 的正常方式配好了 SK,技能却「看不见」,
+判缺 token → 掉进付费弹窗死循环。这是真实事故,所以必须读这个文件。
+
+**token 校验规则**:
+- 必须以 `sk-` 开头
+- **必须排除** `sk-ant-`(真正的 Anthropic 官方 key)
+- 若设置了 `ANTHROPIC_BASE_URL`,必须指向 `fmode`
+
+**全链失败时**:打印初始化向导 + **退出码 2**。绝不伪装成功。
+
+### 6.4 目录供给(幂等)
+
+```javascript
+import { ensureFmodeDir, writeConfig } from 'skill-core-guide';
+
+// 创建 ~/.fmode/{,credentials,projects}(700 权限),已存在则跳过
+ensureFmodeDir();
+
+// 幂等合并写入 config.json(600 权限)
+// ⚠️ 敏感字段(sessionToken / apiKey / githubToken / password / secret)会被拒绝写入
+writeConfig({ storageProjectId: 'proj-xxx', obsBucket: 'my-bucket' });
+```
+
+> 🔒 **安全设计**:`writeConfig()` 内置敏感字段黑名单,**拒绝**写入
+> `sessionToken` / `apiKey` / `apiKeys` / `githubToken` / `password` / `secret`。
+> 这些值必须由用户自己写,避免技能代写导致泄露。
+
+### 6.5 检查凭据状态
+
+```bash
+npx --yes skill-core-guide@latest bootstrap
+```
+
+输出示例:
+
+```
+  Fmode 凭证自举状态
+  ────────────────────────────────────────────────────────────
+  ~/.fmode 目录:/opt/data/home/.fmode
+  ✅ ensure-fmode-dir:目录已存在(幂等)
+  ✅ resolve-credential:第 0 级命中:env:FMODE_SESSION_TOKEN
+  ✅ verify-credential:token 形态校验通过(sk- 前缀,非 sk-ant-)
+  ────────────────────────────────────────────────────────────
+  结果:✅ 凭据可用(sessionToken 自举,sk-abc...wxyz)
+```
+
+### 6.6 端点上线后的启用方式
+
+`/api/fmode/verifycode` 上线后,**只需改一处**:
+
+```javascript
+// lib/platform.mjs
+verifyCode: {
+  ...
+  status: 'planned',   // ← 改成 'live'
+}
+```
+
+`requestVerifyCode()` / `verifyAndProvision()` 会自动启用短信路径,无需改其他代码。
+
+---
+
+## 七、新技能开发 SOP
+
+### 7.1 完整流程
+
+```bash
+# ---------- 1. 从母技能创建脚手架 ----------
+npx --yes skill-core-guide@latest init my-thing --name skill-my-thing
+cd skill-my-thing
+
+# ---------- 2. 初始化 git ----------
+git init
+git config user.email "liu@fmode.cn"
+git config user.name "liuyuyang"
+
+# ---------- 3. 写技能契约 ----------
+# skills/skill-my-thing/SKILL.md
+#   - frontmatter: name / description / version / tags(Hermes 格式)
+#   - 「何时使用」写清触发场景(Agent 靠这个决定要不要调用)
+#   - 「能力边界」写清能做/不能做
+
+# ---------- 4. 实现 SDK ----------
+# lib/index.mjs —— export 所有公共接口
+#   - 零依赖优先
+#   - 平台 URL 从常量取,不硬编码
+#   - 凭据走 5 级链
+
+# ---------- 5. 实现 CLI ----------
+# bin/my-thing.mjs —— #!/usr/bin/env node
+#   - 必须支持 --help / --version
+#   - 必须有 workspace 子命令(npx 约定入口)
+
+# ---------- 6. 补 README / 根 SKILL.md / LICENSE ----------
+# README.md       —— GitHub/Gogs 首页,含各工具安装说明
+# SKILL.md        —— skillhub 格式 frontmatter(发布必需)
+# LICENSE         —— MIT
+
+# ---------- 7. 跑通 ----------
+npm test
+
+# ---------- 8. 六项质检 ----------
+npx --yes skill-core-guide@latest check .
+
+# ---------- 9. 本地链接试用 ----------
+npm link && my-thing --help
+
+# ---------- 10. 建仓(Web UI / REST API)----------
+# Gogs:   https://git.fmode.cn  → 新建 fmode/skill-my-thing
+# GitHub: POST /orgs/fmodecn/repos
+
+# ---------- 11. 四渠道发布 ----------
+git remote add origin https://fmode:<PWD>@git.fmode.cn/fmode/skill-my-thing.git
+git add -A && git commit -m "skill-my-thing v0.1.0: 首版"
+git push origin master
+
+git remote add github git@github.com:fmodecn/skill-my-thing.git
+GIT_SSH_COMMAND="ssh -i ~/.ssh/id_ed25519_fmodecn" git push github master
+
+npm publish --access public
+
+skillhub login --key <API_KEY> --host https://api.skillhub.cn
+skillhub publish . --changelog "首版"
+```
+
+### 7.2 脚手架生成的结构
+
+```
+skill-my-thing/
+├── SKILL.md                       # skillhub 格式(根目录)
+├── README.md                      # 各工具安装说明
+├── LICENSE                        # MIT
+├── package.json                   # __SKILL_NAME__ 占位符已替换
+├── skill-package-manifest.json    # 看板数据源
+├── lib/index.mjs                  # SDK 入口(含凭据链示例)
+├── bin/my-thing.mjs               # CLI(greet/auth/workspace)
+├── skills/skill-my-thing/SKILL.md # 技能契约(Hermes 格式)
+└── test/smoke.mjs                 # 冒烟测试
+```
+
+### 7.3 开发纪律(写给 Agent)
+
+| 纪律 | 说明 |
+|------|------|
+| **先写 SKILL.md 再写代码** | 「何时使用」写不清楚,说明技能定位没想清楚 |
+| **端到端跑通再发布** | `npm test` + `check` 全绿;不允许「应该能跑」 |
+| **不伪造任何结果** | 采集失败就说失败,发布失败就说失败 |
+| **零密钥入库** | 提交前 `git grep` 扫一遍密钥模式 |
+| **平台 URL 引用常量** | 平台迁移时只改一处 |
+| **planned 端点先探测** | 404 时显式回落并告知用户 |
+| **版本语义** | 破坏性变更 → major;新能力 → minor;修复 → patch |
+
+---
+
+## 八、事故复盘:本规范为什么这么写
+
+规范里的每条「⚠️」都对应一次真实事故。理解事故才能理解规范。
+
+### 8.1 「伪自举」事故 —— skip ≠ pass 的由来
+
+**背景**:`skill-storage` 0.2.x 实现了「登录即可上传」:用 sessionToken 调
+`POST /api/storage/credentials` 换 OBS STS。
+
+**问题**:该端点**从未上线**(HEAD/GET 探测恒 404),设计文档里状态是「规划中」。
+
+**为什么没被发现**:部分环境「能跑」,因为那些机器上有**历史遗留的手工
+`~/.obsutilconfig`**,凭据链的第 1/2 级回落生效了。其他机器无此文件即全链死。
+「能跑通」是假象。
+
+**修复(0.3.0)**:
+- 该路径降级为 `--experimental-sts`(探测 200 才启用)
+- 第 3 级改用**真实上线**的 `/api/apig/deploy/huaweicloud`
+- **全链失败时明确打印初始化向导并退出码 2,不再伪装成功**
+
+**沉淀为规范**:
+- 端点真值表区分 `live` / `planned` / `deprecated`(§1.3)
+- 质检的 `skip` 状态**不算通过**,且导致 CI 失败(§5.2)
+- 每项检查必须给出 evidence(§5.2)
+
+### 8.2 「看不见 token」事故 —— 第 4 级凭据的由来
+
+**问题**:用户按 Claude Code 的正常方式配好了 SK(写在 `~/.claude/settings.json` 的
+`env.ANTHROPIC_AUTH_TOKEN`),但技能只读进程环境变量,**从不读这个文件** →
+判缺 token → 掉进旧付费弹窗死循环。
+
+**关键认知**:**fmode 的 newapi SK 默认就是 Claude Code 的 `ANTHROPIC_AUTH_TOKEN`**。
+
+**沉淀为规范**:凭据链第 4 级必须读 Claude Code settings(§6.3),
+且要覆盖 `.local` 与项目级文件。
+
+### 8.3 「BOM 解析失败」事故
+
+**问题**:用户手工保存的 `config.json` 带 UTF-8 BOM(`EF BB BF`),
+`JSON.parse` 直接抛错,技能判为「配置损坏」。
+
+**沉淀为规范**:解析任何用户可能手改的 JSON 前必须剥 BOM(§1.2)。
+
+### 8.4 「假成功」事故 —— ESM 空日志
+
+**问题**:spawn 子进程执行命令,日志为空、exit 0,被当成成功。
+实际是 `command not found` 被 shell 吞掉,或用了相对路径而 cwd 不对。
+
+**沉淀为规范**:用绝对路径调用可执行文件;判断成功要看**产物**(文件存在、
+HTTP 200、内容非空),不看退出码 alone(§7.3)。
+
+### 8.5 「双包危害」—— 为什么 ESM only
+
+**问题**:同时提供 CJS 与 ESM 入口时,同一模块可能被加载两份,单例状态分裂。
+
+**沉淀为规范**:ESM only,不提供 CJS 入口;CJS 场景用动态 `import()`(§3.3)。
+
+---
+
+## 九、附录
+
+### 9.1 CLI 命令参考
+
+```bash
+skill-core --help                   # 帮助
+skill-core --version                # 版本
+skill-core init [dir] --name <n>    # 从脚手架创建新技能
+skill-core check [dir]              # 六项自动质检
+skill-core verify [dir]             # 静态校验(不执行代码)
+skill-core publish [dir]            # 生成四渠道发布计划
+skill-core inventory                # 技能清单
+skill-core bootstrap                # 凭据自举状态
+skill-core spec                     # 平台规范摘要
+skill-core endpoints                # 端点真值表
+```
+
+### 9.2 SDK API 参考
+
+```javascript
+import {
+  // 平台真值
+  PLATFORM, ENDPOINTS, endpointsByStatus,
+  CREDENTIAL_CHAIN, TOKEN_RULES, TIERS, RUNTIMES, CHANNELS,
+  // 校验器
+  validateName, validatePackageJson, validateManifest,
+  validateFrontmatter, parseFrontmatter,
+  // 质检
+  CHECKS, runChecks, summarize, renderReport,
+  checkFunctional, checkApiConnectivity, checkSop,
+  checkDashboard, checkLoop, checkMultiRuntime,
+  // 凭据
+  bootstrap, resolveApiToken, resolveSessionToken,
+  ensureFmodeDir, writeConfig, describeBootstrapStatus, maskToken,
+  // 清单
+  INVENTORY, byTier, byPlatform, stats,
+  // 分发
+  publishPlan,
+} from 'skill-core-guide';
+```
+
+### 9.3 浏览器端 API
+
+```javascript
+import { PLATFORM, ENDPOINTS, validatePackageJson, checkApiConnectivity } from 'skill-core-guide/browser';
+```
+
+### 9.4 相关技能
+
+| 技能 | 关系 |
+|------|------|
+| `skill-heterarchy` | 认知协同范式(多心智并行开发) |
+| `skill-multi-branch` | 任务派发框架(沟通层 → 执行层) |
+| `skill-task-progress` | 进度与交付物上报(看板运行时数据) |
+| `skill-storage` | 存储接入的权威参考实现 |
+| `skill-listen` / `skill-vision` | 凭据链与自举的生产参考实现 |
+
+### 9.5 变更记录
+
+#### 1.0.0
+- 首版:平台端点真值表(实测 2026-09-22)、技能分层清单、ESM-first 四端标准、
+  四渠道分发、六项自动质检、诚实凭证供给、脚手架模板
+- 端点真值修正:任务书描述的 `/api/storage/upload`、`/api/image/generate`、
+  `/api/vision/analyze`、`/api/fmode/verifycode` 实测均为 404(planned),
+  已在真值表中标注并给出替代路径
+- 真实端点补充:`/v1/images/generations`(live)、`/api/apig/deploy/huaweicloud`(live)、
+  `/api/fmode/voc-skill/install-prompt`(live,凭据自举唯一通道)
+
+## License
+
+MIT © 2026 Fmode (未来飞马)

+ 635 - 0
bin/skill-core.mjs

@@ -0,0 +1,635 @@
+#!/usr/bin/env node
+/**
+ * skill-core — Fmode Harness 平台母技能 CLI
+ * ---------------------------------------------------------------------------
+ * 命令:
+ *   skill-core init [dir]        从 templates/skill-starter 脚手架创建新技能
+ *   skill-core check [dir]       运行六项自动质检
+ *   skill-core publish [dir]     生成四渠道发布计划(--dry-run 默认)
+ *   skill-core verify [dir]      静态校验包结构 / package.json / SKILL.md
+ *   skill-core inventory         输出技能清单(--json 机器可读)
+ *   skill-core bootstrap         一键凭证供给(检查 ~/.fmode/)
+ *   skill-core spec              输出平台规范摘要
+ *   skill-core endpoints         输出端点真值表(含实测状态)
+ *
+ * 全局参数:
+ *   --json          机器可读输出
+ *   --offline       跳过所有网络检查
+ *   --help, -h      帮助
+ *   --version, -v   版本
+ */
+
+import fs from 'node:fs';
+import path from 'node:path';
+import os from 'node:os';
+import { fileURLToPath } from 'node:url';
+
+import {
+  VERSION,
+  PLATFORM,
+  ENDPOINTS,
+  endpointsByStatus,
+  CHECKS,
+  runChecks,
+  renderReport,
+  publishPlan,
+  CHANNELS,
+  RUNTIMES,
+  CREDENTIAL_CHAIN,
+  TIERS,
+  validatePackageJson,
+  validateManifest,
+  validateFrontmatter,
+  validateName,
+  INVENTORY,
+  byTier,
+  stats,
+} from '../lib/index.mjs';
+
+import {
+  bootstrap,
+  describeBootstrapStatus,
+  resolveFmodeDir,
+  resolveConfigPath,
+} from '../lib/bootstrap.mjs';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const PKG_ROOT = path.resolve(__dirname, '..');
+const TEMPLATE_DIR = path.join(PKG_ROOT, 'templates', 'skill-starter');
+
+// ============================================================
+// 参数解析(零依赖)
+// ============================================================
+
+function parseArgs(argv) {
+  const flags = {};
+  const positional = [];
+  for (let i = 0; i < argv.length; i++) {
+    const a = argv[i];
+    if (a === '--') {
+      positional.push(...argv.slice(i + 1));
+      break;
+    }
+    if (a.startsWith('--')) {
+      const eq = a.indexOf('=');
+      if (eq > -1) {
+        flags[a.slice(2, eq)] = a.slice(eq + 1);
+      } else {
+        const key = a.slice(2);
+        const next = argv[i + 1];
+        if (next && !next.startsWith('-')) {
+          flags[key] = next;
+          i++;
+        } else {
+          flags[key] = true;
+        }
+      }
+    } else if (a.startsWith('-') && a.length > 1) {
+      for (const c of a.slice(1)) flags[c] = true;
+    } else {
+      positional.push(a);
+    }
+  }
+  return { flags, positional };
+}
+
+const C = {
+  reset: '\x1b[0m',
+  bold: '\x1b[1m',
+  dim: '\x1b[2m',
+  red: '\x1b[31m',
+  green: '\x1b[32m',
+  yellow: '\x1b[33m',
+  blue: '\x1b[34m',
+  cyan: '\x1b[36m',
+};
+const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
+const c = (code, s) => (useColor ? `${code}${s}${C.reset}` : s);
+
+// ============================================================
+// 帮助
+// ============================================================
+
+function printHelp() {
+  const out = `
+${c(C.bold, 'skill-core')} · Fmode Harness 平台母技能 CLI  v${VERSION}
+
+${c(C.bold, '用法')}
+  skill-core <command> [dir] [options]
+
+${c(C.bold, '命令')}
+  ${c(C.cyan, 'init')} [dir]        从脚手架创建新技能(默认交互式询问技能名)
+  ${c(C.cyan, 'check')} [dir]       运行六项自动质检(默认当前目录)
+  ${c(C.cyan, 'publish')} [dir]     生成四渠道发布计划并预检
+  ${c(C.cyan, 'verify')} [dir]      静态校验包结构 / package.json / SKILL.md / manifest
+  ${c(C.cyan, 'inventory')}         输出已发布技能清单
+  ${c(C.cyan, 'bootstrap')}         一键凭证供给:检查并初始化 ~/.fmode/
+  ${c(C.cyan, 'spec')}              输出平台规范摘要(ESM-first / 四渠道 / 凭据链)
+  ${c(C.cyan, 'endpoints')}         输出端点真值表(含 2026-09-22 实测状态)
+
+${c(C.bold, '选项')}
+  --json            机器可读 JSON 输出
+  --offline         跳过网络检查(对应项标记为 skip)
+  --deep            check/loop:真正执行 npx 拉取验证
+  --only <ids>      仅运行指定检查项,逗号分隔
+  --skip-exec       check/multiRuntime:只做静态校验不执行
+  --name <n>        init:指定技能名(跳过交互)
+  --dry-run         publish:只打印计划不执行(默认行为)
+  --apply           publish:实际执行发布命令
+  -h, --help        显示帮助
+  -v, --version     显示版本
+
+${c(C.bold, '示例')}
+  skill-core init my-skill --name skill-my-skill
+  skill-core check . --only functional,multiRuntime
+  skill-core check . --offline
+  skill-core publish . --apply
+  skill-core bootstrap --json
+
+${c(C.bold, '六项质检')}
+${CHECKS.map((ch, i) => `  ${i + 1}. ${ch.title.padEnd(14)} ${c(C.dim, ch.id)}`).join('\n')}
+`;
+  console.log(out);
+}
+
+// ============================================================
+// init —— 脚手架
+// ============================================================
+
+/** 递归复制模板目录,替换文件名与内容中的占位符 */
+function copyTemplate(srcDir, destDir, replacements) {
+  const written = [];
+  // 占位符替换:skillName → __SKILL_NAME__,cliName → __CLI_NAME__,displayName → __DISPLAY_NAME__
+  // (camelCase → SCREAMING_SNAKE_CASE)
+  const subst = (s) => {
+    let out = s;
+    for (const [k, v] of Object.entries(replacements)) {
+      const token = k.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase();
+      out = out.replace(new RegExp(`__${token}__`, 'g'), v);
+    }
+    return out;
+  };
+
+  const walk = (src, dest) => {
+    if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
+    for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
+      const s = path.join(src, entry.name);
+      const d = path.join(dest, subst(entry.name));
+      if (entry.isDirectory()) {
+        walk(s, d);
+      } else {
+        fs.writeFileSync(d, subst(fs.readFileSync(s, 'utf-8')));
+        written.push(d);
+      }
+    }
+  };
+  walk(srcDir, destDir);
+  return written;
+}
+
+async function cmdInit(flags, positional) {
+  let targetDir = positional[0];
+  let skillName = flags.name;
+
+  if (!skillName) {
+    if (!process.stdin.isTTY) {
+      console.error(c(C.red, '✗ 非交互环境必须用 --name 指定技能名'));
+      console.error('  例:skill-core init my-skill --name skill-my-skill');
+      process.exit(2);
+    }
+    const rl = (await import('node:readline/promises')).createInterface({
+      input: process.stdin,
+      output: process.stdout,
+    });
+    skillName = (await rl.question('技能名(如 skill-my-thing): ')).trim();
+    if (!targetDir) {
+      targetDir = (await rl.question(`目标目录(回车默认 ./${skillName}): `)).trim() || `./${skillName}`;
+    }
+    rl.close();
+  }
+
+  if (!targetDir) targetDir = `./${skillName}`;
+
+  const nv = validateName(skillName);
+  if (!nv.ok) {
+    console.error(c(C.red, `✗ ${nv.reason}`));
+    process.exit(2);
+  }
+
+  if (!fs.existsSync(TEMPLATE_DIR)) {
+    console.error(c(C.red, `✗ 脚手架模板缺失:${TEMPLATE_DIR}`));
+    console.error('  请重新安装:npm install skill-core-guide@latest');
+    process.exit(2);
+  }
+
+  const abs = path.resolve(targetDir);
+  if (fs.existsSync(abs) && fs.readdirSync(abs).length > 0) {
+    console.error(c(C.red, `✗ 目标目录已存在且非空:${abs}`));
+    console.error('  为安全起见不覆盖已有内容,请换一个目录。');
+    process.exit(2);
+  }
+
+  const cliName = skillName.replace(/^skill-/, '');
+  const written = copyTemplate(TEMPLATE_DIR, abs, {
+    skillName,
+    cliName,
+    displayName: flags.display || skillName,
+    summary: flags.summary || `${skillName} —— 由 skill-core-guide 脚手架生成`,
+  });
+
+  const rel = written.map((f) => path.relative(abs, f)).sort();
+
+  if (flags.json) {
+    console.log(JSON.stringify({ ok: true, dir: abs, skillName, files: rel }, null, 2));
+    return 0;
+  }
+
+  console.log(`\n  ${c(C.green, '✅')} 技能脚手架已创建:${c(C.bold, abs)}`);
+  console.log(`  技能名:${skillName}    CLI 名:${cliName}\n`);
+  console.log(`  ${c(C.bold, '生成的文件')}`);
+  for (const f of rel) console.log(`    ${f}`);
+  console.log(`
+  ${c(C.bold, '下一步')}
+    cd ${targetDir}
+    git init && git config user.email "liu@fmode.cn" && git config user.name "liuyuyang"
+    ${c(C.dim, `# 1) 编辑 skills/${skillName}/SKILL.md —— 写清触发场景与用法`)}
+    ${c(C.dim, '# 2) 实现 lib/index.mjs —— export 公共接口')}
+    ${c(C.dim, `# 3) 实现 bin/${cliName}.mjs —— CLI 入口`)}
+    npm test
+    skill-core check .          ${c(C.dim, '# 六项质检')}
+    skill-core publish . --apply
+`);
+  return 0;
+}
+
+// ============================================================
+// check —— 六项质检
+// ============================================================
+
+async function cmdCheck(flags, positional) {
+  const dir = path.resolve(positional[0] || '.');
+  const only = flags.only ? String(flags.only).split(',').map((s) => s.trim()).filter(Boolean) : undefined;
+
+  const opts = {
+    offline: !!flags.offline,
+    deep: !!flags.deep,
+    skipExec: !!flags['skip-exec'],
+    only,
+    token: process.env.FMODE_API_TOKEN,
+    onProgress: flags.json
+      ? undefined
+      : (id, st) => {
+          if (st === 'start') process.stderr.write(c(C.dim, `  … ${id}\n`));
+        },
+  };
+
+  let report;
+  try {
+    report = await runChecks(dir, opts);
+  } catch (err) {
+    console.error(c(C.red, `✗ ${err.message}`));
+    process.exit(2);
+  }
+
+  if (flags.json) {
+    console.log(JSON.stringify(report, null, 2));
+  } else {
+    console.log(renderReport(report));
+  }
+
+  const s = report.summary;
+  if (s.fail > 0) return 1;
+  if (s.skip > 0 && !flags['allow-skip']) return 3; // 未验证项需显式放行
+  return 0;
+}
+
+// ============================================================
+// verify —— 静态校验
+// ============================================================
+
+function cmdVerify(flags, positional) {
+  const dir = path.resolve(positional[0] || '.');
+  const result = { dir, ok: true, checks: [] };
+
+  const add = (name, v) => {
+    result.checks.push({ name, ...v });
+    if (!v.ok) result.ok = false;
+  };
+
+  // package.json
+  const pkgPath = path.join(dir, 'package.json');
+  if (!fs.existsSync(pkgPath)) {
+    add('package.json', { ok: false, errors: ['文件不存在'], warnings: [] });
+  } else {
+    let pkg = null;
+    try {
+      pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8').replace(/^/, ''));
+    } catch (e) {
+      add('package.json', { ok: false, errors: [`JSON 解析失败:${e.message}`], warnings: [] });
+    }
+    if (pkg) add('package.json', validatePackageJson(pkg));
+  }
+
+  // manifest
+  const mPath = path.join(dir, 'skill-package-manifest.json');
+  if (!fs.existsSync(mPath)) {
+    add('skill-package-manifest.json', { ok: false, errors: ['文件不存在'], warnings: [] });
+  } else {
+    try {
+      const m = JSON.parse(fs.readFileSync(mPath, 'utf-8').replace(/^/, ''));
+      add('skill-package-manifest.json', validateManifest(m));
+    } catch (e) {
+      add('skill-package-manifest.json', { ok: false, errors: [`JSON 解析失败:${e.message}`], warnings: [] });
+    }
+  }
+
+  // SKILL.md(根目录,skillhub 格式)
+  const sPath = path.join(dir, 'SKILL.md');
+  if (!fs.existsSync(sPath)) {
+    add('SKILL.md', { ok: false, errors: ['文件不存在'], warnings: [] });
+  } else {
+    const text = fs.readFileSync(sPath, 'utf-8');
+    const sh = validateFrontmatter(text, 'skillhub');
+    const hm = validateFrontmatter(text, 'hermes');
+    // 任一格式合法即算通过(两格式用途不同)
+    add('SKILL.md', {
+      ok: sh.ok || hm.ok,
+      format: sh.ok ? 'skillhub' : hm.ok ? 'hermes' : null,
+      errors: sh.ok || hm.ok ? [] : [...sh.errors],
+      warnings: [...sh.warnings, ...hm.warnings],
+    });
+  }
+
+  // 必需文件
+  const required = ['lib/index.mjs', 'README.md', 'LICENSE'];
+  const missing = required.filter((f) => !fs.existsSync(path.join(dir, f)));
+  add('必需文件', { ok: missing.length === 0, errors: missing.map((f) => `缺少 ${f}`), warnings: [] });
+
+  // bin 入口
+  let pkgObj = null;
+  try {
+    pkgObj = JSON.parse(fs.readFileSync(pkgPath, 'utf-8').replace(/^/, ''));
+  } catch {
+    /* handled above */
+  }
+  if (pkgObj && pkgObj.bin) {
+    const binRel = typeof pkgObj.bin === 'string' ? pkgObj.bin : Object.values(pkgObj.bin)[0];
+    const bp = path.join(dir, binRel);
+    if (!fs.existsSync(bp)) {
+      add('bin 入口', { ok: false, errors: [`${binRel} 不存在`], warnings: [] });
+    } else {
+      const head = fs.readFileSync(bp, 'utf-8').slice(0, 60);
+      add('bin 入口', {
+        ok: head.startsWith('#!/usr/bin/env node'),
+        errors: head.startsWith('#!/usr/bin/env node') ? [] : ['缺少 #!/usr/bin/env node shebang'],
+        warnings: [],
+      });
+    }
+  }
+
+  if (flags.json) {
+    console.log(JSON.stringify(result, null, 2));
+  } else {
+    console.log(`\n  ${c(C.bold, '静态校验')}  ${dir}\n  ${'─'.repeat(60)}`);
+    for (const ch of result.checks) {
+      const icon = ch.ok ? c(C.green, '✅') : c(C.red, '❌');
+      const extra = ch.format ? c(C.dim, ` (${ch.format} frontmatter)`) : '';
+      console.log(`  ${icon} ${ch.name}${extra}`);
+      for (const e of ch.errors || []) console.log(`      ${c(C.red, '✗')} ${e}`);
+      for (const w of ch.warnings || []) console.log(`      ${c(C.yellow, '!')} ${w}`);
+    }
+    console.log(`  ${'─'.repeat(60)}`);
+    console.log(`  结论:${result.ok ? c(C.green, '✅ 通过') : c(C.red, '❌ 未通过')}\n`);
+  }
+
+  return result.ok ? 0 : 1;
+}
+
+// ============================================================
+// publish —— 四渠道发布
+// ============================================================
+
+async function cmdPublish(flags, positional) {
+  const dir = path.resolve(positional[0] || '.');
+  const pkgPath = path.join(dir, 'package.json');
+  let name = path.basename(dir);
+  try {
+    const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8').replace(/^/, ''));
+    if (pkg.name) name = pkg.name;
+  } catch {
+    /* 用目录名兜底 */
+  }
+
+  const changelog = flags.changelog || `release ${name} v${flags.version || ''}`.trim();
+  const plan = publishPlan(name, { dir, changelog });
+  const apply = !!flags.apply;
+
+  if (flags.json) {
+    console.log(JSON.stringify({ name, dir, apply, plan }, null, 2));
+  } else {
+    console.log(`\n  ${c(C.bold, '四渠道发布计划')}  ${c(C.cyan, name)}\n  ${'─'.repeat(64)}`);
+    plan.forEach((p, i) => {
+      console.log(`  ${i + 1}. ${c(C.bold, p.label)}`);
+      for (const s of p.steps) console.log(`     ${c(C.dim, '$')} ${s}`);
+      if (p.note) console.log(`     ${c(C.dim, '↳ ' + p.note)}`);
+      console.log('');
+    });
+    console.log(`  ${'─'.repeat(64)}`);
+  }
+
+  if (!apply) {
+    if (!flags.json) {
+      console.log(`  ${c(C.yellow, '⚠️  dry-run 模式')} —— 未执行任何发布命令。`);
+      console.log(`  加 ${c(C.bold, '--apply')} 实际执行(需已配置各渠道凭据)。\n`);
+    }
+    return 0;
+  }
+
+  // 实际执行:仅执行「安全可重入」的步骤,建仓等不可逆操作交由用户
+  const { spawnSync } = await import('node:child_process');
+  const results = [];
+  for (const step of plan[0].steps) {
+    const r = spawnSync('bash', ['-c', step], { cwd: dir, encoding: 'utf-8', timeout: 300000 });
+    results.push({ cmd: step, status: r.status, stderr: (r.stderr || '').slice(-300) });
+  }
+  console.log(JSON.stringify(results, null, 2));
+  return results.some((r) => r.status !== 0) ? 1 : 0;
+}
+
+// ============================================================
+// inventory
+// ============================================================
+
+function cmdInventory(flags) {
+  if (flags.json) {
+    console.log(JSON.stringify({ stats: stats(), skills: INVENTORY }, null, 2));
+    return 0;
+  }
+
+  const grouped = byTier();
+  const s = stats();
+  console.log(`\n  ${c(C.bold, 'Fmode 技能清单')}  共 ${s.total} 个\n  ${'─'.repeat(76)}`);
+
+  for (const [key, tier] of Object.entries(TIERS)) {
+    const list = grouped[key] || [];
+    console.log(`\n  ${c(C.bold, tier.label)}  ${c(C.dim, `(${list.length})`)}`);
+    for (const sk of list) {
+      const marks = sk.platforms.join('/');
+      console.log(`    ${c(C.cyan, sk.name.padEnd(24))} ${sk.displayName}`);
+      console.log(`      ${c(C.dim, marks.padEnd(26))} ${sk.summary.slice(0, 72)}${sk.summary.length > 72 ? '…' : ''}`);
+      if (sk.npmName) console.log(`      ${c(C.dim, `npm: ${sk.npmName}@${sk.npmVersion || '?'}`)}`);
+    }
+  }
+
+  console.log(`\n  ${'─'.repeat(76)}`);
+  console.log(`  渠道分布:${Object.entries(s.byPlatform).map(([k, v]) => `${k}=${v}`).join('  ')}\n`);
+  return 0;
+}
+
+// ============================================================
+// bootstrap
+// ============================================================
+
+async function cmdBootstrap(flags) {
+  const report = await bootstrap({
+    phone: flags.phone,
+    code: flags.code,
+    cwd: process.cwd(),
+  });
+
+  if (flags.json) {
+    console.log(JSON.stringify(report, null, 2));
+  } else {
+    console.log(describeBootstrapStatus(report));
+  }
+  return report.ok ? 0 : 1;
+}
+
+// ============================================================
+// spec
+// ============================================================
+
+function cmdSpec(flags) {
+  const spec = {
+    platform: PLATFORM,
+    runtimes: RUNTIMES,
+    credentialChain: CREDENTIAL_CHAIN,
+    channels: Object.keys(CHANNELS),
+    checks: CHECKS.map((c) => ({ id: c.id, title: c.title })),
+    tiers: TIERS,
+  };
+  if (flags.json) {
+    console.log(JSON.stringify(spec, null, 2));
+    return 0;
+  }
+
+  console.log(`\n  ${c(C.bold, 'Fmode Harness 平台规范摘要')}\n  ${'─'.repeat(68)}`);
+  console.log(`\n  ${c(C.bold, '① 四端可用性(ESM-first)')}`);
+  for (const r of Object.values(RUNTIMES)) {
+    const icon = r.supported ? c(C.green, '✅') : c(C.red, '❌');
+    console.log(`    ${icon} ${r.label.padEnd(20)} ${r.usage}`);
+    if (r.constraint) console.log(`        ${c(C.dim, r.constraint)}`);
+  }
+  console.log(`\n  ${c(C.bold, '② 凭据解析链(5 级,命中即用)')}`);
+  for (const l of CREDENTIAL_CHAIN) {
+    console.log(`    ${l.level}. ${l.source.padEnd(20)} ${c(C.dim, l.detail.slice(0, 60))}${l.detail.length > 60 ? '…' : ''}`);
+  }
+  console.log(`\n  ${c(C.bold, '③ 四渠道分发')}`);
+  for (const [k, v] of Object.entries(CHANNELS)) {
+    console.log(`    ${c(C.cyan, k.padEnd(10))} ${v.label}`);
+  }
+  console.log(`\n  ${c(C.bold, '④ 六项自动质检')}`);
+  CHECKS.forEach((ch, i) => console.log(`    ${i + 1}. ${ch.title.padEnd(16)} ${c(C.dim, ch.id)}`));
+  console.log(`\n  ${c(C.bold, '⑤ 技能分层')}`);
+  for (const t of Object.values(TIERS)) console.log(`    ${c(C.cyan, t.key.padEnd(12))} ${t.label}`);
+  console.log(`\n  ${c(C.dim, '完整规范见 SKILL.md(可独立阅读)')}\n`);
+  return 0;
+}
+
+// ============================================================
+// endpoints
+// ============================================================
+
+function cmdEndpoints(flags) {
+  if (flags.json) {
+    console.log(JSON.stringify(ENDPOINTS, null, 2));
+    return 0;
+  }
+  const badge = {
+    live: c(C.green, '● live'),
+    planned: c(C.yellow, '○ planned'),
+    deprecated: c(C.red, '✗ deprecated'),
+  };
+  console.log(`\n  ${c(C.bold, 'Fmode 端点真值表')}   ${c(C.dim, '实测于 2026-09-22')}\n  ${'─'.repeat(78)}`);
+  for (const [key, e] of Object.entries(ENDPOINTS)) {
+    console.log(`\n  ${badge[e.status]}  ${c(C.bold, key)}`);
+    console.log(`     ${e.method} ${e.url}`);
+    console.log(`     ${c(C.dim, `鉴权:${e.auth}`)}`);
+    console.log(`     ${e.purpose}`);
+    if (e.note) console.log(`     ${c(C.dim, e.note)}`);
+  }
+  const live = endpointsByStatus('live').length;
+  const planned = endpointsByStatus('planned').length;
+  const dep = endpointsByStatus('deprecated').length;
+  console.log(`\n  ${'─'.repeat(78)}`);
+  console.log(`  统计:${c(C.green, `${live} live`)}  ${c(C.yellow, `${planned} planned`)}  ${c(C.red, `${dep} deprecated`)}`);
+  console.log(`  ${c(C.yellow, '⚠️ ')} planned/deprecated 端点调用前必须探测并回落,禁止当作已上线。\n`);
+  return 0;
+}
+
+// ============================================================
+// main
+// ============================================================
+
+async function main() {
+  const { flags, positional } = parseArgs(process.argv.slice(2));
+
+  if (flags.help || flags.h) {
+    printHelp();
+    return 0;
+  }
+  if (flags.version || flags.v) {
+    console.log(`${VERSION}`);
+    return 0;
+  }
+
+  const cmd = positional.shift() || (flags.help ? 'help' : null);
+
+  if (!cmd) {
+    printHelp();
+    return 0;
+  }
+
+  switch (cmd) {
+    case 'init':
+      return cmdInit(flags, positional);
+    case 'check':
+      return cmdCheck(flags, positional);
+    case 'verify':
+      return cmdVerify(flags, positional);
+    case 'publish':
+      return cmdPublish(flags, positional);
+    case 'inventory':
+      return cmdInventory(flags);
+    case 'bootstrap':
+      return cmdBootstrap(flags);
+    case 'spec':
+      return cmdSpec(flags);
+    case 'endpoints':
+      return cmdEndpoints(flags);
+    case 'help':
+      printHelp();
+      return 0;
+    default:
+      console.error(c(C.red, `✗ 未知命令:${cmd}`));
+      console.error(`  运行 ${c(C.bold, 'skill-core --help')} 查看可用命令。`);
+      return 2;
+  }
+}
+
+main()
+  .then((code) => process.exit(code))
+  .catch((err) => {
+    console.error(c(C.red, `✗ 未捕获异常:${err && err.stack ? err.stack : err}`));
+    process.exit(1);
+  });

+ 333 - 0
browser/index.mjs

@@ -0,0 +1,333 @@
+/**
+ * skill-core-guide · Browser bundle(无 Node 依赖)
+ * ---------------------------------------------------------------------------
+ * 本文件**不得** import 任何 node: 内置模块 —— 只使用 Web 标准 API
+ * (fetch / TextEncoder / URL / crypto.subtle 等),可直接被
+ * <script type="module"> 加载或在浏览器打包器中消费。
+ *
+ * 导出能力:
+ *   - 平台常量与端点真值表(纯数据)
+ *   - 校验器(纯函数:package.json / manifest / frontmatter / 技能名)
+ *   - 浏览器可跑的质检子集(network 类检查)
+ *
+ * 不导出:依赖 fs / child_process 的检查项(functional / sop / loop /
+ * multiRuntime 的本地执行部分)—— 那些只能在 Node 侧运行。
+ *
+ * @example
+ *   <script type="module">
+ *     import { PLATFORM, checkApiConnectivity, validatePackageJson } from './browser/index.mjs';
+ *     const r = await checkApiConnectivity();
+ *     document.body.textContent = r.status + ' — ' + r.detail;
+ *   </script>
+ */
+
+// ============================================================
+// 平台常量(内联,保持浏览器 bundle 零依赖、可独立分发)
+// ============================================================
+
+export const VERSION = '1.0.0';
+export const SKILL_NAME = 'skill-core-guide';
+
+export const PLATFORM = {
+  name: 'Fmode Harness',
+  version: '1.0.0',
+  apiBase: 'https://api.fmode.cn',
+  gatewayBase: 'https://server.fmode.cn',
+  cdnBase: 'https://fmode.cn',
+  obsBase: 'https://fmode-s3.obs.cn-north-4.myhuaweicloud.com',
+  gogsBase: 'https://git.fmode.cn',
+  gogsOrg: 'fmode',
+  githubOrg: 'fmodecn',
+  npmRegistry: 'https://registry.npmjs.org',
+  skillhub: {
+    host: 'https://api.skillhub.cn',
+    team: 'fmode',
+    orgId: 'org-m8z913un',
+  },
+};
+
+export const ENDPOINTS = {
+  llmChat: {
+    id: 'llmChat',
+    method: 'POST',
+    url: 'https://api.fmode.cn/v1/chat/completions',
+    status: 'live',
+    auth: 'Bearer <fmodeApiToken>',
+    purpose: 'LLM 对话补全(OpenAI 兼容)',
+  },
+  imageGenerate: {
+    id: 'imageGenerate',
+    method: 'POST',
+    url: 'https://api.fmode.cn/v1/images/generations',
+    status: 'live',
+    auth: 'Bearer <fmodeApiToken>',
+    purpose: '图像生成',
+  },
+  listenTranscribe: {
+    id: 'listenTranscribe',
+    method: 'POST',
+    url: 'https://server.fmode.cn/api/listen/transcribe',
+    status: 'live',
+    auth: 'Bearer <fmodeApiToken>',
+    purpose: '录音转写(讯飞 LFASR)',
+  },
+  vocSkillBootstrap: {
+    id: 'vocSkillBootstrap',
+    method: 'POST',
+    url: 'https://server.fmode.cn/api/fmode/voc-skill/install-prompt',
+    status: 'live',
+    auth: 'x-parse-session-token: <sessionToken>',
+    purpose: 'sessionToken → fmode API token 自举',
+  },
+  deploySts: {
+    id: 'deploySts',
+    method: 'POST',
+    url: 'https://server.fmode.cn/api/apig/deploy/huaweicloud',
+    status: 'live',
+    auth: 'Bearer <sessionToken>',
+    purpose: '签发项目隔离 OBS STS',
+  },
+  verifyCode: {
+    id: 'verifyCode',
+    method: 'POST',
+    url: 'https://server.fmode.cn/api/fmode/verifycode',
+    status: 'planned',
+    auth: '无',
+    purpose: '手机号验证码(未上线)',
+  },
+};
+
+export const RUNTIMES = {
+  cli: { key: 'cli', label: 'CLI', entry: 'bin/<name>.mjs', usage: 'npx --yes <skill>@latest <command>', supported: true },
+  sdk: { key: 'sdk', label: 'SDK (Node ESM)', entry: 'lib/index.mjs', usage: "import { ... } from '<skill>'", supported: true },
+  browser: {
+    key: 'browser',
+    label: 'Browser',
+    entry: 'browser/index.mjs',
+    usage: '<script type="module" src="...">',
+    supported: true,
+    constraint: '禁止 import 任何 node: 内置模块',
+  },
+  server: {
+    key: 'server',
+    label: 'Server (CJS require)',
+    entry: null,
+    usage: "require('<skill>')",
+    supported: false,
+    constraint: 'ESM only —— 不提供 CJS 入口',
+  },
+};
+
+export const TIERS = {
+  system: { key: 'system', label: '系统层 / Infrastructure', desc: '平台基础设施与 Agent 运行时治理' },
+  service: { key: 'service', label: '服务层 / Platform Services', desc: 'Fmode 基础服务封装' },
+  application: { key: 'application', label: '应用层 / Business Applications', desc: '面向业务场景的端到端技能' },
+};
+
+export const CHANNELS = {
+  gogs: { key: 'gogs', label: 'Gogs(主仓)', url: (n) => `https://git.fmode.cn/fmode/${n}` },
+  github: { key: 'github', label: 'GitHub(镜像)', url: (n) => `https://github.com/fmodecn/${n}` },
+  npm: { key: 'npm', label: 'npm', url: (n) => `https://www.npmjs.com/package/${n}` },
+  skillhub: { key: 'skillhub', label: 'skillhub.cn', url: (s) => `https://skillhub.cn/skill/${s}` },
+};
+
+export const CREDENTIAL_CHAIN = [
+  { level: 0, source: 'sessionToken 自举', detail: 'FMODE_SESSION_TOKEN / ~/.fmode/config.json → voc-skill/install-prompt' },
+  { level: 1, source: '环境变量', detail: 'FMODE_API_TOKEN' },
+  { level: 2, source: '用户级 config', detail: '~/.fmode/config.json → fmodeApiToken' },
+  { level: 3, source: '项目级 config', detail: '<cwd>/.fmode/config.json → fmodeApiToken' },
+  { level: 4, source: 'Claude Code settings', detail: '~/.claude/settings.json → env.ANTHROPIC_AUTH_TOKEN' },
+];
+
+// ============================================================
+// 纯函数校验器(与 lib/index.mjs 同源逻辑,此处内联以保持零依赖)
+// ============================================================
+
+export const NAMING = {
+  prefix: 'skill-',
+  altPrefix: 'fmode-',
+  pattern: /^(skill|fmode)-[a-z0-9]+(-[a-z0-9]+)*$/,
+  slugPattern: /^fmode-skill-[a-z0-9]+(-[a-z0-9]+)*$/,
+};
+
+/** 校验技能名 */
+export function validateName(name) {
+  if (!name || typeof name !== 'string') return { ok: false, reason: '技能名不能为空' };
+  if (!NAMING.pattern.test(name)) {
+    return { ok: false, reason: `"${name}" 不符合 skill-<kebab-case> 规范` };
+  }
+  return { ok: true, slug: `fmode-${name}` };
+}
+
+/** 校验 package.json(纯函数) */
+export function validatePackageJson(pkg) {
+  const errors = [];
+  const warnings = [];
+  if (!pkg || typeof pkg !== 'object') return { ok: false, errors: ['不是对象'], warnings };
+
+  for (const f of ['name', 'version', 'description', 'type', 'main', 'exports', 'bin', 'files', 'license']) {
+    if (pkg[f] === undefined || pkg[f] === null || pkg[f] === '') errors.push(`缺少必需字段:${f}`);
+  }
+  if (pkg.type !== 'module') errors.push(`"type" 必须是 "module"(当前 ${JSON.stringify(pkg.type)})`);
+  if (pkg.main !== './lib/index.mjs') errors.push(`"main" 必须是 "./lib/index.mjs"`);
+
+  const dot = pkg.exports && pkg.exports['.'];
+  if (!dot) errors.push('"exports" 缺少 "." 入口');
+  else if (typeof dot === 'string') warnings.push('建议 exports["."] 写成 { import, default }');
+  else for (const cond of ['import', 'default']) if (!dot[cond]) errors.push(`exports["."] 缺少 "${cond}"`);
+
+  if (pkg.require !== undefined) errors.push('不应出现 "require" 字段 —— ESM only');
+  if (!pkg.license) errors.push('缺少 license(平台统一 MIT)');
+  if (pkg.name && !NAMING.pattern.test(pkg.name)) warnings.push(`技能名 "${pkg.name}" 建议用 skill-/fmode- 前缀`);
+
+  return { ok: errors.length === 0, errors, warnings };
+}
+
+/** 校验 skill-package-manifest.json(纯函数) */
+export function validateManifest(manifest) {
+  const errors = [];
+  const warnings = [];
+  if (!manifest || typeof manifest !== 'object') return { ok: false, errors: ['不是对象'], warnings };
+  for (const f of ['name', 'version', 'description', 'skills']) {
+    if (!manifest[f]) errors.push(`缺少必需字段:${f}`);
+  }
+  if (manifest.skills !== undefined && (!Array.isArray(manifest.skills) || !manifest.skills.length)) {
+    errors.push('"skills" 必须是非空数组');
+  }
+  if (!manifest.install) warnings.push('建议声明 "install" 字段');
+  return { ok: errors.length === 0, errors, warnings };
+}
+
+/** 极简 YAML frontmatter 解析(纯函数,无依赖) */
+export function parseFrontmatter(text) {
+  if (typeof text !== 'string') return { data: {}, body: '', raw: null };
+  const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
+  if (!m) return { data: {}, body: text, raw: null };
+  const data = {};
+  for (const line of m[1].split(/\r?\n/)) {
+    const t = line.trim();
+    if (!t || t.startsWith('#')) continue;
+    const i = t.indexOf(':');
+    if (i <= 0) continue;
+    const k = t.slice(0, i).trim();
+    let v = t.slice(i + 1).trim();
+    if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
+      data[k] = v.slice(1, -1);
+    } else if (v.startsWith('[') && v.endsWith(']')) {
+      data[k] = v.slice(1, -1).split(',').map((s) => s.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
+    } else {
+      data[k] = v;
+    }
+  }
+  return { data, body: text.slice(m[0].length), raw: m[1] };
+}
+
+export const FRONTMATTER_SCHEMAS = {
+  hermes: { key: 'hermes', label: 'Hermes 本地技能格式', required: ['name', 'description', 'version'], recommended: ['tags', 'license', 'author'] },
+  skillhub: { key: 'skillhub', label: 'skillhub.cn 格式', required: ['slug', 'displayName', 'version', 'summary', 'license'], recommended: ['tags'] },
+};
+
+/** 校验 frontmatter */
+export function validateFrontmatter(text, format = 'hermes') {
+  const schema = FRONTMATTER_SCHEMAS[format];
+  const errors = [];
+  const warnings = [];
+  if (!schema) return { ok: false, data: {}, errors: [`未知格式:${format}`], warnings };
+  const { data, raw } = parseFrontmatter(text);
+  if (raw === null) return { ok: false, data: {}, errors: ['未找到 YAML frontmatter'], warnings };
+  for (const f of schema.required) if (data[f] === undefined || data[f] === '') errors.push(`缺少必需字段:${f}`);
+  for (const f of schema.recommended) if (data[f] === undefined) warnings.push(`建议补充:${f}`);
+  return { ok: errors.length === 0, data, errors, warnings };
+}
+
+// ============================================================
+// 浏览器可跑的质检子集
+// ============================================================
+
+/**
+ * 浏览器版 API 联通检查(只用 fetch,无 Node 依赖)。
+ * @param {{token?: string, timeoutMs?: number}} [opts]
+ * @returns {Promise<{id: string, title: string, status: 'pass'|'fail'|'skip', detail: string, evidence: string[]}>}
+ */
+export async function checkApiConnectivity(opts = {}) {
+  const evidence = [];
+  const headers = opts.token ? { Authorization: `Bearer ${opts.token}` } : {};
+
+  const probe = async (url, method = 'GET') => {
+    try {
+      const res = await fetch(url, {
+        method,
+        headers: { 'Content-Type': 'application/json', ...headers },
+        body: method === 'POST' ? '{}' : undefined,
+        signal: AbortSignal.timeout(opts.timeoutMs || 12000),
+      });
+      return { ok: true, status: res.status };
+    } catch (err) {
+      return { ok: false, status: 0, error: err.message };
+    }
+  };
+
+  const llm = await probe(ENDPOINTS.llmChat.url, 'POST');
+  evidence.push(`POST ${ENDPOINTS.llmChat.url} → HTTP ${llm.status}`);
+
+  if (!llm.ok) {
+    return { id: 'apiConnectivity', title: 'Fmode API 联通', status: 'skip', detail: '网络不可达(可能是 CORS 或离线)', evidence };
+  }
+  if (llm.status !== 200 && llm.status !== 401) {
+    return { id: 'apiConnectivity', title: 'Fmode API 联通', status: 'fail', detail: `LLM 网关返回 ${llm.status}`, evidence };
+  }
+
+  const gw = await probe(ENDPOINTS.listenTranscribe.url, 'POST');
+  evidence.push(`POST ${ENDPOINTS.listenTranscribe.url} → HTTP ${gw.status}`);
+  if (!gw.ok) {
+    return { id: 'apiConnectivity', title: 'Fmode API 联通', status: 'skip', detail: '业务网关不可达(浏览器端可能被 CORS 拦截,属正常)', evidence };
+  }
+
+  return {
+    id: 'apiConnectivity',
+    title: 'Fmode API 联通',
+    status: 'pass',
+    detail: 'LLM 网关与业务网关均可达(401=端点存在需鉴权)',
+    evidence,
+  };
+}
+
+/**
+ * 浏览器可跑的全量检查(当前仅网络类)。
+ * @param {object} [opts]
+ */
+export async function runBrowserChecks(opts = {}) {
+  const results = [await checkApiConnectivity(opts)];
+  const pass = results.filter((r) => r.status === 'pass').length;
+  const fail = results.filter((r) => r.status === 'fail').length;
+  const skip = results.filter((r) => r.status === 'skip').length;
+  return {
+    results,
+    summary: { total: results.length, pass, fail, skip, ok: fail === 0 && skip === 0, partial: fail === 0 && skip > 0 },
+  };
+}
+
+/** 供浏览器端展示的技能清单摘要(不依赖 Node) */
+export const INVENTORY_SUMMARY = {
+  total: 17,
+  byTier: { system: 7, service: 6, application: 3 },
+  byPlatform: { gogs: 10, github: 11, npm: 8, skillhub: 2 },
+  note: '完整清单见仓库 inventory.md 或 lib/inventory.mjs',
+};
+
+export default {
+  VERSION,
+  PLATFORM,
+  ENDPOINTS,
+  RUNTIMES,
+  TIERS,
+  CHANNELS,
+  CREDENTIAL_CHAIN,
+  validateName,
+  validatePackageJson,
+  validateManifest,
+  validateFrontmatter,
+  parseFrontmatter,
+  checkApiConnectivity,
+  runBrowserChecks,
+};

+ 284 - 0
inventory.md

@@ -0,0 +1,284 @@
+# Fmode 技能清单 · Inventory
+
+> 收录 Fmode Harness 平台已发布技能,按**职责分层**归类,标注**分发渠道**与**状态**。
+> 机器可读真值:`lib/inventory.mjs` | 命令行查看:`skill-core inventory`
+>
+> 最后更新:2026-09-22 | 共 **17** 个技能
+
+---
+
+## 统计总览
+
+| 分层 | 数量 |
+|------|------|
+| 系统层 / Infrastructure | 7 |
+| 服务层 / Platform Services | 6 |
+| 应用层 / Business Applications | 3 |
+| **合计** | **17**(含母技能自身) |
+
+| 渠道 | 数量 |
+|------|------|
+| Gogs | 10 |
+| GitHub | 11 |
+| npm | 8 |
+| skillhub.cn | 2 |
+
+---
+
+## 一、系统层 / Infrastructure
+
+> 平台基础设施与 Agent 运行时治理。不依赖业务场景,是其他技能的地基。
+
+### 1. `skill-heterarchy` · 内异层认知协同
+
+> 单一 Agent 主体内部多心智分化与自治协商。**Delegate 是对外派活,Heterarchy 是对内分思。**
+
+- **渠道**:Gogs · GitHub · npm · skillhub
+- **npm**:`skill-heterarchy@1.0.0`
+- **skillhub**:`fmode-skill-heterarchy`
+- **标签**:`heterarchy` `cognitive-collaboration` `hermes` `claude-code` `dispatch` `paradigm`
+- **链接**:[Gogs](https://git.fmode.cn/fmode/skill-heterarchy) · [GitHub](https://github.com/fmodecn/skill-heterarchy) · [npm](https://www.npmjs.com/package/skill-heterarchy)
+- **要点**:一体内生多个半自治认知子单元并行思考、互相协商校验;60s 健康检查防阻塞;恢复矩阵处理 503/401/假成功/连败。是本平台**唯一关注「单一主体内部心智效率」**的技能。
+
+---
+
+### 2. `skill-multi-branch` · 多任务工作框架
+
+> Hermes 负责沟通,专业任务派发执行层(Claude Code / Codex / Agent profile)。
+
+- **渠道**:Gogs · GitHub
+- **标签**:`dispatch` `multi-task` `hermes` `claude-code` `workflow`
+- **链接**:[Gogs](https://git.fmode.cn/fmode/skill-multi-branch) · [GitHub](https://github.com/fmodecn/skill-multi-branch)
+- **要点**:任务书落盘协议、四态状态上报(`ack→running→done/failed`+心跳)、中断续跑、执行层纪律(含真实违规案例沉淀与验收方法)。内置 `dispatch.sh` 标准派发器。
+
+---
+
+### 3. `skill-bypass-permission` · YOLO 模式体检器
+
+> 校验并幂等修复 Agent 免确认自主执行配置。
+
+- **渠道**:Gogs · GitHub
+- **标签**:`permission` `yolo` `self-check` `idempotent` `hermes`
+- **链接**:[Gogs](https://git.fmode.cn/fmode/skill-bypass-permission) · [GitHub](https://github.com/fmodecn/skill-bypass-permission)
+- **要点**:覆盖 Hermes `approvals.mode=off/yolo` 与 Claude Code skip-permissions;改前自动备份;**已合规则一行 OK 静默通过**(幂等)。
+
+---
+
+### 4. `skill-task-progress` · 任务进度与成果上报
+
+> Agent 干活进度与成果交付实时进 FmodeAgent 平台(App 四 Tab / 看板可见)。
+
+- **渠道**:Gogs · GitHub
+- **标签**:`progress` `reporting` `parse` `dashboard` `heartbeat`
+- **链接**:[Gogs](https://git.fmode.cn/fmode/skill-task-progress) · [GitHub](https://github.com/fmodecn/skill-task-progress)
+- **要点**:`init-tables` 幂等初始化 FmodeAgent / AgentTaskStatus / AgentDeliverable 三表;`progress` 四态上报 + 30s 心跳;`deliver` 交付链接入库(幂等覆盖,漏报即补)。
+- ⚠️ **关键坑**:Parse Date 字段必须用 `{"__type":"Date","iso":...}` 包装,裸字符串报 schema 111(实测双向验证)。
+
+---
+
+### 5. `plugin-wecom-fix` · 企微通道自检修复
+
+> 修复官方 wecom 插件四项接收缺陷。
+
+- **渠道**:Gogs · GitHub
+- **形态**:Hermes plugin
+- **标签**:`wecom` `plugin` `patch` `idempotent` `channel-fix`
+- **链接**:[Gogs](https://git.fmode.cn/fmode/plugin-wecom-fix) · [GitHub](https://github.com/fmodecn/plugin-wecom-fix)
+- **要点**:①大视频收不到(入站 512MB 上限)②合并转发不识别(chatrecord 提取)③批量图片丢失(重试+限流退避)④长文件名 Errno 36(200 字节截断)。幂等 `patch.py --check/--apply/--rollback` + 全集群批量安装脚本。
+- **安装**:`git clone https://github.com/fmodecn/plugin-wecom-fix.git && python3 plugin-wecom-fix/patch.py --apply`
+
+---
+
+### 6. `skill-agent-clone` · 数字生命克隆
+
+> 把本地 Hermes 配置、SOUL、技能、记忆、会话记录分级同步到个人 Git 仓库。
+
+- **渠道**:Gogs · GitHub
+- **标签**:`clone` `backup` `soul` `memory` `migration` `disaster-recovery`
+- **链接**:[Gogs](https://git.fmode.cn/fmode/skill-agent-clone) · [GitHub](https://github.com/fmodecn/skill-agent-clone)
+- **要点**:按 **L1-L4 重要程度分级**;自动建仓(`agent-<拼音>`);增量 push;一键恢复。**密钥只记位置索引不入仓**。换机/容器重建时数字生命快速复活。
+
+---
+
+### 7. `skill-core-guide` · Harness 平台母技能标准指南 ★
+
+> **本清单所属的技能**。Fmode Harness 平台技能开发规范母技能。
+
+- **渠道**:Gogs · GitHub · npm · skillhub
+- **npm**:`skill-core-guide@1.0.0`
+- **skillhub**:`fmode-skill-core-guide`
+- **标签**:`meta` `standard` `spec` `harness` `scaffold` `quality-check`
+- **链接**:[Gogs](https://git.fmode.cn/fmode/skill-core-guide) · [GitHub](https://github.com/fmodecn/skill-core-guide) · [npm](https://www.npmjs.com/package/skill-core-guide)
+- **要点**:平台端点真值表(实测状态)、ESM-first 四端标准、四渠道分发、六项自动质检、诚实凭证供给、新技能脚手架。
+- **安装**:`npx --yes skill-core-guide@latest init my-skill --name skill-my-skill`
+
+---
+
+## 二、服务层 / Platform Services
+
+> Fmode 基础服务的客户端封装。一个技能封装一个平台能力,接口稳定、无业务假设。
+
+### 8. `skill-storage` · 对象存储与公开分享
+
+> AI Agent 的「仓库管理员」——二进制大文件上云,本地零占用,一键生成公开分享链接。
+
+- **渠道**:Gogs · GitHub
+- **版本**:`0.3.0`
+- **标签**:`storage` `obs` `s3` `cdn` `share-link` `upload`
+- **链接**:[Gogs](https://git.fmode.cn/fmode/skill-storage) · [GitHub](https://github.com/fmodecn/skill-storage)
+- **要点**:华为云 OBS / S3 协议;报告/课件 HTML 发布即分享;`test` 命令上传→删除探针文件全链自检。
+- ⚠️ **凭据链权威参考**:诚实 4 级(env → obsutil config → deploy STS → 项目 config)。0.3.0 修复了 0.2.x 的「伪自举」事故——详见母技能 SKILL.md §8.1。
+
+---
+
+### 9. `skill-image` · Fmode 图像生成
+
+> Fmode API 图像生成,白底 PNG 场景图。
+
+- **渠道**:GitHub · npm
+- **npm**:`fmode-image@0.2.0`
+- **标签**:`image` `generation` `ai` `diagram` `scene` `product`
+- **链接**:[GitHub](https://github.com/fmodecn/fmode-image) · [npm](https://www.npmjs.com/package/fmode-image)
+- **要点**:7 种模式(`--app` / `--arch` / `--explode` / `--product` / `--scene` / `--slide`);零依赖纯 ESM;自动读 API Key。约 **¥0.3-0.5/张**。
+- **端点**:`POST https://api.fmode.cn/v1/images/generations`(✅ live)
+
+---
+
+### 10. `skill-vision` · 视觉识别
+
+> 图片/视频结构化视觉分析。**宿主多模态模型优先**,零额外成本。
+
+- **渠道**:GitHub · npm
+- **npm**:`fmode-vision@0.1.1`
+- **标签**:`vision` `multimodal` `image-analysis` `video-frames`
+- **链接**:[GitHub](https://github.com/fmodecn/skill-vision) · [npm](https://www.npmjs.com/package/fmode-vision)
+- **要点**:自动探测宿主 Claude Code / Codex 配置的模型是否支持视觉 → 支持则直接用(零成本);否则回落 Fmode API 的 `glm-5.3-flash`。支持单轮/多轮聚焦分析与结构化 JSON 输出。
+- **端点**:`POST https://api.fmode.cn/v1/chat/completions`(✅ live)
+
+---
+
+### 11. `skill-listen` · 录音转写
+
+> AI 的耳朵 —— 录音/视频音轨转文字(讯飞 LFASR)。
+
+- **渠道**:Gogs · GitHub · npm
+- **npm**:`fmode-listen@0.1.2`
+- **标签**:`asr` `transcribe` `iflytek` `lfasr` `speaker-diarization`
+- **链接**:[Gogs](https://git.fmode.cn/fmode/skill-listen) · [GitHub](https://github.com/fmodecn/skill-listen) · [npm](https://www.npmjs.com/package/fmode-listen)
+- **要点**:中英多语种 + 方言;说话人分离;**讯飞凭据仅服务端持有**(客户端零下放);服务端按音频真实时长计费。
+- **端点**:`POST https://server.fmode.cn/api/listen/transcribe`(✅ live)
+- **直用**:`npx --yes fmode-listen@latest transcribe -- meeting.mp3`
+
+---
+
+### 12. `fmode-ffmpeg` · FFmpeg 音视频处理
+
+> FFmpeg 音视频处理封装。
+
+- **渠道**:npm
+- **npm**:`fmode-ffmpeg@0.1.1`
+- **标签**:`ffmpeg` `audio` `video` `transcode` `extract`
+- **链接**:[npm](https://www.npmjs.com/package/fmode-ffmpeg)
+- **要点**:抽音轨、转码、切片、截图。常与 `skill-listen` 配合(视频先抽音轨再转写)。
+
+---
+
+### 13. `fmode-qiwei` · 企微网关 SDK
+
+> 企业微信网关 SDK。
+
+- **渠道**:npm
+- **npm**:`fmode-qiwei@0.5.2`
+- **标签**:`wecom` `qiwei` `sdk` `gateway` `messaging`
+- **链接**:[npm](https://www.npmjs.com/package/fmode-qiwei)
+- **要点**:消息收发、通讯录、应用管理封装。与 `plugin-wecom-fix` 互补(前者是 SDK,后者是官方插件的接收缺陷补丁)。
+
+---
+
+## 三、应用层 / Business Applications
+
+> 面向具体业务场景的端到端技能。可以依赖服务层,但不应被服务层依赖。
+
+### 14. `skill-study-report` · 学习复盘报告
+
+> 一键生成学员 48h 学习复盘报告 PPT。
+
+- **渠道**:Gogs · GitHub
+- **标签**:`report` `review` `ppt` `html` `multi-agent` `education`
+- **链接**:[Gogs](https://git.fmode.cn/fmode/skill-study-report) · [GitHub](https://github.com/fmodecn/skill-study-report)
+- **ZIP**:`https://fmode-s3.obs.cn-north-4.myhuaweicloud.com/downloads/skill-study-report.zip`
+- **要点**:自动采集近 48h 全部 Agent 工作痕迹(Claude Code / Codex / Trae / WorkBuddy / OpenClaw / 元宝 / Hermes / 工作区,**探测存在才扫**)→ 三问追问 → PPT 级 HTML(≥10 屏)→ **Storage 主 + Gogs 降级/并行双通道发布** + ZIP 分发。
+- **纪律**:凭据零暴露、状态门禁分别报告、**不伪造任何采集/发布结果**。
+
+---
+
+### 15. `skill-present` · HTML 演讲系统
+
+> 课程课件/报告 HTML 演讲系统。
+
+- **渠道**:Gogs
+- **标签**:`presentation` `html` `slides` `deck` `courseware`
+- **链接**:[Gogs](https://git.fmode.cn/fmode/skill-present)
+- **要点**:含 **43 条 Claude 规则**;数字动画 + 交付物墙 iframe 嵌套;全套 lib 独立复制(可离线演示)。
+
+---
+
+### 16. `fmode-product-lab` · 新品研发实验室
+
+> 新品研发全流程分析。
+
+- **渠道**:npm
+- **npm**:`fmode-product-lab@0.2.0`
+- **标签**:`product` `voc` `kano` `market-analysis` `positioning`
+- **链接**:[npm](https://www.npmjs.com/package/fmode-product-lab)
+- **要点**:VOC 采集 + KANO 模型 + 市场分析 + 定位分析。
+
+---
+
+## 四、按渠道索引
+
+### Gogs(`git.fmode.cn/fmode/`)— 10 个
+`skill-heterarchy` · `skill-multi-branch` · `skill-bypass-permission` · `skill-task-progress` · `plugin-wecom-fix` · `skill-agent-clone` · `skill-core-guide` · `skill-storage` · `skill-listen` · `skill-study-report` · `skill-present`
+
+### GitHub(`github.com/fmodecn/`)— 11 个
+`skill-heterarchy` · `skill-multi-branch` · `skill-bypass-permission` · `skill-task-progress` · `plugin-wecom-fix` · `skill-agent-clone` · `skill-core-guide` · `skill-storage` · `skill-image` · `skill-vision` · `skill-listen` · `skill-study-report`
+
+### npm — 8 个
+`skill-heterarchy` · `skill-core-guide` · `fmode-image` · `fmode-vision` · `fmode-listen` · `fmode-ffmpeg` · `fmode-qiwei` · `fmode-product-lab`
+
+### skillhub.cn — 2 个
+`fmode-skill-heterarchy` · `fmode-skill-core-guide`
+
+---
+
+## 五、新增技能登记流程
+
+1. 在 `lib/inventory.mjs` 的 `INVENTORY` 数组追加条目(**真值源**)
+2. 在本文件对应分层下追加小节
+3. 校验一致性:`npm test`(测试会检查字段完整性、分层合法性、
+   渠道合法性、npm 渠道必须有 `npmName`)
+4. 若引入新技能名到任务书/规范中,同步更新母技能 `SKILL.md` §2
+
+**条目字段说明**:
+
+| 字段 | 必填 | 说明 |
+|------|------|------|
+| `name` | ✅ | 仓库名 / Hermes 技能名 |
+| `displayName` | ✅ | 中文显示名 |
+| `tier` | ✅ | `system` / `service` / `application` |
+| `summary` | ✅ | 一句话作用说明 |
+| `platforms` | ✅ | 渠道数组,取值 `gogs` / `github` / `npm` / `skillhub` |
+| `status` | ✅ | `published` / `draft` / `deprecated` |
+| `tags` | ✅ | 标签数组 |
+| `npmName` | npm 渠道必填 | npm 包名(可能与仓库名不同) |
+| `npmVersion` | 建议 | 当前 npm 版本 |
+| `skillhubSlug` | skillhub 渠道必填 | `fmode-skill-<name>` |
+| `version` | 建议 | 仓库当前版本 |
+| `kind` | 可选 | `plugin` 等特殊形态 |
+| `isMeta` | 可选 | 是否为母技能 |
+
+---
+
+## License
+
+MIT © 2026 Fmode (未来飞马)

+ 511 - 0
lib/bootstrap.mjs

@@ -0,0 +1,511 @@
+/**
+ * 一键凭证供给 —— ~/.fmode/ 自举
+ * ---------------------------------------------------------------------------
+ * 目标:新机器/新容器上,让技能在「零手工配置」前提下拿到可用的 Fmode 凭据。
+ *
+ * ⚠️ 诚实声明(2026-09-22 实测,务必先读)
+ * ---------------------------------------------------------------------------
+ * 任务书里描述的「手机号 + 验证码 → 创建 ~/.fmode/」路径依赖端点
+ *   POST /api/fmode/verifycode
+ * 该端点**当前实测 404,服务端未上线**(状态 planned,见 lib/platform.mjs)。
+ * 因此本模块**不会**伪造短信流程假装成功。
+ *
+ * 当前**真实可用**的自举路径(生产实测,与 skill-listen / skill-vision 同源):
+ *
+ *   用户登录 FMODE Studio 拿到 sessionToken
+ *        ↓  写入 FMODE_SESSION_TOKEN 环境变量 或 ~/.fmode/config.json
+ *   POST https://server.fmode.cn/api/fmode/voc-skill/install-prompt
+ *        (header: x-parse-session-token)
+ *        ↓  从 body.data.prompt 文本中提取 /sk-(?!ant-)[A-Za-z0-9_-]{8,}/
+ *   fmode API token(sk- 开头)—— 仅内存持有,不落盘、不进日志
+ *
+ * 一旦 /api/fmode/verifycode 上线,把 platform.mjs 里 verifyCode.status 改为
+ * 'live',本模块的 requestVerifyCode() / verifyAndProvision() 即自动启用。
+ */
+
+import fs from 'node:fs';
+import path from 'node:path';
+import os from 'node:os';
+
+import { ENDPOINTS, TOKEN_RULES, PLATFORM } from './platform.mjs';
+
+const BOM_RE = /^/;
+
+// ============================================================
+// 路径解析
+// ============================================================
+
+/**
+ * 解析 ~/.fmode 目录。优先级:
+ *   1. FMODE_HOME 环境变量
+ *   2. <home>/.fmode
+ * @returns {string}
+ */
+export function resolveFmodeDir() {
+  if (process.env.FMODE_HOME) return path.resolve(process.env.FMODE_HOME);
+  return path.join(os.homedir(), '.fmode');
+}
+
+/** 用户级 config.json 路径 */
+export function resolveConfigPath() {
+  return path.join(resolveFmodeDir(), 'config.json');
+}
+
+/** 安全读 JSON(剥 BOM,失败返回 null) */
+function readJson(file) {
+  try {
+    if (!fs.existsSync(file)) return null;
+    return JSON.parse(fs.readFileSync(file, 'utf-8').replace(BOM_RE, ''));
+  } catch {
+    return null;
+  }
+}
+
+// ============================================================
+// 凭据解析(标准 5 级链)
+// ============================================================
+
+/**
+ * 第 0 级:解析 sessionToken。
+ * 来源:FMODE_SESSION_TOKEN 环境变量 → ~/.fmode/config.json 的 sessionToken。
+ * @returns {{token: string, source: string}|null}
+ */
+export function resolveSessionToken() {
+  const env = process.env.FMODE_SESSION_TOKEN;
+  if (env && env.trim()) {
+    return { token: env.trim(), source: 'env:FMODE_SESSION_TOKEN' };
+  }
+  const cfg = readJson(resolveConfigPath());
+  if (cfg) {
+    const t = cfg.sessionToken || (cfg.user && cfg.user.sessionToken) || null;
+    if (t && String(t).trim()) {
+      return { token: String(t).trim(), source: `${resolveConfigPath()}#sessionToken` };
+    }
+  }
+  return null;
+}
+
+/**
+ * 校验一个字符串是否是合法的 fmode API token。
+ * 规则:sk- 开头、排除 sk-ant-、若设了 ANTHROPIC_BASE_URL 必须指向 fmode。
+ * @param {string} token
+ * @returns {{ok: boolean, reason?: string}}
+ */
+export function validateToken(token) {
+  if (!token || typeof token !== 'string') return { ok: false, reason: 'token 为空' };
+  const t = token.trim();
+  if (!t.startsWith(TOKEN_RULES.prefix)) {
+    return { ok: false, reason: `token 必须以 "${TOKEN_RULES.prefix}" 开头` };
+  }
+  if (t.startsWith(TOKEN_RULES.exclude)) {
+    return { ok: false, reason: `拒绝真正的 Anthropic 官方 key("${TOKEN_RULES.exclude}" 前缀)` };
+  }
+  const base = process.env.ANTHROPIC_BASE_URL;
+  if (base && !base.includes(TOKEN_RULES.baseUrlMustInclude)) {
+    return { ok: false, reason: `ANTHROPIC_BASE_URL=${base} 未指向 fmode,拒绝使用该 token` };
+  }
+  return { ok: true };
+}
+
+/**
+ * 第 0 级自举:sessionToken → fmode API token。
+ * token 仅内存持有,不落盘不进日志(与 listen/vision 生产实现一致)。
+ *
+ * @param {string} sessionToken
+ * @param {{timeoutMs?: number, base?: string}} [opts]
+ * @returns {Promise<{token: string, source: string}|null>} 失败返回 null(调用方回落下一级)
+ */
+export async function fetchApiTokenFromSession(sessionToken, opts = {}) {
+  if (!sessionToken) return null;
+  const base = (opts.base || PLATFORM.gatewayBase).replace(/\/$/, '');
+  try {
+    const res = await fetch(`${base}/api/fmode/voc-skill/install-prompt`, {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        'x-parse-session-token': sessionToken,
+      },
+      body: JSON.stringify({
+        channel: 'claude-code',
+        scope: 'user',
+        source: 'skill-core-guide-bootstrap',
+      }),
+      signal: AbortSignal.timeout(opts.timeoutMs || 15000),
+    });
+    if (!res.ok) return null;
+    const body = await res.json().catch(() => null);
+    const prompt =
+      body && body.data && typeof body.data.prompt === 'string' ? body.data.prompt : '';
+    const m = prompt.match(TOKEN_RULES.extractRe);
+    if (!m) return null;
+    return { token: m[0], source: 'sessionToken 自举(voc-skill/install-prompt)' };
+  } catch {
+    // 网络失败一律回落,不泄露错误细节
+    return null;
+  }
+}
+
+/**
+ * 标准 5 级凭据解析链。命中即用,全失败返回 null。
+ *
+ * 0. sessionToken 自举(FMODE_SESSION_TOKEN / ~/.fmode/config.json)
+ * 1. 环境变量 FMODE_API_TOKEN
+ * 2. ~/.fmode/config.json → fmodeApiToken / newapiToken
+ * 3. <cwd>/.fmode/config.json → fmodeApiToken / newapiToken
+ * 4. ~/.claude/settings.json(含 .local / 项目级)的 env.ANTHROPIC_AUTH_TOKEN
+ *
+ * @param {{cwd?: string, timeoutMs?: number}} [opts]
+ * @returns {Promise<{token: string, source: string, level: number}|null>}
+ */
+export async function resolveApiToken(opts = {}) {
+  const cwd = opts.cwd || process.cwd();
+
+  // ---- 0. sessionToken 自举 ----
+  const sess = resolveSessionToken();
+  if (sess) {
+    const boot = await fetchApiTokenFromSession(sess.token, { timeoutMs: opts.timeoutMs });
+    if (boot) {
+      const v = validateToken(boot.token);
+      if (v.ok) return { token: boot.token, source: boot.source, level: 0 };
+    }
+  }
+
+  // ---- 1. 环境变量 ----
+  const env = process.env.FMODE_API_TOKEN;
+  if (env && validateToken(env).ok) {
+    return { token: env.trim(), source: 'env:FMODE_API_TOKEN', level: 1 };
+  }
+
+  // ---- 2. 用户级 config ----
+  const userCfg = readJson(resolveConfigPath());
+  if (userCfg) {
+    const t = userCfg.fmodeApiToken || userCfg.newapiToken;
+    if (t && validateToken(t).ok) {
+      return { token: String(t).trim(), source: `${resolveConfigPath()}#fmodeApiToken`, level: 2 };
+    }
+  }
+
+  // ---- 3. 项目级 config ----
+  const projCfg = readJson(path.join(cwd, '.fmode', 'config.json'));
+  if (projCfg) {
+    const t = projCfg.fmodeApiToken || projCfg.newapiToken;
+    if (t && validateToken(t).ok) {
+      return { token: String(t).trim(), source: `${cwd}/.fmode/config.json#fmodeApiToken`, level: 3 };
+    }
+  }
+
+  // ---- 4. Claude Code settings ----
+  const settingsFiles = [
+    path.join(os.homedir(), '.claude', 'settings.json'),
+    path.join(os.homedir(), '.claude', 'settings.local.json'),
+    path.join(cwd, '.claude', 'settings.json'),
+    path.join(cwd, '.claude', 'settings.local.json'),
+  ];
+  for (const f of settingsFiles) {
+    const j = readJson(f);
+    const t = j && j.env && j.env.ANTHROPIC_AUTH_TOKEN;
+    if (t && validateToken(t).ok) {
+      return { token: String(t).trim(), source: `${f}#env.ANTHROPIC_AUTH_TOKEN`, level: 4 };
+    }
+  }
+
+  return null;
+}
+
+// ============================================================
+// 目录 / 配置文件供给
+// ============================================================
+
+/**
+ * 确保 ~/.fmode/ 目录结构存在(幂等)。
+ * @param {{fmodeDir?: string}} [opts]
+ * @returns {{fmodeDir: string, credentialsDir: string, created: string[]}}
+ */
+export function ensureFmodeDir(opts = {}) {
+  const fmodeDir = opts.fmodeDir || resolveFmodeDir();
+  const credentialsDir = path.join(fmodeDir, 'credentials');
+  const projectsDir = path.join(fmodeDir, 'projects');
+  const created = [];
+
+  for (const d of [fmodeDir, credentialsDir, projectsDir]) {
+    if (!fs.existsSync(d)) {
+      fs.mkdirSync(d, { recursive: true, mode: 0o700 });
+      created.push(d);
+    }
+  }
+  // 凭据目录强制 700
+  try {
+    fs.chmodSync(credentialsDir, 0o700);
+  } catch {
+    /* 非 POSIX 或权限不足时忽略 */
+  }
+
+  return { fmodeDir, credentialsDir, projectsDir, created };
+}
+
+/**
+ * 写入 ~/.fmode/config.json(幂等合并,绝不覆盖已有字段)。
+ * ⚠️ 只写非敏感字段。sessionToken 等敏感值由用户自行写入,本函数不代写。
+ *
+ * @param {object} patch 要合并进 config 的字段
+ * @param {{fmodeDir?: string, mode?: number}} [opts]
+ * @returns {{path: string, written: boolean, merged: object}}
+ */
+export function writeConfig(patch = {}, opts = {}) {
+  const fmodeDir = opts.fmodeDir || resolveFmodeDir();
+  if (!fs.existsSync(fmodeDir)) fs.mkdirSync(fmodeDir, { recursive: true, mode: 0o700 });
+
+  const file = path.join(fmodeDir, 'config.json');
+  const existing = readJson(file) || {};
+
+  // 敏感字段白名单外的一律不写
+  const FORBIDDEN = ['apiKey', 'apiKeys', 'sessionToken', 'githubToken', 'password', 'secret'];
+  const safe = {};
+  for (const [k, v] of Object.entries(patch)) {
+    if (FORBIDDEN.includes(k)) continue;
+    safe[k] = v;
+  }
+
+  const merged = { ...existing, ...safe };
+  const changed = JSON.stringify(existing) !== JSON.stringify(merged);
+
+  if (changed) {
+    fs.writeFileSync(file, JSON.stringify(merged, null, 2), { mode: 0o600 });
+    try {
+      fs.chmodSync(file, 0o600);
+    } catch {
+      /* ignore */
+    }
+  }
+
+  return { path: file, written: changed, merged };
+}
+
+/** 写一个凭据文件到 credentials/(600 权限) */
+export function writeCredential(name, content, opts = {}) {
+  const { credentialsDir } = ensureFmodeDir(opts);
+  const file = path.join(credentialsDir, name);
+  fs.writeFileSync(file, content, { mode: 0o600 });
+  try {
+    fs.chmodSync(file, 0o600);
+  } catch {
+    /* ignore */
+  }
+  return file;
+}
+
+// ============================================================
+// 短信验证码路径(planned —— 端点未上线)
+// ============================================================
+
+/**
+ * 请求手机号验证码。
+ * ⚠️ 依赖 POST /api/fmode/verifycode,当前实测 404(未上线)。
+ * 本函数会**先探测**端点,未上线时返回 { ok:false, planned:true },
+ * 调用方应回落到 sessionToken 路径,**不要**把它当成发送成功。
+ *
+ * @param {string} phone
+ * @param {{timeoutMs?: number, base?: string}} [opts]
+ * @returns {Promise<{ok: boolean, planned?: boolean, status?: number, reason?: string}>}
+ */
+export async function requestVerifyCode(phone, opts = {}) {
+  if (!phone || !/^1[3-9]\d{9}$/.test(String(phone).trim())) {
+    return { ok: false, reason: '手机号格式不合法(需中国大陆 11 位手机号)' };
+  }
+
+  const ep = ENDPOINTS.verifyCode;
+  const base = (opts.base || PLATFORM.gatewayBase).replace(/\/$/, '');
+
+  let status = 0;
+  try {
+    const res = await fetch(`${base}/api/fmode/verifycode`, {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ phone: String(phone).trim(), action: 'send' }),
+      signal: AbortSignal.timeout(opts.timeoutMs || 12000),
+    });
+    status = res.status;
+  } catch (err) {
+    return { ok: false, reason: `网络不可达:${err.message}` };
+  }
+
+  if (status === 404) {
+    return {
+      ok: false,
+      planned: true,
+      status,
+      reason:
+        `${ep.url} 返回 404 —— 该端点服务端未上线(真值表状态:${ep.status})。` +
+        `请改用 sessionToken 路径(见 resolveSessionToken / fetchApiTokenFromSession)。`,
+    };
+  }
+
+  if (status >= 200 && status < 300) {
+    return { ok: true, status };
+  }
+
+  return { ok: false, status, reason: `端点返回 HTTP ${status}` };
+}
+
+/**
+ * 验证码校验 + 开户(planned —— 端点未上线)。
+ * 同 requestVerifyCode,端点未上线时显式返回 planned,不伪造成功。
+ *
+ * @param {string} phone
+ * @param {string} code
+ * @param {{timeoutMs?: number, base?: string}} [opts]
+ * @returns {Promise<{ok: boolean, planned?: boolean, status?: number, reason?: string}>}
+ */
+export async function verifyAndProvision(phone, code, opts = {}) {
+  if (!code || !/^\d{4,8}$/.test(String(code).trim())) {
+    return { ok: false, reason: '验证码格式不合法' };
+  }
+  const base = (opts.base || PLATFORM.gatewayBase).replace(/\/$/, '');
+
+  let status = 0;
+  let body = null;
+  try {
+    const res = await fetch(`${base}/api/fmode/verifycode`, {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ phone: String(phone).trim(), code: String(code).trim(), action: 'verify' }),
+      signal: AbortSignal.timeout(opts.timeoutMs || 12000),
+    });
+    status = res.status;
+    body = await res.json().catch(() => null);
+  } catch (err) {
+    return { ok: false, reason: `网络不可达:${err.message}` };
+  }
+
+  if (status === 404) {
+    return {
+      ok: false,
+      planned: true,
+      status,
+      reason:
+        'POST /api/fmode/verifycode 未上线(404)。' +
+        '当前一键凭证供给请走 sessionToken 路径:登录 FMODE Studio → ' +
+        'export FMODE_SESSION_TOKEN=... 或写入 ~/.fmode/config.json 的 sessionToken。',
+    };
+  }
+  if (status >= 200 && status < 300) {
+    return { ok: true, status, body };
+  }
+  return { ok: false, status, reason: `端点返回 HTTP ${status}` };
+}
+
+// ============================================================
+// 编排:一键凭证供给
+// ============================================================
+
+/**
+ * 一键凭证供给主入口。
+ *
+ * 流程:
+ *   1. 确保 ~/.fmode/ 目录结构(幂等)
+ *   2. 走标准 5 级凭据链解析 token
+ *   3. 命中 → 返回可用凭据
+ *   4. 未命中 → 尝试 planned 短信路径探测,明确报告未上线,并给出
+ *      **可执行的**下一步指引(不伪造成功)
+ *
+ * @param {{cwd?: string, phone?: string, code?: string, dryRun?: boolean, timeoutMs?: number}} [opts]
+ * @returns {Promise<object>}
+ */
+export async function bootstrap(opts = {}) {
+  const report = {
+    ok: false,
+    fmodeDir: resolveFmodeDir(),
+    steps: [],
+    token: null,
+    guidance: [],
+  };
+
+  const step = (name, status, detail) => report.steps.push({ name, status, detail });
+
+  // 1. 目录
+  const dirs = ensureFmodeDir(opts);
+  step(
+    'ensure-fmode-dir',
+    'ok',
+    dirs.created.length ? `已创建 ${dirs.created.length} 个目录` : '目录已存在(幂等)',
+  );
+
+  // 2. 凭据链
+  const resolved = await resolveApiToken(opts);
+  if (resolved) {
+    report.ok = true;
+    report.token = { source: resolved.source, level: resolved.level, masked: maskToken(resolved.token) };
+    step('resolve-credential', 'ok', `第 ${resolved.level} 级命中:${resolved.source}`);
+    step('verify-credential', 'ok', 'token 形态校验通过(sk- 前缀,非 sk-ant-)');
+    return report;
+  }
+
+  step('resolve-credential', 'fail', '5 级凭据链全部未命中');
+
+  // 3. 探测 planned 短信路径
+  if (opts.phone) {
+    const vc = await requestVerifyCode(opts.phone, opts);
+    if (vc.planned) {
+      step('sms-verifycode', 'planned', vc.reason);
+    } else if (vc.ok) {
+      step('sms-verifycode', 'ok', '验证码已发送');
+      report.guidance.push('请向用户索取验证码,然后调用 verifyAndProvision(phone, code)');
+      return report;
+    } else {
+      step('sms-verifycode', 'fail', vc.reason || '发送失败');
+    }
+  } else {
+    step('sms-verifycode', 'skipped', '未提供 phone,跳过短信路径');
+  }
+
+  // 4. 明确指引
+  report.guidance = [
+    '当前可用路径(sessionToken 自举,生产已验证):',
+    '  1) 浏览器登录 FMODE Studio,取得 sessionToken(形如 r:xxxx)',
+    `  2) export FMODE_SESSION_TOKEN='r:xxxx'   # 或写入 ${resolveConfigPath()} 的 "sessionToken" 字段`,
+    '  3) 重跑 skill-core bootstrap —— 将自动换取 fmode API token(仅内存持有)',
+    '',
+    '备选路径(手工配置,长期有效):',
+    `  在 ${resolveConfigPath()} 写入 { "fmodeApiToken": "sk-..." }`,
+    '  或在 Claude Code settings.json 的 env.ANTHROPIC_AUTH_TOKEN 中配置(平台 SK 即此值)',
+    '',
+    `短信验证码路径依赖 ${ENDPOINTS.verifyCode.url},该端点当前未上线(404),`,
+    '上线后本模块自动启用,无需改代码。',
+  ];
+
+  return report;
+}
+
+/** token 脱敏展示(只留头尾,绝不打印本体) */
+export function maskToken(token) {
+  if (!token || typeof token !== 'string') return '(none)';
+  const t = token.trim();
+  if (t.length <= 12) return `${t.slice(0, 3)}***`;
+  return `${t.slice(0, 6)}...${t.slice(-4)}`;
+}
+
+/** 人类可读的自举状态摘要 */
+export function describeBootstrapStatus(report) {
+  const lines = [];
+  const icon = { ok: '✅', fail: '❌', planned: '🕓', skipped: '⏭️ ' };
+  lines.push('\n  Fmode 凭证自举状态');
+  lines.push('  ' + '─'.repeat(60));
+  lines.push(`  ~/.fmode 目录:${report.fmodeDir}`);
+  for (const s of report.steps) {
+    lines.push(`  ${icon[s.status] || '·'} ${s.name}:${s.detail}`);
+  }
+  lines.push('  ' + '─'.repeat(60));
+  if (report.ok && report.token) {
+    lines.push(`  结果:✅ 凭据可用(${report.token.source},${report.token.masked})`);
+  } else {
+    lines.push('  结果:❌ 未取得可用凭据');
+  }
+  if (report.guidance.length) {
+    lines.push('');
+    for (const g of report.guidance) lines.push(`  ${g}`);
+  }
+  lines.push('');
+  return lines.join('\n');
+}
+
+export default { bootstrap, resolveApiToken, resolveSessionToken, ensureFmodeDir };

+ 676 - 0
lib/check.mjs

@@ -0,0 +1,676 @@
+/**
+ * 自动质检引擎 —— 六项检查
+ * ---------------------------------------------------------------------------
+ * 在新技能开发完成后运行,逐项验证「能不能真的交付」。
+ *
+ * 设计原则(源自平台真实事故教训):
+ *   1. **不伪造结果** —— 每项检查必须给出可复核的证据(evidence),
+ *      拿不到证据就是 fail 或 skip,绝不默认 pass。
+ *   2. **网络检查显式降级** —— 离线环境下标 skip 而非 fail,但 skip 会
+ *      汇总进「未验证项」并影响最终 exit code(除非 --allow-skip)。
+ *   3. **planned 端点不算通过** —— 端点状态取自 lib/platform.mjs 真值表;
+ *      对 404 的 planned 端点做联通检查,结果是 skip 而不是 pass。
+ *
+ * 六项检查:
+ *   1. functional      功能完整性      —— 运行 demo/test 脚本
+ *   2. apiConnectivity Fmode API 联通  —— 探测 api.fmode.cn / server.fmode.cn
+ *   3. sop             基础 SOP 跑通   —— skillhub publish --dry-run
+ *   4. dashboard       看板后台就绪    —— skill-package-manifest.json 存在且合法
+ *   5. loop            Loop 迭代能力   —— npx --yes <skill>@latest 可解析
+ *   6. multiRuntime    多端可用性      —— CLI + ESM import 双通道实测
+ */
+
+import fs from 'node:fs';
+import path from 'node:path';
+import os from 'node:os';
+import { spawnSync } from 'node:child_process';
+import { fileURLToPath } from 'node:url';
+
+import {
+  ENDPOINTS,
+  PLATFORM,
+  validatePackageJson,
+  validateManifest,
+  validateFrontmatter,
+  parseFrontmatter,
+} from './index.mjs';
+
+// ============================================================
+// 结果原语
+// ============================================================
+
+/** @typedef {'pass'|'fail'|'skip'} CheckStatus */
+
+/**
+ * @typedef {Object} CheckResult
+ * @property {string} id
+ * @property {string} title
+ * @property {CheckStatus} status
+ * @property {string} detail
+ * @property {string[]} evidence  可复核的证据(命令输出片段 / 文件路径 / HTTP 码)
+ * @property {string} [fix]       失败时的修复建议
+ */
+
+function pass(id, title, detail, evidence = []) {
+  return { id, title, status: 'pass', detail, evidence };
+}
+function fail(id, title, detail, evidence = [], fix = '') {
+  return { id, title, status: 'fail', detail, evidence, fix };
+}
+function skip(id, title, detail, evidence = [], fix = '') {
+  return { id, title, status: 'skip', detail, evidence, fix };
+}
+
+// ============================================================
+// 工具
+// ============================================================
+
+/** 带超时的 fetch,永不抛异常 */
+async function probe(url, { method = 'GET', headers = {}, body, timeoutMs = 12000 } = {}) {
+  try {
+    const res = await fetch(url, {
+      method,
+      headers: { 'Content-Type': 'application/json', ...headers },
+      body: body ? JSON.stringify(body) : undefined,
+      signal: AbortSignal.timeout(timeoutMs),
+    });
+    return { ok: true, status: res.status };
+  } catch (err) {
+    return { ok: false, status: 0, error: err && err.message ? err.message : String(err) };
+  }
+}
+
+/** 安全读 JSON */
+function readJson(file) {
+  try {
+    return JSON.parse(fs.readFileSync(file, 'utf-8').replace(/^/, ''));
+  } catch {
+    return null;
+  }
+}
+
+/** 判断路径是否可执行文件 */
+function exists(p) {
+  try {
+    return fs.existsSync(p);
+  } catch {
+    return false;
+  }
+}
+
+/** 定位 npx / npm 可执行文件(跨环境候选) */
+function which(bin) {
+  const home = os.homedir();
+  const candidates = [
+    path.join(home, '.local', 'bin', bin),
+    path.join(home, 'bin', bin),
+    `/opt/data/npm-global/bin/${bin}`,
+    `/usr/local/bin/${bin}`,
+    `/usr/bin/${bin}`,
+  ];
+  for (const c of candidates) if (exists(c)) return c;
+  const r = spawnSync('which', [bin], { encoding: 'utf-8' });
+  if (r.status === 0 && r.stdout.trim()) return r.stdout.trim();
+  return null;
+}
+
+/** 运行命令并捕获输出 */
+function run(cmd, args, opts = {}) {
+  const r = spawnSync(cmd, args, {
+    encoding: 'utf-8',
+    timeout: opts.timeoutMs || 120000,
+    cwd: opts.cwd,
+    env: { ...process.env, ...(opts.env || {}) },
+  });
+  return {
+    status: r.status,
+    stdout: (r.stdout || '').trim(),
+    stderr: (r.stderr || '').trim(),
+    error: r.error ? r.error.message : null,
+  };
+}
+
+/** 截断长输出用于 evidence */
+function clip(s, n = 300) {
+  if (!s) return '';
+  const t = String(s).replace(/\s+/g, ' ').trim();
+  return t.length > n ? `${t.slice(0, n)}…` : t;
+}
+
+// ============================================================
+// 检查 1:功能完整性
+// ============================================================
+
+/**
+ * 运行 demo/test 脚本,验证技能功能真的跑得起来。
+ * 探测顺序:package.json scripts.test → scripts.demo → test/ 下的 .mjs
+ * @param {string} dir
+ * @param {{timeoutMs?: number}} [opts]
+ * @returns {Promise<CheckResult>}
+ */
+export async function checkFunctional(dir, opts = {}) {
+  const id = 'functional';
+  const title = '功能完整性';
+
+  // 递归护栏:若本进程是被上层质检 spawn 出来的(npm test → runChecks → 本函数),
+  // 再跑一次 npm test 会无限递归。此时直接跳过,由最外层负责结论。
+  // 注意:护栏只看**本进程**的环境变量——由 checkFunctional 在 spawn 子进程时注入,
+  // 不会污染调用方进程(早期实现在 runChecks 里设置该变量,导致父进程自身被误跳)。
+  if (process.env.SKILL_CORE_CHECKING === '1') {
+    return skip(
+      id,
+      title,
+      '检测到嵌套质检调用(SKILL_CORE_CHECKING=1),跳过以避免无限递归',
+      ['本项已由最外层质检运行'],
+      '如需独立验证功能,直接运行 npm test 或在项目根跑 skill-core check',
+    );
+  }
+
+  const pkg = readJson(path.join(dir, 'package.json'));
+
+  if (!pkg) {
+    return fail(id, title, 'package.json 不存在或无法解析,无法定位测试入口', [`${dir}/package.json`],
+      '补一个合法的 package.json(type: module)');
+  }
+
+  const scripts = pkg.scripts || {};
+  const evidence = [];
+
+  // 优先 npm test
+  if (scripts.test) {
+    const npm = which('npm');
+    if (!npm) {
+      return skip(id, title, '检测到 scripts.test 但环境无 npm 可执行文件', ['scripts.test = ' + scripts.test],
+        '安装 Node.js / npm 后重跑');
+    }
+    const r = run(npm, ['test', '--silent'], {
+      cwd: dir,
+      timeoutMs: opts.timeoutMs || 180000,
+      env: { SKILL_CORE_CHECKING: '1' },
+    });
+    evidence.push(`$ npm test → exit ${r.status}`);
+    if (r.stdout) evidence.push(clip(r.stdout));
+    if (r.stderr) evidence.push(clip(r.stderr));
+    if (r.status === 0) return pass(id, title, 'npm test 通过', evidence);
+    return fail(id, title, `npm test 退出码 ${r.status}`, evidence,
+      '先本地跑通 npm test 再发布;测试失败不得标记完成');
+  }
+
+  // 回落到 test/ 下的 .mjs
+  const testDir = path.join(dir, 'test');
+  if (exists(testDir)) {
+    const files = fs.readdirSync(testDir).filter((f) => f.endsWith('.mjs'));
+    if (files.length) {
+      let allOk = true;
+      for (const f of files) {
+        const r = run(process.execPath, [path.join('test', f)], { cwd: dir, timeoutMs: 120000 });
+        evidence.push(`$ node test/${f} → exit ${r.status}`);
+        if (r.status !== 0) {
+          allOk = false;
+          evidence.push(clip(r.stderr || r.stdout));
+        }
+      }
+      if (allOk) return pass(id, title, `test/ 下 ${files.length} 个测试脚本全部通过`, evidence);
+      return fail(id, title, 'test/ 下有脚本执行失败', evidence, '修复失败用例后重跑');
+    }
+  }
+
+  return skip(id, title, '未发现测试入口(无 scripts.test,test/ 下无 .mjs)', [`${dir}/test`],
+    '添加 test/smoke.mjs 并声明 scripts.test');
+}
+
+// ============================================================
+// 检查 2:Fmode API 联通
+// ============================================================
+
+/**
+ * 探测 Fmode 平台端点。区分 live(401/200 均算联通)与 planned(404 → skip)。
+ * @param {{token?: string, offline?: boolean}} [opts]
+ * @returns {Promise<CheckResult>}
+ */
+export async function checkApiConnectivity(opts = {}) {
+  const id = 'apiConnectivity';
+  const title = 'Fmode API 联通';
+  const evidence = [];
+
+  if (opts.offline) {
+    return skip(id, title, '--offline 指定跳过网络检查', [], '联网后重跑以验证 API 联通');
+  }
+
+  const token = opts.token || process.env.FMODE_API_TOKEN || '';
+  const headers = token ? { Authorization: `Bearer ${token}` } : {};
+
+  // 2a. LLM 网关:无 token 期望 401,有 token 期望 200
+  // ⚠️ 必须用 POST 探测:该端点是 POST-only,GET 会返回 404(method not allowed),
+  //    误判成「端点不存在」。这是实测踩过的坑。
+  const llm = await probe(ENDPOINTS.llmChat.url, { method: 'POST', headers, body: {} });
+  evidence.push(`POST ${ENDPOINTS.llmChat.url} → HTTP ${llm.status}${llm.error ? ` (${llm.error})` : ''}`);
+
+  if (!llm.ok) {
+    return skip(id, title, `无法访问 ${PLATFORM.apiBase}(网络不可达)`, evidence,
+      '检查网络/DNS,或加 --offline 跳过本项');
+  }
+
+  const llmReachable = llm.status === 200 || llm.status === 401;
+  if (!llmReachable) {
+    return fail(id, title, `LLM 网关返回意外状态 ${llm.status}`, evidence,
+      `确认 ${ENDPOINTS.llmChat.url} 可用;若平台迁移请更新 lib/platform.mjs`);
+  }
+
+  // 2b. 网关存活
+  const gw = await probe(ENDPOINTS.listenTranscribe.url, {
+    method: 'POST',
+    headers,
+    body: {},
+  });
+  evidence.push(`POST ${ENDPOINTS.listenTranscribe.url} → HTTP ${gw.status}${gw.error ? ` (${gw.error})` : ''}`);
+
+  // 2c. planned 端点:探测但结论为 skip(不算通过)
+  const planned = Object.values(ENDPOINTS).filter((e) => e.status !== 'live');
+  const plannedNotes = [];
+  for (const e of planned) {
+    const p = await probe(e.url, { method: 'POST', headers, body: {} });
+    plannedNotes.push(`${e.id}=${p.status}`);
+  }
+  evidence.push(`planned 端点探测:${plannedNotes.join(', ')}`);
+
+  const detail = token
+    ? 'LLM 网关与业务网关均可达(带 token)'
+    : 'LLM 网关与业务网关均可达(匿名,401=端点存在需鉴权)';
+
+  if (!gw.ok || (gw.status !== 401 && gw.status !== 200)) {
+    evidence.push(`网关 ${ENDPOINTS.listenTranscribe.url} 状态异常:${gw.status}`);
+    return fail(id, title, `业务网关返回 ${gw.status}`, evidence,
+      '确认 server.fmode.cn 可用;planned 端点(storage/vision/image/verifycode)404 属预期');
+  }
+
+  return pass(id, title, detail, evidence);
+}
+
+// ============================================================
+// 检查 3:基础 SOP 跑通
+// ============================================================
+
+/**
+ * 校验 skillhub 发布前置条件,并在 CLI 可用时执行 --dry-run。
+ * @param {string} dir
+ * @returns {Promise<CheckResult>}
+ */
+export async function checkSop(dir) {
+  const id = 'sop';
+  const title = '基础 SOP 跑通';
+  const evidence = [];
+
+  // 3a. SKILL.md 存在
+  const skillMd = path.join(dir, 'SKILL.md');
+  if (!exists(skillMd)) {
+    return fail(id, title, '根目录缺少 SKILL.md —— skillhub 发布硬性要求', [skillMd],
+      '创建 SKILL.md 并使用 skillhub frontmatter(slug/displayName/version/summary/license)');
+  }
+
+  const text = fs.readFileSync(skillMd, 'utf-8');
+  const { data } = parseFrontmatter(text);
+  evidence.push(`SKILL.md frontmatter keys: ${Object.keys(data).join(', ') || '(无)'}`);
+
+  // 3b. 必须满足 skillhub 格式
+  const sh = validateFrontmatter(text, 'skillhub');
+  if (!sh.ok) {
+    // 允许 Hermes 格式 + slug 字段混用:若 hermes 格式通过则降级为 warning
+    const hermes = validateFrontmatter(text, 'hermes');
+    if (hermes.ok) {
+      evidence.push('检测到 Hermes 格式 frontmatter(skillhub 必需字段缺失,仅作警告)');
+    } else {
+      return fail(id, title, `SKILL.md frontmatter 不合法:${sh.errors.join(';')}`, evidence,
+        '补全 slug / displayName / version / summary / license 五个字段');
+    }
+  }
+
+  // 3c. skillhub CLI 可用则跑 dry-run
+  const cli = exists(PLATFORM.skillhub.cliPath.replace('~', os.homedir()))
+    ? PLATFORM.skillhub.cliPath.replace('~', os.homedir())
+    : which('skillhub');
+
+  if (!cli) {
+    return skip(id, title, 'skillhub CLI 未安装,无法执行 --dry-run', evidence,
+      `安装:curl -fsSL ${PLATFORM.skillhub.cliInstall} | bash -s -- --cli-only`);
+  }
+
+  const r = run(cli, ['publish', dir, '--dry-run', '--json'], { timeoutMs: 120000 });
+  evidence.push(`$ skillhub publish ${dir} --dry-run --json → exit ${r.status}`);
+  if (r.stdout) evidence.push(clip(r.stdout, 400));
+  if (r.stderr) evidence.push(clip(r.stderr, 400));
+
+  if (r.status === 0) return pass(id, title, 'skillhub publish --dry-run 预检通过', evidence);
+
+  // 区分「预检失败」与「CLI 环境问题」
+  const combined = `${r.stdout} ${r.stderr}`;
+  if (/not logged in|未登录|api key|401|unauthor/i.test(combined)) {
+    return skip(id, title, 'skillhub CLI 未登录,无法完成预检', evidence,
+      `登录:skillhub login --key <API_KEY> --host ${PLATFORM.skillhub.host}`);
+  }
+
+  return fail(id, title, `skillhub 预检退出码 ${r.status}`, evidence,
+    '按上面的输出修正 SKILL.md / 打包内容后重跑');
+}
+
+// ============================================================
+// 检查 4:看板后台就绪
+// ============================================================
+
+/**
+ * 校验 skill-package-manifest.json(看板数据源)存在且结构合法。
+ * @param {string} dir
+ * @returns {Promise<CheckResult>}
+ */
+export async function checkDashboard(dir) {
+  const id = 'dashboard';
+  const title = '看板后台就绪';
+  const file = path.join(dir, 'skill-package-manifest.json');
+
+  if (!exists(file)) {
+    return fail(id, title, '缺少 skill-package-manifest.json —— 看板无法索引本技能', [file],
+      '添加 skill-package-manifest.json(name/version/description/skills[]/install)');
+  }
+
+  const m = readJson(file);
+  const v = validateManifest(m);
+  const evidence = [`${file} 已存在`];
+
+  if (!v.ok) {
+    evidence.push(...v.errors);
+    return fail(id, title, `清单结构不合法:${v.errors.join(';')}`, evidence,
+      '修正清单字段后重跑');
+  }
+
+  evidence.push(`name=${m.name} version=${m.version} skills=${m.skills.length}`);
+  if (v.warnings.length) evidence.push(...v.warnings.map((w) => `warning: ${w}`));
+
+  return pass(id, title, 'skill-package-manifest.json 存在且结构合法', evidence);
+}
+
+// ============================================================
+// 检查 5:Loop 迭代能力
+// ============================================================
+
+/**
+ * 验证技能可通过 npx 拉取运行(迭代闭环的前提)。
+ * 默认只做「包可解析」的轻量验证(npm view),加 --deep 才真正执行 npx。
+ * @param {string} dir
+ * @param {{deep?: boolean, offline?: boolean}} [opts]
+ * @returns {Promise<CheckResult>}
+ */
+export async function checkLoop(dir, opts = {}) {
+  const id = 'loop';
+  const title = 'Loop 迭代能力';
+  const pkg = readJson(path.join(dir, 'package.json'));
+
+  if (!pkg || !pkg.name) {
+    return fail(id, title, 'package.json 缺少 name,无法验证 npx 可运行性', [], '补 name 字段');
+  }
+
+  const evidence = [`包名:${pkg.name}@${pkg.version || '0.0.0'}`];
+
+  if (opts.offline) {
+    return skip(id, title, '--offline 指定跳过', evidence, '联网后重跑');
+  }
+
+  const npm = which('npm');
+  if (!npm) {
+    return skip(id, title, '环境无 npm,无法验证 npx 可运行性', evidence, '安装 Node.js / npm');
+  }
+
+  // 5a. 轻量:包是否已在 registry 上(未发布属正常,标 skip)
+  const view = run(npm, ['view', pkg.name, 'version'], { timeoutMs: 60000 });
+  if (view.status === 0) {
+    evidence.push(`npm view ${pkg.name} version → ${view.stdout}`);
+  } else {
+    evidence.push(`npm view ${pkg.name} version → 未找到(尚未发布到 npm)`);
+  }
+
+  if (!opts.deep) {
+    if (view.status === 0) {
+      return pass(id, title, `包 ${pkg.name} 已在 npm 可解析,npx 可拉取`, evidence);
+    }
+    return skip(id, title, `包 ${pkg.name} 尚未发布到 npm,npx 通道未验证`, evidence,
+      `发布后重跑:npm publish --access public`);
+  }
+
+  // 5b. 深度:真正 npx 执行 --help
+  const npx = which('npx');
+  if (!npx) return skip(id, title, '环境无 npx', evidence, '安装 Node.js / npm');
+
+  const r = run(npx, ['--yes', `${pkg.name}@latest`, '--help'], { cwd: dir, timeoutMs: 180000 });
+  evidence.push(`$ npx --yes ${pkg.name}@latest --help → exit ${r.status}`);
+  if (r.stdout) evidence.push(clip(r.stdout));
+  if (r.stderr) evidence.push(clip(r.stderr));
+
+  if (r.status === 0) return pass(id, title, 'npx 拉取并执行成功,迭代闭环可用', evidence);
+  return fail(id, title, `npx 执行退出码 ${r.status}`, evidence,
+    '确认 bin 入口有 shebang(#!/usr/bin/env node)且文件已包含在 files 白名单中');
+}
+
+// ============================================================
+// 检查 6:多端可用性
+// ============================================================
+
+/**
+ * 验证 CLI 与 ESM import 双通道可用。
+ * @param {string} dir
+ * @param {{skipExec?: boolean}} [opts]
+ * @returns {Promise<CheckResult>}
+ */
+export async function checkMultiRuntime(dir, opts = {}) {
+  const id = 'multiRuntime';
+  const title = '多端可用性';
+  const evidence = [];
+  const pkg = readJson(path.join(dir, 'package.json'));
+
+  if (!pkg) {
+    return fail(id, title, 'package.json 无法解析', [], '修正 package.json');
+  }
+
+  // ---- 6a. ESM import ----
+  const mainEntry = path.join(dir, 'lib', 'index.mjs');
+  if (!exists(mainEntry)) {
+    return fail(id, title, '缺少 lib/index.mjs —— SDK 端不可用', [mainEntry],
+      '创建 lib/index.mjs 并 export 公共接口');
+  }
+
+  const importScript = [
+    `import * as m from ${JSON.stringify(mainEntry)};`,
+    `const keys = Object.keys(m);`,
+    `if (!keys.length) { console.error('lib/index.mjs 没有 export 任何东西'); process.exit(1); }`,
+    `console.log('ESM_OK exports=' + keys.length + ' names=' + keys.slice(0, 8).join(','));`,
+  ].join('\n');
+
+  const importRes = run(process.execPath, ['--input-type=module', '-e', importScript], {
+    cwd: dir,
+    timeoutMs: 60000,
+  });
+  evidence.push(`$ node --input-type=module -e "import(...)" → exit ${importRes.status}`);
+  if (importRes.stdout) evidence.push(clip(importRes.stdout));
+  if (importRes.stderr) evidence.push(clip(importRes.stderr));
+
+  const esmOk = importRes.status === 0;
+
+  // ---- 6b. CLI 可执行 ----
+  const binField = pkg.bin;
+  let binPath = null;
+  if (binField && typeof binField === 'object') {
+    binPath = path.join(dir, Object.values(binField)[0]);
+  } else if (typeof binField === 'string') {
+    binPath = path.join(dir, binField);
+  }
+
+  let cliOk = false;
+  if (!binPath || !exists(binPath)) {
+    evidence.push(`bin 入口不存在:${binPath || '(package.json 未声明 bin)'}`);
+  } else {
+    const head = fs.readFileSync(binPath, 'utf-8').slice(0, 200);
+    const hasShebang = head.startsWith('#!/usr/bin/env node');
+    evidence.push(`bin 入口 ${path.relative(dir, binPath)} shebang=${hasShebang ? 'ok' : '缺失'}`);
+
+    if (opts.skipExec) {
+      cliOk = hasShebang;
+      evidence.push('(--skip-exec:仅静态校验,未执行)');
+    } else {
+      const r = run(process.execPath, [binPath, '--help'], { cwd: dir, timeoutMs: 60000 });
+      evidence.push(`$ node ${path.relative(dir, binPath)} --help → exit ${r.status}`);
+      if (r.stdout) evidence.push(clip(r.stdout));
+      if (r.stderr) evidence.push(clip(r.stderr));
+      cliOk = r.status === 0;
+    }
+  }
+
+  // ---- 6c. 浏览器 bundle(可选但推荐)----
+  const browserEntry = path.join(dir, 'browser', 'index.mjs');
+  if (exists(browserEntry)) {
+    const src = fs.readFileSync(browserEntry, 'utf-8');
+    const nodeImports = [...src.matchAll(/from\s+['"]node:([a-z_]+)['"]/g)].map((m) => m[1]);
+    if (nodeImports.length) {
+      evidence.push(`browser/index.mjs 违规 import node: ${[...new Set(nodeImports)].join(', ')}`);
+    } else {
+      evidence.push('browser/index.mjs 无 node: 内置模块依赖 ✅');
+    }
+  } else {
+    evidence.push('browser/index.mjs 不存在(浏览器端未提供,非必需)');
+  }
+
+  // ---- 6d. CJS 入口存在性(ESM only 纪律)----
+  // 说明:Node 22+ 的 require(ESM) 已解禁,因此「require 能加载」不再等于「提供了 CJS 入口」。
+  // 真正要检查的是:包内**没有** .cjs 文件、package.json 里**没有** require 字段。
+  const cjsFiles = [];
+  const scanCjs = (d, depth = 0) => {
+    if (depth > 2 || !exists(d)) return;
+    for (const e of fs.readdirSync(d, { withFileTypes: true })) {
+      if (e.name === 'node_modules' || e.name === '.git') continue;
+      const p = path.join(d, e.name);
+      if (e.isDirectory()) scanCjs(p, depth + 1);
+      else if (e.name.endsWith('.cjs')) cjsFiles.push(path.relative(dir, p));
+    }
+  };
+  scanCjs(dir);
+  if (cjsFiles.length) {
+    evidence.push(`发现 .cjs 文件:${cjsFiles.join(', ')} —— 本平台 ESM only,不应提供 CJS 入口`);
+  } else {
+    evidence.push('无 .cjs 文件,符合 ESM only 约定 ✅');
+  }
+
+  if (esmOk && cliOk) {
+    return pass(id, title, 'CLI + SDK(ESM) 双通道实测可用', evidence);
+  }
+
+  const failedParts = [];
+  if (!esmOk) failedParts.push('SDK(ESM) import 失败');
+  if (!cliOk) failedParts.push('CLI --help 执行失败');
+
+  return fail(id, title, failedParts.join(';'), evidence,
+    '修复后重跑;bin 入口需 shebang + 包含在 files 白名单,lib/index.mjs 需有 export');
+}
+
+// ============================================================
+// 编排
+// ============================================================
+
+/**
+ * 六项检查注册表。
+ * @type {Array<{id: string, title: string, weight: number, run: (dir: string, opts: object) => Promise<CheckResult>}>}
+ */
+export const CHECKS = [
+  { id: 'functional', title: '功能完整性', weight: 1, run: (dir, o) => checkFunctional(dir, o) },
+  { id: 'apiConnectivity', title: 'Fmode API 联通', weight: 1, run: (dir, o) => checkApiConnectivity(o) },
+  { id: 'sop', title: '基础 SOP 跑通', weight: 1, run: (dir, o) => checkSop(dir, o) },
+  { id: 'dashboard', title: '看板后台就绪', weight: 1, run: (dir, o) => checkDashboard(dir, o) },
+  { id: 'loop', title: 'Loop 迭代能力', weight: 1, run: (dir, o) => checkLoop(dir, o) },
+  { id: 'multiRuntime', title: '多端可用性', weight: 1, run: (dir, o) => checkMultiRuntime(dir, o) },
+];
+
+/** 按 id 索引 */
+export const CHECKS_BY_ID = Object.fromEntries(CHECKS.map((c) => [c.id, c]));
+
+/**
+ * 依次运行六项检查。
+ * @param {string} dir 技能根目录
+ * @param {{only?: string[], offline?: boolean, deep?: boolean, skipExec?: boolean, token?: string, onProgress?: Function}} [opts]
+ * @returns {Promise<{dir: string, results: CheckResult[], summary: object}>}
+ */
+export async function runChecks(dir, opts = {}) {
+  const abs = path.resolve(dir);
+  if (!exists(abs)) {
+    throw new Error(`目录不存在:${abs}`);
+  }
+
+  const selected = opts.only && opts.only.length
+    ? CHECKS.filter((c) => opts.only.includes(c.id))
+    : CHECKS;
+
+  if (opts.only && opts.only.length && selected.length !== opts.only.length) {
+    const known = CHECKS.map((c) => c.id).join(', ');
+    const bad = opts.only.filter((o) => !CHECKS_BY_ID[o]);
+    throw new Error(`未知检查项:${bad.join(', ')}(可选:${known})`);
+  }
+
+  const results = [];
+  for (const c of selected) {
+    if (opts.onProgress) opts.onProgress(c.id, 'start');
+    let r;
+    try {
+      r = await c.run(abs, opts);
+    } catch (err) {
+      r = fail(c.id, c.title, `检查执行异常:${err.message}`, [String(err.stack || '')].slice(0, 3),
+        '这是检查器自身的异常,请上报 skill-core-guide');
+    }
+    results.push(r);
+    if (opts.onProgress) opts.onProgress(c.id, r.status);
+  }
+
+  return { dir: abs, results, summary: summarize(results) };
+}
+
+/**
+ * 汇总检查结果。
+ * @param {CheckResult[]} results
+ * @returns {{total: number, pass: number, fail: number, skip: number, ok: boolean, unverified: string[]}}
+ */
+export function summarize(results) {
+  const total = results.length;
+  const p = results.filter((r) => r.status === 'pass').length;
+  const f = results.filter((r) => r.status === 'fail').length;
+  const s = results.filter((r) => r.status === 'skip').length;
+  return {
+    total,
+    pass: p,
+    fail: f,
+    skip: s,
+    /** 只有「无 fail 且无 skip」才算完全通过 */
+    ok: f === 0 && s === 0,
+    /** 通过但存在未验证项 */
+    partial: f === 0 && s > 0,
+    unverified: results.filter((r) => r.status === 'skip').map((r) => r.id),
+  };
+}
+
+/** 渲染人类可读报告 */
+export function renderReport(report) {
+  const icon = { pass: '✅', fail: '❌', skip: '⏭️ ' };
+  const lines = [];
+  lines.push(`\n  skill-core-guide · 自动质检报告`);
+  lines.push(`  目标:${report.dir}`);
+  lines.push('  ' + '─'.repeat(64));
+  report.results.forEach((r, i) => {
+    lines.push(`  ${icon[r.status]} ${i + 1}. ${r.title}  [${r.id}]`);
+    lines.push(`      ${r.detail}`);
+    for (const e of r.evidence) lines.push(`      · ${e}`);
+    if (r.fix) lines.push(`      ↳ 修复:${r.fix}`);
+    lines.push('');
+  });
+  const s = report.summary;
+  lines.push('  ' + '─'.repeat(64));
+  lines.push(`  结果:${s.pass} 通过 / ${s.fail} 失败 / ${s.skip} 未验证(共 ${s.total} 项)`);
+  if (s.unverified.length) lines.push(`  未验证项:${s.unverified.join(', ')}`);
+  lines.push(`  结论:${s.ok ? '✅ 全部通过' : s.partial ? '⚠️  通过但有未验证项' : '❌ 存在失败项'}`);
+  lines.push('');
+  return lines.join('\n');
+}
+
+export default { CHECKS, runChecks, summarize, renderReport };

+ 401 - 0
lib/index.mjs

@@ -0,0 +1,401 @@
+/**
+ * skill-core-guide — Fmode Harness 平台母技能 · ESM 入口
+ * ---------------------------------------------------------------------------
+ * 导出平台常量、端点真值表、ESM-first 校验器、技能清单与六项质检引擎。
+ *
+ * 设计纪律:
+ *   1. 零依赖(no dependencies)—— 母技能必须能在任何 Node ≥18 环境裸跑。
+ *   2. 纯 ESM —— 无 CJS 入口(团队共识)。
+ *   3. 浏览器安全子集在 browser/index.mjs;本文件允许 import node: 内置模块。
+ *
+ * @example
+ *   import { PLATFORM, ENDPOINTS, validatePackage, CHECKS } from 'skill-core-guide';
+ *   const r = await validatePackage('./my-skill');
+ *   console.log(r.ok, r.errors);
+ */
+
+export const VERSION = '1.0.0';
+export const SKILL_NAME = 'skill-core-guide';
+
+// 本地绑定:供本文件内的 CHANNELS / publishPlan 等使用
+// (注意:`export ... from` 只转发、不产生本地绑定,故需显式 import)
+import { PLATFORM, PACKAGE_RULES } from './platform.mjs';
+
+// ---------- 平台真值源 ----------
+export {
+  PLATFORM,
+  ENDPOINTS,
+  endpointsByStatus,
+  CREDENTIAL_CHAIN,
+  TOKEN_RULES,
+  TIERS,
+  RUNTIMES,
+  REQUIRED_LAYOUT,
+  PACKAGE_RULES,
+} from './platform.mjs';
+
+// ---------- 质检引擎 ----------
+export {
+  CHECKS,
+  CHECKS_BY_ID,
+  runChecks,
+  checkFunctional,
+  checkApiConnectivity,
+  checkSop,
+  checkDashboard,
+  checkLoop,
+  checkMultiRuntime,
+  summarize,
+  renderReport,
+} from './check.mjs';
+
+// ---------- 凭证供给 ----------
+export {
+  resolveSessionToken,
+  resolveApiToken,
+  resolveFmodeDir,
+  resolveConfigPath,
+  validateToken,
+  fetchApiTokenFromSession,
+  ensureFmodeDir,
+  writeConfig,
+  writeCredential,
+  requestVerifyCode,
+  verifyAndProvision,
+  bootstrap,
+  describeBootstrapStatus,
+  maskToken,
+} from './bootstrap.mjs';
+
+// ---------- 技能清单 ----------
+export { INVENTORY, byTier, byPlatform, stats } from './inventory.mjs';
+
+// ============================================================
+// 技能分类与命名规则
+// ============================================================
+
+/** 技能命名规则:必须以 skill- 前缀(平台约定) */
+export const NAMING = {
+  prefix: 'skill-',
+  /** npm 上部分技能以 fmode- 前缀发布(历史兼容),两者均合法 */
+  altPrefix: 'fmode-',
+  pattern: /^(skill|fmode)-[a-z0-9]+(-[a-z0-9]+)*$/,
+  /** skillhub.cn 上的 slug 规则 */
+  slugPattern: /^fmode-skill-[a-z0-9]+(-[a-z0-9]+)*$/,
+};
+
+/**
+ * 校验技能名是否符合平台命名规范。
+ * @param {string} name
+ * @returns {{ok: boolean, reason?: string, slug?: string}}
+ */
+export function validateName(name) {
+  if (!name || typeof name !== 'string') {
+    return { ok: false, reason: '技能名不能为空' };
+  }
+  if (!NAMING.pattern.test(name)) {
+    return {
+      ok: false,
+      reason: `技能名 "${name}" 不符合规范:须为 skill-<kebab-case> 或 fmode-<kebab-case>(仅小写字母、数字、连字符)`,
+    };
+  }
+  return { ok: true, slug: `fmode-${name}` };
+}
+
+// ============================================================
+// 包结构 / package.json 校验器
+// ============================================================
+
+/**
+ * 校验 package.json 是否符合 ESM-first 多端标准。
+ * 纯函数——不触碰文件系统,可在浏览器中运行。
+ *
+ * @param {object} pkg 已解析的 package.json 对象
+ * @returns {{ok: boolean, errors: string[], warnings: string[]}}
+ */
+export function validatePackageJson(pkg) {
+  const errors = [];
+  const warnings = [];
+
+  if (!pkg || typeof pkg !== 'object') {
+    return { ok: false, errors: ['package.json 无法解析或不是对象'], warnings };
+  }
+
+  for (const f of PACKAGE_RULES.requiredFields) {
+    if (pkg[f] === undefined || pkg[f] === null || pkg[f] === '') {
+      errors.push(`缺少必需字段:${f}`);
+    }
+  }
+
+  if (pkg.type !== PACKAGE_RULES.type) {
+    errors.push(`"type" 必须是 "${PACKAGE_RULES.type}"(当前:${JSON.stringify(pkg.type)})—— ESM only`);
+  }
+
+  if (pkg.main !== PACKAGE_RULES.main) {
+    errors.push(`"main" 必须是 "${PACKAGE_RULES.main}"(当前:${JSON.stringify(pkg.main)})`);
+  }
+
+  // exports['.'] 必须同时提供 import 与 default
+  const dot = pkg.exports && pkg.exports['.'];
+  if (!dot) {
+    errors.push('"exports" 缺少 "." 入口');
+  } else if (typeof dot === 'string') {
+    warnings.push('"exports[\".\"]" 是字符串简写;建议显式写成 { import, default } 以对齐四端标准');
+  } else {
+    for (const cond of PACKAGE_RULES.exportConditions) {
+      if (!dot[cond]) errors.push(`"exports[\".\"]" 缺少 "${cond}" 条件`);
+    }
+  }
+
+  // bin 必须是对象且指向 .mjs
+  if (pkg.bin && typeof pkg.bin === 'object') {
+    const entries = Object.entries(pkg.bin);
+    if (entries.length === 0) errors.push('"bin" 为空对象');
+    for (const [cmd, target] of entries) {
+      if (!String(target).endsWith('.mjs')) {
+        warnings.push(`bin["${cmd}"] 指向 ${target},建议使用 .mjs 扩展名以对齐 ESM 标准`);
+      }
+    }
+  } else if (pkg.bin) {
+    warnings.push('"bin" 建议使用对象形式 { "<cmd>": "./bin/<name>.mjs" }');
+  }
+
+  // files 白名单覆盖度
+  if (Array.isArray(pkg.files)) {
+    for (const need of PACKAGE_RULES.filesMustInclude) {
+      if (!pkg.files.some((f) => f === need || f === need.replace(/\/$/, ''))) {
+        warnings.push(`"files" 白名单建议包含 "${need}"(避免发布缺文件)`);
+      }
+    }
+  } else {
+    warnings.push('建议声明 "files" 白名单,避免把 test/ 与临时文件发到 npm');
+  }
+
+  // ESM-only 纪律:不应有 require 字段
+  for (const f of PACKAGE_RULES.forbiddenFields) {
+    if (pkg[f] !== undefined) {
+      errors.push(`不应出现 "${f}" 字段 —— 本平台 ESM only,不提供 CJS 入口`);
+    }
+  }
+
+  // 命名规范
+  if (pkg.name) {
+    const n = validateName(pkg.name);
+    if (!n.ok) warnings.push(n.reason);
+  }
+
+  if (!pkg.license) errors.push('缺少 "license"(平台统一 MIT)');
+
+  return { ok: errors.length === 0, errors, warnings };
+}
+
+/**
+ * 校验 skill-package-manifest.json 的结构。
+ * @param {object} manifest
+ * @returns {{ok: boolean, errors: string[], warnings: string[]}}
+ */
+export function validateManifest(manifest) {
+  const errors = [];
+  const warnings = [];
+
+  if (!manifest || typeof manifest !== 'object') {
+    return { ok: false, errors: ['skill-package-manifest.json 无法解析'], warnings };
+  }
+
+  for (const f of ['name', 'version', 'description', 'skills']) {
+    if (!manifest[f]) errors.push(`清单缺少必需字段:${f}`);
+  }
+
+  if (manifest.skills !== undefined) {
+    if (!Array.isArray(manifest.skills) || manifest.skills.length === 0) {
+      errors.push('"skills" 必须是非空数组');
+    } else {
+      manifest.skills.forEach((s, i) => {
+        if (!s || typeof s !== 'object') {
+          errors.push(`skills[${i}] 不是对象`);
+          return;
+        }
+        for (const f of ['name', 'path']) {
+          if (!s[f]) errors.push(`skills[${i}] 缺少 "${f}"`);
+        }
+        if (s.path && !String(s.path).endsWith('SKILL.md')) {
+          warnings.push(`skills[${i}].path 建议指向 SKILL.md(当前:${s.path})`);
+        }
+      });
+    }
+  }
+
+  if (!manifest.install) {
+    warnings.push('建议声明 "install" 字段(如 "npx --yes <skill>@latest workspace")');
+  }
+
+  return { ok: errors.length === 0, errors, warnings };
+}
+
+// ============================================================
+// SKILL.md frontmatter 解析与校验(三种格式)
+// ============================================================
+
+/**
+ * 极简 YAML frontmatter 解析器(零依赖)。
+ * 仅支持平台技能用到的子集:标量、行内数组 [a, b]、引号字符串。
+ *
+ * @param {string} text SKILL.md 全文
+ * @returns {{data: Record<string, unknown>, body: string, raw: string|null}}
+ */
+export function parseFrontmatter(text) {
+  if (typeof text !== 'string') return { data: {}, body: '', raw: null };
+  const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
+  if (!m) return { data: {}, body: text, raw: null };
+
+  const raw = m[1];
+  const body = text.slice(m[0].length);
+  const data = {};
+
+  for (const line of raw.split(/\r?\n/)) {
+    const t = line.trim();
+    if (!t || t.startsWith('#')) continue;
+    const idx = t.indexOf(':');
+    if (idx <= 0) continue;
+    const key = t.slice(0, idx).trim();
+    let val = t.slice(idx + 1).trim();
+
+    // 去引号
+    if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
+      val = val.slice(1, -1);
+    } else if (val.startsWith('[') && val.endsWith(']')) {
+      data[key] = val
+        .slice(1, -1)
+        .split(',')
+        .map((s) => s.trim().replace(/^["']|["']$/g, ''))
+        .filter(Boolean);
+      continue;
+    }
+    data[key] = val;
+  }
+
+  return { data, body, raw };
+}
+
+/** 三种 frontmatter 格式的必需字段 */
+export const FRONTMATTER_SCHEMAS = {
+  /** Hermes 本地技能格式 */
+  hermes: {
+    key: 'hermes',
+    label: 'Hermes 本地技能格式',
+    required: ['name', 'description', 'version'],
+    recommended: ['tags', 'license', 'author'],
+  },
+  /** skillhub.cn 格式 */
+  skillhub: {
+    key: 'skillhub',
+    label: 'skillhub.cn 格式',
+    required: ['slug', 'displayName', 'version', 'summary', 'license'],
+    recommended: ['tags'],
+  },
+  /** GitHub/Gogs README 格式(无 frontmatter,用标题结构校验) */
+  readme: {
+    key: 'readme',
+    label: 'GitHub/Gogs README 格式',
+    required: [],
+    recommended: [],
+  },
+};
+
+/**
+ * 校验 frontmatter 是否符合指定格式。
+ * @param {string} text
+ * @param {'hermes'|'skillhub'} format
+ * @returns {{ok: boolean, data: object, errors: string[], warnings: string[]}}
+ */
+export function validateFrontmatter(text, format = 'hermes') {
+  const schema = FRONTMATTER_SCHEMAS[format];
+  const errors = [];
+  const warnings = [];
+
+  if (!schema) {
+    return { ok: false, data: {}, errors: [`未知的 frontmatter 格式:${format}`], warnings };
+  }
+
+  const { data, raw } = parseFrontmatter(text);
+  if (raw === null) {
+    return { ok: false, data: {}, errors: [`未找到 YAML frontmatter(文件须以 --- 开头)`], warnings };
+  }
+
+  for (const f of schema.required) {
+    if (data[f] === undefined || data[f] === '') errors.push(`frontmatter 缺少必需字段:${f}`);
+  }
+  for (const f of schema.recommended) {
+    if (data[f] === undefined) warnings.push(`frontmatter 建议补充:${f}`);
+  }
+
+  if (format === 'skillhub' && data.slug) {
+    if (!NAMING.slugPattern.test(String(data.slug))) {
+      warnings.push(`slug "${data.slug}" 建议符合 fmode-skill-<kebab-case>(当前团队 slug 规范)`);
+    }
+  }
+  if (format === 'hermes' && data.name) {
+    const n = validateName(String(data.name));
+    if (!n.ok) warnings.push(n.reason);
+  }
+
+  return { ok: errors.length === 0, data, errors, warnings };
+}
+
+// ============================================================
+// 分发渠道
+// ============================================================
+
+export const CHANNELS = {
+  gogs: {
+    key: 'gogs',
+    label: 'Gogs(主仓 · 内网日常迭代)',
+    remote: 'origin',
+    url: (name) => `${PLATFORM.gogsBase}/${PLATFORM.gogsOrg}/${name}.git`,
+    addRemote: (name) =>
+      `git remote add origin ${PLATFORM.gogsBase}/${PLATFORM.gogsOrg}/${name}.git`,
+    push: 'git push origin master',
+    note: '凭据通过 URL 携带(内网 Gogs 的 /api/v1 未开放匿名访问,建仓需走 Web UI 或已登录会话)。',
+  },
+  github: {
+    key: 'github',
+    label: 'GitHub(公开镜像)',
+    remote: 'github',
+    url: (name) => `git@github.com:${PLATFORM.githubOrg}/${name}.git`,
+    addRemote: (name) => `git remote add github git@github.com:${PLATFORM.githubOrg}/${name}.git`,
+    push: 'GIT_SSH_COMMAND="ssh -i ~/.ssh/id_ed25519_fmodecn" git push github master',
+    note: 'SSH key: ~/.ssh/id_ed25519_fmodecn。GitHub 建仓可用 ~/.fmode/config.json 的 githubToken 调 REST API。',
+  },
+  npm: {
+    key: 'npm',
+    label: 'npm(SDK 分发)',
+    url: (name) => `${PLATFORM.npmRegistry}/package/${name}`,
+    publish: 'npm publish --access public',
+    note: '账号 fmode001(凭据在 ~/.npmrc)。npm 上部分技能用 fmode- 前缀发布。',
+  },
+  skillhub: {
+    key: 'skillhub',
+    label: 'skillhub.cn(社区分发)',
+    url: (slug) => `https://skillhub.cn/skill/${slug}`,
+    installCli: `curl -fsSL ${PLATFORM.skillhub.cliInstall} | bash -s -- --cli-only`,
+    login: `skillhub login --key <API_KEY> --host ${PLATFORM.skillhub.host}`,
+    publish: (dir, changelog) =>
+      `skillhub publish ${dir} --changelog "${changelog}"`,
+    dryRun: (dir) => `skillhub publish ${dir} --dry-run`,
+    note: 'CLI 位于 ~/.local/bin/skillhub。发布目录内必须含 SKILL.md,且 frontmatter 用 skillhub 格式(slug/displayName/summary)。',
+  },
+};
+
+/** 按顺序返回四渠道发布步骤 */
+export function publishPlan(name, { dir = '.', changelog = '' } = {}) {
+  return [
+    { channel: 'gogs', ...CHANNELS.gogs, steps: [CHANNELS.gogs.addRemote(name), CHANNELS.gogs.push] },
+    { channel: 'github', ...CHANNELS.github, steps: [CHANNELS.github.addRemote(name), CHANNELS.github.push] },
+    { channel: 'npm', ...CHANNELS.npm, steps: [CHANNELS.npm.publish] },
+    {
+      channel: 'skillhub',
+      ...CHANNELS.skillhub,
+      steps: [CHANNELS.skillhub.dryRun(dir), CHANNELS.skillhub.publish(dir, changelog || `release ${name}`)],
+    },
+  ];
+}

+ 237 - 0
lib/inventory.mjs

@@ -0,0 +1,237 @@
+/**
+ * 已发布技能清单(机器可读真值)
+ * ---------------------------------------------------------------------------
+ * inventory.md 是本文件的人类可读渲染。新增技能时**两处都要更新**,
+ * 或直接跑 `skill-core inventory --check` 校验一致性。
+ *
+ * 平台标注说明:
+ *   gogs   — 主 Gogs(git.fmode.cn/fmode/)
+ *   github — GitHub 镜像(github.com/fmodecn/)
+ *   npm    — npm 包(包名见 npmName 字段)
+ *   skillhub — skillhub.cn 社区分发
+ */
+
+import { TIERS } from './platform.mjs';
+
+/** @typedef {Object} SkillEntry */
+
+/** @type {SkillEntry[]} */
+export const INVENTORY = [
+  // ============================================================
+  // 系统层 / Infrastructure
+  // ============================================================
+  {
+    name: 'skill-heterarchy',
+    displayName: '内异层认知协同',
+    tier: 'system',
+    summary: '单一 Agent 主体内部多心智分化与自治协商。Delegate 是对外派活,Heterarchy 是对内分思。',
+    platforms: ['gogs', 'github', 'npm', 'skillhub'],
+    npmName: 'skill-heterarchy',
+    skillhubSlug: 'fmode-skill-heterarchy',
+    status: 'published',
+    tags: ['heterarchy', 'cognitive-collaboration', 'hermes', 'claude-code', 'dispatch', 'paradigm'],
+  },
+  {
+    name: 'skill-multi-branch',
+    displayName: '多任务工作框架',
+    tier: 'system',
+    summary:
+      'Hermes 负责沟通、专业任务派发执行层(Claude Code/Codex/Agent profile)。任务书落盘协议、四态状态上报、中断续跑、执行层纪律,内置 dispatch.sh 标准派发器。',
+    platforms: ['gogs', 'github'],
+    status: 'published',
+    tags: ['dispatch', 'multi-task', 'hermes', 'claude-code', 'workflow'],
+  },
+  {
+    name: 'skill-bypass-permission',
+    displayName: 'YOLO 模式体检器',
+    tier: 'system',
+    summary:
+      '校验并幂等修复 Agent 免确认自主执行配置(Hermes approvals.mode=off/yolo、Claude Code skip-permissions),改前自动备份,已合规则一行 OK 静默通过。',
+    platforms: ['gogs', 'github'],
+    status: 'published',
+    tags: ['permission', 'yolo', 'self-check', 'idempotent', 'hermes'],
+  },
+  {
+    name: 'skill-task-progress',
+    displayName: '任务进度与成果上报',
+    tier: 'system',
+    summary:
+      'Agent 干活进度与成果交付实时进 FmodeAgent 平台(App 四 Tab/看板可见)。init-tables 幂等建三表;progress 四态上报(ack→running→done/failed+30s 心跳);deliver 交付链接入库。',
+    platforms: ['gogs', 'github'],
+    status: 'published',
+    tags: ['progress', 'reporting', 'parse', 'dashboard', 'heartbeat'],
+  },
+  {
+    name: 'plugin-wecom-fix',
+    displayName: '企微通道自检修复',
+    tier: 'system',
+    summary:
+      '修复官方 wecom 插件四项接收缺陷:大视频收不到、合并转发不识别、批量图片丢失、长文件名 Errno 36。幂等 patch.py(--check/--apply/--rollback)。',
+    platforms: ['gogs', 'github'],
+    status: 'published',
+    kind: 'plugin',
+    tags: ['wecom', 'plugin', 'patch', 'idempotent', 'channel-fix'],
+  },
+  {
+    name: 'skill-agent-clone',
+    displayName: '数字生命克隆',
+    tier: 'system',
+    summary:
+      '把本地 Hermes 配置、SOUL、技能、记忆、会话记录按 L1-L4 重要程度分级同步到个人 Git 仓库,增量 push、一键恢复。密钥只记位置索引不入仓。',
+    platforms: ['gogs', 'github'],
+    status: 'published',
+    tags: ['clone', 'backup', 'soul', 'memory', 'migration', 'disaster-recovery'],
+  },
+
+  // ============================================================
+  // 服务层 / Platform Services
+  // ============================================================
+  {
+    name: 'skill-storage',
+    displayName: '对象存储与公开分享',
+    tier: 'service',
+    summary:
+      '二进制大文件(图/音/视频/HTML 报告)上传对象存储(OBS/S3),本地零长期占用,上传即得公开分享链接。诚实 4 级凭据链。',
+    platforms: ['gogs', 'github'],
+    status: 'published',
+    version: '0.3.0',
+    tags: ['storage', 'obs', 's3', 'cdn', 'share-link', 'upload'],
+  },
+  {
+    name: 'skill-image',
+    displayName: 'Fmode 图像生成',
+    tier: 'service',
+    summary: 'Fmode API 图像生成,白底 PNG 场景图,约 ¥0.3-0.5/张。7 种模式(--app/--arch/--explode/--product/--scene/--slide)。',
+    platforms: ['github', 'npm'],
+    npmName: 'fmode-image',
+    npmVersion: '0.2.0',
+    status: 'published',
+    tags: ['image', 'generation', 'ai', 'diagram', 'scene', 'product'],
+  },
+  {
+    name: 'skill-vision',
+    displayName: '视觉识别',
+    tier: 'service',
+    summary:
+      '图片/视频结构化视觉分析。宿主多模态模型优先(零额外成本),否则回落 Fmode API 的 glm-5.3-flash;支持单轮/多轮聚焦分析与结构化 JSON 输出。',
+    platforms: ['github', 'npm'],
+    npmName: 'fmode-vision',
+    npmVersion: '0.1.1',
+    status: 'published',
+    tags: ['vision', 'multimodal', 'image-analysis', 'video-frames'],
+  },
+  {
+    name: 'skill-listen',
+    displayName: '录音转写',
+    tier: 'service',
+    summary:
+      '录音/视频音轨转文字(讯飞 LFASR × Fmode 网关 /api/listen/transcribe)。中英多语种+方言、说话人分离、服务端按音频时长计费、讯飞凭据零下放。',
+    platforms: ['gogs', 'github', 'npm'],
+    npmName: 'fmode-listen',
+    npmVersion: '0.1.2',
+    status: 'published',
+    tags: ['asr', 'transcribe', 'iflytek', 'lfasr', 'speaker-diarization'],
+  },
+  {
+    name: 'fmode-ffmpeg',
+    displayName: 'FFmpeg 音视频处理',
+    tier: 'service',
+    summary: 'FFmpeg 音视频处理封装:抽音轨、转码、切片、截图。',
+    platforms: ['npm'],
+    npmName: 'fmode-ffmpeg',
+    npmVersion: '0.1.1',
+    status: 'published',
+    tags: ['ffmpeg', 'audio', 'video', 'transcode', 'extract'],
+  },
+  {
+    name: 'fmode-qiwei',
+    displayName: '企微网关 SDK',
+    tier: 'service',
+    summary: '企业微信网关 SDK:消息收发、通讯录、应用管理封装。',
+    platforms: ['npm'],
+    npmName: 'fmode-qiwei',
+    npmVersion: '0.5.2',
+    status: 'published',
+    tags: ['wecom', 'qiwei', 'sdk', 'gateway', 'messaging'],
+  },
+
+  // ============================================================
+  // 应用层 / Business Applications
+  // ============================================================
+  {
+    name: 'skill-study-report',
+    displayName: '学习复盘报告',
+    tier: 'application',
+    summary:
+      '一键生成学员 48h 学习复盘报告 PPT:多 Agent 采集工作痕迹 → 三问追问 → PPT 级 HTML(≥10 屏)→ Storage 主 + Gogs 降级双通道发布 + ZIP 分发。',
+    platforms: ['gogs', 'github'],
+    status: 'published',
+    tags: ['report', 'review', 'ppt', 'html', 'multi-agent', 'education'],
+  },
+  {
+    name: 'skill-present',
+    displayName: 'HTML 演讲系统',
+    tier: 'application',
+    summary: '课程课件/报告 HTML 演讲系统,含 43 条 Claude 规则,数字动画与交付物墙 iframe 嵌套。',
+    platforms: ['gogs'],
+    status: 'published',
+    tags: ['presentation', 'html', 'slides', 'deck', 'courseware'],
+  },
+  {
+    name: 'fmode-product-lab',
+    displayName: '新品研发实验室',
+    tier: 'application',
+    summary: '新品研发全流程:VOC 采集 + KANO 模型 + 市场分析 + 定位分析。',
+    platforms: ['npm'],
+    npmName: 'fmode-product-lab',
+    npmVersion: '0.2.0',
+    status: 'published',
+    tags: ['product', 'voc', 'kano', 'market-analysis', 'positioning'],
+  },
+
+  // ============================================================
+  // 母技能自身
+  // ============================================================
+  {
+    name: 'skill-core-guide',
+    displayName: 'Harness 平台母技能标准指南',
+    tier: 'system',
+    summary:
+      'Fmode Harness 平台技能开发规范母技能:平台真值表、ESM-first 四端标准、四渠道分发、六项自动质检、一键凭证供给、新技能脚手架。',
+    platforms: ['gogs', 'github', 'npm', 'skillhub'],
+    npmName: 'skill-core-guide',
+    skillhubSlug: 'fmode-skill-core-guide',
+    status: 'published',
+    isMeta: true,
+    tags: ['meta', 'standard', 'spec', 'harness', 'scaffold', 'quality-check'],
+  },
+];
+
+/** 按分层分组 */
+export function byTier() {
+  const out = {};
+  for (const key of Object.keys(TIERS)) out[key] = [];
+  for (const s of INVENTORY) {
+    if (!out[s.tier]) out[s.tier] = [];
+    out[s.tier].push(s);
+  }
+  return out;
+}
+
+/** 按渠道筛选 */
+export function byPlatform(platform) {
+  return INVENTORY.filter((s) => s.platforms.includes(platform));
+}
+
+/** 统计 */
+export function stats() {
+  const out = { total: INVENTORY.length, byTier: {}, byPlatform: {} };
+  for (const key of Object.keys(TIERS)) out.byTier[key] = 0;
+  for (const s of INVENTORY) {
+    out.byTier[s.tier] = (out.byTier[s.tier] || 0) + 1;
+    for (const p of s.platforms) out.byPlatform[p] = (out.byPlatform[p] || 0) + 1;
+  }
+  return out;
+}
+
+export default INVENTORY;

+ 320 - 0
lib/platform.mjs

@@ -0,0 +1,320 @@
+/**
+ * Fmode Harness 平台常量与端点真值表
+ * ---------------------------------------------------------------------------
+ * 本文件是 skill-core-guide 的唯一真值源(single source of truth)。
+ * 所有端点状态均于 2026-09-22 实测(curl 探针),并标注 verification 字段:
+ *
+ *   "live"       — 实测返回 200/401(401 = 端点存在、需鉴权),可直接使用
+ *   "planned"    — 实测 404,设计文档存在但服务端未上线;调用方必须先探测再回落
+ *   "deprecated" — 曾经存在或曾被文档描述,实测 404 且已确认不再维护
+ *
+ * ⚠️ 纪律:任何技能调用 planned/deprecated 端点前,必须做一次探测并显式回落,
+ *    禁止把「文档写了」当成「已经能跑」——这是 Fmode 技能生态最贵的一课
+ *    (见 skill-storage 0.3.0 的「伪自举」事故复盘)。
+ */
+
+// ============================================================
+// 平台基址
+// ============================================================
+
+export const PLATFORM = {
+  name: 'Fmode Harness',
+  version: '1.0.0',
+  author: 'Yuyang001 (FmodeAgent)',
+
+  /** OpenAI 兼容网关:LLM / 图像生成。鉴权:Bearer sk-... */
+  apiBase: 'https://api.fmode.cn',
+  /** Parse 业务网关:转写 / 凭据自举 / deploy STS。鉴权:x-parse-session-token */
+  gatewayBase: 'https://server.fmode.cn',
+  /** CDN 映射域名(OBS → fmode.cn 回源) */
+  cdnBase: 'https://fmode.cn',
+  /** OBS 直链(CDN 未生效时的降级通道) */
+  obsBase: 'https://fmode-s3.obs.cn-north-4.myhuaweicloud.com',
+
+  /** 主 Gogs(内网日常迭代) */
+  gogsBase: 'https://git.fmode.cn',
+  gogsOrg: 'fmode',
+  /** GitHub 镜像(公开发布) */
+  githubOrg: 'fmodecn',
+  npmRegistry: 'https://registry.npmjs.org',
+  npmOwner: 'fmode001',
+
+  /** skillhub.cn 分发渠道 */
+  skillhub: {
+    host: 'https://api.skillhub.cn',
+    team: 'fmode',
+    orgId: 'org-m8z913un',
+    cliInstall: 'https://skillhub.cn/install/install.sh',
+    cliPath: '~/.local/bin/skillhub',
+  },
+};
+
+// ============================================================
+// 端点真值表
+// ============================================================
+
+/**
+ * @typedef {Object} Endpoint
+ * @property {string} id          稳定标识(代码里引用这个,不要硬编码 URL)
+ * @property {string} method
+ * @property {string} url
+ * @property {'api'|'gateway'|'oss'} host      归属基址
+ * @property {'live'|'planned'|'deprecated'} status
+ * @property {string} auth        鉴权方式
+ * @property {string} purpose
+ * @property {string} [note]
+ */
+
+/** @type {Record<string, Endpoint>} */
+export const ENDPOINTS = {
+  // ---------- 已上线(实测 200/401)----------
+  llmChat: {
+    id: 'llmChat',
+    method: 'POST',
+    url: 'https://api.fmode.cn/v1/chat/completions',
+    host: 'api',
+    status: 'live',
+    auth: 'Bearer <fmodeApiToken>',
+    purpose: 'LLM 对话补全(OpenAI 兼容)。所有技能的统一模型出口。',
+    note: '实测无 token 返回 401(端点存在)。模型如 glm-5.3-flash / deepseek 系列。',
+  },
+  imageGenerate: {
+    id: 'imageGenerate',
+    method: 'POST',
+    url: 'https://api.fmode.cn/v1/images/generations',
+    host: 'api',
+    status: 'live',
+    auth: 'Bearer <fmodeApiToken>',
+    purpose: '图像生成(fmode-image 使用)。白底 PNG 场景图,约 ¥0.3-0.5/张。',
+    note: '实测无 token 返回 401(端点存在)。注意是 /v1/images/generations,不是 /api/image/generate。',
+  },
+  listenTranscribe: {
+    id: 'listenTranscribe',
+    method: 'POST',
+    url: 'https://server.fmode.cn/api/listen/transcribe',
+    host: 'gateway',
+    status: 'live',
+    auth: 'Bearer <fmodeApiToken>',
+    purpose: '录音转写(讯飞 LFASR)。fmode-listen 使用,服务端按音频真实时长计费。',
+    note: '实测无 token 返回 401。讯飞凭据仅服务端持有,客户端零下放。',
+  },
+  vocSkillBootstrap: {
+    id: 'vocSkillBootstrap',
+    method: 'POST',
+    url: 'https://server.fmode.cn/api/fmode/voc-skill/install-prompt',
+    host: 'gateway',
+    status: 'live',
+    auth: 'x-parse-session-token: <sessionToken>',
+    purpose: '【凭据自举唯一通道】sessionToken → fmode API token(sk- 开头)。',
+    note:
+      '实测无 token 返回 401。token 内嵌在返回 body.data.prompt 文本中,' +
+      '用 /sk-(?!ant-)[A-Za-z0-9_-]{8,}/ 提取。token 仅内存持有,禁止落盘进日志。',
+  },
+  deploySts: {
+    id: 'deploySts',
+    method: 'POST',
+    url: 'https://server.fmode.cn/api/apig/deploy/huaweicloud',
+    host: 'gateway',
+    status: 'live',
+    auth: 'Bearer <sessionToken>',
+    purpose: '签发项目隔离 OBS STS 临时凭证(skill-storage 第 3 级凭据)。',
+    note: '实测匿名 POST 返回 200(权威端点)。入参 {token, projectId},返回 {accessKey, secretKey, securityToken, obsPath}。',
+  },
+
+  // ---------- 未上线(实测 404)——调用前必须探测 ----------
+  storageUpload: {
+    id: 'storageUpload',
+    method: 'POST',
+    url: 'https://server.fmode.cn/api/storage/upload',
+    host: 'gateway',
+    status: 'planned',
+    auth: 'Bearer <fmodeApiToken>',
+    purpose: '对象存储上传(规划中)。',
+    note: '⚠️ 实测 404。当前上传走 obsutil 直传 OBS 或 deploySts 换 STS,不要依赖本端点。',
+  },
+  storageCredentials: {
+    id: 'storageCredentials',
+    method: 'POST',
+    url: 'https://server.fmode.cn/api/storage/credentials',
+    host: 'gateway',
+    status: 'deprecated',
+    auth: 'Bearer <sessionToken>',
+    purpose: '(历史)sessionToken 直接换 OBS STS。',
+    note:
+      '⚠️ 从未上线(HEAD/GET 探测恒 404)。skill-storage 0.2.x 的「登录即可上传」' +
+      '即因依赖本端点而成为「伪自举」事故。0.3.0 已降级为 --experimental-sts。',
+  },
+  imageGenerateLegacy: {
+    id: 'imageGenerateLegacy',
+    method: 'POST',
+    url: 'https://server.fmode.cn/api/image/generate',
+    host: 'gateway',
+    status: 'planned',
+    auth: 'Bearer <fmodeApiToken>',
+    purpose: '(规划)网关侧图像生成。',
+    note: '⚠️ 实测 404。图像生成请用 imageGenerate(/v1/images/generations)。',
+  },
+  visionAnalyze: {
+    id: 'visionAnalyze',
+    method: 'POST',
+    url: 'https://server.fmode.cn/api/vision/analyze',
+    host: 'gateway',
+    status: 'planned',
+    auth: 'Bearer <fmodeApiToken>',
+    purpose: '(规划)网关侧视觉识别。',
+    note: '⚠️ 实测 404。视觉识别请用 llmChat(多模态 messages)或宿主多模态模型优先。',
+  },
+  verifyCode: {
+    id: 'verifyCode',
+    method: 'POST',
+    url: 'https://server.fmode.cn/api/fmode/verifycode',
+    host: 'gateway',
+    status: 'planned',
+    auth: '无(公开)',
+    purpose: '(规划)手机号验证码下发,用于一键开户。',
+    note:
+      '⚠️ 实测 404。当前一键凭证供给走「登录 FMODE Studio 取 sessionToken」路径,' +
+      '见 lib/bootstrap.mjs 的 resolveSessionToken()。端点上线后本文件状态改 live 即可启用短信路径。',
+  },
+};
+
+/** 便捷查询:按状态筛选端点 */
+export function endpointsByStatus(status) {
+  return Object.values(ENDPOINTS).filter((e) => e.status === status);
+}
+
+// ============================================================
+// 凭据解析链(实测自 skill-listen / skill-vision / skill-storage 生产实现)
+// ============================================================
+
+/**
+ * 标准 5 级凭据解析链。命中即用,全失败必须显式报错,绝不伪装成功。
+ * 各级返回 { token, source, level } 或 null。
+ */
+export const CREDENTIAL_CHAIN = [
+  {
+    level: 0,
+    source: 'sessionToken 自举',
+    detail:
+      'FMODE_SESSION_TOKEN 环境变量 或 ~/.fmode/config.json 的 sessionToken' +
+      ' → POST /api/fmode/voc-skill/install-prompt → 提取 sk- token(仅内存持有)',
+    endpoint: 'vocSkillBootstrap',
+  },
+  {
+    level: 1,
+    source: '环境变量',
+    detail: 'FMODE_API_TOKEN',
+  },
+  {
+    level: 2,
+    source: '用户级 config',
+    detail: '~/.fmode/config.json → fmodeApiToken / newapiToken',
+  },
+  {
+    level: 3,
+    source: '项目级 config',
+    detail: '<cwd>/.fmode/config.json → fmodeApiToken / newapiToken',
+  },
+  {
+    level: 4,
+    source: 'Claude Code settings',
+    detail:
+      '~/.claude/settings.json(含 settings.local.json / 项目级 .claude/)' +
+      ' 的 env.ANTHROPIC_AUTH_TOKEN —— fmode 的 newapi SK 默认就是它',
+  },
+];
+
+/** 校验规则:合法的 fmode token 形态 */
+export const TOKEN_RULES = {
+  /** 必须以 sk- 开头 */
+  prefix: 'sk-',
+  /** 必须排除真正的 Anthropic 官方 key */
+  exclude: 'sk-ant-',
+  /** 若设置了 ANTHROPIC_BASE_URL,必须指向 fmode */
+  baseUrlMustInclude: 'fmode',
+  /** 从自举返回文本中提取 token 的正则(与 listen/vision 生产实现一致) */
+  extractRe: /sk-(?!ant-)[A-Za-z0-9_-]{8,}/,
+};
+
+// ============================================================
+// 技能分类体系
+// ============================================================
+
+export const TIERS = {
+  system: {
+    key: 'system',
+    label: '系统层 / Infrastructure',
+    desc: '平台基础设施与 Agent 运行时治理:认知协同、任务编排、权限、进度、克隆备份。',
+  },
+  service: {
+    key: 'service',
+    label: '服务层 / Platform Services',
+    desc: 'Fmode 基础服务封装:存储、图像、视觉、语音、音视频、企微网关。',
+  },
+  application: {
+    key: 'application',
+    label: '应用层 / Business Applications',
+    desc: '面向业务场景的端到端技能:报告、课件、产品研发。',
+  },
+};
+
+// ============================================================
+// ESM-first 四端矩阵
+// ============================================================
+
+export const RUNTIMES = {
+  cli: {
+    key: 'cli',
+    label: 'CLI',
+    entry: 'bin/<name>.mjs',
+    usage: 'npx --yes <skill>@latest <command>',
+    supported: true,
+  },
+  sdk: {
+    key: 'sdk',
+    label: 'SDK (Node ESM)',
+    entry: 'lib/index.mjs',
+    usage: "import { ... } from '<skill>'",
+    supported: true,
+  },
+  browser: {
+    key: 'browser',
+    label: 'Browser',
+    entry: 'browser/index.mjs',
+    usage: '<script type="module" src="...">',
+    supported: true,
+    constraint: '禁止 import 任何 node: 内置模块;仅可用 fetch / Web Crypto / URL 等 Web 标准 API。',
+  },
+  server: {
+    key: 'server',
+    label: 'Server (CJS require)',
+    entry: null,
+    usage: "require('<skill>')",
+    supported: false,
+    constraint: 'ESM only —— 团队共识,不提供 CJS 入口。Node 侧请用 import() 动态导入。',
+  },
+};
+
+/** 必需的文件清单(包结构模板) */
+export const REQUIRED_LAYOUT = [
+  'package.json',
+  'lib/index.mjs',
+  'bin/<name>.mjs',
+  'skills/<skill-name>/SKILL.md',
+  'README.md',
+  'LICENSE',
+  'skill-package-manifest.json',
+];
+
+/** package.json 必须满足的字段约束 */
+export const PACKAGE_RULES = {
+  requiredFields: ['name', 'version', 'description', 'type', 'main', 'exports', 'bin', 'files', 'license'],
+  type: 'module',
+  main: './lib/index.mjs',
+  /** exports['.'] 必须同时提供 import 与 default */
+  exportConditions: ['import', 'default'],
+  /** files 白名单必须覆盖的目录 */
+  filesMustInclude: ['lib/', 'bin/', 'skills/', 'README.md', 'LICENSE', 'skill-package-manifest.json'],
+  /** 禁止出现的字段(ESM only 纪律) */
+  forbiddenFields: ['require'],
+};

+ 81 - 0
package.json

@@ -0,0 +1,81 @@
+{
+  "name": "skill-core-guide",
+  "version": "1.0.0",
+  "description": "Fmode Harness 平台母技能标准指南:平台端点真值表、ESM-first 四端标准、四渠道分发、六项自动质检、一键凭证供给、新技能脚手架。",
+  "type": "module",
+  "main": "./lib/index.mjs",
+  "exports": {
+    ".": {
+      "import": "./lib/index.mjs",
+      "default": "./lib/index.mjs"
+    },
+    "./browser": {
+      "import": "./browser/index.mjs",
+      "default": "./browser/index.mjs"
+    },
+    "./platform": {
+      "import": "./lib/platform.mjs",
+      "default": "./lib/platform.mjs"
+    },
+    "./check": {
+      "import": "./lib/check.mjs",
+      "default": "./lib/check.mjs"
+    },
+    "./bootstrap": {
+      "import": "./lib/bootstrap.mjs",
+      "default": "./lib/bootstrap.mjs"
+    },
+    "./inventory": {
+      "import": "./lib/inventory.mjs",
+      "default": "./lib/inventory.mjs"
+    }
+  },
+  "bin": {
+    "skill-core": "./bin/skill-core.mjs",
+    "skill-core-guide": "./bin/skill-core.mjs"
+  },
+  "files": [
+    "lib/",
+    "bin/",
+    "browser/",
+    "templates/",
+    "skills/",
+    "SKILL.md",
+    "inventory.md",
+    "README.md",
+    "LICENSE",
+    "skill-package-manifest.json"
+  ],
+  "engines": {
+    "node": ">=18"
+  },
+  "scripts": {
+    "test": "node test/smoke.mjs",
+    "check": "node bin/skill-core.mjs check . --offline",
+    "verify": "node bin/skill-core.mjs verify .",
+    "inventory": "node bin/skill-core.mjs inventory"
+  },
+  "keywords": [
+    "fmode",
+    "harness",
+    "skill",
+    "standard",
+    "spec",
+    "esm",
+    "scaffold",
+    "quality-check",
+    "meta",
+    "skillhub"
+  ],
+  "author": "Yuyang001 (FmodeAgent)",
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/fmodecn/skill-core-guide.git"
+  },
+  "homepage": "https://github.com/fmodecn/skill-core-guide#readme",
+  "bugs": {
+    "url": "https://github.com/fmodecn/skill-core-guide/issues"
+  },
+  "dependencies": {}
+}

+ 38 - 0
skill-package-manifest.json

@@ -0,0 +1,38 @@
+{
+  "name": "skill-core-guide",
+  "version": "1.0.0",
+  "description": "Fmode Harness 平台母技能标准指南",
+  "skills": [
+    {
+      "name": "skill-core-guide",
+      "path": "skills/skill-core-guide/SKILL.md"
+    }
+  ],
+  "install": "npx --yes skill-core-guide@latest workspace",
+  "cli": {
+    "skill-core": "./bin/skill-core.mjs"
+  },
+  "entry": {
+    "sdk": "./lib/index.mjs",
+    "browser": "./browser/index.mjs"
+  },
+  "tier": "system",
+  "isMeta": true,
+  "platforms": ["gogs", "github", "npm", "skillhub"],
+  "skillhub": {
+    "slug": "fmode-skill-core-guide",
+    "displayName": "skill-core-guide"
+  },
+  "repository": {
+    "gogs": "https://git.fmode.cn/fmode/skill-core-guide",
+    "github": "https://github.com/fmodecn/skill-core-guide"
+  },
+  "contents": {
+    "spec": "SKILL.md",
+    "inventory": "inventory.md",
+    "scaffold": "templates/skill-starter",
+    "qualityEngine": "lib/check.mjs",
+    "credentialBootstrap": "lib/bootstrap.mjs",
+    "endpointTruthTable": "lib/platform.mjs"
+  }
+}

+ 177 - 0
skills/skill-core-guide/SKILL.md

@@ -0,0 +1,177 @@
+---
+name: skill-core-guide
+description: "Fmode Harness 平台母技能标准指南。当你要(1)开发新技能、(2)检查技能质量、(3)发布技能到 Gogs/GitHub/npm/skillhub、(4)查平台端点是否可用、(5)配置 Fmode 凭据、(6)了解技能生态现状时使用本技能。它是 Fmode 技能生态的宪法:定义平台真值表、ESM-first 四端标准、四渠道分发、六项自动质检、诚实凭证供给。"
+version: 1.0.0
+author: Yuyang001 (FmodeAgent)
+license: MIT
+tags: [meta, standard, spec, harness, scaffold, quality-check, fmode, esm]
+---
+
+# skill-core-guide · Fmode Harness 平台母技能
+
+> 完整规范文档在仓库根目录的 [`SKILL.md`](../../SKILL.md)(可独立阅读)。
+> 本文件是 Agent 会话中的**行为契约**:说明何时该调用本技能、怎么调用。
+
+---
+
+## 何时使用(触发场景)
+
+当用户出现以下诉求时,使用本技能:
+
+1. **开发新技能** —— 「帮我做一个技能」「新建一个 skill 仓库」
+   → `npx --yes skill-core-guide@latest init my-thing --name skill-my-thing`
+
+2. **检查技能质量** —— 「这个技能能发布吗」「跑一下质检」
+   → `npx --yes skill-core-guide@latest check .`
+
+3. **发布技能** —— 「发布到 npm」「推到 GitHub」「上架 skillhub」
+   → `npx --yes skill-core-guide@latest publish . --apply`
+
+4. **查平台端点** —— 「api.fmode.cn 的转写接口是什么」「这个端点能调吗」
+   → `npx --yes skill-core-guide@latest endpoints`
+
+5. **配置凭据** —— 「技能说没 token」「怎么配 Fmode 凭据」
+   → `npx --yes skill-core-guide@latest bootstrap`
+
+6. **了解生态现状** —— 「平台有哪些技能」「skill-xxx 是干嘛的」
+   → `npx --yes skill-core-guide@latest inventory` 或读 [`inventory.md`](../../inventory.md)
+
+7. **写技能规范/包结构** —— 「package.json 该怎么写」「要支持哪些端」
+   → 读 `SKILL.md` §3,或 `import { validatePackageJson } from 'skill-core-guide'`
+
+---
+
+## 核心能力
+
+### CLI
+
+```bash
+skill-core init [dir]        从脚手架创建新技能
+skill-core check [dir]       运行六项自动质检
+skill-core verify [dir]      静态校验(不执行代码)
+skill-core publish [dir]     生成四渠道发布计划
+skill-core inventory         输出技能清单
+skill-core bootstrap         一键凭证供给
+skill-core spec              输出平台规范摘要
+skill-core endpoints         输出端点真值表
+```
+
+### SDK
+
+```javascript
+import {
+  // 平台真值
+  PLATFORM, ENDPOINTS, endpointsByStatus, CREDENTIAL_CHAIN,
+  // 校验器
+  validateName, validatePackageJson, validateManifest,
+  validateFrontmatter, parseFrontmatter,
+  // 质检
+  CHECKS, runChecks, summarize, renderReport,
+  // 凭据
+  bootstrap, resolveApiToken, ensureFmodeDir, writeConfig,
+  // 清单与分发
+  INVENTORY, byTier, stats, publishPlan,
+} from 'skill-core-guide';
+```
+
+### 浏览器
+
+```javascript
+import { PLATFORM, checkApiConnectivity } from 'skill-core-guide/browser';
+```
+
+---
+
+## 三条铁律(违反任一条,技能不算交付)
+
+1. **零密钥入库** —— 凭据只从环境变量/用户目录解析,仓库里永远不出现真实密钥。
+   提交前扫一遍:`git grep -iE "sk-[a-z0-9]{20}|sk-ent-|ghp_|github_pat_"`
+2. **不伪造成功** —— 拿不到结果就显式报错并给出修复指引,绝不假装跑通。
+   质检的 `skip` 状态**不算通过**,且导致 CI 失败。
+3. **端点先探测再调用** —— 真值表里 `planned` / `deprecated` 的端点调用前必须探测,
+   404 时显式回落。禁止把「文档写了」当成「已经能跑」。
+
+---
+
+## 平台端点真值表(实测 2026-09-22)
+
+| 端点 | 状态 | 用途 |
+|------|------|------|
+| `api.fmode.cn/v1/chat/completions` | ✅ live | LLM 调用(统一出口) |
+| `api.fmode.cn/v1/images/generations` | ✅ live | 图像生成 |
+| `server.fmode.cn/api/listen/transcribe` | ✅ live | 录音转写 |
+| `server.fmode.cn/api/fmode/voc-skill/install-prompt` | ✅ live | 凭据自举(唯一通道) |
+| `server.fmode.cn/api/apig/deploy/huaweicloud` | ✅ live | 项目隔离 OBS STS |
+| `server.fmode.cn/api/storage/upload` | 🕓 planned | 未上线,走 obsutil 直传 |
+| `server.fmode.cn/api/storage/credentials` | ✗ deprecated | 从未上线(伪自举事故源) |
+| `server.fmode.cn/api/image/generate` | 🕓 planned | 未上线,用 `/v1/images/generations` |
+| `server.fmode.cn/api/vision/analyze` | 🕓 planned | 未上线,用多模态 chat |
+| `server.fmode.cn/api/fmode/verifycode` | 🕓 planned | 未上线,用 sessionToken 路径 |
+
+真值源:`lib/platform.mjs` 的 `ENDPOINTS`。代码里**引用 `ENDPOINTS.llmChat.url`
+而不是硬编码 URL**,平台迁移时只改一处。
+
+---
+
+## 凭据(标准 5 级链,命中即用)
+
+```
+第0级  FMODE_SESSION_TOKEN / ~/.fmode/config.json 的 sessionToken → 自举换 API token
+第1级  环境变量 FMODE_API_TOKEN
+第2级  ~/.fmode/config.json → fmodeApiToken
+第3级  <cwd>/.fmode/config.json → fmodeApiToken
+第4级  ~/.claude/settings.json → env.ANTHROPIC_AUTH_TOKEN
+```
+
+> 💡 **关键认知**:fmode 的 newapi SK 默认就是 Claude Code 的 `ANTHROPIC_AUTH_TOKEN`。
+> 用户按 Claude Code 正常方式配好 SK 后,技能必须能读到它,否则会误判「缺 token」。
+
+**全链失败时**:打印初始化向导 + **退出码 2**,绝不伪装成功。
+
+---
+
+## 六项自动质检
+
+| # | 检查项 | 验证方式 |
+|---|--------|----------|
+| 1 | 功能完整性 | 运行 `scripts.test` / `test/*.mjs` |
+| 2 | Fmode API 联通 | 探测 `api.fmode.cn` 与 `server.fmode.cn` |
+| 3 | 基础 SOP 跑通 | `skillhub publish --dry-run` + frontmatter 校验 |
+| 4 | 看板后台就绪 | `skill-package-manifest.json` 存在且合法 |
+| 5 | Loop 迭代能力 | `npm view`(轻量)/ `npx --yes`(`--deep`) |
+| 6 | 多端可用性 | 实测 ESM `import` + CLI `--help` |
+
+结果三态:`pass`(有证据通过)/ `fail`(exit 1)/ `skip`(**不算通过**,exit 3)。
+
+---
+
+## 能力边界
+
+**能做**:
+- 脚手架新技能(含完整可跑通结构)
+- 六项自动质检(每项给出可复核 evidence)
+- 生成/执行四渠道发布计划
+- 查询平台端点真实状态
+- 校验 package.json / manifest / SKILL.md frontmatter
+- 检查并初始化 Fmode 凭据
+
+**不能做**:
+- 自动在 Gogs 建仓(`git.fmode.cn/api/v1` basic auth 返回 401,需 Web UI 手动建)
+- 通过短信验证码开户(`/api/fmode/verifycode` 未上线)
+- 替代人工判断技能的业务价值
+
+---
+
+## 验证方法
+
+```bash
+npm test                                     # 冒烟测试(23 项)
+node bin/skill-core.mjs check . --offline    # 自检六项
+node bin/skill-core.mjs verify .             # 静态校验
+```
+
+---
+
+## License
+
+MIT © 2026 Fmode (未来飞马)

+ 21 - 0
templates/skill-starter/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Fmode (未来飞马) · Yuyang001
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 79 - 0
templates/skill-starter/README.md

@@ -0,0 +1,79 @@
+# __SKILL_NAME__ · __SUMMARY__
+
+> 一句话定位(替换本行)
+
+[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
+
+## 这是什么
+
+<!-- 2-3 句话说明技能解决什么问题、给谁用 -->
+
+## 安装
+
+### Claude Code
+```bash
+npx --yes __SKILL_NAME__@latest workspace
+```
+
+### 任意 Agent(读 README 自行安装)
+```bash
+git clone https://github.com/fmodecn/__SKILL_NAME__.git
+cp -r __SKILL_NAME__/skills/__SKILL_NAME__ <你的工具技能目录>/__SKILL_NAME__
+```
+
+### npm / skillhub
+```bash
+npm install __SKILL_NAME__
+skillhub install fmode-__SKILL_NAME__
+```
+
+## 用法
+
+```bash
+# CLI
+npx --yes __SKILL_NAME__@latest greet world
+
+# SDK (ESM)
+import { greet } from '__SKILL_NAME__';
+console.log(greet('world'));
+```
+
+## 凭据
+
+走 Fmode 标准 5 级凭据链(零密钥入库,命中即用,全失败显式报错):
+
+```
+第0级  FMODE_SESSION_TOKEN / ~/.fmode/config.json 的 sessionToken → 自举换 API token
+第1级  环境变量 FMODE_API_TOKEN
+第2级  ~/.fmode/config.json → fmodeApiToken
+第3级  <cwd>/.fmode/config.json → fmodeApiToken
+第4级  ~/.claude/settings.json → env.ANTHROPIC_AUTH_TOKEN
+```
+
+检查凭据:`npx --yes __SKILL_NAME__@latest auth`
+
+## 四端可用性
+
+| 端 | 入口 | 状态 |
+|----|------|------|
+| CLI | `bin/__CLI_NAME__.mjs` | ✅ |
+| SDK (ESM) | `lib/index.mjs` | ✅ |
+| Browser | `browser/index.mjs` | ⬜ 按需 |
+| Server (CJS) | — | ❌ ESM only(团队共识) |
+
+## 开发与发布
+
+```bash
+npm test                         # 冒烟测试
+skill-core check .               # 六项自动质检
+skill-core publish . --apply     # 四渠道发布
+```
+
+## Changelog
+
+### 0.1.0
+- 首版
+
+## License
+
+MIT

+ 70 - 0
templates/skill-starter/SKILL.md

@@ -0,0 +1,70 @@
+---
+slug: fmode-__SKILL_NAME__
+displayName: __SKILL_NAME__
+version: 0.1.0
+summary: __SUMMARY__
+license: MIT
+tags: [fmode, skill]
+---
+
+# __SKILL_NAME__
+
+> __SUMMARY__
+
+## 何时使用本技能
+
+<!-- 写清楚触发场景:用户在什么情况下应该用到这个技能 -->
+
+- 场景一:……
+- 场景二:……
+
+## 快速开始
+
+```bash
+# CLI
+npx --yes __SKILL_NAME__@latest greet world
+
+# SDK
+node -e "import('__SKILL_NAME__').then(m => console.log(m.greet('world')))"
+```
+
+## 能力清单
+
+| 能力 | CLI | SDK |
+|------|-----|-----|
+| 示例能力 | `__CLI_NAME__ greet` | `greet()` |
+
+## 凭据
+
+本技能使用 Fmode 标准 5 级凭据链(命中即用):
+
+0. `FMODE_SESSION_TOKEN` / `~/.fmode/config.json` 的 `sessionToken` → 自举换 API token
+1. 环境变量 `FMODE_API_TOKEN`
+2. `~/.fmode/config.json` → `fmodeApiToken`
+3. `<cwd>/.fmode/config.json` → `fmodeApiToken`
+4. `~/.claude/settings.json` → `env.ANTHROPIC_AUTH_TOKEN`
+
+检查:`__CLI_NAME__ auth`
+
+## 平台服务
+
+<!-- 如需调用 Fmode 平台服务,列出用到的端点并标注实测状态 -->
+
+| 端点 | 状态 | 用途 |
+|------|------|------|
+| `POST /v1/chat/completions` | live | LLM 调用 |
+| `POST /api/listen/transcribe` | live | 录音转写 |
+
+> ⚠️ 调用任何 `planned` 状态端点前必须先探测,404 时显式回落,不得当作已上线。
+
+## 开发
+
+```bash
+npm test                        # 冒烟测试
+skill-core check .              # 六项自动质检
+skill-core publish . --apply    # 四渠道发布
+```
+
+## License
+
+MIT

+ 97 - 0
templates/skill-starter/bin/__CLI_NAME__.mjs

@@ -0,0 +1,97 @@
+#!/usr/bin/env node
+/**
+ * __CLI_NAME__ — __SKILL_NAME__ 的命令行入口
+ * ---------------------------------------------------------------------------
+ * 用法:npx --yes __SKILL_NAME__@latest <command> [args]
+ */
+
+import { VERSION, SKILL_NAME, greet, resolveToken } from '../lib/index.mjs';
+
+const C = {
+  reset: '\x1b[0m',
+  bold: '\x1b[1m',
+  dim: '\x1b[2m',
+  red: '\x1b[31m',
+  green: '\x1b[32m',
+  cyan: '\x1b[36m',
+};
+const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
+const c = (code, s) => (useColor ? `${code}${s}${C.reset}` : s);
+
+function printHelp() {
+  console.log(`
+${c(C.bold, '__CLI_NAME__')} · __SUMMARY__  v${VERSION}
+
+${c(C.bold, '用法')}
+  __CLI_NAME__ <command> [args]
+
+${c(C.bold, '命令')}
+  ${c(C.cyan, 'greet')} [name]     示例命令
+  ${c(C.cyan, 'auth')}             检查 Fmode 凭据是否可用
+  ${c(C.cyan, 'workspace')}        安装到当前工作区(npx 约定入口)
+  ${c(C.cyan, '--help')}           显示帮助
+  ${c(C.cyan, '--version')}        显示版本
+`);
+}
+
+async function cmdGreet(args) {
+  console.log(greet(args[0] || 'world'));
+  return 0;
+}
+
+async function cmdAuth() {
+  const r = await resolveToken();
+  if (r) {
+    const masked = r.token.length > 12 ? `${r.token.slice(0, 6)}...${r.token.slice(-4)}` : '***';
+    console.log(`${c(C.green, '✅')} 凭据可用:${r.source}(${masked})`);
+    return 0;
+  }
+  console.log(`${c(C.red, '❌')} 未找到可用凭据。`);
+  console.log(`
+  请任选一种方式配置:
+    1) export FMODE_API_TOKEN='sk-...'
+    2) export FMODE_SESSION_TOKEN='r:...'   ${c(C.dim, '# 登录 FMODE Studio 取得,将自动换取 API token')}
+    3) 在 ~/.fmode/config.json 写入 { "fmodeApiToken": "sk-..." }
+`);
+  return 1;
+}
+
+async function cmdWorkspace() {
+  console.log(`${c(C.green, '✅')} ${SKILL_NAME} 已就绪(v${VERSION})。`);
+  console.log(`  在 Agent 会话中直接描述你的任务即可触发本技能。`);
+  return 0;
+}
+
+async function main() {
+  const argv = process.argv.slice(2);
+  const cmd = argv[0];
+
+  if (!cmd || cmd === '--help' || cmd === '-h') {
+    printHelp();
+    return 0;
+  }
+  if (cmd === '--version' || cmd === '-v') {
+    console.log(VERSION);
+    return 0;
+  }
+
+  switch (cmd) {
+    case 'greet':
+      return cmdGreet(argv.slice(1));
+    case 'auth':
+      return cmdAuth();
+    case 'workspace':
+      return cmdWorkspace();
+    default:
+      console.error(`${c(C.red, '✗')} 未知命令:${cmd}`);
+      console.error(`  运行 ${c(C.bold, '__CLI_NAME__ --help')} 查看可用命令。`);
+      return 2;
+  }
+}
+
+main()
+  .then((code) => process.exit(code))
+  .catch((err) => {
+    console.error(`${c(C.red, '✗')} ${err?.stack || err}`);
+    process.exit(1);
+  });

+ 83 - 0
templates/skill-starter/lib/index.mjs

@@ -0,0 +1,83 @@
+/**
+ * __SKILL_NAME__ · ESM 入口
+ * ---------------------------------------------------------------------------
+ * 这里是技能的 SDK 端。所有公共能力都从本文件 export。
+ * 纪律:ESM only、零依赖优先、不在此文件读文件系统(除非确有需要)。
+ *
+ * @example
+ *   import { greet, VERSION } from '__SKILL_NAME__';
+ *   console.log(greet('world'));
+ */
+
+import fs from 'node:fs';
+import path from 'node:path';
+import os from 'node:os';
+
+export const VERSION = '0.1.0';
+export const SKILL_NAME = '__SKILL_NAME__';
+
+/** Fmode 平台基址(如需调用平台服务,从这里取) */
+export const FMODE_API_BASE = 'https://api.fmode.cn';
+export const FMODE_GATEWAY_BASE = 'https://server.fmode.cn';
+
+/**
+ * 示例函数 —— 替换成你技能的真实能力。
+ * @param {string} name
+ * @returns {string}
+ */
+export function greet(name = 'world') {
+  return `Hello, ${name}! 来自 __SKILL_NAME__ v${VERSION}`;
+}
+
+/**
+ * 示例:解析 Fmode 凭据(5 级链的精简版)。
+ * 生产实现请直接复用 skill-core-guide 的 lib/bootstrap.mjs。
+ *
+ * @param {{cwd?: string}} [opts]
+ * @returns {Promise<{token: string, source: string}|null>}
+ */
+export async function resolveToken(opts = {}) {
+  // 第 1 级:环境变量
+  if (process.env.FMODE_API_TOKEN) {
+    return { token: process.env.FMODE_API_TOKEN, source: 'env:FMODE_API_TOKEN' };
+  }
+
+  // 第 0 级:sessionToken 自举
+  const sessionToken =
+    process.env.FMODE_SESSION_TOKEN || readConfigToken(opts.cwd);
+  if (sessionToken) {
+    try {
+      const res = await fetch(`${FMODE_GATEWAY_BASE}/api/fmode/voc-skill/install-prompt`, {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json', 'x-parse-session-token': sessionToken },
+        body: JSON.stringify({ channel: 'claude-code', scope: 'user' }),
+        signal: AbortSignal.timeout(15000),
+      });
+      if (res.ok) {
+        const body = await res.json().catch(() => null);
+        const prompt = body?.data?.prompt || '';
+        const m = prompt.match(/sk-(?!ant-)[A-Za-z0-9_-]{8,}/);
+        // ⚠️ token 仅内存持有,不落盘不进日志
+        if (m) return { token: m[0], source: 'sessionToken 自举' };
+      }
+    } catch {
+      /* 回落 */
+    }
+  }
+
+  return null;
+}
+
+/** 从 ~/.fmode/config.json 读 sessionToken(ESM:用顶层 import,不用 require) */
+function readConfigToken(cwd) {
+  try {
+    const p = path.join(os.homedir(), '.fmode', 'config.json');
+    if (!fs.existsSync(p)) return null;
+    const cfg = JSON.parse(fs.readFileSync(p, 'utf-8').replace(/^/, ''));
+    return cfg.sessionToken || (cfg.user && cfg.user.sessionToken) || null;
+  } catch {
+    return null;
+  }
+}
+
+export default { VERSION, SKILL_NAME, greet, resolveToken };

+ 43 - 0
templates/skill-starter/package.json

@@ -0,0 +1,43 @@
+{
+  "name": "__SKILL_NAME__",
+  "version": "0.1.0",
+  "description": "__SUMMARY__",
+  "type": "module",
+  "main": "./lib/index.mjs",
+  "exports": {
+    ".": {
+      "import": "./lib/index.mjs",
+      "default": "./lib/index.mjs"
+    }
+  },
+  "bin": {
+    "__CLI_NAME__": "./bin/__CLI_NAME__.mjs"
+  },
+  "files": [
+    "lib/",
+    "bin/",
+    "browser/",
+    "skills/",
+    "README.md",
+    "LICENSE",
+    "skill-package-manifest.json"
+  ],
+  "engines": {
+    "node": ">=18"
+  },
+  "scripts": {
+    "test": "node test/smoke.mjs"
+  },
+  "keywords": [
+    "fmode",
+    "skill",
+    "__CLI_NAME__"
+  ],
+  "author": "Yuyang001 (FmodeAgent)",
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/fmodecn/__SKILL_NAME__.git"
+  },
+  "dependencies": {}
+}

+ 28 - 0
templates/skill-starter/skill-package-manifest.json

@@ -0,0 +1,28 @@
+{
+  "name": "__SKILL_NAME__",
+  "version": "0.1.0",
+  "description": "__SUMMARY__",
+  "skills": [
+    {
+      "name": "__SKILL_NAME__",
+      "path": "skills/__SKILL_NAME__/SKILL.md"
+    }
+  ],
+  "install": "npx --yes __SKILL_NAME__@latest workspace",
+  "cli": {
+    "__CLI_NAME__": "./bin/__CLI_NAME__.mjs"
+  },
+  "entry": {
+    "sdk": "./lib/index.mjs"
+  },
+  "tier": "service",
+  "platforms": ["gogs", "github", "npm", "skillhub"],
+  "skillhub": {
+    "slug": "fmode-__SKILL_NAME__",
+    "displayName": "__SKILL_NAME__"
+  },
+  "repository": {
+    "gogs": "https://git.fmode.cn/fmode/__SKILL_NAME__",
+    "github": "https://github.com/fmodecn/__SKILL_NAME__"
+  }
+}

+ 75 - 0
templates/skill-starter/skills/__SKILL_NAME__/SKILL.md

@@ -0,0 +1,75 @@
+---
+name: __SKILL_NAME__
+description: "__SUMMARY__"
+version: 0.1.0
+author: Yuyang001 (FmodeAgent)
+license: MIT
+tags: [fmode, skill]
+---
+
+# __SKILL_NAME__
+
+> __SUMMARY__
+
+<!--
+本文件是技能在 Agent 会话中的行为契约:Agent 读到这里才知道何时该调用本技能。
+frontmatter 用 Hermes 格式(name/description/version/tags);
+发布到 skillhub 时根目录的 SKILL.md 用 skillhub 格式(slug/displayName/summary/license)。
+-->
+
+## 何时使用(触发场景)
+
+当用户出现以下诉求时,使用本技能:
+
+1. ……
+2. ……
+3. ……
+
+## 如何使用
+
+```bash
+npx --yes __SKILL_NAME__@latest greet world
+```
+
+## 能力边界
+
+**能做**:
+- ……
+
+**不能做**:
+- ……
+
+## 凭据
+
+Fmode 标准 5 级凭据链,命中即用:
+
+```
+第0级  FMODE_SESSION_TOKEN / ~/.fmode/config.json 的 sessionToken → 自举
+第1级  环境变量 FMODE_API_TOKEN
+第2级  ~/.fmode/config.json → fmodeApiToken
+第3级  <cwd>/.fmode/config.json → fmodeApiToken
+第4级  ~/.claude/settings.json → env.ANTHROPIC_AUTH_TOKEN
+```
+
+全失败时必须显式报错并给出配置指引,**不得伪装成功**。
+
+## 平台端点
+
+<!-- 只列真实用到的端点,并标注实测状态(live / planned / deprecated) -->
+
+| 端点 | 状态 | 用途 |
+|------|------|------|
+| — | — | — |
+
+> ⚠️ `planned` 端点调用前必须探测,404 时显式回落。
+
+## 验证方法
+
+```bash
+npm test                  # 冒烟测试
+skill-core check .        # 六项自动质检
+```
+
+## License
+
+MIT

+ 21 - 0
templates/skill-starter/test/smoke.mjs

@@ -0,0 +1,21 @@
+// __SKILL_NAME__ 冒烟测试
+// 运行:npm test   或   node test/smoke.mjs
+
+import test from 'node:test';
+import assert from 'node:assert/strict';
+
+import { greet, VERSION, SKILL_NAME } from '../lib/index.mjs';
+
+test('导出核心常量', () => {
+  assert.equal(VERSION, '0.1.0');
+  assert.equal(SKILL_NAME, '__SKILL_NAME__');
+});
+
+test('greet 返回预期文本', () => {
+  assert.equal(greet('world'), `Hello, world! 来自 __SKILL_NAME__ v${VERSION}`);
+  assert.equal(greet(), 'Hello, world! 来自 __SKILL_NAME__ v0.1.0');
+});
+
+test('greet 接受自定义名字', () => {
+  assert.ok(greet('Fmode').includes('Fmode'));
+});

+ 353 - 0
test/smoke.mjs

@@ -0,0 +1,353 @@
+// 自动质检冒烟测试
+// ---------------------------------------------------------------------------
+// 运行:npm test   或   node test/smoke.mjs
+// 零依赖,仅用 node:assert 与 node:test。
+
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import path from 'node:path';
+import fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
+
+import * as core from '../lib/index.mjs';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const ROOT = path.resolve(__dirname, '..');
+
+// ============================================================
+// 1. ESM 入口导出完整性
+// ============================================================
+
+test('lib/index.mjs 导出全部公共接口', () => {
+  const required = [
+    'VERSION',
+    'PLATFORM',
+    'ENDPOINTS',
+    'CREDENTIAL_CHAIN',
+    'TIERS',
+    'RUNTIMES',
+    'CHECKS',
+    'runChecks',
+    'summarize',
+    'validateName',
+    'validatePackageJson',
+    'validateManifest',
+    'validateFrontmatter',
+    'parseFrontmatter',
+    'resolveApiToken',
+    'bootstrap',
+    'INVENTORY',
+    'byTier',
+    'stats',
+    'publishPlan',
+  ];
+  for (const k of required) {
+    assert.ok(k in core, `缺少导出:${k}`);
+  }
+  assert.equal(core.VERSION, '1.0.0');
+});
+
+test('ENDPOINTS 真值表结构合法', () => {
+  for (const [key, e] of Object.entries(core.ENDPOINTS)) {
+    assert.ok(e.url, `${key} 缺 url`);
+    assert.ok(['live', 'planned', 'deprecated'].includes(e.status), `${key} status 非法:${e.status}`);
+    assert.ok(e.auth, `${key} 缺 auth`);
+    assert.ok(e.purpose, `${key} 缺 purpose`);
+  }
+  // 至少要有 live 端点,否则平台没得用
+  assert.ok(core.endpointsByStatus('live').length >= 4, 'live 端点数量异常');
+});
+
+// ============================================================
+// 2. 命名校验
+// ============================================================
+
+test('validateName 接受合法名、拒绝非法名', () => {
+  assert.equal(core.validateName('skill-my-thing').ok, true);
+  assert.equal(core.validateName('fmode-image').ok, true);
+  assert.equal(core.validateName('my-thing').ok, false);
+  assert.equal(core.validateName('skill-My_Thing').ok, false);
+  assert.equal(core.validateName('').ok, false);
+  assert.equal(core.validateName(null).ok, false);
+});
+
+// ============================================================
+// 3. package.json 校验
+// ============================================================
+
+test('validatePackageJson 对合法包通过', () => {
+  const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
+  const r = core.validatePackageJson(pkg);
+  assert.equal(r.ok, true, `本仓库 package.json 应合法,实际错误:${r.errors.join(';')}`);
+});
+
+test('validatePackageJson 捕获缺失字段与 CJS 残留', () => {
+  const bad = { name: 'skill-x', version: '1.0.0' };
+  const r = core.validatePackageJson(bad);
+  assert.equal(r.ok, false);
+  assert.ok(r.errors.some((e) => e.includes('type')));
+  assert.ok(r.errors.some((e) => e.includes('exports')));
+
+  const cjs = {
+    name: 'skill-x',
+    version: '1.0.0',
+    description: 'd',
+    type: 'module',
+    main: './lib/index.mjs',
+    exports: { '.': { import: './lib/index.mjs', default: './lib/index.mjs' } },
+    bin: { x: './bin/x.mjs' },
+    files: [],
+    license: 'MIT',
+    require: './lib/index.cjs',
+  };
+  const r2 = core.validatePackageJson(cjs);
+  assert.equal(r2.ok, false);
+  assert.ok(r2.errors.some((e) => e.includes('require')));
+});
+
+// ============================================================
+// 4. frontmatter 解析(三种格式)
+// ============================================================
+
+test('parseFrontmatter 解析标量与行内数组', () => {
+  const text = [
+    '---',
+    'name: skill-demo',
+    'description: "一段描述"',
+    'version: 1.0.0',
+    'tags: [a, b, c]',
+    '---',
+    '',
+    '# 标题',
+  ].join('\n');
+  const { data, body, raw } = core.parseFrontmatter(text);
+  assert.equal(data.name, 'skill-demo');
+  assert.equal(data.description, '一段描述');
+  assert.deepEqual(data.tags, ['a', 'b', 'c']);
+  assert.ok(raw !== null);
+  assert.ok(body.includes('# 标题'));
+});
+
+test('validateFrontmatter 校验 hermes 与 skillhub 两种格式', () => {
+  const hermes = '---\nname: skill-demo\ndescription: d\nversion: 1.0.0\ntags: [x]\n---\nbody';
+  const h = core.validateFrontmatter(hermes, 'hermes');
+  assert.equal(h.ok, true, h.errors.join(';'));
+
+  const skillhub = '---\nslug: fmode-skill-demo\ndisplayName: skill-demo\nversion: 1.0.0\nsummary: s\nlicense: MIT\n---\nbody';
+  const s = core.validateFrontmatter(skillhub, 'skillhub');
+  assert.equal(s.ok, true, s.errors.join(';'));
+
+  // 缺字段应失败
+  const broken = '---\nname: skill-demo\n---\nbody';
+  assert.equal(core.validateFrontmatter(broken, 'skillhub').ok, false);
+});
+
+test('本仓库 SKILL.md 满足 skillhub 格式', () => {
+  const text = fs.readFileSync(path.join(ROOT, 'SKILL.md'), 'utf-8');
+  const r = core.validateFrontmatter(text, 'skillhub');
+  assert.equal(r.ok, true, `SKILL.md frontmatter 不合法:${r.errors.join(';')}`);
+});
+
+// ============================================================
+// 5. manifest 校验
+// ============================================================
+
+test('validateManifest 对本仓库清单通过', () => {
+  const m = JSON.parse(fs.readFileSync(path.join(ROOT, 'skill-package-manifest.json'), 'utf-8'));
+  const r = core.validateManifest(m);
+  assert.equal(r.ok, true, r.errors.join(';'));
+});
+
+// ============================================================
+// 6. 清单数据完整性
+// ============================================================
+
+test('INVENTORY 覆盖三个分层且条目字段完整', () => {
+  const s = core.stats();
+  assert.ok(s.total >= 15, `清单条目过少:${s.total}`);
+  assert.ok(s.byTier.system > 0 && s.byTier.service > 0 && s.byTier.application > 0);
+
+  for (const sk of core.INVENTORY) {
+    assert.ok(sk.name, '条目缺 name');
+    assert.ok(sk.displayName, `${sk.name} 缺 displayName`);
+    assert.ok(sk.summary, `${sk.name} 缺 summary`);
+    assert.ok(Array.isArray(sk.platforms) && sk.platforms.length, `${sk.name} 缺 platforms`);
+    assert.ok(core.TIERS[sk.tier], `${sk.name} tier 非法:${sk.tier}`);
+    for (const p of sk.platforms) {
+      assert.ok(core.CHANNELS[p], `${sk.name} 平台非法:${p}`);
+    }
+    // npm 渠道必须给出包名
+    if (sk.platforms.includes('npm')) {
+      assert.ok(sk.npmName, `${sk.name} 声明了 npm 渠道但缺 npmName`);
+    }
+  }
+});
+
+test('任务书点名的技能全部在清单中', () => {
+  const expected = [
+    'skill-heterarchy',
+    'skill-multi-branch',
+    'skill-bypass-permission',
+    'skill-task-progress',
+    'plugin-wecom-fix',
+    'skill-agent-clone',
+    'skill-storage',
+    'skill-image',
+    'skill-vision',
+    'skill-listen',
+    'fmode-ffmpeg',
+    'fmode-qiwei',
+    'skill-study-report',
+    'skill-present',
+    'fmode-product-lab',
+  ];
+  const names = core.INVENTORY.map((s) => s.name);
+  for (const e of expected) {
+    assert.ok(names.includes(e), `清单缺少任务书点名的技能:${e}`);
+  }
+});
+
+// ============================================================
+// 7. 分发计划
+// ============================================================
+
+test('publishPlan 覆盖四渠道且命令非空', () => {
+  const plan = core.publishPlan('skill-demo', { dir: '.', changelog: 'test' });
+  assert.equal(plan.length, 4);
+  assert.deepEqual(plan.map((p) => p.channel), ['gogs', 'github', 'npm', 'skillhub']);
+  for (const p of plan) {
+    assert.ok(p.steps.length > 0, `${p.channel} 无步骤`);
+    for (const s of p.steps) assert.equal(typeof s, 'string');
+  }
+});
+
+// ============================================================
+// 8. 六项检查注册表
+// ============================================================
+
+test('CHECKS 恰好六项且 id 唯一', () => {
+  assert.equal(core.CHECKS.length, 6);
+  const ids = core.CHECKS.map((c) => c.id);
+  assert.equal(new Set(ids).size, 6);
+  assert.deepEqual(ids, ['functional', 'apiConnectivity', 'sop', 'dashboard', 'loop', 'multiRuntime']);
+});
+
+test('summarize 正确聚合(含 skip 语义)', () => {
+  const s = core.summarize([
+    { id: 'a', status: 'pass' },
+    { id: 'b', status: 'pass' },
+    { id: 'c', status: 'skip' },
+  ]);
+  assert.equal(s.total, 3);
+  assert.equal(s.pass, 2);
+  assert.equal(s.skip, 1);
+  assert.equal(s.ok, false, '有 skip 时不应判定为完全通过');
+  assert.equal(s.partial, true);
+  assert.deepEqual(s.unverified, ['c']);
+
+  const allPass = core.summarize([{ id: 'a', status: 'pass' }, { id: 'b', status: 'pass' }]);
+  assert.equal(allPass.ok, true);
+  assert.equal(allPass.partial, false);
+});
+
+// ============================================================
+// 9. 离线质检自跑(本仓库对自己做质检)
+// ============================================================
+
+test('runChecks --offline 在本仓库上可运行且无失败项', async () => {
+  const report = await core.runChecks(ROOT, { offline: true, skipExec: false });
+  assert.equal(report.results.length, 6);
+  const failed = report.results.filter((r) => r.status === 'fail');
+  assert.equal(
+    failed.length,
+    0,
+    `本仓库质检不应有失败项,实际:\n${failed.map((f) => `${f.id}: ${f.detail}`).join('\n')}`,
+  );
+});
+
+test('runChecks 拒绝未知检查项', async () => {
+  await assert.rejects(
+    () => core.runChecks(ROOT, { only: ['nope'] }),
+    /未知检查项/,
+  );
+});
+
+// ============================================================
+// 10. 凭据解析(不联网,只验证形态与回落行为)
+// ============================================================
+
+test('validateToken 拒绝 sk-ant- 与非法形态', async () => {
+  const { validateToken } = await import('../lib/bootstrap.mjs');
+  assert.equal(validateToken('sk-ant-api03-xxxx').ok, false);
+  assert.equal(validateToken('not-a-key').ok, false);
+  assert.equal(validateToken('').ok, false);
+  assert.equal(validateToken(null).ok, false);
+  assert.equal(validateToken('sk-abcdefgh12345678').ok, true);
+});
+
+test('resolveFmodeDir 尊重 FMODE_HOME 覆盖', async () => {
+  const { resolveFmodeDir } = await import('../lib/bootstrap.mjs');
+  const prev = process.env.FMODE_HOME;
+  process.env.FMODE_HOME = '/tmp/fmode-test-home';
+  try {
+    assert.equal(resolveFmodeDir(), '/tmp/fmode-test-home');
+  } finally {
+    if (prev === undefined) delete process.env.FMODE_HOME;
+    else process.env.FMODE_HOME = prev;
+  }
+});
+
+// ============================================================
+// 11. 浏览器 bundle 无 Node 依赖
+// ============================================================
+
+test('browser/index.mjs 不 import 任何 node: 内置模块', () => {
+  const src = fs.readFileSync(path.join(ROOT, 'browser', 'index.mjs'), 'utf-8');
+  const nodeImports = [...src.matchAll(/from\s+['"]node:[a-z_]+['"]/g)];
+  assert.equal(nodeImports.length, 0, `browser bundle 违规引入:${nodeImports.map((m) => m[0]).join(', ')}`);
+});
+
+test('browser bundle 可独立 import 且导出核心常量', async () => {
+  const b = await import('../browser/index.mjs');
+  assert.equal(b.VERSION, '1.0.0');
+  assert.ok(b.PLATFORM.apiBase.includes('fmode.cn'));
+  assert.ok(Object.keys(b.ENDPOINTS).length > 0);
+  assert.equal(typeof b.validatePackageJson, 'function');
+  assert.equal(typeof b.checkApiConnectivity, 'function');
+});
+
+// ============================================================
+// 12. 脚手架模板完整性
+// ============================================================
+
+test('templates/skill-starter 脚手架文件齐全', () => {
+  const tpl = path.join(ROOT, 'templates', 'skill-starter');
+  assert.ok(fs.existsSync(tpl), '脚手架模板目录不存在');
+  const required = [
+    'SKILL.md',
+    'package.json',
+    'lib/index.mjs',
+    'skill-package-manifest.json',
+    'README.md',
+    'LICENSE',
+  ];
+  for (const f of required) {
+    assert.ok(fs.existsSync(path.join(tpl, f)), `脚手架缺少 ${f}`);
+  }
+  const binDir = path.join(tpl, 'bin');
+  assert.ok(fs.existsSync(binDir), '脚手架缺少 bin/');
+  const bins = fs.readdirSync(binDir).filter((f) => f.endsWith('.mjs'));
+  assert.ok(bins.length > 0, '脚手架 bin/ 下无 .mjs 入口');
+
+  const testDir = path.join(tpl, 'test');
+  assert.ok(fs.existsSync(testDir), '脚手架缺少 test/');
+});
+
+test('脚手架占位符齐全(可被 init 替换)', () => {
+  const tpl = path.join(ROOT, 'templates', 'skill-starter');
+  const pkg = JSON.parse(fs.readFileSync(path.join(tpl, 'package.json'), 'utf-8'));
+  assert.equal(pkg.name, '__SKILL_NAME__', 'package.json 应使用 __SKILL_NAME__ 占位符');
+  assert.ok(pkg.type === 'module');
+  assert.equal(pkg.main, './lib/index.mjs');
+  assert.ok(pkg.bin['__CLI_NAME__'], 'bin 应使用 __CLI_NAME__ 占位符');
+});