|
@@ -1,24 +1,72 @@
|
|
|
---
|
|
---
|
|
|
name: skill-heterarchy
|
|
name: skill-heterarchy
|
|
|
description: "内异层认知协同 — 在单一智能体主体内部,分化多心智并行思考,主控不阻塞、子单元互校验、阻塞自愈。超级技能(ESM 多端可用)。"
|
|
description: "内异层认知协同 — 在单一智能体主体内部,分化多心智并行思考,主控不阻塞、子单元互校验、阻塞自愈。超级技能(ESM 多端可用)。"
|
|
|
-version: 1.1.0
|
|
|
|
|
|
|
+version: 1.2.0
|
|
|
author: Yuyang001 (FmodeAgent)
|
|
author: Yuyang001 (FmodeAgent)
|
|
|
-license: MIT
|
|
|
|
|
|
|
+license: MPL-2.0
|
|
|
copyright: "Copyright (c) 2026 未来飞马 Fmode"
|
|
copyright: "Copyright (c) 2026 未来飞马 Fmode"
|
|
|
-tags: [未来飞马, 智能体技能, 超级技能, 系统级, 平台基础设施, FmodeAgent, FmodeCode, heterarchy, dispatch, cognitive-collaboration, parallel]
|
|
|
|
|
|
|
+tags: [未来飞马, 智能体技能, 超级技能, 系统级, 平台基础设施, FmodeAgent, Hermes Agent, FmodeCode, FmodeCode / Claude Code, heterarchy, dispatch, cognitive-collaboration, parallel, parallel-batch-orchestration, batch-orchestration, topological-sort, async-receipt, timeout-fallback, cdn-verification, obs-deploy, progress-board]
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
# skill-heterarchy — 对内分思实操技能
|
|
# skill-heterarchy — 对内分思实操技能
|
|
|
|
|
|
|
|
> Delegate 是对外派活,Heterarchy 是对内分思。
|
|
> Delegate 是对外派活,Heterarchy 是对内分思。
|
|
|
-> 本技能用 Hermes 底层工具在单一 Agent 内部分化多心智并行思考。
|
|
|
|
|
|
|
+> 本技能用 FmodeAgent / Hermes Agent 的底层工具在单一 Agent 内部分化多心智并行思考。
|
|
|
> 加载方式:`skill_view(name='skill-heterarchy')`
|
|
> 加载方式:`skill_view(name='skill-heterarchy')`
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
|
|
+## 〇、FmodeAgent / Hermes Agent 工具并行调用机制(并行调用铁律)
|
|
|
|
|
+
|
|
|
|
|
+每次 FmodeAgent / Hermes Agent 处理用户消息时,**可以在同一轮对话内同时发起多个独立的工具调用**。这是 Heterarchy 能真正"并行"的底层前提——如果连工具调用都串行发,后面的并行派发就是空谈。
|
|
|
|
|
+
|
|
|
|
|
+### 0.1 同轮并发的能力组合
|
|
|
|
|
+
|
|
|
|
|
+| 场景 | 同一轮并发发出 |
|
|
|
|
|
+|------|----------------|
|
|
|
|
|
+| 多路信息采集 | 多个 `terminal()`(不同命令) |
|
|
|
|
|
+| 边读边搜 | `read_file()` + `search_files()` |
|
|
|
|
|
+| 边调研边干活 | `web_search()` + 一个后台 FmodeCode / Claude Code 启动 |
|
|
|
|
|
+| 多进程编排 | 多个 `tool_call()` 管理不同进程 |
|
|
|
|
|
+
|
|
|
|
|
+### 0.2 铁律:独立无依赖 → 必须并发
|
|
|
|
|
+
|
|
|
|
|
+**原则**:两个工具调用之间若**没有数据依赖**(后者不需要前者的输出),就必须放在**同一轮**发出,而不是分成多轮对话串行。
|
|
|
|
|
+
|
|
|
|
|
+**反例(串行 3 轮对话,浪费 3 个往返):**
|
|
|
|
|
+```
|
|
|
|
|
+第 1 轮:read_file(A) → 等结果
|
|
|
|
|
+第 2 轮:read_file(B) → 等结果
|
|
|
|
|
+第 3 轮:web_search(...) → 等结果
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**正例(一轮搞定,延迟≈最慢的那一个):**
|
|
|
|
|
+```
|
|
|
|
|
+第 1 轮:read_file(A) + read_file(B) + web_search(...) ← 同轮并发
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 0.3 判断方法
|
|
|
|
|
+
|
|
|
|
|
+在发工具调用前,对每个待发调用问一句:
|
|
|
|
|
+> "我这次调用需要用到另一个调用的输出吗?"
|
|
|
|
|
+
|
|
|
|
|
+- **不需要** → 并入本轮,并发发出。
|
|
|
|
|
+- **需要** → 才拆到下一轮,等前置返回。
|
|
|
|
|
+
|
|
|
|
|
+### 0.4 与 Heterarchy 的衔接
|
|
|
|
|
+
|
|
|
|
|
+本机制是后续所有章节(§X 批量分析、§Y 进度表、§Z 异步回执、§V 拓扑分批)的**地基**:
|
|
|
|
|
+- 认知拆解(§一)判定出的"无依赖维度",靠本章的并发机制真正并行落地;
|
|
|
|
|
+- FmodeCode / Claude Code 派发(§三)多个 `terminal(background=True, notify=True)` 必须在同一轮发出,才能同时启动多个心智;
|
|
|
|
|
+- 一旦发现有依赖,才退回 §V 的拓扑分批。
|
|
|
|
|
+
|
|
|
|
|
+**一句话**:先把能并的都并出去,再把剩下的按依赖排序。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
## 零、何时触发本技能
|
|
## 零、何时触发本技能
|
|
|
|
|
|
|
|
-当你(Hermes)收到**一个复杂任务,明显可以拆成多个独立维度并行处理**时:
|
|
|
|
|
|
|
+当你(FmodeAgent / Hermes Agent)收到**一个复杂任务,明显可以拆成多个独立维度并行处理**时:
|
|
|
|
|
|
|
|
| 触发词 | 示例 |
|
|
| 触发词 | 示例 |
|
|
|
|--------|------|
|
|
|--------|------|
|
|
@@ -36,15 +84,15 @@ tags: [未来飞马, 智能体技能, 超级技能, 系统级, 平台基础设
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 一、任务拆解(认知分化 — Hermes 自己做)
|
|
|
|
|
|
|
+## 一、任务拆解(认知分化 — FmodeAgent / Hermes Agent 自己做)
|
|
|
|
|
|
|
|
-这是唯一不能交给 CC 的步骤。Hermes 必须亲自:
|
|
|
|
|
|
|
+这是唯一不能交给 FmodeCode / Claude Code 的步骤。FmodeAgent / Hermes Agent 必须亲自:
|
|
|
1. 理解用户真实需求
|
|
1. 理解用户真实需求
|
|
|
2. 识别可并行的独立维度(每个维度 = 一个独立认知子单元)
|
|
2. 识别可并行的独立维度(每个维度 = 一个独立认知子单元)
|
|
|
3. 判断子单元之间是否有依赖关系
|
|
3. 判断子单元之间是否有依赖关系
|
|
|
-4. 确定项目工作目录(每个 CC 进程必须在正确的 git 仓库根目录启动)
|
|
|
|
|
|
|
+4. 确定项目工作目录(每个 FmodeCode / Claude Code 进程必须在正确的 git 仓库根目录启动)
|
|
|
|
|
|
|
|
-**实操模板(Hermes 的思考过程):**
|
|
|
|
|
|
|
+**实操模板(FmodeAgent / Hermes Agent 的思考过程):**
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
收到:"四个主题的 HTML PPT 需要图文并茂增强"
|
|
收到:"四个主题的 HTML PPT 需要图文并茂增强"
|
|
@@ -57,7 +105,7 @@ tags: [未来飞马, 智能体技能, 超级技能, 系统级, 平台基础设
|
|
|
└→ 依赖:无(四主题可完全并行)
|
|
└→ 依赖:无(四主题可完全并行)
|
|
|
|
|
|
|
|
项目根目录:/opt/data/git-repos/skill-present
|
|
项目根目录:/opt/data/git-repos/skill-present
|
|
|
-CC 必须在项目根目录启动,否则读不到 CLAUDE.md 的项目规则。
|
|
|
|
|
|
|
+FmodeCode / Claude Code 必须在项目根目录启动,否则读不到 CLAUDE.md 的项目规则。
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
@@ -80,7 +128,7 @@ CC 必须在项目根目录启动,否则读不到 CLAUDE.md 的项目规则。
|
|
|
7. 禁止事项(不要重写、不要改标题、不要改结构)
|
|
7. 禁止事项(不要重写、不要改标题、不要改结构)
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-任务书写入临时文件 `/tmp/cc-xxx-task.md`,用 `cat` 管道喂入 CC,不用 `$(cat)` 避免路径问题:
|
|
|
|
|
|
|
+任务书写入临时文件 `/tmp/cc-xxx-task.md`,用 `cat` 管道喂入 FmodeCode / Claude Code,不用 `$(cat)` 避免路径问题:
|
|
|
|
|
|
|
|
**正确姿势:**
|
|
**正确姿势:**
|
|
|
```bash
|
|
```bash
|
|
@@ -95,12 +143,12 @@ cat /tmp/cc-xxx-task.md | /opt/data/npm-global/bin/claude -p --dangerously-skip-
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 三、Hermes 协同工具调用表(核心实操)
|
|
|
|
|
|
|
+## 三、FmodeAgent / Hermes Agent 协同工具调用表(核心实操)
|
|
|
|
|
|
|
|
### 3.1 启动心智 — terminal + background + notify
|
|
### 3.1 启动心智 — terminal + background + notify
|
|
|
|
|
|
|
|
```python
|
|
```python
|
|
|
-# Hermes 代码中:
|
|
|
|
|
|
|
+# FmodeAgent / Hermes Agent 代码中:
|
|
|
terminal(
|
|
terminal(
|
|
|
command=f"""
|
|
command=f"""
|
|
|
export PATH="/opt/data/npm-global/bin:$PATH"
|
|
export PATH="/opt/data/npm-global/bin:$PATH"
|
|
@@ -156,7 +204,7 @@ read_file(path="{repo}/path/to/architecture.md")
|
|
|
|
|
|
|
|
```python
|
|
```python
|
|
|
# 检查 settings.json 确保模型名正确
|
|
# 检查 settings.json 确保模型名正确
|
|
|
-# 用户钦点:Hermes=deepseek/deepseek-v4.1-flash, CC=deepseek/deepseek-v4.1-flash[1m]
|
|
|
|
|
|
|
+# 用户钦点: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)
|
|
terminal("cat {project_root}/.claude/settings.json", timeout=5)
|
|
|
# 如发现模型名不对,patch 修正
|
|
# 如发现模型名不对,patch 修正
|
|
|
```
|
|
```
|
|
@@ -169,7 +217,7 @@ terminal("cat {project_root}/.claude/settings.json", timeout=5)
|
|
|
收到复杂任务
|
|
收到复杂任务
|
|
|
│
|
|
│
|
|
|
├→ 可以拆成 2-5 个独立维度?
|
|
├→ 可以拆成 2-5 个独立维度?
|
|
|
-│ ├→ 否 → 单一 CC 进程处理 或 本 Agent 直接完成
|
|
|
|
|
|
|
+│ ├→ 否 → 单一 FmodeCode / Claude Code 进程处理 或 本 Agent 直接完成
|
|
|
│ └→ 是 → 继续
|
|
│ └→ 是 → 继续
|
|
|
│
|
|
│
|
|
|
├→ 子单元之间有依赖关系?
|
|
├→ 子单元之间有依赖关系?
|
|
@@ -179,13 +227,13 @@ terminal("cat {project_root}/.claude/settings.json", timeout=5)
|
|
|
│ └→ 否 → 全并行启动
|
|
│ └→ 否 → 全并行启动
|
|
|
│
|
|
│
|
|
|
├→ 每个子单元的工作量?
|
|
├→ 每个子单元的工作量?
|
|
|
-│ ├→ 简单(< 50 行代码/单页)→ 不启 CC,本 Agent 直接写
|
|
|
|
|
-│ ├→ 中等(需要完整思考/调研/创作)→ 独立 CC 进程
|
|
|
|
|
|
|
+│ ├→ 简单(< 50 行代码/单页)→ 不启 FmodeCode / Claude Code,本 Agent 直接写
|
|
|
|
|
+│ ├→ 中等(需要完整思考/调研/创作)→ 独立 FmodeCode / Claude Code 进程
|
|
|
│ └→ 复杂(需要跨文件多模块/项目级)→ 优先用 kanban_create 委派给 fullstack profile
|
|
│ └→ 复杂(需要跨文件多模块/项目级)→ 优先用 kanban_create 委派给 fullstack profile
|
|
|
│
|
|
│
|
|
|
├→ 项目目录正确吗?
|
|
├→ 项目目录正确吗?
|
|
|
-│ ├→ 每个 CC 必须在项目根目录启动
|
|
|
|
|
-│ └→ 错目录 → CC 读不到 CLAUDE.md → 规则丢失 → 出次品
|
|
|
|
|
|
|
+│ ├→ 每个 FmodeCode / Claude Code 必须在项目根目录启动
|
|
|
|
|
+│ └→ 错目录 → FmodeCode / Claude Code 读不到 CLAUDE.md → 规则丢失 → 出次品
|
|
|
│
|
|
│
|
|
|
└→ 启动后:
|
|
└→ 启动后:
|
|
|
├→ 60s 后检查存活
|
|
├→ 60s 后检查存活
|
|
@@ -225,9 +273,9 @@ terminal(f"tail -30 {log_path}", timeout=10)
|
|
|
|
|
|
|
|
# 2. 如果是"从零重做"(完全失败)
|
|
# 2. 如果是"从零重做"(完全失败)
|
|
|
# 缩小范围:大任务拆小
|
|
# 缩小范围:大任务拆小
|
|
|
-# 禁用 CC 的某些能力:机器产出内容需要再确认
|
|
|
|
|
|
|
+# 禁用 FmodeCode / Claude Code 的某些能力:机器产出内容需要再确认
|
|
|
|
|
|
|
|
-# 3. 连败 3 次同域 → 停止 CC 重试
|
|
|
|
|
|
|
+# 3. 连败 3 次同域 → 停止 FmodeCode / Claude Code 重试
|
|
|
# 本 Agent 直接处理 或 拆成更细的原子指令
|
|
# 本 Agent 直接处理 或 拆成更细的原子指令
|
|
|
```
|
|
```
|
|
|
|
|
|
|
@@ -237,11 +285,11 @@ terminal(f"tail -30 {log_path}", timeout=10)
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
Step 1: 认知拆解
|
|
Step 1: 认知拆解
|
|
|
-Hermes 消化用户需求:"四个主题需要图文并茂增强,框架不动,加图"
|
|
|
|
|
|
|
+FmodeAgent / Hermes Agent 消化用户需求:"四个主题需要图文并茂增强,框架不动,加图"
|
|
|
→ 四主题可完全并行,项目根目录=skill-present
|
|
→ 四主题可完全并行,项目根目录=skill-present
|
|
|
|
|
|
|
|
Step 2: 心智分化
|
|
Step 2: 心智分化
|
|
|
-Hermes 为每个主题编写独立任务书(/tmp/cc-v6-0x-resume.md)
|
|
|
|
|
|
|
+FmodeAgent / Hermes Agent 为每个主题编写独立任务书(/tmp/cc-v6-0x-resume.md)
|
|
|
每份包含:当前 commit hash + 结构框架 + 纪律 + 验收标准
|
|
每份包含:当前 commit hash + 结构框架 + 纪律 + 验收标准
|
|
|
|
|
|
|
|
Step 3: 并行启动
|
|
Step 3: 并行启动
|
|
@@ -276,7 +324,7 @@ push 所有 commit → OBS sync → CDN 刷新 → URL 验证 → 发用户
|
|
|
- ✅ `cat task.md | claude -p` → 管道直送,无文件路径依赖
|
|
- ✅ `cat task.md | claude -p` → 管道直送,无文件路径依赖
|
|
|
|
|
|
|
|
### 7.2 项目根目录
|
|
### 7.2 项目根目录
|
|
|
-- ❌ 在父目录启动 CC → CC 读不到 `.claude/rules/`
|
|
|
|
|
|
|
+- ❌ 在父目录启动 FmodeCode / Claude Code → 读不到 `.claude/rules/`
|
|
|
- ✅ 必须在 git 项目根目录启动(读得到 CLAUDE.md + rules + settings.json)
|
|
- ✅ 必须在 git 项目根目录启动(读得到 CLAUDE.md + rules + settings.json)
|
|
|
|
|
|
|
|
### 7.3 图片验证只看 200
|
|
### 7.3 图片验证只看 200
|
|
@@ -295,15 +343,15 @@ push 所有 commit → OBS sync → CDN 刷新 → URL 验证 → 发用户
|
|
|
|
|
|
|
|
## 八、技能与本 Agent 内置工具的关系
|
|
## 八、技能与本 Agent 内置工具的关系
|
|
|
|
|
|
|
|
-本技能**不替代** Hermes 内置工具,而是**编排**它们:
|
|
|
|
|
|
|
+本技能**不替代** FmodeAgent / Hermes Agent 内置工具,而是**编排**它们:
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
-Hermes 收到任务
|
|
|
|
|
|
|
+FmodeAgent / Hermes Agent 收到任务
|
|
|
│
|
|
│
|
|
|
├→ skill-heterarchy(本技能)→ 决策是否启用异层认知
|
|
├→ skill-heterarchy(本技能)→ 决策是否启用异层认知
|
|
|
│ └→ 认知分化 → 写任务书
|
|
│ └→ 认知分化 → 写任务书
|
|
|
│
|
|
│
|
|
|
-├→ terminal(Hermes 工具)→ 后台启动 CC 进程
|
|
|
|
|
|
|
+├→ terminal(FmodeAgent / Hermes Agent 工具)→ 后台启动 FmodeCode / Claude Code 进程
|
|
|
├→ process_manage → 监控存活
|
|
├→ process_manage → 监控存活
|
|
|
├→ read_file / patch / git → 收敛验证
|
|
├→ read_file / patch / git → 收敛验证
|
|
|
├→ web_search → 前置调研
|
|
├→ web_search → 前置调研
|
|
@@ -312,8 +360,481 @@ Hermes 收到任务
|
|
|
|
|
|
|
|
**何时用 delegate_task 而不是 Heterarchy?**
|
|
**何时用 delegate_task 而不是 Heterarchy?**
|
|
|
- 子任务是"帮我查点东西"(轻量检索)→ `delegate_task`
|
|
- 子任务是"帮我查点东西"(轻量检索)→ `delegate_task`
|
|
|
-- 子任务是"完整思考 + 产出可交付物" → Heterarchy CC
|
|
|
|
|
|
|
+- 子任务是"完整思考 + 产出可交付物" → Heterarchy + FmodeCode / Claude Code
|
|
|
|
|
|
|
|
**何时用 kanban_create 而不是 Heterarchy?**
|
|
**何时用 kanban_create 而不是 Heterarchy?**
|
|
|
- 任务需要跨 Profile 的独立 MCP 环境 → `kanban_create`
|
|
- 任务需要跨 Profile 的独立 MCP 环境 → `kanban_create`
|
|
|
-- 任务在本 Agent 的能力范围内,只是需要并行 → Heterarchy
|
|
|
|
|
|
|
+- 任务在本 Agent 的能力范围内,只是需要并行 → Heterarchy
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## X、execute_code 批量分析模式
|
|
|
|
|
+
|
|
|
|
|
+当要对**多个文件/多条数据**做 grep、统计、聚合、转换时,不要用一串 `terminal()` 逐个敲。改用 FmodeAgent / Hermes Agent 内置的 `execute_code`(Python 内核),一次进程内跑完。
|
|
|
|
|
+
|
|
|
|
|
+### X.1 适用场景
|
|
|
|
|
+
|
|
|
|
|
+| 场景 | 例子 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| 跨目录 grep + 聚合 | 统计所有技能里各模型名的出现次数 |
|
|
|
|
|
+| 多 HTTP 请求并行测试 | 一次性 curl 全部产物 URL 并汇总 Content-Type |
|
|
|
|
|
+| 批量文件读写 + 统计 | 扫描所有 `.html`,统计图片引用数 |
|
|
|
|
|
+| 数据格式转换 | JSON ↔ CSV ↔ YAML 互转 |
|
|
|
|
|
+| git diff 分析 | 汇总一次 commit 涉及的文件类型/行数 |
|
|
|
|
|
+
|
|
|
|
|
+### X.2 原则
|
|
|
|
|
+
|
|
|
|
|
+> **超过 2 个文件需要 grep / 统计 / 聚合 → 用 `execute_code`,而不是逐个 `terminal`。**
|
|
|
|
|
+
|
|
|
|
|
+逐个 `terminal` 的代价:每个文件一轮往返 + 一次进程启动开销;`execute_code` 把这些压成**一次调用、一个摘要**。
|
|
|
|
|
+
|
|
|
|
|
+### X.3 模板
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+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")))
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### X.4 纪律
|
|
|
|
|
+
|
|
|
|
|
+- **只 print 摘要**,不要把整个文件内容倒进上下文——那等于没省。
|
|
|
|
|
+- 需要逐条证据时,print 结构化 JSON(`json.dumps(..., indent=2)`)而不是裸文本。
|
|
|
|
|
+- 网络类批量任务用 `concurrent.futures.ThreadPoolExecutor` 并发,不要 for 循环串行 curl。
|
|
|
|
|
+- `execute_code` 适合**确定性脚本活**;需要"整体思考/创作"的部分仍走 §三 的 FmodeCode / Claude Code 派发。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Y、多任务进度表(状态看板)
|
|
|
|
|
+
|
|
|
|
|
+FmodeAgent / Hermes Agent 在同时推进多个 FmodeCode / Claude Code 后台任务时,**每次回复用户前**先构造一张状态表,让用户一眼看清全局。不要只说"还在跑"。
|
|
|
|
|
+
|
|
|
|
|
+### Y.1 状态看板模板
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+# 每次回复用户前构造(在 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 |
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Y.2 纪律
|
|
|
|
|
+
|
|
|
|
|
+- **状态三态**:🟡 运行中 / ✅ 已完成 / ❌ 失败(重派)——不要造模糊词。
|
|
|
|
|
+- **时长要真实**:从 `process_manage(action="list")` 的 `uptime_seconds` 或启动时间戳推算,不要拍脑袋。
|
|
|
|
|
+- 失败项必须注明**下一步动作**(重派/续做/放弃),否则看板只是情绪价值。
|
|
|
|
|
+- 状态表是给用户的**唯一真相源**:有它就不必再让用户追问"到哪了"。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Z、主 Session 不阻塞(异步回执模式)
|
|
|
|
|
+
|
|
|
|
|
+Heterarchy 的核心承诺:**主 Session 永远不被某个 FmodeCode / Claude Code 任务卡住**。收到请求后立即回执,派完就撒手,靠 notify 回调驱动后续。
|
|
|
|
|
+
|
|
|
|
|
+### Z.1 标准流程
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+收到用户请求
|
|
|
|
|
+ ├→ ① 立即发一条回执:「已收到,开始执行,分配 X 个任务同时进行」
|
|
|
|
|
+ ├→ ② 全 FmodeCode / Claude Code Dispatch:terminal(background=True, notify=True) 全部同轮发出(见 §〇)
|
|
|
|
|
+ ├→ ③ 不等完成,先回复用户:「任务已派发,等完成通知」
|
|
|
|
|
+ └→ ④ 当 FmodeCode / Claude Code notify 到达(作为新消息进来),再验证并交付
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Z.2 关键点
|
|
|
|
|
+
|
|
|
|
|
+| 步骤 | 要点 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| ① 回执 | 明确告知**拆成几个任务**、**预计并行**,让用户安心 |
|
|
|
|
|
+| ② 派发 | 所有 `terminal(background=True, notify=True)` **同一轮**发出,不要等第一个再发第二个 |
|
|
|
|
|
+| ③ 撒手 | 派发完**立刻**回复用户,**绝不**用前台 `sleep` 干等 |
|
|
|
|
|
+| ④ 回调 | notify 到达 = 一次新的唤醒,此时才 `git log` / 验证 / 交付 |
|
|
|
|
|
+
|
|
|
|
|
+### Z.3 反模式
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+❌ 派发 → 前台 sleep 600 → 查状态 → 再 sleep → ...(主 Session 被阻塞,用户干等)
|
|
|
|
|
+✅ 派发 → 立即回复 → 挂起 → notify 唤醒 → 验证交付
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**为什么重要**:前台阻塞会占满对话轮次、无法响应用户中途插话、也无法同时推进其它任务。异步回执把"等待"交给事件系统,主 Session 只负责**编排**。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## W、超时兜底机制
|
|
|
|
|
+
|
|
|
|
|
+每个 FmodeCode / Claude Code 任务必须设 `timeout=1800`(30 分钟)。terminal 前台最大 600s,超过会自动转后台 + notify。
|
|
|
|
|
+
|
|
|
|
|
+### W.1 派发时统一带上超时
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+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,
|
|
|
|
|
+)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### W.2 超时处理流程
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+notify: timeout (exit 124)
|
|
|
|
|
+ ├→ tail -30 log → 看中途产物
|
|
|
|
|
+ ├→ 如果已有部分产出:
|
|
|
|
|
+ │ └→ 写接力任务书(标明已完成部分 + 待做部分)→ 重派
|
|
|
|
|
+ ├→ 如果零产出(空日志):
|
|
|
|
|
+ │ └→ 改小任务范围,重新派发
|
|
|
|
|
+ └→ 连续 2 次超时 → 拆分子任务更小粒度
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### W.3 诊断命令
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+# exit 124 = timeout;先看日志尾部和是否已 commit
|
|
|
|
|
+terminal(f"tail -30 {log_path}", timeout=10)
|
|
|
|
|
+terminal(f"cd {repo} && git log --oneline -3", timeout=10) # 有无中途 commit?
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### W.4 处置矩阵
|
|
|
|
|
+
|
|
|
|
|
+| 症状 | 判断 | 处置 |
|
|
|
|
|
+|------|------|------|
|
|
|
|
|
+| exit 124 + 日志有产物 | 大任务未跑完 | 写**接力任务书**:已完成 X / 待做 Y |
|
|
|
|
|
+| exit 124 + 日志空 | 卡在启动/首轮推理 | 缩小范围重派,或换更小模型 |
|
|
|
|
|
+| 连续 2 次 exit 124 | 任务粒度过大 | 拆成更小原子子任务 |
|
|
|
|
|
+| 日志刷屏无进展 | 死循环/反复重试 | 杀进程,改任务书约束(禁某操作) |
|
|
|
|
|
+
|
|
|
|
|
+**铁律**:超时**不等于失败**——先看中途产物。有产物就走接力(省时省 token),零产物才重做。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## V、拓扑排序(有依赖的任务分批并行)
|
|
|
|
|
+
|
|
|
|
|
+当子单元之间**存在依赖**时,不能无脑全并行。先建模依赖图,再分批:无依赖的批次并行,有依赖的批次等前置完成。
|
|
|
|
|
+
|
|
|
|
|
+### V.1 依赖建模
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+task_a → task_b → task_c (串行链)
|
|
|
|
|
+task_a → task_b (task_b 依赖 task_a)
|
|
|
|
|
+ task_c (task_c 独立,与 a/b 无依赖)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### V.2 分批并行启动
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+第 1 批(无依赖):task_a + task_c 同时启动
|
|
|
|
|
+ → 等 task_a 完成 → 取产出
|
|
|
|
|
+第 2 批(依赖 task_a):task_b 启动
|
|
|
|
|
+ → 等 task_b 完成
|
|
|
|
|
+交付
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### V.3 执行要点
|
|
|
|
|
+
|
|
|
|
|
+| 要点 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| 批内并行 | 同批任务**同一轮**发出(§〇),互不等待 |
|
|
|
|
|
+| 批间串行 | 下一批要等本批**关键路径**任务完成 |
|
|
|
|
|
+| 跨批传料 | 前置任务的 commit / 文件作为后置任务书的 inline 素材(§3.4) |
|
|
|
|
|
+| 关键路径优先 | 先启动最长依赖链的头,让串行链尽早开跑 |
|
|
|
|
|
+
|
|
|
|
|
+### V.4 与 §四决策树的衔接
|
|
|
|
|
+
|
|
|
|
|
+§四的决策树已判定"有依赖 → 拓扑排序,分批并行"。本章是它的**落地细则**:
|
|
|
|
|
+- 依赖图为空 → 退化为全并行(第 1 批装下所有任务);
|
|
|
|
|
+- 依赖图为链 → 每批只放 1 个,退化为串行,但**每批内部仍尽量并发**无关任务。
|
|
|
|
|
+
|
|
|
|
|
+**一句话**:能并的并、该等的等——用依赖图说话,不靠感觉。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## U、结果聚合汇报
|
|
|
|
|
+
|
|
|
|
|
+当多个 FmodeCode / Claude Code 任务全部完成(或部分失败)后,**统一聚合成一张交付汇总表**,而不是把 N 个任务的日志碎片甩给用户。
|
|
|
|
|
+
|
|
|
|
|
+### U.1 聚合模板
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+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}
|
|
|
|
|
+""")
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### U.2 纪律
|
|
|
|
|
+
|
|
|
|
|
+- **先验证再汇总**:每条结果都要经过 `git log` + URL Content-Type 验证(§T / §P),不能拿"日志看着像成功"充数。
|
|
|
|
|
+- **失败项必须带错误**:`❌` 行要写清原因(超时/503/空日志),便于用户决策。
|
|
|
|
|
+- **汇总表 = 交付凭证**:表里每个 ✅ 都是可点击验证的链接。
|
|
|
|
|
+- 部分失败**不阻塞交付**:先交付成功的,失败的进入 §W/§五 的重派队列,并在表下注明"重派中"。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## T、OBS + CDN 部署验证链
|
|
|
|
|
+
|
|
|
|
|
+FmodeCode / Claude Code 产出 HTML / 图片后,部署链路必须**逐环节验证**,不能 sync 完就当完事。
|
|
|
|
|
+
|
|
|
|
|
+### T.1 部署链
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+1. OBS sync → obs://www-fmode.cn/dev/xxx/
|
|
|
|
|
+2. CDN 刷新(华为云 CDN 缓存清除)
|
|
|
|
|
+3. 验证所有 URL
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### T.2 逐 URL 验证
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+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
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### T.3 Python 版(批量)
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+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}")
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### T.4 纪律
|
|
|
|
|
+
|
|
|
|
|
+- **CDN 刷新后必须重验**:不刷新可能拿到旧缓存,刷新后可能拿到 404——两头都要 curl。
|
|
|
|
|
+- **断言而非观察**:`assert resp == 200` / `assert ct.startswith(...)`,不满足就报错,不要"看着还行"。
|
|
|
|
|
+- 部署链是**串行依赖**(sync → 刷新 → 验证),天然适合 §V 的拓扑链:每步是上一步的依赖。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## S、FmodeCode / Claude Code 环境自举
|
|
|
|
|
+
|
|
|
|
|
+每次启动 FmodeCode / Claude Code 前必须确保环境变量齐备,否则轻则 401/503,重则 exit 127。
|
|
|
|
|
+
|
|
|
|
|
+### S.1 必备环境
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+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 状态不持久):
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+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,
|
|
|
|
|
+)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### S.2 settings.json 模型名
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+# 必须写入正确的模型名,少前缀会 503
|
|
|
|
|
+terminal(f"cat {project_root}/.claude/settings.json", timeout=5)
|
|
|
|
|
+# FmodeCode / Claude Code 模型:deepseek/deepseek-v4.1-flash[1m]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### S.3 自举检查清单
|
|
|
|
|
+
|
|
|
|
|
+| 检查项 | 失败症状 | 修法 |
|
|
|
|
|
+|--------|----------|------|
|
|
|
|
|
+| `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 状态不共享。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## R、工作目录纪律
|
|
|
|
|
+
|
|
|
|
|
+FmodeCode / Claude Code 进程的工作目录**决定它能否读到项目规则**。目录错了,规则静默丢失,产出必是次品。
|
|
|
|
|
+
|
|
|
|
|
+### R.1 铁律
|
|
|
|
|
+
|
|
|
|
|
+- 每个 FmodeCode / Claude Code 进程的 `workdir` **必须**设为 **git 项目根目录**。
|
|
|
|
|
+- 错目录 → FmodeCode / Claude Code 读不到 `.claude/rules/` / `CLAUDE.md` / `settings.json` → **规则丢失**。
|
|
|
|
|
+- 在 `terminal()` 内用 `cd {workdir} && ...` **双重保证**(`workdir` 参数 + 命令内 `cd`)。
|
|
|
|
|
+
|
|
|
|
|
+### R.2 正误对照
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+# ✅ 双重保证
|
|
|
|
|
+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")
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### R.3 为什么双重
|
|
|
|
|
+
|
|
|
|
|
+- `workdir` 参数是**声明**,但某些封装/重定向可能不生效;
|
|
|
|
|
+- 命令内 `cd` 是**执行保证**,两者叠加才能确保万无一失。
|
|
|
|
|
+- 派发前先确认根目录:`git rev-parse --show-toplevel` 的结果应与 `project_root` 一致。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Q、版本规范与发布检查
|
|
|
|
|
+
|
|
|
|
|
+### Q.1 版本号小步迭代
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+0.0.1 → 0.0.12 → 0.0.99 → 0.1.1
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- **只增不减**:新版本号必须**大于**当前已发布/已提交的版本,绝不回退。
|
|
|
|
|
+- **小步快跑**:补丁 +0.0.1,新增能力 +0.1.0,破坏性变更 +1.0.0。
|
|
|
|
|
+- **三处对齐**:`SKILL.md` frontmatter、`package.json`、`lib/index.mjs`(`VERSION`)必须一致。
|
|
|
|
|
+
|
|
|
|
|
+### Q.2 发布前检查清单
|
|
|
|
|
+
|
|
|
|
|
+引用 **skill-core-guide §3.5** 的 13 项发布前检查(逐项勾选,缺一不可):
|
|
|
|
|
+
|
|
|
|
|
+1. frontmatter 必填字段齐全(name/description/version/author/license/tags)
|
|
|
|
|
+2. `description` 写清"何时用"而非"是什么"
|
|
|
|
|
+3. 版本号三处对齐且单调递增
|
|
|
|
|
+4. `package.json` `files` 字段精确声明发布内容
|
|
|
|
|
+5. `main`/`exports` 指向 `lib/index.mjs`
|
|
|
|
|
+6. `bin` 可执行且有 shebang
|
|
|
|
|
+7. 无硬编码密钥/凭据
|
|
|
|
|
+8. README 含简介 / 快速开始 / 许可
|
|
|
|
|
+9. LICENSE 与 frontmatter 的 `license` 一致
|
|
|
|
|
+10. 商标声明完整(未来飞马 / Harness Loop / RSI)
|
|
|
|
|
+11. 六项自动质检全绿(`skip` 不算通过)
|
|
|
|
|
+12. 四渠道版本号一致
|
|
|
|
|
+13. commit message 不含内部工具名
|
|
|
|
|
+
|
|
|
|
|
+### Q.3 四渠道发布
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+Gogs → GitHub → npm → skillhub
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- 顺序固定:先内网 Gogs,再 GitHub,再 npm,最后 skillhub 上架。
|
|
|
|
|
+- 每渠道发布后**验证**:npm 用 `npm view <pkg> version`,git 用 `git ls-remote`。
|
|
|
|
|
+- 任一渠道失败 → 停止后续渠道,先修复,避免版本分叉。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## P、CDN 验证铁律
|
|
|
|
|
+
|
|
|
|
|
+### P.1 200 ≠ 真 200
|
|
|
|
|
+
|
|
|
|
|
+> 对象存储/CDN 在资源不存在时,**可能返回 404 首页且 HTTP 状态是 200**。
|
|
|
|
|
+
|
|
|
|
|
+只看 `%{http_code}` 会得到**假绿**。必须叠加 **Content-Type** 判断。
|
|
|
|
|
+
|
|
|
|
|
+### P.2 分类型验证
|
|
|
|
|
+
|
|
|
|
|
+| 资源 | 必须满足 |
|
|
|
|
|
+|------|----------|
|
|
|
|
|
+| 图片 | `Content-Type: image/*` |
|
|
|
|
|
+| HTML | `Content-Type: text/html` |
|
|
|
|
|
+| 其它(JS/CSS/JSON) | `Content-Type: application/*` |
|
|
|
|
|
+
|
|
|
|
|
+### P.3 逐 URL 验证,不可跳过
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+# ✅ 同时取 code 和 content_type
|
|
|
|
|
+curl -m 10 -o /dev/null -w "%{http_code} %{content_type}\n" "$url"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+# ✅ 断言式:不满足即报错
|
|
|
|
|
+assert code == "200", f"HTTP {code}: {url}"
|
|
|
|
|
+assert ct.startswith(("text/html", "image/", "application/")), f"类型异常 {ct}: {url}"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### P.4 铁律汇总
|
|
|
|
|
+
|
|
|
|
|
+1. **200 不是通行证**——必须看 Content-Type。
|
|
|
|
|
+2. **逐 URL 验证**,不接受"抽查几个"。
|
|
|
|
|
+3. **断言而非目测**——写成 `assert`,失败即中断。
|
|
|
|
|
+4. **CDN 刷新前后各验一次**(§T)——防旧缓存、防刷新后 404。
|
|
|
|
|
+5. **图裂是最隐蔽的坑**——404 首页的 200 会让 HTML 看着正常,实际图全裂。
|
|
|
|
|
+
|
|
|
|
|
+**一句话**:每个 URL 都要 `curl + code + content_type` 三重确认,一个都不能跳。
|