# 外部脚本与工具参考 > Agent 跑 spec/ skill 时通过 Bash 调用的脚本接口手册. **所有工具现在都在 `spec/tools/` 下**, skill 完全自包含 — Agent 不需要 Read 任何 spec/ 外的脚本源码, 只需要知道接口. ## 1. `spec/tools/taxonomy-lookup.py` — 实质/形式 词表查询 **用途**: 阶段二 2B 子任务. 查询外部 JSON 词表 (`分类库导出_实质_*.json` / `分类库导出_形式_*.json`, 911 + 565 条路径). 词表本身**不进 LLM context**, 完全走 tool query. **子命令**: | 子命令 | 用法 | 返回 | |---|---|---| | 列二级路径 | `python spec/tools/taxonomy-lookup.py --dim 实质 --list-l2` | 各 L2 路径 + 该子树叶子数 | | 看子树 | `python spec/tools/taxonomy-lookup.py --dim 实质 --subtree /表象/视觉` | 该子树所有叶子 | | 字符串匹配 | `python spec/tools/taxonomy-lookup.py --dim 形式 --match "中景特写"` | 排序候选路径 (单 token 行为) | | 多 token 聚合 | `python spec/tools/taxonomy-lookup.py --dim 实质 --match "年轻女性 卧室 床上"` | 各 token 各自打分聚合, 覆盖多 token 的 path bonus | | 层级下钻 | `python spec/tools/taxonomy-lookup.py --dim 实质 --narrow "AI生成 提示词"` | 从 L2 起按子树语义打分逐层下钻 top-K (兜底 match miss) | | 校验路径存在 | `python spec/tools/taxonomy-lookup.py --dim 实质 --validate /理念/知识/化学` | OK / PARTIAL / MISS (exit 0/1) | **关于 `--match` 的多 token 行为** (2026-05-22 起): - query **含空白** 自动拆 token, 每个 token (≥2 字, 单字噪声大被丢) 独立打分, path 级聚合 score - 命中 ≥2 token 的 path 给 coverage bonus (score ×= 1 + 0.3·(cov-1)), 防止单 token 大量命中压死多 token 弱命中 - **单 token** (无空白) 行为跟旧版完全一致, 不影响已有 query - 全 miss 时输出提示, 引导转 `--narrow` 兜底 **关于 `--narrow` 的层级下钻** (2026-05-22 新增): - 从所有 L2 出发, 对每个候选子树计算 `(token 命中次数, 命中的 token 数)`, 按 cov ↘ hits ↘ 排序 - 每层取 `--top-per-level` (默认 3), 进下一层只展开这些候选的直接子节点 - 直到 `--depth` (默认 3 层, L2→L3→L4→L5) 或全部到叶子 - 比 `--match` 容错更强: alias 完全没覆盖但路径段语义沾边的 case 也能召回 (e.g. "镜头语言" 不在 alias 但 `/呈现/视觉/视觉制作/构图编排` 路径段直接对路) **Agent 用法 (典型 funnel)**: 1. `--list-l2` 决定大致 domain (实质先选 /理念 vs /表象, 形式先选 /呈现 vs /架构) 2. `--match ` 多 token 召回候选 (大概率一步搞定) 3. **若 `--match` 返 (无匹配)** → `--narrow ` 按子树语义下钻 4. `--subtree ` 看候选 leaf 5. `--validate ` 确认后写回 workflow.json 对应 IO 的 substance/form 字段 **词表文件路径** (按优先级 fallback): 1. `$TAXONOMY_DIR/分类库导出_{实质|形式}_*.json` (env override) 2. `procedure-dsl/data/分类库导出_*.json` 3. `examples/process_pipeline/script/resource/分类库导出_*.json` 4. `examples/process_pipeline/test_script/procedure_dsl_agent/data/分类库导出_*.json` 5. `~/Downloads/分类库导出_*.json` --- ## 2. `spec/tools/wf-patch.py` — workflow.json 安全批量字段设置器 **用途**: Phase 1.3 (批量加 anchor) + Phase 2 (批量填 effect/action/type/substance/form). **凡是「给一批 step / IO 设结构化字段」的活都用它, 不要写 Python 脚本去生成 / 批改 workflow.json** —— 脚本拼 JSON 易踩转义 / 控制字符坑 (会把文件搞坏), 还把本该逐 step 的语义判断埋进一次性脚本. 分工: **你只出语义决策 (path=value), 工具负责安全 json.dump + 写入即校验**. **核心保证**: - **安全 IO**: 工具自己 load → 改 → dump(ensure_ascii=False), 你永远不手写 JSON, 不会写坏文件 - **写入即校验 (fail-fast)**: 每条赋值立刻对照字典树 / 本工序 type_registry / anchor 格式校验; **任何一条非法 → 报具体哪条错, 整批不写** (不产出悄悄错的文件). 校验规则跟 lint 同源 (同款叶子派生), **patch 通过 = lint 必过** **用法**: ```bash # 单条 / 多条 --set (path=value, 只在第一个 = 处切; value 可含 = 和空格, 整体加引号) python spec/tools/wf-patch.py --workflow outputs/case-{N}/workflow.json \ --set 'p1.s1.inputs[0].anchor=← s0.主角图' \ --set 'p1.s2.effect=主体生成' \ --set 'p1.s2.action=生成/图像生成/文生图' # 批量: 几十处 anchor / 字段一次过 — 写一份 patch 清单 (你仍逐条显式决策每个值), 一条命令应用 python spec/tools/wf-patch.py --workflow outputs/case-{N}/workflow.json --patch _scratch/anchors.json # anchors.json = [{"path":"p1.s1.inputs[0].anchor","value":"← s0.x"}, ...] # 只校验不写 python spec/tools/wf-patch.py --workflow ... --set '...' --dry-run # 透传回填: anchor 设好后, 自动把"原样透传"的 value/directive 从源逐字抄过来 python spec/tools/wf-patch.py --workflow outputs/case-{N}/workflow.json --resolve-passthrough ``` **`--resolve-passthrough` (重要, 省掉重复抄写)**: spec 要求 `value` **逐字回填数据本身**, 不能写「(同 sN 输出)」「见 sN」这类引用占位 (那是 anchor 的活, lint 会报). 但一个 prompt 被下游 N 步原样复用时, 手抄 N 遍既烦又是 agent 写占位偷懒的根源. 这个模式让你**只在源头 (s1 的 output) 填一次真值**, 其余透传位置只管设 anchor (`← s1.xxx`), 然后工具顺 anchor 把源 value **逐字抄到**每个 value 空/占位的 IO + 每个「同 sN.x」的 directive, **迭代到不动点** (链式透传也覆盖). 源找不到的会 `⚠` 报出来. 可单独跑, 也可跟在 `--set/--patch` 后 (先赋值再回填). **路径语法** (proc / step 按 **id** 寻址, 不是下标; 只有真列表才用 `[i]`; 嵌套步 id 带点如 `s2.1` 也支持): | 路径 | 设的字段 | |---|---| | `p1.s2.effect` | step 标量 (effect / via / action / feature / control / kind / intent / group) | | `p1.s1.inputs[0].anchor` | IO 字段 (anchor / type / substance / form / name / value) | | `p1.s2.1.outputs[0].type` | 嵌套步的 IO | | `p1.s2.focus` | step 的 focus 数组 (逗号分隔: `focus=via,action,out-type-0`) | | `p1.purpose` | procedure 头部 (name / purpose / category / platform / author) | | `p1.declarations.inputs[0].desc` | declarations 内任意字段 (通用下钻) | | `source.url` | case-level 原帖信息 (platform / author / date / url / title / excerpt) | | `p1.type_registry.场景图.extends` | 注册 case-specific 类型 (自动建 type_registry 段) | **`--unset PATH`** (删字段, 可重复): 删掉某字段, 取代手 Edit 删. e.g. `--unset p1.declarations.inputs[0].inferred` (declarations 不收 inferred, schema 会拒). 字段本就不存在 → 跳过 (幂等, 不报错). **各字段校验规则**: - `effect` → effect.json 叶子 (给全路径会自动归一到叶名) - `action` → action.json 叶子 / 叶路径 (给叶名自动展开成全路径) - `type` → type.json 叶子 **或** 本 procedure 已注册的 type_registry 名 (没注册会提示先 `--set p.type_registry.X.extends=<叶子>`) - `extends` → 必须桥到 type.json 叶子 - `substance` / `form` / `url` → substance/form 走 taxonomy-lookup `--validate`; 三者均可传 `__null__` 设 JSON null - `anchor` → 必须 `←` (输入引用) 或 `→` (输出去向) 开头 - `feature` → 受控词 {随机 / 幂等 / 人工 / 本地 / 写外部 / 读外部 / -} - `control` → 受控词 {并行 / 遍历 / 分支 / 请求 / 等待 / -} - `kind` → {step / block / nested / atom} - `focus` → 逗号分隔 → 数组 (空串 → `[]`) - 其余 (name / value / intent / via / desc / purpose / ...) → 自由文本, 不校验 **仍用 Write / Edit 的只剩** (尽量别碰生 JSON, 这两类才用): 骨架首次创建 (Phase 1.2 从 template Write); `instruction` 列表 (套列表结构, 手动 Edit; 透传 directive 用 `--resolve-passthrough`). 改字段 / 删字段 / 改 source 现在都走 wf-patch, **不要再 Read→Edit 改 workflow.json** (那会反复重读、烧 token). **退出码**: `0` 全通过并写入 (或 `--dry-run` 通过) / `1` 有校验失败 (整批未写) / `2` CLI 错 / 文件不存在 / JSON 损坏. --- ## 3. `spec/tools/render-case.py` — 阶段三 workflow.json → HTML 渲染 + schema 校验 **用途**: 阶段三. 接收 `workflow.json` (多工序 `procedures:[]`), 在内存组装成 case_data (merge `--source-input` 原帖 + `--page-title` + `--case-id`), 跑 schema 校验, 渲染输出 HTML. **不落盘 case_data.json**. **Agent 用法** (推荐): workflow.json 已含全部 procedures + 标注后: ```bash # 1. 只校验, 看 schema 错 (建议带 --source-input, 让校验看到的就是 merge 后的最终版) python spec/tools/render-case.py \ --workflow outputs/case-{N}/workflow.json \ --source-input input/case-{N}-raw.json \ --page-title "Case {N} · <主题>" \ --case-id {N} \ --validate # 2. 校验通过后渲染输出 (--source-input 必带, 否则 HTML 折叠原文区只剩 60 字 excerpt) python spec/tools/render-case.py \ --workflow outputs/case-{N}/workflow.json \ --source-input input/case-{N}-raw.json \ --page-title "Case {N} · <主题>" \ --case-id {N} \ --out outputs/case-{N}/case-{N}-.html ``` **关于 `--source-input`** (2026-05-22 新增): renderer 直接从原帖 raw json 抽 `body_text` + 封面 + 图集兜底, in-place 填到 `case_data.source` —— Agent 不必手工复制原文内容. 行为: - `body_text` / `cover_image` 直接覆盖 case_data 同字段 - `title` / `url` 仅在 case_data 缺时填 - `excerpt` / `author` / `date` / `platform` **不动** (那些是 Agent 推断的友好版本) - **图集兜底**: 检查 raw.body_text 已 inline 的 `[image:URL]` 标记, 把 raw.image_url_list 里没 inline 也不是封面的图 append 到 body 末尾 (加 `--- 附图 ---` 分隔符). 适配"小红书短文 + 多图独立列" 和 "微信公众号长文 + inline 图" 两种平台 **输入契约**: 见 [`spec/format/case-data.schema.json`](format/case-data.schema.json) (canonical JSON Schema Draft 2020-12, 受控字段 + enum + 条件约束 `if/then`). **模板**: [`spec/templates/workflow.template.json`](templates/workflow.template.json) (复制到 `outputs/case-{N}/workflow.json` 后替换 `<填:...>` 占位符). ⚠ **绝对不要参考其他 `outputs/case-*/build_html.py` 或 `outputs/case-*/*.json`** — 那些是 case-specific 产物, 不是模板. 唯一 canonical 模板是 `spec/templates/workflow.template.json`. **输出码**: - `0` — 成功 (渲染或校验通过) - `1` — IO / schema / 渲染异常 - `2` — CLI 参数错误 **依赖**: - 必需: `spec/tools/renderer.py` (跟本脚本同目录, 自动 import) - 可选: `jsonschema` (装了用 Draft 2020-12 完整校验; 没装走 minimal check fallback) --- ## 4. `spec/tools/lint-case.py` — 轻量 lint + 自动 record 新 type **用途**: 阶段三. 跑完 `render-case.py` 后跑一次. 干三件事: 1. **type 完整性 hint**: 扫 workflow.json 各 procedure 的 IO type 字段, 找出"用了 case-specific type 但该 procedure 的 `type_registry` 漏注册 / 缺 extends / 缺 desc"的情况, 打 stdout 给 Agent / 用户看 2. **value 自包含 hint**: 扫每个 IO 的 value + 每个 directive, 揪出「(同 sN 输出)」「见 sN」「← sN」这类**引用占位** —— spec 要求 value 逐字回填数据本身 (引用归 anchor), 这种占位 schema/type 检查抓不到. 命中会提示跑 `wf-patch.py --resolve-passthrough` 自动回填 3. **副作用: auto-record**: 把各 procedure.type_registry 里的 case-specific entry 自动 append 到 `spec/taxonomy/type_suggestions.md` 累积条目段 (Agent **不必手工 Write** suggestions, 工具代劳) **幂等**: dedup key = `(type_name, case_id)` 二元组. 同 case 重跑不重复; 不同 case 同名允许 (跨 case 频次是升级信号). **用法**: ```bash # 默认: 检测 + 自动 record python spec/tools/lint-case.py --workflow outputs/case-{N}/workflow.json --case-id {N} # 只检测不写 python spec/tools/lint-case.py --workflow outputs/case-{N}/workflow.json --case-id {N} --no-record ``` **输出范例** (case 含 silent gap 的情况): ``` [lint] case-1 (workflow.json) · type 完整性: 2 个提示 - [p1] step[1].outputs[0].type='主角图' 是 case-specific 但该 procedure 的 type_registry 没注册 - [p1] step[8].inputs[1].type='主角图' 是 case-specific 但该 procedure 的 type_registry 没注册 · 无新 type 可 record (type_registry 为空 — 全部 type 命中字典叶子) ``` **退出码**: 始终 0 (不阻塞流程). 解析 2 = CLI 参数错 / 文件不存在. **设计哲学**: 不严格. 检测项是 hint 不是 fail; record 是副作用不是核心契约. Agent 看 stdout 决定回不回去补 case_data.type_registry. --- ## 5. `spec/tools/renderer.py` — HTML 渲染主模板 (Python 模块) **用途**: `render-case.py` 内部 import 的渲染主模板, 1379 行. 提供 `build_html(case_data: dict) -> str` 入口. **Agent 不直接 Read 它** — 太长, 走 `render-case.py` 调用即可. 直接 Read 它源码会浪费 ~18k context, 没意义. **Agent 唯一应该看的接口**: [`spec/format/case-data.schema.json`](format/case-data.schema.json) (输入契约) + [`spec/format/procedure-table.md`](format/procedure-table.md) (输出 HTML 的结构规范). --- ## 6. `run_procedure_dsl.py` — runner 本身 (不在 skill 内) 跑 Agent 的入口脚本. **Agent 不读它** — 它在跑 Agent. 但 Agent 可能想知道**自己被怎么起的**: - OAuth Max 模式 (走 ~/.claude 凭证, 不计 API 费) - `allowed_tools = ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "Task"]` # 🟢 包含 Task 召唤子 Agent 工具 - `permission_mode = "bypassPermissions"` (Agent 全自动, 不停下来问) - `cwd = procedure-dsl/` (Agent 的工作目录) - `--resume` 支持 (中断后续跑) - runner 实时把每 turn 写到 `outputs/case-{N}/_trace.md` --- ## 7. 文件路径约定 (per case) | 路径 | 内容 | 谁负责 | |---|---|---| | `input/case-{N}-raw.json` | 原 case 素材 (title / link / body_text / image_url_list / ...) | 人 (Agent 读, 不写) | | `outputs/case-{N}/` | Agent 工作目录 (一 case 一目录) | Agent (写产物) | | `outputs/case-{N}/.session_id` | SDK session UUID, 用于 `--resume` | runner 写, Agent 不动 | | `outputs/case-{N}/_trace.md` | runner 写的实时执行流水 | runner (Agent 别 Read) | | `outputs/case-{N}/_scratch/` | **sanctioned scratch 区** — 只用于 dump 大 Bash 输出 (taxonomy --subtree 长结果 / find 结果) 之后 Read 切片, 或一次性 smoke test. runner 预创建. **不要用项目根的 `scratch/`**. ⚠ **不是写 build/normalize 脚本生成 workflow.json 的地方** —— 那个用 §2 `wf-patch.py` | Agent (随便 dump, 跟着 case 一起清理) | | `outputs/case-{N}/understanding.md` | 阶段一 1.1 心智模型 (含多工序判断) | Agent | | `outputs/case-{N}/workflow.json` | **唯一中间产物** — Phase 1.2 Write 骨架 (从 template), 之后**逐字段演化**: 单处用 Edit, 批量结构化字段 (1.3 anchor / 2 effect·action·type·substance·form) 用 §2 `wf-patch.py`. **绝不写 Python 脚本生成 / 批改它**. 符合 case-data.schema.json (`procedures:[]`) | Agent | | `outputs/case-{N}/case-{N}-.md` | 阶段三 .md 输出 (按 spec/format/md-structure.md) | Agent | | `outputs/case-{N}/case-{N}-.html` | 阶段三 .html 输出 (跑 render-case.py 生成) | render-case.py | --- ## 8. 外部 JSON 词表 (实质 / 形式) 不进 spec/ `分类库导出_*.json` 词表为什么不放 spec/: - **太大**: 实质 911 条 + 形式 565 条路径, 进 LLM context 占 30k+ tokens - **变化频率不同**: spec 演化慢, 词表演化快 (随分类学的迭代) - **走 tool 比走 Read 更经济**: 配 `spec/tools/taxonomy-lookup.py` 的 funnel API (`--list-l2 / --subtree / --match`), 一次 query 几百 tokens, 比整树 Read 省 50-100x Agent 阶段二 2B 完全通过 tool 查, 不要 Read 词表 JSON 本身. --- ## 9. `Agent` (或 `Task`) — Claude Agent SDK 子 Agent 召唤工具 **用途**: 用于在 Phase 2 (归一化与标注) 中将庞大重负载的主 Agent 上下文(含 14+ Base64 图片及 40k+ 规格说明书)与细碎耗费 Token 的高频分类查询进行**上下文隔离与并发分流**。 **工作原理**: - 允许主 Agent 创建并行的、无图片和规格历史包袱的轻量化子 Agent。 - **安全 IO**: 工具自己 load → 改 → dump(ensure_ascii=False), 你永远不手写 JSON, 不会写坏文件 - **写入即校验 (fail-fast)**: 每条赋值立刻对照字典树 / 本工序 type_registry / anchor 格式校验; **任何一条非法 → 报具体哪条错, 整批不写** (不产出悄悄错的文件). 校验规则跟 lint 同源 (同款叶子派生), **patch 通过 = lint 必过** **用法**: ```bash # 单条 / 多条 --set (path=value, 只在第一个 = 处切; value 可含 = 和空格, 整体加引号) python spec/tools/wf-patch.py --workflow outputs/case-{N}/workflow.json \ --set 'p1.s1.inputs[0].anchor=← s0.主角图' \ --set 'p1.s2.effect=主体生成' \ --set 'p1.s2.action=生成/图像生成/文生图' # 批量: 几十处 anchor / 字段一次过 — 写一份 patch 清单 (你仍逐条显式决策每个值), 一条命令应用 python spec/tools/wf-patch.py --workflow outputs/case-{N}/workflow.json --patch _scratch/anchors.json # anchors.json = [{"path":"p1.s1.inputs[0].anchor","value":"← s0.x"}, ...] # 只校验不写 python spec/tools/wf-patch.py --workflow ... --set '...' --dry-run # 透传回填: anchor 设好后, 自动把"原样透传"的 value/directive 从源逐字抄过来 python spec/tools/wf-patch.py --workflow outputs/case-{N}/workflow.json --resolve-passthrough ``` **`--resolve-passthrough` (重要, 省掉重复抄写)**: spec 要求 `value` **逐字回填数据本身**, 不能写「(同 sN 输出)」「见 sN」这类引用占位 (那是 anchor 的活, lint 会报). 但一个 prompt 被下游 N 步原样复用时, 手抄 N 遍既烦又是 agent 写占位偷懒的根源. 这个模式让你**只在源头 (s1 的 output) 填一次真值**, 其余透传位置只管设 anchor (`← s1.xxx`), 然后工具顺 anchor 把源 value **逐字抄到**每个 value 空/占位的 IO + 每个「同 sN.x」的 directive, **迭代到不动点** (链式透传也覆盖). 源找不到的会 `⚠` 报出来. 可单独跑, 也可跟在 `--set/--patch` 后 (先赋值再回填). **路径语法** (proc / step 按 **id** 寻址, 不是下标; 只有真列表才用 `[i]`; 嵌套步 id 带点如 `s2.1` 也支持): | 路径 | 设的字段 | |---|---| | `p1.s2.effect` | step 标量 (effect / via / action / feature / control / kind / intent / group) | | `p1.s1.inputs[0].anchor` | IO 字段 (anchor / type / substance / form / name / value) | | `p1.s2.1.outputs[0].type` | 嵌套步的 IO | | `p1.s2.focus` | step 的 focus 数组 (逗号分隔: `focus=via,action,out-type-0`) | | `p1.purpose` | procedure 头部 (name / purpose / category / platform / author) | | `p1.declarations.inputs[0].desc` | declarations 内任意字段 (通用下钻) | | `source.url` | case-level 原帖信息 (platform / author / date / url / title / excerpt) | | `p1.type_registry.场景图.extends` | 注册 case-specific 类型 (自动建 type_registry 段) | **`--unset PATH`** (删字段, 可重复): 删掉某字段, 取代手 Edit 删. e.g. `--unset p1.declarations.inputs[0].inferred` (declarations 不收 inferred, schema 会拒). 字段本就不存在 → 跳过 (幂等, 不报错). **各字段校验规则**: - `effect` → effect.json 叶子 (给全路径会自动归一到叶名) - `action` → action.json 叶子 / 叶路径 (给叶名自动展开成全路径) - `type` → type.json 叶子 **或** 本 procedure 已注册的 type_registry 名 (没注册会提示先 `--set p.type_registry.X.extends=<叶子>`) - `extends` → 必须桥到 type.json 叶子 - `substance` / `form` / `url` → substance/form 走 taxonomy-lookup `--validate`; 三者均可传 `__null__` 设 JSON null - `anchor` → 必须 `←` (输入引用) 或 `→` (输出去向) 开头 - `feature` → 受控词 {随机 / 幂等 / 人工 / 本地 / 写外部 / 读外部 / -} - `control` → 受控词 {并行 / 遍历 / 分支 / 请求 / 等待 / -} - `kind` → {step / block / nested / atom} - `focus` → 逗号分隔 → 数组 (空串 → `[]`) - 其余 (name / value / intent / via / desc / purpose / ...) → 自由文本, 不校验 **仍用 Write / Edit 的只剩** (尽量别碰生 JSON, 这两类才用): 骨架首次创建 (Phase 1.2 从 template Write); `instruction` 列表 (套列表结构, 手动 Edit; 透传 directive 用 `--resolve-passthrough`). 改字段 / 删字段 / 改 source 现在都走 wf-patch, **不要再 Read→Edit 改 workflow.json** (那会反复重读、烧 token). **退出码**: `0` 全通过并写入 (或 `--dry-run` 通过) / `1` 有校验失败 (整批未写) / `2` CLI 错 / 文件不存在 / JSON 损坏. --- ## 3. `spec/tools/render-case.py` — 阶段三 workflow.json → HTML 渲染 + schema 校验 **用途**: 阶段三. 接收 `workflow.json` (多工序 `procedures:[]`), 在内存组装成 case_data (merge `--source-input` 原帖 + `--page-title` + `--case-id`), 跑 schema 校验, 渲染输出 HTML. **不落盘 case_data.json**. **Agent 用法** (推荐): workflow.json 已含全部 procedures + 标注后: ```bash # 1. 只校验, 看 schema 错 (建议带 --source-input, 让校验看到的就是 merge 后的最终版) python spec/tools/render-case.py \ --workflow outputs/case-{N}/workflow.json \ --source-input input/case-{N}-raw.json \ --page-title "Case {N} · <主题>" \ --case-id {N} \ --validate # 2. 校验通过后渲染输出 (--source-input 必带, 否则 HTML 折叠原文区只剩 60 字 excerpt) python spec/tools/render-case.py \ --workflow outputs/case-{N}/workflow.json \ --source-input input/case-{N}-raw.json \ --page-title "Case {N} · <主题>" \ --case-id {N} \ --out outputs/case-{N}/case-{N}-.html ``` **关于 `--source-input`** (2026-05-22 新增): renderer 直接从原帖 raw json 抽 `body_text` + 封面 + 图集兜底, in-place 填到 `case_data.source` —— Agent 不必手工复制原文内容. 行为: - `body_text` / `cover_image` 直接覆盖 case_data 同字段 - `title` / `url` 仅在 case_data 缺时填 - `excerpt` / `author` / `date` / `platform` **不动** (那些是 Agent 推断的友好版本) - **图集兜底**: 检查 raw.body_text 已 inline 的 `[image:URL]` 标记, 把 raw.image_url_list 里没 inline 也不是封面的图 append 到 body 末尾 (加 `--- 附图 ---` 分隔符). 适配"小红书短文 + 多图独立列" 和 "微信公众号长文 + inline 图" 两种平台 **输入契约**: 见 [`spec/format/case-data.schema.json`](format/case-data.schema.json) (canonical JSON Schema Draft 2020-12, 受控字段 + enum + 条件约束 `if/then`). **模板**: [`spec/templates/workflow.template.json`](templates/workflow.template.json) (复制到 `outputs/case-{N}/workflow.json` 后替换 `<填:...>` 占位符). ⚠ **绝对不要参考其他 `outputs/case-*/build_html.py` 或 `outputs/case-*/*.json`** — 那些是 case-specific 产物, 不是模板. 唯一 canonical 模板是 `spec/templates/workflow.template.json`. **输出码**: - `0` — 成功 (渲染或校验通过) - `1` — IO / schema / 渲染异常 - `2` — CLI 参数错误 **依赖**: - 必需: `spec/tools/renderer.py` (跟本脚本同目录, 自动 import) - 可选: `jsonschema` (装了用 Draft 2020-12 完整校验; 没装走 minimal check fallback) --- ## 4. `spec/tools/lint-case.py` — 轻量 lint + 自动 record 新 type **用途**: 阶段三. 跑完 `render-case.py` 后跑一次. 干三件事: 1. **type 完整性 hint**: 扫 workflow.json 各 procedure 的 IO type 字段, 找出"用了 case-specific type 但该 procedure 的 `type_registry` 漏注册 / 缺 extends / 缺 desc"的情况, 打 stdout 给 Agent / 用户看 2. **value 自包含 hint**: 扫每个 IO 的 value + 每个 directive, 揪出「(同 sN 输出)」「见 sN」「← sN」这类**引用占位** —— spec 要求 value 逐字回填数据本身 (引用归 anchor), 这种占位 schema/type 检查抓不到. 命中会提示跑 `wf-patch.py --resolve-passthrough` 自动回填 3. **副作用: auto-record**: 把各 procedure.type_registry 里的 case-specific entry 自动 append 到 `spec/taxonomy/type_suggestions.md` 累积条目段 (Agent **不必手工 Write** suggestions, 工具代劳) **幂等**: dedup key = `(type_name, case_id)` 二元组. 同 case 重跑不重复; 不同 case 同名允许 (跨 case 频次是升级信号). **用法**: ```bash # 默认: 检测 + 自动 record python spec/tools/lint-case.py --workflow outputs/case-{N}/workflow.json --case-id {N} # 只检测不写 python spec/tools/lint-case.py --workflow outputs/case-{N}/workflow.json --case-id {N} --no-record ``` **输出范例** (case 含 silent gap 的情况): ``` [lint] case-1 (workflow.json) · type 完整性: 2 个提示 - [p1] step[1].outputs[0].type='主角图' 是 case-specific 但该 procedure 的 type_registry 没注册 - [p1] step[8].inputs[1].type='主角图' 是 case-specific 但该 procedure 的 type_registry 没注册 · 无新 type 可 record (type_registry 为空 — 全部 type 命中字典叶子) ``` **退出码**: 始终 0 (不阻塞流程). 解析 2 = CLI 参数错 / 文件不存在. **设计哲学**: 不严格. 检测项是 hint 不是 fail; record 是副作用不是核心契约. Agent 看 stdout 决定回不回去补 case_data.type_registry. --- ## 5. `spec/tools/renderer.py` — HTML 渲染主模板 (Python 模块) **用途**: `render-case.py` 内部 import 的渲染主模板, 1379 行. 提供 `build_html(case_data: dict) -> str` 入口. **Agent 不直接 Read 它** — 太长, 走 `render-case.py` 调用即可. 直接 Read 它源码会浪费 ~18k context, 没意义. **Agent 唯一应该看的接口**: [`spec/format/case-data.schema.json`](format/case-data.schema.json) (输入契约) + [`spec/format/procedure-table.md`](format/procedure-table.md) (输出 HTML 的结构规范). --- ## 6. `run_procedure_dsl.py` — runner 本身 (不在 skill 内) 跑 Agent 的入口脚本. **Agent 不读它** — 它在跑 Agent. 但 Agent 可能想知道**自己被怎么起的**: - OAuth Max 模式 (走 ~/.claude 凭证, 不计 API 费) - `allowed_tools = ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "Task"]` # 🟢 包含 Task 召唤子 Agent 工具 - `permission_mode = "bypassPermissions"` (Agent 全自动, 不停下来问) - `cwd = procedure-dsl/` (Agent 的工作目录) - `--resume` 支持 (中断后续跑) - runner 实时把每 turn 写到 `outputs/case-{N}/_trace.md` --- ## 7. 文件路径约定 (per case) | 路径 | 内容 | 谁负责 | |---|---|---| | `input/case-{N}-raw.json` | 原 case 素材 (title / link / body_text / image_url_list / ...) | 人 (Agent 读, 不写) | | `outputs/case-{N}/` | Agent 工作目录 (一 case 一目录) | Agent (写产物) | | `outputs/case-{N}/.session_id` | SDK session UUID, 用于 `--resume` | runner 写, Agent 不动 | | `outputs/case-{N}/_trace.md` | runner 写的实时执行流水 | runner (Agent 别 Read) | | `outputs/case-{N}/_scratch/` | **sanctioned scratch 区** — 只用于 dump 大 Bash 输出 (taxonomy --subtree 长结果 / find 结果) 之后 Read 切片, 或一次性 smoke test. runner 预创建. **不要用项目根的 `scratch/`**. ⚠ **不是写 build/normalize 脚本生成 workflow.json 的地方** —— 那个用 §2 `wf-patch.py` | Agent (随便 dump, 跟着 case 一起清理) | | `outputs/case-{N}/understanding.md` | 阶段一 1.1 心智模型 (含多工序判断) | Agent | | `outputs/case-{N}/workflow.json` | **唯一中间产物** — Phase 1.2 Write 骨架 (从 template), 之后**逐字段演化**: 单处用 Edit, 批量结构化字段 (1.3 anchor / 2 effect·action·type·substance·form) 用 §2 `wf-patch.py`. **绝不写 Python 脚本生成 / 批改它**. 符合 case-data.schema.json (`procedures:[]`) | Agent | | `outputs/case-{N}/case-{N}-.md` | 阶段三 .md 输出 (按 spec/format/md-structure.md) | Agent | | `outputs/case-{N}/case-{N}-.html` | 阶段三 .html 输出 (跑 render-case.py 生成) | render-case.py | --- ## 8. 外部 JSON 词表 (实质 / 形式) 不进 spec/ `分类库导出_*.json` 词表为什么不放 spec/: - **太大**: 实质 911 条 + 形式 565 条路径, 进 LLM context 占 30k+ tokens - **变化频率不同**: spec 演化慢, 词表演化快 (随分类学的迭代) - **走 tool 比走 Read 更经济**: 配 `spec/tools/taxonomy-lookup.py` 的 funnel API (`--list-l2 / --subtree / --match`), 一次 query 几百 tokens, 比整树 Read 省 50-100x Agent 阶段二 2B 完全通过 tool 查, 不要 Read 词表 JSON 本身. --- ## 9. `Agent` (或 `Task`) — Claude Agent SDK 子 Agent 召唤工具 **用途**: 用于在 Phase 2 (归一化与标注) 中将庞大重负载的主 Agent 上下文(含 14+ Base64 图片及 40k+ 规格说明书)与细碎耗费 Token 的高频分类查询进行**上下文隔离与并发分流**。 **工作原理**: - 允许主 Agent 创建并行的、无图片和规格历史包袱的轻量化子 Agent。 - 子 Agent 具有极其干净的小型 Context 窗口(约 2k tokens),它专门携带 `taxonomy-lookup.py` 工具链去查字典,并返回标准的 `patch_2a.json` 或 `patch_2b.json` 回归主 Agent,由主 Agent 通过 `wf-patch.py` 写入 `workflow.json`。 - **降本 90% 以上,速度提升 100% 以上 (多路并发)**! **召唤语法与参数**: ```json Agent( subagent_type="Explore", // 固定为 "Explore" (用于分析与工具调用) 或 "Write" (用于产物生成) description="子任务标题: 例如 Phase 2B 变量实质与形式词表查询", prompt="[给子 Agent 的明确任务书] 例如: 读 outputs/case-N/_scratch/task_2b.json 任务文件,然后为其中各变量调用 taxonomy-lookup.py 查出实质与形式路径,生成标准的 patch_2b.json 并把其具体内容写回给我。" ) ```