name: skill-heterarchy description: "预计超 3 步或 2 分钟的任务,先拆并行维度派 FmodeCode/Claude Code。主控写任务书+验收,不阻塞。" version: 1.2.0 author: Yuyang001 (FmodeAgent) license: MPL-2.0 copyright: "Copyright (c) 2026 未来飞马 Fmode"
Delegate 是对外派活,Heterarchy 是对内分思。 本技能用 FmodeAgent / Hermes Agent 的底层工具在单一 Agent 内部分化多心智并行思考。 加载方式:
skill_view(name='skill-heterarchy')
每次 FmodeAgent / Hermes Agent 处理用户消息时,可以在同一轮对话内同时发起多个独立的工具调用。这是 Heterarchy 能真正"并行"的底层前提——如果连工具调用都串行发,后面的并行派发就是空谈。
| 场景 | 同一轮并发发出 |
|---|---|
| 多路信息采集 | 多个 terminal()(不同命令) |
| 边读边搜 | read_file() + search_files() |
| 边调研边干活 | web_search() + 一个后台 FmodeCode / Claude Code 启动 |
| 多进程编排 | 多个 tool_call() 管理不同进程 |
原则:两个工具调用之间若没有数据依赖(后者不需要前者的输出),就必须放在同一轮发出,而不是分成多轮对话串行。
反例(串行 3 轮对话,浪费 3 个往返):
第 1 轮:read_file(A) → 等结果
第 2 轮:read_file(B) → 等结果
第 3 轮:web_search(...) → 等结果
正例(一轮搞定,延迟≈最慢的那一个):
第 1 轮:read_file(A) + read_file(B) + web_search(...) ← 同轮并发
在发工具调用前,对每个待发调用问一句:
"我这次调用需要用到另一个调用的输出吗?"
本机制是后续所有章节(§X 批量分析、§Y 进度表、§Z 异步回执、§V 拓扑分批)的地基:
terminal(background=True, notify=True) 必须在同一轮发出,才能同时启动多个心智;一句话:先把能并的都并出去,再把剩下的按依赖排序。
本节 1.2.0 起改为硬规则。原因:旧版只写了「当你收到一个复杂任务, 明显可以拆成多个独立维度并行处理时」——判断太软,忙碌的 Agent 会直接跳过, 于是长任务串行硬啃、用户干等。触发条件必须是可自检的是非题,不是感觉。
| # | 自检问题 | 是 |
|---|---|---|
| 1 | 预计需要 > 8 个工具调用? | → 拆 |
| 2 | 预计有任何一次等待 > 30 秒(安装/构建/克隆/遍历)? | → 拆 |
| 3 | 目标是 N 个同类对象(N ≥ 3)?如「所有容器」「几十个生命」「批量安装」 | → 拆 |
| 4 | 任务含 ≥ 2 个可独立完成的维度(如「改代码」+「改文档」+「写测试」)? | → 拆 |
| 5 | 用户说了 批量 / 全部 / 所有 / 几十个 / 并发 / 尽快? | → 拆 |
判定纪律:不确定就按「拆」处理。 拆错的代价只是多写一份任务书; 不拆的代价是用户干等几十分钟、主控上下文被中间数据淹没。
任务:给 34 台数字生命容器批量铺 3 个技能 + 改云函数 + 建验收机制
实际:整轮串行自己啃 —— 47 轮工具调用,单条命令 60/90/120 秒地等,
期间用户在聊天里反复问「怎么还没反应」,主控上下文涨到 129k。
本应:拆成 4 个认知子单元并行派 Claude Code(技能改造 / 容器分发 /
云函数部署 / 验收器开发),主控只写任务书 + 验收。
教训:任务明明命中自检 1/2/3/5 四条,却因为「触发条件是软描述」而没被拦住。
「多个主题需要完成」「同时做这几件事」「这几个部分需要…」「尽量并行」 「批量」「全舰队」「所有容器」「几十个生命」「铺开」「逐台」
terminal() 或 execute_code()kanban_create 委派,不是 Heterarchy这是唯一不能交给 FmodeCode / Claude Code 的步骤。FmodeAgent / Hermes Agent 必须亲自:
实操模板(FmodeAgent / Hermes Agent 的思考过程):
收到:"四个主题的 HTML PPT 需要图文并茂增强"
拆解:
├→ 主题一:数字分身(独立 → 无前置依赖)
├→ 主题二:Harness(独立 → 无前置依赖)
├→ 主题三:Loop(独立 → 无前置依赖)
├→ 主题四:RSI(独立 → 无前置依赖)
└→ 依赖:无(四主题可完全并行)
项目根目录:/opt/data/git-repos/skill-present
FmodeCode / Claude Code 必须在项目根目录启动,否则读不到 CLAUDE.md 的项目规则。
每个认知子单元需要一份独立的任务书,包含:
1. 背景上下文(用户原始需求)
2. 本次要做什么(明确范围,不要多做)
3. 要保留什么(已有的内容别动)
4. inline 素材(路径、关键描述)
5. 纪律约束:
- 框架不可改
- 自绘 SVG 保留
- 案例 CDN 图优先用已有
- skill-image 只用于核心架构图/场景图
6. 验收标准(git commit、Content-Type 验证、URL 200)
7. 禁止事项(不要重写、不要改标题、不要改结构)
任务书写入临时文件 /tmp/cc-xxx-task.md,用 cat 管道喂入 FmodeCode / Claude Code,不用 $(cat) 避免路径问题:
正确姿势:
cat /tmp/cc-xxx-task.md | /opt/data/npm-global/bin/claude -p --dangerously-skip-permissions > /tmp/cc-xxx.log 2>&1 &
错误姿势(踩过坑):
# 不要用 $(cat file) —— 文件路径在后台进程可能不同
# 不要省略绝对路径 —— exit 127 找不到 claude
# FmodeAgent / Hermes Agent 代码中:
terminal(
command=f"""
export PATH="/opt/data/npm-global/bin:$PATH"
cd {project_root}
cat {task_file_path} | /opt/data/npm-global/bin/claude \\
-p --dangerously-skip-permissions \\
> {log_path} 2>&1
""",
background=True,
notify=True,
timeout=1800,
workdir=project_root
)
# → 返回 session_id 用于后续监控
# 启动后 60s 首次健康检查
terminal("sleep 60 && pgrep -f 'claude -p' | wc -l", timeout=90)
# 或主动轮询
tool_call(calls=[{"name": "process_manage", "arguments": {"action": "list"}}])
# → 检查 uptime_seconds 是否在增长
# → 检查 output_preview 是否为空
# 检查是否 commit
terminal("cd {repo} && git log --oneline -3", timeout=10)
# 验证图片 Content-Type(重要!只查 HTTP 200 会被 404 首页骗)
terminal(f"""
for url in $(grep -roh 'https://fmode.cn[^"'"'"' )]*\\.png' {repo}/*.html 2>/dev/null | sort -u); do
CT=$(curl -s -m 10 -o /dev/null -w "%{{content_type}}" "$url")
[[ "$CT" == image/* ]] || echo "❌ 图裂: $CT $url"
done""", timeout=30)
# 如果主题一的 commit 包含架构图,主题二需要引用
terminal("cd {repo} && git diff HEAD~1 --name-only", timeout=10)
# 读文件内容作为 inline 素材塞进主题二的任务书
read_file(path="{repo}/path/to/architecture.md")
# 检查 settings.json 确保模型名正确
# 用户钦点:FmodeAgent / Hermes Agent=deepseek/deepseek-v4.1-flash, FmodeCode / Claude Code=deepseek/deepseek-v4.1-flash[1m]
terminal("cat {project_root}/.claude/settings.json", timeout=5)
# 如发现模型名不对,patch 修正
收到复杂任务
│
├→ 可以拆成 2-5 个独立维度?
│ ├→ 否 → 单一 FmodeCode / Claude Code 进程处理 或 本 Agent 直接完成
│ └→ 是 → 继续
│
├→ 子单元之间有依赖关系?
│ ├→ 是 → 拓扑排序:先做无依赖的,串行分批
│ │ ├→ 所有独立 → 全并行(最多 5 个同时)
│ │ └→ 部分依赖 → 分批并行:依赖关系跨批次传递
│ └→ 否 → 全并行启动
│
├→ 每个子单元的工作量?
│ ├→ 简单(< 50 行代码/单页)→ 不启 FmodeCode / Claude Code,本 Agent 直接写
│ ├→ 中等(需要完整思考/调研/创作)→ 独立 FmodeCode / Claude Code 进程
│ └→ 复杂(需要跨文件多模块/项目级)→ 优先用 kanban_create 委派给 fullstack profile
│
├→ 项目目录正确吗?
│ ├→ 每个 FmodeCode / Claude Code 必须在项目根目录启动
│ └→ 错目录 → FmodeCode / Claude Code 读不到 CLAUDE.md → 规则丢失 → 出次品
│
└→ 启动后:
├→ 60s 后检查存活
├→ 存活 → 等待 notify → 收敛验收
└→ 全死 → 执行恢复流程
一旦 process_manage(action="list") 显示所有进程已退出,按以下顺序处理:
terminal(f"tail -30 {log_path}", timeout=10)
| 日志症状 | 诊断 | 处置 |
|---|---|---|
command not found: claude 或 exit 127 |
claude 不在 PATH | 用 /opt/data/npm-global/bin/claude 绝对路径 |
503 No available channel 或 exit 1 |
模型渠道不可用 | 检查 ~/.claude/settings.json 模型名(少 deepseek/ 前缀会 503) |
401 或 Not logged in |
Token 失效 | 检查 ANTHROPIC_AUTH_TOKEN=$FMODE_API_KEY |
| 日志为空 + exit 0 | 假成功 | 从 $(cat) 改 cat pipe方式重派 |
| 日志有内容但 exit 1 | 任务执行中出错 | 读日志末尾判断:模型退火 / 文件读写错 / 权限问题 |
| 通知没来、长时间无响应 | 进程阻塞 | pgrep -f claude 如果还在跑则等待;否则杀进程重派 |
# 1. 如果是"续做"(之前的部分产物可用)
# 在新的任务书开头写明:
# "已完成:xxx(state) 继续做:yyy(未完成部分)"
# 2. 如果是"从零重做"(完全失败)
# 缩小范围:大任务拆小
# 禁用 FmodeCode / Claude Code 的某些能力:机器产出内容需要再确认
# 3. 连败 3 次同域 → 停止 FmodeCode / Claude Code 重试
# 本 Agent 直接处理 或 拆成更细的原子指令
Step 1: 认知拆解
FmodeAgent / Hermes Agent 消化用户需求:"四个主题需要图文并茂增强,框架不动,加图"
→ 四主题可完全并行,项目根目录=skill-present
Step 2: 心智分化
FmodeAgent / Hermes Agent 为每个主题编写独立任务书(/tmp/cc-v6-0x-resume.md)
每份包含:当前 commit hash + 结构框架 + 纪律 + 验收标准
Step 3: 并行启动
terminal(background=True, notify=True, workdir=skill-present,
command=f"cat /tmp/cc-v6-01-resume.md | /opt/data/npm-global/bin/claude -p --dangerously-skip-permissions > /tmp/cc-v6-01-r2.log 2>&1")
terminal(background=True, notify=True, workdir=skill-present,
command=f"cat /tmp/cc-v6-02-resume.md | ...") # 主题二
terminal(background=True, notify=True, workdir=skill-present,
command=f"cat /tmp/cc-v6-03-resume.md | ...") # 主题三
terminal(background=True, notify=True, workdir=skill-present,
command=f"cat /tmp/cc-v6-04-resume.md | ...") # 主题四
Step 4: 阻塞自愈(首次 60s 后)
process_manage(action="list") → 检查所有子单元存活
Step 5: 心智收敛(逐个子单元 notify)
每个 notify 到达:
└→ git log --oneline 检查 commit →
└→ curl Content-Type 验证图片 →
└→ 记录产出摘要
Step 6: 统一交付
push 所有 commit → OBS sync → CDN 刷新 → URL 验证 → 发用户
claude -p "$(cat task.md)" → 路径丢失,后台进程找不到文件cat task.md | claude -p → 管道直送,无文件路径依赖.claude/rules/curl -o /dev/null -w "%{http_code}" → 200 可能是 404 首页curl -o /dev/null -w "%{content_type}" → 必须是 image/*files: ["lib/", "bin/", ...] → npm publish 把无关文件全打包本技能不替代 FmodeAgent / Hermes Agent 内置工具,而是编排它们:
FmodeAgent / Hermes Agent 收到任务
│
├→ skill-heterarchy(本技能)→ 决策是否启用异层认知
│ └→ 认知分化 → 写任务书
│
├→ terminal(FmodeAgent / Hermes Agent 工具)→ 后台启动 FmodeCode / Claude Code 进程
├→ process_manage → 监控存活
├→ read_file / patch / git → 收敛验证
├→ web_search → 前置调研
└→ delegate_task → 轻量子任务(与 Heterarchy 可组合使用)
何时用 delegate_task 而不是 Heterarchy?
delegate_task何时用 kanban_create 而不是 Heterarchy?
kanban_create当要对多个文件/多条数据做 grep、统计、聚合、转换时,不要用一串 terminal() 逐个敲。改用 FmodeAgent / Hermes Agent 内置的 execute_code(Python 内核),一次进程内跑完。
| 场景 | 例子 |
|---|---|
| 跨目录 grep + 聚合 | 统计所有技能里各模型名的出现次数 |
| 多 HTTP 请求并行测试 | 一次性 curl 全部产物 URL 并汇总 Content-Type |
| 批量文件读写 + 统计 | 扫描所有 .html,统计图片引用数 |
| 数据格式转换 | JSON ↔ CSV ↔ YAML 互转 |
| git diff 分析 | 汇总一次 commit 涉及的文件类型/行数 |
超过 2 个文件需要 grep / 统计 / 聚合 → 用
execute_code,而不是逐个terminal。
逐个 terminal 的代价:每个文件一轮往返 + 一次进程启动开销;execute_code 把这些压成一次调用、一个摘要。
import os, json, subprocess, glob
# 1. 收集目标
targets = glob.glob("/opt/data/git-repos/*/SKILL.md")
# 2. 批量处理
summary = []
for t in targets:
try:
with open(t, encoding="utf-8") as f:
text = f.read()
summary.append({
"path": t,
"lines": text.count("\n") + 1,
"has_version": "version:" in text,
})
except Exception as e:
summary.append({"path": t, "error": str(e)})
# 3. 只打印结果摘要(不要 print 原始大文本,会污染上下文)
print(f"扫描 {len(summary)} 个文件")
print(json.dumps([s for s in summary if s.get("error")], ensure_ascii=False, indent=2))
print("缺失 version 的文件数:", sum(1 for s in summary if not s.get("has_version")))
json.dumps(..., indent=2))而不是裸文本。concurrent.futures.ThreadPoolExecutor 并发,不要 for 循环串行 curl。execute_code 适合确定性脚本活;需要"整体思考/创作"的部分仍走 §三 的 FmodeCode / Claude Code 派发。FmodeAgent / Hermes Agent 在同时推进多个 FmodeCode / Claude Code 后台任务时,每次回复用户前先构造一张状态表,让用户一眼看清全局。不要只说"还在跑"。
# 每次回复用户前构造(在 execute_code 或直接拼字符串)
tasks = [
("主题一 · 数字分身", "running", "3m12s"),
("主题二 · Harness", "done", "4m05s"),
("主题三 · Loop", "failed", "1m47s"),
]
ICON = {"running": "🟡 运行中", "done": "✅ 已完成", "failed": "❌ 失败(重派)"}
rows = "\n".join(
f"| {name} | {ICON[status]} | {dur} |" for name, status, dur in tasks
)
print(f"""
| 任务 | 状态 | 时长 |
|------|------|------|
{rows}
""")
输出示例:
| 任务 | 状态 | 时长 |
|------|------|------|
| 主题一 · 数字分身 | 🟡 运行中 | 3m12s |
| 主题二 · Harness | ✅ 已完成 | 4m05s |
| 主题三 · Loop | ❌ 失败(重派) | 1m47s |
process_manage(action="list") 的 uptime_seconds 或启动时间戳推算,不要拍脑袋。Heterarchy 的核心承诺:主 Session 永远不被某个 FmodeCode / Claude Code 任务卡住。收到请求后立即回执,派完就撒手,靠 notify 回调驱动后续。
收到用户请求
├→ ① 立即发一条回执:「已收到,开始执行,分配 X 个任务同时进行」
├→ ② 全 FmodeCode / Claude Code Dispatch:terminal(background=True, notify=True) 全部同轮发出(见 §〇)
├→ ③ 不等完成,先回复用户:「任务已派发,等完成通知」
└→ ④ 当 FmodeCode / Claude Code notify 到达(作为新消息进来),再验证并交付
| 步骤 | 要点 |
|---|---|
| ① 回执 | 明确告知拆成几个任务、预计并行,让用户安心 |
| ② 派发 | 所有 terminal(background=True, notify=True) 同一轮发出,不要等第一个再发第二个 |
| ③ 撒手 | 派发完立刻回复用户,绝不用前台 sleep 干等 |
| ④ 回调 | notify 到达 = 一次新的唤醒,此时才 git log / 验证 / 交付 |
❌ 派发 → 前台 sleep 600 → 查状态 → 再 sleep → ...(主 Session 被阻塞,用户干等)
✅ 派发 → 立即回复 → 挂起 → notify 唤醒 → 验证交付
为什么重要:前台阻塞会占满对话轮次、无法响应用户中途插话、也无法同时推进其它任务。异步回执把"等待"交给事件系统,主 Session 只负责编排。
每个 FmodeCode / Claude Code 任务必须设 timeout=1800(30 分钟)。terminal 前台最大 600s,超过会自动转后台 + notify。
terminal(
command=f"cat {task_file} | /opt/data/npm-global/bin/claude -p --dangerously-skip-permissions > {log} 2>&1",
background=True,
notify=True,
timeout=1800, # ← 30 分钟硬上限
workdir=project_root,
)
notify: timeout (exit 124)
├→ tail -30 log → 看中途产物
├→ 如果已有部分产出:
│ └→ 写接力任务书(标明已完成部分 + 待做部分)→ 重派
├→ 如果零产出(空日志):
│ └→ 改小任务范围,重新派发
└→ 连续 2 次超时 → 拆分子任务更小粒度
# exit 124 = timeout;先看日志尾部和是否已 commit
terminal(f"tail -30 {log_path}", timeout=10)
terminal(f"cd {repo} && git log --oneline -3", timeout=10) # 有无中途 commit?
| 症状 | 判断 | 处置 |
|---|---|---|
| exit 124 + 日志有产物 | 大任务未跑完 | 写接力任务书:已完成 X / 待做 Y |
| exit 124 + 日志空 | 卡在启动/首轮推理 | 缩小范围重派,或换更小模型 |
| 连续 2 次 exit 124 | 任务粒度过大 | 拆成更小原子子任务 |
| 日志刷屏无进展 | 死循环/反复重试 | 杀进程,改任务书约束(禁某操作) |
铁律:超时不等于失败——先看中途产物。有产物就走接力(省时省 token),零产物才重做。
当子单元之间存在依赖时,不能无脑全并行。先建模依赖图,再分批:无依赖的批次并行,有依赖的批次等前置完成。
task_a → task_b → task_c (串行链)
task_a → task_b (task_b 依赖 task_a)
task_c (task_c 独立,与 a/b 无依赖)
第 1 批(无依赖):task_a + task_c 同时启动
→ 等 task_a 完成 → 取产出
第 2 批(依赖 task_a):task_b 启动
→ 等 task_b 完成
交付
| 要点 | 说明 |
|---|---|
| 批内并行 | 同批任务同一轮发出(§〇),互不等待 |
| 批间串行 | 下一批要等本批关键路径任务完成 |
| 跨批传料 | 前置任务的 commit / 文件作为后置任务书的 inline 素材(§3.4) |
| 关键路径优先 | 先启动最长依赖链的头,让串行链尽早开跑 |
§四的决策树已判定"有依赖 → 拓扑排序,分批并行"。本章是它的落地细则:
一句话:能并的并、该等的等——用依赖图说话,不靠感觉。
当多个 FmodeCode / Claude Code 任务全部完成(或部分失败)后,统一聚合成一张交付汇总表,而不是把 N 个任务的日志碎片甩给用户。
results = []
for notify in notifies:
log = read_log(notify.log_path)
if "commit" in log or success_marker(log):
results.append(("✅", notify.task_name, notify.url))
else:
results.append(("❌", notify.task_name, error_of(log)))
rows = "\n".join(f"| {name} | {mark} | {link} |" for mark, name, link in results)
print(f"""
## 并行任务交付汇总
| 任务 | 结果 | 链接 |
|------|------|------|
{rows}
""")
git log + URL Content-Type 验证(§T / §P),不能拿"日志看着像成功"充数。❌ 行要写清原因(超时/503/空日志),便于用户决策。FmodeCode / Claude Code 产出 HTML / 图片后,部署链路必须逐环节验证,不能 sync 完就当完事。
1. OBS sync → obs://www-fmode.cn/dev/xxx/
2. CDN 刷新(华为云 CDN 缓存清除)
3. 验证所有 URL
for url in $ARTIFACTS; do
code=$(curl -m 10 -o /dev/null -w "%{http_code}" "$url")
ct=$(curl -m 10 -o /dev/null -w "%{content_type}" "$url")
if [[ "$code" != "200" ]]; then echo "❌ HTTP $code $url"; continue; fi
case "$ct" in
text/html*|image/*|application/*) echo "✅ $code $ct $url" ;;
*) echo "❌ 类型异常: $ct $url" ;;
esac
done
import subprocess
def check(url):
code = subprocess.run(["curl","-m","10","-o","/dev/null","-w","%{http_code}",url],
capture_output=True, text=True).stdout
ct = subprocess.run(["curl","-m","10","-o","/dev/null","-w","%{content_type}",url],
capture_output=True, text=True).stdout
ok = code == "200" and ct.startswith(("text/html","image/","application/"))
return ok, code, ct
bad = [(u, c, t) for u in artifacts for ok, c, t in [check(u)] if not ok]
print("全部通过" if not bad else f"❌ {len(bad)} 个异常: {bad}")
assert resp == 200 / assert ct.startswith(...),不满足就报错,不要"看着还行"。每次启动 FmodeCode / Claude Code 前必须确保环境变量齐备,否则轻则 401/503,重则 exit 127。
export PATH="/opt/data/npm-global/bin:$PATH"
export ANTHROPIC_AUTH_TOKEN=$FMODE_API_KEY
export ANTHROPIC_BASE_URL=https://api.fmode.cn
或内联写进 terminal 命令(推荐,避免 shell 状态不持久):
terminal(
command=f"""
export PATH="/opt/data/npm-global/bin:$PATH"
export ANTHROPIC_AUTH_TOKEN=$FMODE_API_KEY
export ANTHROPIC_BASE_URL=https://api.fmode.cn
cd {project_root}
cat {task_file} | /opt/data/npm-global/bin/claude -p --dangerously-skip-permissions > {log} 2>&1
""",
background=True, notify=True, timeout=1800, workdir=project_root,
)
# 必须写入正确的模型名,少前缀会 503
terminal(f"cat {project_root}/.claude/settings.json", timeout=5)
# FmodeCode / Claude Code 模型:deepseek/deepseek-v4.1-flash[1m]
| 检查项 | 失败症状 | 修法 |
|---|---|---|
PATH 含 npm-global |
exit 127 command not found |
绝对路径 /opt/data/npm-global/bin/claude |
ANTHROPIC_AUTH_TOKEN |
401 / Not logged in | =$FMODE_API_KEY |
ANTHROPIC_BASE_URL |
连不上网关 | https://api.fmode.cn |
| settings.json 模型名 | 503 No available channel | 补全 deepseek/ 前缀 |
纪律:环境设置内联在启动命令里,不要依赖"上一次 export 还在"——后台进程与交互 shell 状态不共享。
FmodeCode / Claude Code 进程的工作目录决定它能否读到项目规则。目录错了,规则静默丢失,产出必是次品。
workdir 必须设为 git 项目根目录。.claude/rules/ / CLAUDE.md / settings.json → 规则丢失。terminal() 内用 cd {workdir} && ... 双重保证(workdir 参数 + 命令内 cd)。# ✅ 双重保证
terminal(
command=f"cd {project_root} && cat {task_file} | /opt/data/npm-global/bin/claude -p ...",
workdir=project_root,
background=True, notify=True,
)
# ❌ 在父目录启动 → 读不到 .claude/rules/ → 规则丢失
terminal(command="cat task.md | claude -p ...", workdir="/opt/data/git-repos")
workdir 参数是声明,但某些封装/重定向可能不生效;cd 是执行保证,两者叠加才能确保万无一失。git rev-parse --show-toplevel 的结果应与 project_root 一致。0.0.1 → 0.0.12 → 0.0.99 → 0.1.1
SKILL.md frontmatter、package.json、lib/index.mjs(VERSION)必须一致。引用 skill-core-guide §3.5 的 13 项发布前检查(逐项勾选,缺一不可):
description 写清"何时用"而非"是什么"package.json files 字段精确声明发布内容main/exports 指向 lib/index.mjsbin 可执行且有 shebanglicense 一致skip 不算通过)Gogs → GitHub → npm → skillhub
npm view <pkg> version,git 用 git ls-remote。对象存储/CDN 在资源不存在时,可能返回 404 首页且 HTTP 状态是 200。
只看 %{http_code} 会得到假绿。必须叠加 Content-Type 判断。
| 资源 | 必须满足 |
|---|---|
| 图片 | Content-Type: image/* |
| HTML | Content-Type: text/html |
| 其它(JS/CSS/JSON) | Content-Type: application/* |
# ✅ 同时取 code 和 content_type
curl -m 10 -o /dev/null -w "%{http_code} %{content_type}\n" "$url"
# ✅ 断言式:不满足即报错
assert code == "200", f"HTTP {code}: {url}"
assert ct.startswith(("text/html", "image/", "application/")), f"类型异常 {ct}: {url}"
assert,失败即中断。一句话:每个 URL 都要 curl + code + content_type 三重确认,一个都不能跳。