Skip to content
Back to skills

Infocard Authoring Workflow

ASecurity

Light-route single-card authoring without subagent.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 19, 2026
toolspythonrustgoshellbashreactnodegitapidocumentation

Works with

  • vscode
  • terminal
  • cli
  • api

Security analysis

A100/100

Pro scans all 10 files and shows the line behind each finding

Scanned October 19, 2026

npx -y skills add ccwq/infocard-pub --skill infocard-authoring-workflow --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Infocard Authoring Workflow?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Infocard Authoring Workflow
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ccwq-infocard-authoring-workflow/badge)](https://www.skillsdirectory.com/skills/ccwq-infocard-authoring-workflow)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: infocard-authoring-workflow
description: "Light-route single-card authoring without subagent."
category: infocard
tags: [infocard, authoring, light-route, python, template-clone]
---

# infocard-authoring-workflow · Light-Route Direct Authoring

## Light-route contract

普通单来源卡在 HTML 创作前必须准备以下文件,并运行 `node scripts/verify-infocard-preflight.js --authoring-dir .docs/<run-id>/<slug>`:

- `.docs/<run-id>/<slug>/project-brief.json`
- `facts.json`
- `theme-decision.json`
- `card.html.meta.yaml`
- `promotion-manifest.json`

`project-brief.json` 固定使用 `hero`、`summary`、`core_capabilities`、`tech_stack`、`usage`、`use_cases`、`risk_boundary`、`sources` 槽位。契约失败时停止 authoring,不在 promotion 阶段补 schema。

## Subagent delegation vs. light route decision

Two authoring paths; choose by content readiness:

| Condition | Path |
|---|---|
| facts.json gathered (推文 + GitHub API + README 全文),theme known | Subagent delegation — parallel, faster |
| Need multi-source research, ambiguous attribution | Subagent delegation |
| Facts in hand + theme known + 1 card | Light route — no subagent overhead |
| Token Plan 429 on subagent | Fallback to light route |

**Subagent delegation advantages**: parallel research + HTML writing, good for 9-section progressive-doc.

**Subagent delegation limits**: no preflight schema; info-leak check runs post-write; **synchronous return when `delegation.max_concurrent_children` is at capacity** — same output structure, no quality difference.

**Theme pre-check 是硬约束**:每次派发生成前,主线程必须:
1. 查 `docs/202610*.html` 最近 6 张卡的实际 `data-theme` 值
2. 查在途委派已占用主题(当前会话 active children 的 theme-decision.json)
3. 从 `theme/themes.json` 过滤合格候选
4. 排除近 6 张 + 在途占用后,生成 `.docs/<run-id>/<slug>/theme-decision.json`
5. 委派 prompt 中写入:`selected_theme` 字段值即 `data-theme` 取值,子智能体**不得自行选择或更改**
6. 子智能体产出后,发布前校验 `data-theme` 是否与 decision record 一致;不符则退回重做

**Subagent delegation pattern** (tested 2026-10-12): facts already gathered in `/tmp/<slug>_material.txt` → `mkdir .docs/<run-id>/<slug>/` → **主线程先执行 theme pre-check** → delegate with full inline facts + `theme-decision.json` path → subagent reads `theme-decision.json.selected_theme` and uses it as `data-theme` value, writes five files to its own directory.

**主题家族去重规则**:`q-style` 是未注册虚构名称;`q` 和 `pixelstack` 是 bare slug;别名不重置占用窗口。`q` 与 `q-style` 视为同一家族(后者不能绕过占用约束)。

## When to use this skill

Trigger: publishing a single infocard where you have full content and no multi-source cross-validation is needed. Specifically:

- User gave a URL with content → light route
- Subagent delegation failed (Token Plan 429) → fallback to this workflow
- Single-source research card with clear content → prefer this over subagent

**Do NOT use** when: multi-source cross-validation needed, content is ambiguous, user explicitly requested subagent parallel authoring, or research scope exceeds main-thread context.

### Solvable social-source ambiguity is not an automatic pause

When a social post contains a recommendation plus an image/screenshot but omits the canonical project or book name, do not immediately ask the user to choose among every superficially similar search result. First run a bounded first-party matching check using distinctive OCR and structural fingerprints: chapter names, item counts, code-line/read-time figures, author identity, README outline, and public reading links.

If one candidate has a strong multi-signal match, proceed with an explicit attribution boundary in the bundle and card copy, for example: “原帖未点名;根据配图 OCR 与公开 README 交叉匹配,本文聚焦该项目(高置信度)。” Keep the social post's exact wording separate from the matched project's first-party facts, and list rejected candidates only in internal research notes unless the user asked for a comparison.

Ask one clarification question only if the bounded match remains materially uncertain or choosing among candidates would change the requested artifact. Existing explicit “创建/发布” authorization remains valid through a solvable attribution gap; do not reset it merely because the source post is terse.

This pattern is documented for reuse in `references/social-source-attribution-matching.md`.

## Recovery takeover gate (when session context is lost)

When a user asks to find and take over unfinished cards from disk, do not count bundles or HTML files as published-work units. First inventory all run bundles, declared card directories, remote/public evidence. Classify each candidate as `制作中待发布`, `已发布待审计`, `历史残留/不可直接接管`, or `环境/仓库污染`; report counts from that classification before claiming takeover.

For a recovered batch, write directly into the primary checkout using `write_file`. Copy declared artifacts by allowlist, regenerate shared indexes once, and validate every card with the live bundle gate. Recovery bundles must contain canonical fields (`slug`, `html_path`, `meta_path`, `asset_dir`, `manifest_path`, `source_url`, `style`, `keywords`, `wiki`). Normalize slugs to lowercase kebab-case and update bundle paths, HTML filenames, and sidecars together. A local commit is not proof of “待发布” until remote branch and public URL evidence are checked.

When `visual_review.required=true`, a screenshot result with any critical or major defect blocks release. A successful build, HTTP 200, or DOM check cannot be upgraded to visual pass. Keep the per-card `critical / major / minor` disposition in run-local evidence.

**User expectation for interrupted runs:** If the user says “继续 / go / 直到完成”, keep executing to a terminal state rather than returning a plan or pausing after discovery. If a hard visual gate blocks release, report the exact blocker and preserve the recovery evidence; do not call the batch complete.

**Visual disagreement rule:** A local `.table-scroll` wrapper and `scrollWidth == clientWidth` prove only page-level mechanical containment. They do not prove discoverability or visual readability. Dense tables need a visible mobile affordance (for example a short “横向滑动查看完整对比” cue or a partial next-column reveal), and multi-column cards need visible right-side padding/border. Re-screenshot after each repair. If vision still reports clipping while DOM checks pass, keep `VISUAL_PENDING` and do not push.

See `references/recovery-takeover-inventory.md` for the evidence matrix and recovery classification recipe.
See `references/chatgpt-url-to-infocard.md` for the verified workflow to extract infocard content from ChatGPT conversation URLs via `abc` — navigate, snapshot-parse table/cells/code blocks, and `abc screenshot` visual verification.
See `references/interrupted-batch-recovery.md` for the class-level recovery sequence and visual-gate disagreement handling.
See `references/github-api-workarounds.md` for GitHub API quirks and the reliable git-push pattern for this repo.
See `references/wechat-inline-body-compatibility.md` for the verified Handline-to-WeChat inline-body conversion and validator gate.
See `references/color-material-wechat-inline-recipe.md` for the Color Material editorial-to-WeChat natural-flow conversion, allowed-tag contract, leaf-marker audit, and deterministic static checks.
See `references/graph-paper-wechat-inline-recipe.md` for the graph-paper/manual strict-tag migration, independent allowlist scan, and the `<br>` to whitespace-leaf correction.
See `references/vscode-marketplace-card-build-notes.md` for the VS Code Marketplace / Open VSX marketplace-card workflow, provenance split, and build/commit notes from the 2026-08-15 card.

### GitHub 来源子智能体委派:必须内联已验证事实(2026-09-10 实测)

当来源是 GitHub URL 时,**父线程先用 `web_extract` 提取项目元数据**,再把结构化事实内联到 delegate_task 的 context 中。不要只给 URL 让子智能体自己重新抓取。

**失败根因**:delegate_task 的 `goal` 字符串中包含未展开的模板标记(如 `<clear Chinese title>`)时,batch 报错 `"unexpanded template marker"`。子智能体上下文无法从工作目录访问父线程的 web 提取结果。

**已验证的稳定委派模式**:
```python
# 父线程先提取
web_extract(urls=['https://github.com/shaom/infocard-skills'], char_limit=15000)
# → 获得 Stars/Forks/Description 等字段

# 在 delegate_task goal 中内联实际值(不用占位符)
goal = f"""Write a card for infocard-skills.
Stars: {stars} | License: {license}
Core skill: {skill_name} — {description}
Supported layouts: {layouts}"""
```

**实测教训**:在 goal 中写 `<clear Chinese title>` 等裸占位符 → subagent 报错 `"unexpanded template marker"` → 必须替换为具体值。`theme-decision.json` 等父线程已生成的辅助文件也要在 context 中注明路径。

## Direct authoring rule

Authoring happens only inside the primary repository checkout's `.docs/<run-id>/<slug>/` directory. This is the correct and only authoring location.

The Author must not directly write `docs/`, `assets/`, `_index.yaml`, `index.html`, Git state, `/tmp/infocard*`, a clone, or any Git worktree. Create candidate HTML, sidecar, facts, visual evidence, and `promotion-manifest.json` in `.docs`; `infocard-pub-publisher` is the only role that promotes declared files to formal public paths.

## Poster-shell rebuild and responsive repair

Use a rebuild rather than repeated patches when CSS rules duplicate/override each other, grid and flex rules conflict, two repair rounds failed, or the user explicitly asks for a rebuild. Rebuild only the `.docs/<run-id>/<slug>/card.html` candidate; retain every required content section and let Publisher promote it after visual evidence passes.

### Structural contract

For a numbered poster-shell card, keep the number, stripe, and body structurally separate:

```css
.skill-card { display:grid; grid-template-columns:100px 1fr; position:relative; min-height:96px; }
.card-num { grid-column:1; grid-row:1; align-self:center; text-align:right; padding-right:28px; }
.card-stripe { position:absolute; left:96px; top:18px; bottom:18px; width:1px; }
.card-body { grid-column:2; grid-row:1; min-width:0; padding:14px 22px 14px 18px; }
@media (max-width:720px) {
  .skill-card { grid-template-columns:68px 1fr; }
  .card-num { padding-right:16px; }
  .card-stripe { left:64px; top:14px; bottom:14px; }
  .card-body { grid-column:2; min-width:0; padding:10px 10px 10px 14px; }
}
```

Do not let a body fall into the number column. If the mobile variant switches to flex, verify that the body has an explicit usable width; a generic `flex:1` patch may lose to a more-specific media rule. For every rebuild, inspect DOM parentage before changing CSS: a prematurely closed tag can turn a description into a grid sibling, which no overflow rule can fix.

### 自建类必须自带窄屏规则(委派子智能体时的强制项)

**根因(2026-10-13 实测,一天内五张卡连续复现)**:委派时说「复用模板类名」,子智能体仍会另建 `.risk-grid` / `.grid-2` / `.grid-3` / `.matrix` / `.flow` / `.comparison-table` / `.data-table` / `.env-table` / `.analysis-block` / `.code-block` / `.mapping-row` / `.step-row` / `.wide-section` 等类。它只为**模板自带类**补 `@media (max-width:720px)`,自建类一个都不写 —— 窄屏下栅格不塌陷、宽表挤爆、内容贴边。

**委派时必须写死的三件事**:

1. **自建类也要在 720px media 内覆盖**:所有 `grid-template-columns: repeat(N, ...)` 的类在窄屏改 `1fr`(或 2 列);padding / font-size 收窄一档。
2. **所有 `<table>` 包滚动容器**:`<div class="table-wrap">` + media 内 `min-width:600px`(多列对比表 720px)+ `overflow-x:auto` + 可见滚动条。浏览器对无 min-width 的宽表会强行压缩列宽,导致文字逐字换行、右列被裁。
3. **代码块也要窄屏规则**:`overflow-x:auto` + 字号下调(桌面 .85rem → 移动 .76rem 左右)+ padding 收窄;`white-space:pre` 下不滚动就是横向溢出。
4. **`.section-head`(含编号方块的章节头)补 `padding-left:14px`**,否则编号块贴左边——历史高频 bug。

**验收命令**(收口时跑,不通过就打回或主线补):

```bash
# 表格必须全部被 table-wrap 包裹
grep -c '<table' card.html; grep -c '<div class="table-wrap">' card.html
# 720px media 必须存在且覆盖上述类
grep -o '@media[^{]*720px' card.html
grep -o 'table-wrap\|code-block\|analysis-block\|grid-template-columns:1fr' card.html | sort | uniq -c
```

判定口径:`<table>` 数量 == `table-wrap` 数量;720px media 内必须能 grep 到 `table-wrap` 与至少一个代码块类名或 `grid-template-columns:1fr`。详见 `infocard-pub-hardening` 的 H-16 / H-17 与 H-16 详细节扫描脚本(可对整个 `docs/` 批量跑)。

### Required verification

At desktop and 390px mobile, inspect `scrollWidth`, `clientWidth`, body geometry, number/card center alignment, computed display, and relevant `grid-column`/flex values. Then capture fresh visual evidence. A structural or CSS change invalidates every prior screenshot and manifest.

Never use a worktree, branch, clone, force-push, or `/tmp/infocard*` for a poster-shell rebuild.

## Repo-local skill placement

When the workflow belongs to one repository, prefer placing the skill inside the repo under `<repo>/.agents/skills/` so the project owns its own procedures. Hermes also recognizes `<repo>/.hermes/skills/`, but `.agents/skills/` is the clearer cross-tool convention when the repo may be used by more than one agent harness.

Key behavior:
- project-local skills are only active for sessions started inside that repo
- project-local skills override same-named global/local skills inside their home repo
- Hermes only loads them after the repo is trusted
- quarantined skills do not enter the index, even if present on disk

## Spec-first implementation

For repo-local skill rollout or skill-heavy repo work, do not jump straight from idea to file edits. First turn the current discussion into a spec with `to-spec`, then delegate implementation to a subagent that follows the spec and returns concrete changes plus verification evidence.

Use this sequence:
1. scope the repo-local skill or workflow boundary
2. write the spec first (`skill:to-spec`)
3. implement with a subagent
4. verify in the target repo before declaring the skill usable

This keeps project-specific workflows separate from the global skill library and makes later consolidation easier.

**Preflight:** record `git status --short` in the primary checkout before writing. Ambient changes are preserved and excluded from this run; a dirty checkout is not permission to create a worktree, reset, stash, clean, or stage unrelated files.

**No worktree for authoring.** Use `write_file` only under `.docs/<run-id>/<slug>/`. The Publisher later verifies the manifest and promotes the declared public files. Authors never stage.

## Core workflow (6 steps)

### Step 1 · Template prep

1. Create the authoring root: `.docs/<run-id>/<slug>/`.
2. Set candidate targets: `.docs/<run-id>/<slug>/card.html` and `.docs/<run-id>/<slug>/card.html.meta.yaml`.
3. Read `theme/<style>.html` as skeleton input; write the derived candidate only under `.docs`.
4. Create `.docs/<run-id>/<slug>/promotion-manifest.json` mapping candidate sources to final `docs/<slug>.html`, matching sidecar, and declared `assets/` targets.
5. Verify the candidate and manifest landed under `.docs`; do not create formal `docs/` files in the Author phase.

### Theme decision input

`infocard-theme-assignment` is the only owner of content-to-theme association. **Before authoring (whether light-route or subagent delegation), the main thread must run the pre-check to generate `theme-decision.json`**; the authoring phase only consumes the frozen decision.

Authoring must not maintain a lookup table, recommend a concrete theme, or perform a second selection.

The record must already contain `content_type`, `content_shape`, `candidate_themes`, `excluded_themes`, `selection_weights`, `seed`, `selected_theme`, and `user_override`. Confirm that the selected theme is registered, the record is internally consistent, and the HTML `data-theme` plus sidecar `style` normalize to that selected bare slug. If missing or inconsistent, stop with `THEME_BLOCKED` and return to theme assignment.

**Pre-check executed by main thread before any authoring (required for every card)**:
```
1. grep data-theme= docs/202610*.html (tail -6) → recent 6 occupied themes
2. Check in-flight theme-decision.json from active delegate_task children
3. Filter candidates from theme/themes.json by content capability requirements
4. Exclude occupied → write .docs/<run-id>/<slug>/theme-decision.json
5. Pass selected_theme into authoring context (subagent or light-route)
6. After authoring: verify card.html data-theme == selected_theme; if mismatch → BLOCKED_AT_THEME_MISMATCH, return to authoring
```

Use the selected registered theme's skill and `theme/<selected_theme>.html` as skeleton input. User-selected themes remain preferred only when the assignment record marks them accepted after capability filtering; authoring never bypasses that check.

**Content depth guide ("内容不要吝啬")**: When user asks for practical-guide level:
- Expand comparison tables with full detail rows
- Add step-by-step operation guides (`.step-item` + `.step-n` numbered blocks)
- Include syntax-highlighted code blocks (dark `#1a1a2e` bg + color spans)
- Add known-issues / troubleshooting section
- Tool cards: install + config + CLI + JVM memory fix + PowerShell scripts
- Hardblue sections: `.section-head` + `.section-no` (96px red/blue/black blocks) + `.card` grid + `.risk` top-color stripes

### UI 组件库类卡片的 Hero 简化原则(darkblue 专用)

**触发**:卡片介绍一个有 Live Demo 的 UI 组件 / React 库,Live Demo 是核心价值。

**反模式**:Hero 右侧放置占一半高度的浏览器 mockup(Live Demo 截图)。被用户明确否定——截图≠真实动画,卡片变长但信息密度低,且截图与实际效果视觉不一致。

**正确结构**:
1. Hero 仅保留左侧:kicker + h1 + subtitle + badge row + **醒目蓝色 CTA 按钮**(不要放大浏览器 mockup)
2. CTA 示例:`<a class="cta" href="https://...live-demo...">↗ 体验 9 种动画</a>`
3. 9 个状态用 **紧凑 SVG 静态示意**(每 orb 一个 56×56 SVG)+ state name + 中文描述。**不要用占 Hero 一半高度的浏览器 mockup 截图**(用户明确否定:"超长截图不应该存在,用一个最后占位抽象图来表示右侧")
4. Footer 兜底 Live Demo 链接

**为什么 SVG 示意必须保留**:用户原话"所有的 svg 都消失了,也没有动画"——去掉 SVG 后只剩纯文字,信息密度反而比原来更低。正确做法是去掉 mockup 截图,保留紧凑 SVG 网格。

**CTA CSS 要点**:
```css
.cta {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--blue); color: #fff; font-size: 14px; font-weight: 700;
  padding: 11px 20px; border-radius: 6px;
  width: fit-content;   /* ← grid 1fr 下必须!否则占满整行 */
  box-shadow: 0 4px 16px rgba(74,120,255,.35);
}
```

**已验证案例**:`thinking-orbs`(1569 ★,MIT,React 18+ / Pure JS 出口)→ darkblue + 去 mockup + 紧凑 SVG 示意
- Commit `0865ecd` · 桌面 0/0/0,移动 0/0/1
- https://ccwq.github.io/infocard-pub/docs/20260805-thinking-orbs.html

**移动端截屏规范**:用 `--window-size=390,1500`(非 1800),能覆盖一屏信息卡完整内容又不截到页面底部空白。

```bash
google-chrome --headless=new --disable-gpu \
  --window-size=390,1500 \
  --screenshot=/tmp/card-mobile.png \
  "http://127.0.0.1:PORT/docs/<slug>.html"
```

The decision record replaces the former four-line `theme_primary/theme_fallback/theme_reject` note. Keep rejection reasons and weights in JSON; do not copy them into a competing authoring contract. Any HTML, structure, CSS, content, or theme change invalidates prior visual evidence.

### Step 2 · Write HTML directly (preferred)

### Investigation dossier density gate

For a multi-source controversy or any card upgraded after feedback such as “内容空洞 / 充分调查 / 作为调查记者”, do not retain a thin fact-check skeleton. Build an investigation dossier with: a conclusion-led overview, a causally connected timeline, at least two attributable primary quotations/notices, a sourced scale-data block, ≥3 stakeholder positions, one legal/industry/comparable-case context, and a clearly separated evidence boundary.

A card fails this gate if it is mainly “confirmed / unverified” labels, if its timeline lacks actor-action-evidence detail, or if deleting most paragraphs leaves the conclusion unchanged. This gate governs content density; `wang-reporter-investigation-standard` remains the single source of truth for evidence traceability.

**Completion criterion:** each section adds an independently sourced fact, explanation, or bounded inference; no disputed claim is upgraded beyond its evidence tier.

**Method**: Use `write_file` directly — simpler and faster than Python scripts, works reliably for HTML content up to ~20KB. The 

**Metadata naming rule**: Name the sidecar exactly `<html-basename>.meta.yaml`, and set `slug` to the full date-prefixed basename used by the repository (for example, `20260729-alacritty`), not merely the project name. The build's metadata-shape pass treats a short slug as a warning and may block verification.

**Content discipline for tool cards**: Prefer claims that are directly supported by the project's README or official documentation. Treat live GitHub counts and benchmark numbers as time-sensitive; either date them, label them approximate, or omit them. Preserve the repository's own feature boundaries, especially when a deliberate omission (such as no built-in tabs) is part of the product's design.

### Model-router claim separation

For AI gateway, provider-switcher, or model-router cards, keep these layers separate in prose and comparison tables:

1. **account/login state** — OAuth cache or an account displayed by a client;
2. **catalog visibility** — a model listed in the client menu or model map;
3. **request route and billing** — the endpoint, credentials, protocol adapter, and upstream that actually receive the request.

Never infer layer 3 from layer 1 or 2. “Official account remains visible”, “model appears in `/model`”, and “OpenAI-compatible” are not proof of official subscription billing, endpoint success, or complete tool/streaming/image compatibility. State the route’s required endpoint, credential, protocol, and known capability limit; distinguish native protocol passthrough from conversion adapters.

**Completion criterion:** every routing advantage names its layer; catalog, OAuth, protocol conversion, local inference source, and billing claims each retain their own first-party evidence.

Python script approach remains as a fallback for very large content.

**If `write_file` is blocked for unsupported scripting reasons**:
- Switch to direct file tools instead of trying to force Python execution.
- Keep the content in a single write, then validate with the repo build.

**Template source priority**:
1. Read `theme/themes.json`, then use `theme/<selected_theme>.html` as the complete template skeleton; never use a default theme or copy a theme HTML into a stylesheet link
2. No worktree template lookup needed

**Python script fallback** (only needed for ~15KB+ content where direct write_file is risky):
```python
import re
path = 'docs/<slug>.html'
with open(path, 'r', encoding='utf-8') as f:
    html = f.read()

html = html.replace('<title>infocard-darkblue-style 元素演示</title>',
                   '<title>Your Title Here</title>')

new_body = r'''...your HTML content...'''
html_new = re.sub(r'<main class="page">.*?</html>', new_body, html, flags=re.DOTALL)
with open(path, 'w', encoding='utf-8') as f:
    f.write(html_new)
print("OK:", len(html_new), "bytes")
```

Execute: `python3 /tmp/gen_<slug>.py`

### Step 3 · Write meta.yaml

**⚠️ 子智能体 meta.yaml 三大常见错误(需人工复核)**:
1. `---` 首尾符:子智能体有时在文件首尾各写一个 `---`,导致 YAML 被解析为多文档,报 `expected a single document in the stream, but found more` 而使 build 失败。**修复:删除全部 `---`,meta.yaml 必须只有一个文档**。
2. 缺 `style` 字段:子智能体有时遗漏 `style` 字段。**修复:补全 `style: <theme>`**。
3. `date` 填为内容原始日期而非发布时间:用户要求 `date` 为**发布时间**,不是内容来源的原始日期。**修复:统一填当天发布日**。

**Twitter/X 来源的额外字段**:
- `x_status_id`:X 帖子的数字 ID(如 `2081932972559855907`)
- `author`:格式为 `"yibie (@yibie)"`(显示名 + 括号内用户名)

**格式规则**:
- 文件首尾都不要有 `---`(js-yaml 会把 `---` 当成多文档分隔符,即使只有一处也会引发问题)
- No em-dash `—` in title/desc(导致 YAML 解析歧义)
- `path` field:双引号字符串 `"docs/<slug>.html"`
- `date` and `updated`:`"YYYY-MM-DD HH:MM:SS"` 格式(空格分隔,无 T,无时区后缀);填**实际运行时 UTC 时间**:`date -u +"%Y-%m-%d %H:%M:%S"`
- `category`:用户指定值,非固定 "knowledge";常见如 `工具推荐`、`knowledge` 等
- `slug`:默认无 YYYYMMDD 前缀(如 `agent-routing`);用户指定加前缀则从用户
- Required: slug / title / desc / date / updated / tags / category / author / source / source_url / style / path

Correct template:
```yaml
slug: agent-routing
title: "agent-routing v1.3.0:Subagent 模型智能路由"
desc: "给 subagent 分配「最合适的模型」通常靠经验和运气。agent-routing 把这个决策变成可重复的 calibrate 流程——Haiku/Sonnet/Opus 三级路由 + per-agent effort level。"
date: "2026-07-28 23:10:42"
updated: "2026-07-28 23:10:42"
tags: ["agent-skills", "routing", "model-selection", "Claude-Code"]
category: 工具推荐
source: Twitter
source_url: "https://x.com/i/status/2081932972559855907"
author: "yibie (@yibie)"
x_status_id: "2081932972559855907"
style: infocard-hardblue-style
path: "docs/20260729-agent-routing.html"
```

### subagent 产出后主线程必做:meta.yaml 字段完整性检查

subagent 产出的 meta.yaml 经常缺 `category` 字段(build-site.js 增量构建会报 `missing fields category` 而中断)。主线程接管后、build 前必须检查并补全。

```bash
# 主线程接管后立即检查
grep "^category:" docs/<slug>.html.meta.yaml || echo "MISSING category!"
```

缺失时用 patch 补上(常见值:`工具介绍`):
```
旧: desc: "..."
新: desc: "..."
category: 工具介绍
date:
```

**实测案例**:GordenPPTSkill 卡(2026-10-11),subagent 产 category 缺失 → 主线程 patch 补上 → build-site 成功 → commit `39ad250c`

---

### Build 后 meta.yaml 被 sync-build-timestamps 覆盖

`sync-build-timestamps` 在 build 时用 Shanghai 时区时间覆盖 `updated` 字段。**不要依赖 meta.yaml 中的 `updated` 作为实际发布时间**,以 build 输出日志中的 `build_ts` 为准。

典型 build 输出:
```
[build-site] build_ts=2026-07-29 07:11:50 Asia/Shanghai
[sync-build-timestamps] NEW docs/20260729-agent-routing.html.meta.yaml | date=updated=2026-07-29 07:11:50
```

### Commit 时不要 push

Commit + push in the same session after verifying the build succeeds.
```bash
git add docs/20260729-agent-routing.html docs/20260729-agent-routing.html.meta.yaml
git commit -m "feat: publish agent-routing subagent model routing"
# git push ...   # 跳过,不要 push
```

### 仓库 URL 待核实的标注方式

当用户提供仓库 URL 但标注"需核实"时:
1. 在 `source_url` 字段保留原始 URL
2. 在 HTML 正文和 desc 中加 `(需核实)` 标注
3. 不要自行搜索核实(除非用户明确要求)

### Step 4 · Build

```bash
npm run build
```

Expected: `wrote _index.yaml and injected index.html (N cards)`

If build fails with "single document in stream, but found more": check meta.yaml for trailing `---`, em-dash in fields, or multi-doc YAML.

**Skip-build shortcut (new card + index already correct):**

When ALL of the following are true, you can skip `npm run build` entirely:
- New HTML and meta.yaml are freshly written
- `_index.yaml` and `index.html` were already regenerated by a prior build (e.g. during draft validation)
- No other cards were modified in this session

Then go directly to Step 5 commit — the index is already correct.

**Build-vs-commit decision tree:**
1. Modified existing card HTML/meta → must run `npm run build` (regenerates index)
2. Brand new card, index stale → run `npm run build` then Step 5
3. Brand new card, index already correct from prior run → skip build, go straight to Step 5

### Commit + Push

```bash
git add docs/<slug>.html docs/<slug>.html.meta.yaml _index.yaml index.html
git commit -m "feat: publish <title>"
git push origin main
```

### Step 6 · Verify + Cleanup

Wait ~80s for GitHub Pages deploy, then:
```bash
curl -sI --max-time 15 "https://ccwq.github.io/infocard-pub/docs/<slug>.html"
# Expect: HTTP/2 200
```

## Known pitfalls

## Content gate: every card/item that looks clickable MUST have a real href

**Symptom** (verified 2026-08-18 on `deepseek-harness-learning`): A resource-collection card displays 4 section cards + 12 sub-resource items, all visually styled as clickable cards, but zero have actual `<a href>` attributes. Total page links = 1 (Twitter attribution only). All section cards were `<a class="mini-card">` without href; all sub-resource items were plain `<div>`, not `<a>`.

**Root cause**: Authoring created dead `<a>` tags (no href attribute) and non-linked `<div>` elements that visually resemble links. Visual screenshot review cannot detect this — it requires structural HTML inspection.

**Detection (run before build/commit — `web_extract` strips href attributes, do not use it):**
```python
import subprocess, re
r = subprocess.run(['curl','-s','-L','--max-time','30',
    'https://ccwq.github.io/infocard-pub/docs/<slug>.html'],
    capture_output=True, text=True)
html = r.stdout

hrefs = re.findall(r'<a[^>]+href=["\']([^"\']+)["\']', html)
hrefs_external = [h for h in hrefs if h.startswith('http')]
print(f"Total links: {len(hrefs)}, External links: {len(hrefs_external)}")
print(f"External URLs: {hrefs_external}")

# Also check for dead <a> tags (class contains 'card'/'link' but no href)
dead_a = re.findall(r'<a[^>]+class="([^"]*(?:card|link)[^"]*)"[^>]*(?<!href)>', html)
print(f"Dead <a> (class=card but no href): {len(dead_a)}")
```

**Rule**: Every visually clickable resource item must be a real `<a href="URL">`. If a resource entry has no URL target, it must NOT be visually styled as a card or link — use a plain label instead.

**Fix pattern — convert `<article>` to `<a>`:**

```html
<!-- Before: dead <article> -->
<article class="flow-item cyan">
  <div class="t">🏠 GitHub 仓库</div>
  <div class="c">DeepSeek Harness 官方源代码仓库</div>
</article>

<!-- After: real <a> -->
<a class="flow-item cyan" href="https://github.com/deepseek-ai/deepseek-harness" target="_blank" rel="noreferrer">
  <div class="t">🏠 GitHub 仓库</div>
  <div class="c">DeepSeek Harness 官方源代码仓库</div>
</a>

<!-- Before: <article class="mini-card"> in hero-visual -->
<article class="mini-card">
  <div class="mini-icon cyan" aria-hidden="true"><svg>...</svg></div>
  <div class="label">Official</div>
  <div class="value">官方仓库</div>
  <div class="desc">源代码、README、示例、架构说明</div>
</article>

<!-- After: <a class="mini-card"> -->
<a class="mini-card" href="https://github.com/deepseek-ai/deepseek-harness" target="_blank" rel="noreferrer">
  <div class="mini-icon cyan" aria-hidden="true"><svg>...</svg></div>
  <div class="label">Official</div>
  <div class="value">官方仓库</div>
  <div class="desc">源代码、README、示例、架构说明</div>
</a>
```

**URL research workflow** (before authoring the HTML):
1. Web search for the official project URL (GitHub repo, docs site)
2. Web search for each sub-resource's doc path (README, guide, changelog, examples/)
3. Verify all URLs return HTTP 200 via `curl -sI --max-time 15 "URL" | head -1`
4. Map each visual resource item → its verified URL before writing HTML

**PASS**: links >= expected_resource_count AND dead_a == 0 → proceed to build.
**FAIL**: any dead `<a>` tag or link count < expected → repair HTML before push.

**⚠️ `web_extract` strips href attributes — never use it for link verification.** Always use `curl` + regex as shown above. `web_extract` is safe for extracting readable prose content, not for structural HTML inspection.

## Critical: patch stops on duplicate match in HTML files

**Symptom**: `patch` reports "duplicate" or "found 2 matches" but `read_file` still shows the wrong content. Subsequent patch calls keep returning "duplicate" and making no progress, even with different `old_string` values.

**Root cause**: When two elements share the same wrapping structure (e.g. two `<div class="feat">` blocks with identical class/label hierarchy), `patch` finds both and blocks. After the first successful patch on one element, both lines become identical — patch now sees the other element as a new duplicate and blocks again. The tool reports "1 files modified" on the first pass, but the second pass finds two identical instances and stops.

**Concrete case from 2026-08-06** (Exa Search API card): Both `/search` panel and `/contents` panel had a `<div class="feat">` with label "认证". After patching the `/search` panel correctly, both panels' lines became identical, so the next patch for the other element reported "duplicate" and changed nothing. 15+ patch attempts were made over ~30 minutes.

**Recovery patterns (in order of reliability)**:

1. **`replace_all=true` on a fully unique substring** — only when all instances need identical replacement:
   ```python
   patch(path=html, old_string='<div class="value">WRONG</div>',
         new_string='<div class="value">CORRECT</div>', replace_all=True)
   ```

2. **Widest unique context** — include surrounding lines that only exist around the target element:
   ```python
   # Patch the whole block including unique surrounding lines
   patch(path=html,
         old_string='''  <section class="shell">
     <div class="panel">
       <div class="panel-head"><span class="panel-kicker">POST /search</span></div>
       <div class="feat"><div class="label">端点</div><div class="value">https://api.exa.ai/search</div></div>
       <div class="feat"><div class="label">认证</div><div class="value">WRONG</div></div>''',  # ← only /search has this exact URL
         new_string='''  <section class="shell">
     <div class="panel">
       <div class="panel-head"><span class="panel-kicker">POST /search</span></div>
       <div class="feat"><div class="label">端点</div><div class="value">https://api.exa.ai/search</div></div>
       <div class="feat"><div class="label">认证</div><div class="value">CORRECT</div></div>''')
   ```

3. **Line-by-line Python replacement** (most reliable for HTML with near-identical div structures):
   ```python
   with open(path, 'r', encoding='utf-8') as f:
       lines = f.readlines()
   new_lines = []
   for line in lines:
       if 'WRONG_TEXT' in line:
           new_lines.append(line.replace('WRONG_TEXT', 'CORRECT_TEXT'))
       else:
           new_lines.append(line)
   with open(path, 'w', encoding='utf-8') as f:
       f.writelines(new_lines)
   ```

4. **Byte-level replacement for invisible character differences** — when text looks identical but Python can't find it:
   ```python
   with open(path, 'rb') as f:
       content = f.read()
   # Find by hex/byte pattern
   idx = content.find(b'AUTHORIZATION_SUBSTRING')
   chunk = content[idx:idx+50]
   print(chunk.hex())  # See exact bytes
   content = content.replace(b'OLD_BYTES', b'NEW_BYTES')
   with open(path, 'wb') as f:
       f.write(content)
   ```
   Use this when text appears in `repr()` output but `in` check returns False — indicates invisible Unicode or encoding differences.

**Rule**: After any `patch` that reports "duplicate", always `read_file` the affected lines to verify actual content. A "duplicate" block means nothing was changed. Don't trust "1 files modified" without confirmation.

**Prevention**: Before patching elements that may be duplicated in an HTML file, scan first:
```bash
grep -n 'class="feat"' docs/<slug>.html | grep '认证'
# If output shows 2 lines with same context → use Python line-by-line method
```

### Git push directly to main (no PR needed)

```bash
git push origin main
```

If non-fast-forward:
```bash
git fetch origin main && git rebase origin/main
git push origin main
```

No worktree, no PR, no branch dance.

### Section number class: verify against deployed template, not the skill warning

The `theme/hardblue.html` template uses `.section-no` (96×96px grid cell with 3px border) as the numbered block inside `.section-head`. The CSS defines `.section-no` with a `display:grid; place-items:center` layout and color variants `.section-no.b` (blue) / `.section-no.k` (black). **This is the correct class — use it.**

The earlier skill warning about `.sec-no` was based on a different or outdated source. Always verify against `theme/<style>.html` directly before writing section numbers.

```css
/* ✅ ACTUAL: sec-no — used in every deployed hardblue card */
.sec-no{
  width:34px;height:34px;
  display:flex;align-items:center;justify:center;
  background:var(--black);color:#fff;
  font-size:13px;font-weight:900;
  border:1.5px solid var(--black);
  flex-shrink:0;box-shadow:var(--shadow-sm)
}

/* ❌ WRONG: section-no — does not exist in deployed HTML */
```

Always use `.sec-no` for section numbering. The hardblue SKILL.md's table entry for `section-no` is outdated.

### meta.yaml multi-doc parse failure
**Symptom**: `npm run build` throws `Error: Index build failed: expected a single document in the stream, but found more`

**Causes** (in order of frequency):
1. Trailing `---` at end of file → delete it
2. Em-dash `—` (U+2014) in title/desc → replace with `|`
3. File missing trailing newline → add `\n`
4. Very long `style` attribute value in `path` field → use simpler quoted string

**Diagnosis**: `node -e "const yaml=require('./assets/home/vendor/js-yaml.min.js'); const d=require('fs').readFileSync('docs/<slug>.html.meta.yaml','utf8'); console.log(yaml.loadAll(d).length)"` — if count > 1, fix the above.

### Python heredoc timeout
**Symptom**: `python3 - <<'PY' ...` returns exit -1 with no output, or times out.

**Cause**: Content > ~15KB hits orchestrator command gate.

**Fix**: Use `write_file` directly for HTML content up to ~20KB — this is now the preferred method. Only fall back to the Python script approach for very large content.

### execute_code blocked in background/cron context

**Symptom**: `execute_code` returns error: "Cron jobs run without a user present to approve it."

**Cause**: `execute_code` runs arbitrary Python with tool access. When the calling context is a cron profile or background delegation, it is blocked.

**Fix**: Use `terminal` with inline Python via `python3 - <<'PY' ...` or `python3 -c "..."` instead. For file operations, use `write_file` / `patch` directly.

### Parallel multi-card publish

For 2+ cards simultaneously: write each directly into `docs/`, then run one shared build.

```bash
# All cards written directly to docs/
npm run build  # regenerates _index.yaml + index.html for all
git add docs/<slug1>.html docs/<slug2>.html _index.yaml index.html
git commit -m "feat: publish <slug1> + <slug2> cards"
git push origin main
```

Pages deploys once. Verify all URLs in one loop.

### darkblue template title not replaced
The `theme/darkblue.html` template contains `<title>infocard-darkblue-style 元素演示</title>`. Always replace before the `re.sub`.

### Subagent timeout → main thread takes over (primary recovery path)

When a dispatched subagent times out (e.g. `Non-streaming API call timed out after 90s` or `API call failed after 3 retries`), **do NOT redispatch**. Take the task directly in the main thread. Use `write_file` for HTML (≤40KB, single write, no Python script needed) and `patch` for meta.yaml corrections.

**Step 1 · Diagnose what the subagent left behind**
```bash
# Check in the primary checkout docs/ (subagents often write to parent repo)
REPO_ROOT="$(git rev-parse --show-toplevel)" || exit 1
ls "$REPO_ROOT/docs/" | grep <slug>
# If meta.yaml exists but HTML is missing → partial state, fix it
# If both exist → just build + commit + push
```

**Step 2 · Handle partial state (most common: meta.yaml written, HTML missing)**
```python
# Read the existing meta.yaml to get slug/date/tags
with open('docs/<slug>.html.meta.yaml') as f:
    meta = yaml.safe_load(f)
slug = meta['slug']  # use existing slug, date, tags from subagent's meta.yaml
```

**Step 3 · Write HTML directly with write_file**
- Source the darkblue template CSS by reading `theme/darkblue.html` (lines 500–772 have the full deployed CSS)
- Write the complete HTML in one `write_file` call (≤40KB, reliable)
- Do NOT use Python heredoc or execute_code for HTML — `write_file` is faster and less error-prone
- After writing, fix meta.yaml field ordering/duplicates with `patch`

**Step 4 · Commit + push (no subagent re-dispatch)**
```bash
git add docs/<slug>.html docs/<slug>.html.meta.yaml _index.yaml index.html
git commit -m "feat: add <slug> infocard (YYYY-MM-DD)"
git push origin main
```

**Step 5 · Verify**
```python
import urllib.request, time
time.sleep(45)   # GitHub Actions + Pages deploy time
url = "https://ccwq.github.io/infocard-pub/docs/<slug>.html"
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
resp = urllib.request.urlopen(req, timeout=15)
content = resp.read().decode('utf-8', errors='replace')
checks = ['Tabbit', '上下文', 'GN06', 'Agent', '91.8']
for c in checks:
    print(f"  '{c}': {'✓' if c in content else '✗'}")
```

**Verified 2026-08-19**: Tabbit card. Subagent API timed out at 90s with `Non-streaming API call timed out after 90s`. Subagent left only `meta.yaml` (partial state). Main thread wrote complete 39KB HTML in one `write_file` call, patched meta.yaml, committed, pushed. HTTP 200 in ~45s.

**Rule**: Never redispatch a timed-out subagent. The main thread's direct tool access (write_file, patch) succeeds where subagents fail on infrastructure.

### CSS grid nesting overflow (mobile critical)

**Root cause**: A grid cell with `grid-column: span N` inside a parent grid becomes overflow when the parent grid's column count changes via `@media` query.

**Concrete case**: `agent-row` has `grid-template-columns: 1fr 1fr 1fr` (desktop 3 columns). A `model-card` has `style="grid-column:span 3"` (spans all 3). On mobile `@media(max-width:720px)`, `agent-row` becomes `grid-template-columns: 1fr 1fr` (2 columns). The `span 3` card now overflows because 3 > 2. Vision model reports "text overlap".

**Symptom**: Desktop looks fine. Mobile has severe element collision in the affected section.

**Solution — extract overflowing element as a sibling, not a child**:
```html
<!-- BROKEN: model-card is inside agent-row grid -->
<div class="agent-row">            <!-- 3 cols → 2 cols on mobile -->
  <div class="agent-card">Research Agent</div>
  <div class="agent-card">Biology Agent</div>
  <div class="agent-card">Physics Agent</div>
  <div class="agent-card" style="grid-column:span 2">ML Agent</div>
  <div class="model-card" style="grid-column:span 3">Providers...</div>
  <!--                        ^^^^^^^^^ overflow on mobile! -->
</div>

<!-- FIXED: model-row is OUTSIDE agent-row, as a sibling -->
<div class="panel-body">
  <div class="agent-row">   <!-- 3 cols → 2 cols on mobile, no overflow -->
    <div class="agent-card">Research Agent</div>
    <div class="agent-card">Biology Agent</div>
    <div class="agent-card">Physics Agent</div>
    <div class="agent-card">ML Agent</div>
  </div>
  <div class="model-row">
    <!-- flex-wrap, not grid — safe on any width -->
    <span>OpenAI</span><span>Anthropic</span><span>Google</span>...
  </div>
</div>
```

**CSS rule for extracted row**: Use `display:flex; flex-wrap:wrap; gap:6px` — never another nested grid.

**Rule**: Never put a `span` value inside a grid child that is larger than the grid's mobile column count. Always extract spanning elements as siblings.

**Mobile screenshot height**: Use `--window-size=390,2200` (not 1500) for full-length infocards. 2200 captures the complete card without cutting off the bottom.

<!-- MODIFIED 2026-08-19: 添加 light-route 强制主题门禁 -->

## Deprecated compatibility entry

此旧入口仅作兼容转发,替代入口为 `infocard-card-authoring`。调用时必须保留既有 authoring 语义与 `.docs` 边界;新任务不得默认选择本入口。

Files in this skill

  • SKILL.md44.9 KB
  • references/alacritty-session-notes.md1.3 KB
  • references/chatgpt-url-to-infocard.md2.8 KB
  • references/color-material-wechat-inline-recipe.md2.4 KB
  • references/github-api-workarounds.md3.4 KB
  • references/graph-paper-wechat-inline-recipe.md1.4 KB
  • references/image-asset-path-bug.md3 KB
  • references/social-source-attribution-matching.md3.6 KB
  • references/vscode-marketplace-card-build-notes.md2.7 KB
  • references/wechat-inline-body-compatibility.md1.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.