mcpField
D
devtu-docs-quality

devtu-docs-quality

github.com/mims-harvard测试与质量1.8k181 次安装2 天前更新
代码仓库

关于 devtu-docs-quality

面向 ToolUniverse 文档维护者的质量审查技能,指导检查技术正确性并精简重复内容。它提供分阶段的静态检查、工具调用验证和文档审计清单。

主要功能

  1. 01优先核对技术正确性
  2. 02删除重复和过时内容
  3. 03扫描无效方法调用
  4. 04验证技能中的工具调用
  5. 05检查链接、篇幅与导航

适用场景

  1. 01审查 ToolUniverse 文档变更
  2. 02排查技能示例中的无效工具调用
  3. 03清理重复或过时的设置说明
  4. 04发布前执行文档质量检查

MCP 相关

输入未说明该技能与 MCP 的关系。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mims-harvard/tooluniverse --skill devtu-docs-quality

安装后在 Claude Code 中输入/devtu-docs-quality确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

API_REFERENCE.md
DOCS_STRUCTURE.md
EXAMPLES.md
SUMMARY.md

详细介绍

文档质量保证

两项同等重要的优先事项

执行审核前,必须同时满足以下两项。两者互不取代。

1. 技术正确性——错误的代码比没有代码更糟;它会直接破坏用户的工作流。

2. 越少越好——冗余内容比没有内容更糟;它会造成困惑、削弱信任,并增加维护难度。

不确定技术准确性时:对照源代码验证(src/tooluniverse/execute_function.py)。 不确定内容是否必要时:删除它。


越少越好——核心理念

ToolUniverse 是一款严肃的科研工具。文档应以克制、精准而非篇幅来体现这一点。

“越少越好”在实践中的含义

删除,而非添加。 每次编辑都应以更少的文字、更少的页面、更少的链接收尾。领域发展迅速,过时的说明会造成切实危害。若两种方法都可行,就保留一种。

每个概念只出现一次。 如果设置说明出现在三个地方,读者看其中一处时不知道还有其他内容,维护者每次修改时还必须同步更新三处。选定一个规范位置,其他地方都添加交叉链接。

不要使用表情符号。 标题、导航项、卡片标题和项目符号中的表情符号会让内容显得“由 AI 生成”,损害用户对严肃科研工具的信任。无条件删除——“强调”也不例外。

精简,而非总结。 将 1000 行页面缩减为 30 行指引链接,胜过在 1000 行页面上添加 TL;DR 摘要框。直接精简。

篇幅预算(硬性限制):

页面类型最多行数
各平台设置页面(claude_desktop.rst、cursor.rst 等)15
首页网格区块每个网格最多 4 张卡片
“另请参阅”/“相关内容”链接列表最多 4 个链接
与 Python 指南重复的教程页面0——删除或改为指引链接
“越少越好”决策树

审核任何内容时,请问:

  1. 网站其他地方是否已有此内容? → 删除;添加指向规范位置的交叉链接。
  2. 此内容是否过时或难以保持最新? → 删除;链接到上游来源(例如 aiscientist.tools/setup.md)。
  3. 用户能否从首页点击 2 次以内找到此信息? → 如果可以,且信息已在相应位置,就删除此处的副本。
  4. 这是表情符号、装饰性标题还是填充句? → 无条件删除。
  5. 这是一个指向侧边栏中已有位置的“探索更多”卡片吗? → 删除。

Apple 风格简化规则

审核期间,对所有修改的文件应用这些规则。

标题、项目符号项、卡片标题、toctree 标题、导航栏条目或按钮文本中不得使用表情符号。

单一设置入口。 规范安装路径为:

Read https://aiscientist.tools/setup.md and set up ToolUniverse for me.

各平台页面必须且只能包含三项:(1)官方安装链接;(2)官方 MCP 设置指南链接;(3)上方的设置提示词。除此之外不得包含任何内容——不允许出现 JSON 片段、分步说明或故障排除内容。这些内容应放在设置技能中,位置为 aiscientist.tools/setup.md。

工具数量保持一致。 始终写作“1000+ 个工具”。不得使用“600+”“750+”“1200+”“10000+”或其他数字。

清理导航栏。 不得包含“API”“API Keys”或“Contribution”项。必须将 https://aiscientist.tools 作为第一项链接。

首页“探索更多”网格。 最多 4 张卡片。删除所有目标位置已可从侧边栏或“开始使用”区块访问的卡片。

不得存在失效的 :doc: 引用。 每个 :doc:some/path`` 都必须能对应到磁盘上的 .rst 或 .md 文件。绝不允许存在失效链接——立即删除或替换。

五阶段策略

按顺序运行各阶段——先运行 D(即时),然后运行 E(简化扫描),再运行 C/A/B。

阶段检查内容耗时
D 静态方法扫描错误的方法名(tu.run_batch、tu.call_tool 等)~2 s
E 简化扫描表情符号、错误的数量、失效链接、大小违规~10 s
C 实时代码执行运行时故障(错误的键、错误的返回类型)3-5 min
A 自动验证已弃用的命令、术语不一致15 min
B ToolUniverse 审核导航循环、重复的 MCP 配置、工具数量20 min

阶段 D:静态方法扫描

python3 - <<'EOF'
import re, sys
from pathlib import Path
from collections import defaultdict
DOCS = Path("docs")
EXCLUDE = {"locale", "old", "_build", "__pycache__", "tools", "archive"}
KNOWN_BAD = {
    "list_tools", "run_batch", "run_async", "execute_tool", "call_tool",
    "list_tools_by_category", "configure_api_keys", "get_tool", "get_exposed_name",
    "list_available_methods", "register_tool_from_config", "register_tool",
}
STATIC = [
    (r"\.load_tools\([^)]*(?:use_cache|cache_dir)\s*=", "load_tools() invalid kwargs"),
    (r"ToolUniverse\([^)]*timeout\s*=", "ToolUniverse(timeout=) invalid"),
    (r'"name":\s*"opentarget_', 'old lowercase "opentarget_*" tool name'),
    (r"tu\.run_batch\(", "tu.run_batch() — use tu.run(list, max_workers=N)"),
    (r'\btu\.[A-Z]\w+\s*\(', "tu.ToolName() shorthand — use tu.run({name:...})"),
]
M = re.compile(r'\b(?:tu|tooluni)\.([\w]+)\s*\(')
issues = defaultdict(list)
for f in sorted(list(DOCS.rglob("*.rst")) + list(DOCS.rglob("*.md"))):
    if any(p in f.parts for p in EXCLUDE): continue
    t = f.read_text(errors="replace")
    code = "\n".join(
        re.findall(r"\.\. code-block:: python\n((?:[ \t]+[^\n]*\n|[ \t]*\n)*)", t, re.MULTILINE) +
        re.findall(r"```python\n(.*?)```", t, re.DOTALL))
    if not code.strip(): continue
    rel = str(f.relative_to(DOCS))
    for m in M.finditer(code):
        if not m.group(1).startswith("_") and m.group(1) in KNOWN_BAD:
            issues[rel].append(f"tu.{m.group(1)}()")
    for pat, label in STATIC:
        if re.search(pat, code): issues[rel].append(label)
if issues:
    [print(f"  {f}: {i}") for f in sorted(issues) for i in sorted(set(issues[f]))]
    sys.exit(1)
else:
    print("Phase D clean")
EOF

修复或删除规则——没有例外:

  • 存在正确的替代方案 → 立即修复
  • 功能是自动的/内部的 → 删除调用,必要时添加文字注释
  • 功能不存在 → 彻底删除代码块或章节

最常见的修复:

# wrong → correct
tu.run_batch(list)              → tu.run(list, max_workers=4)
tu.run_async(query)             → await tu.run(query)
tu.call_tool('X', {...})        → tu.run({"name": "X", "arguments": {...}})
tu.execute_tool('X', {...})     → tu.run({"name": "X", "arguments": {...}})
tu.list_tools()                 → tu.list_built_in_tools(mode='list_name')
tu.get_tool('X')                → tu.get_tool_by_name('X')
tu.register_tool(instance)      → tu.register_custom_tool(tool_instance=instance)
tu.register_tool_from_config(c) → tu.register_custom_tool(tool_config=c)
tu.configure_api_keys({})       → REMOVE — use env vars
tu.get_exposed_name(name)       → REMOVE — shortening is automatic
ToolUniverse(timeout=30)        → ToolUniverse()  # no timeout kwarg
load_tools(use_cache=True)      → load_tools()  # no use_cache kwarg
tu.ToolName(key=val)            → tu.run({"name": "ToolName", "arguments": {"key": val}})
opentarget_get_*                → OpenTargets_get_*  (capital O and T)

特殊情况: 当展示有意写错的代码作为错误示例时,将 .. code-block:: python 改为 .. code-block:: text。


阶段 E:简化扫描

python3 - <<'EOF'
import re, sys
from pathlib import Path

DOCS = Path("docs")
EXCLUDE = {"_build", "__pycache__", "locale"}
issues = []

EMOJI_RE = re.compile(
    r'[\U0001F300-\U0001F9FF\U00002600-\U000027BF\U0001FA00-\U0001FA9F'
    r'\U0001F004\U0001F0CF\U00002702-\U000027B0]'
)

def in_code_block(text, pos):
    """Return True if pos is inside a code block."""
    before = text[:pos]
    # RST code blocks
    rst_starts = [m.end() for m in re.finditer(r'\.\. code-block::[^\n]*\n', before)]
    for start in reversed(rst_starts):
        block = text[start:]
        end_match = re.search(r'\n(?![ \t]|\n)', block)
        block_end = start + (end_match.start() if end_match else len(block))
        if start <= pos <= block_end:
            return True
    # Markdown fenced blocks
    fences = list(re.finditer(r'```', before))
    return len(fences) % 2 == 1

for f in sorted(DOCS.rglob("*.rst")) + sorted(DOCS.rglob("*.md")):
    if any(p in f.parts for p in EXCLUDE):
        continue
    text = f.read_text(errors="replace")
    rel = str(f.relative_to(DOCS))

    # Check emojis in prose
    for m in EMOJI_RE.finditer(text):
        if not in_code_block(text, m.start()):
            line = text[:m.start()].count('\n') + 1
            issues.append(f"EMOJI  {rel}:{line}  {repr(m.group())}")

    # Check wrong tool counts
    for bad in ["600+", "750+", "1200+", "10000+"]:
        if bad in text and "tools" in text[text.find(bad):text.find(bad)+20]:
            issues.append(f"COUNT  {rel}  contains '{bad} tools' (use '1000+')")

    # Check broken :doc: references
    for m in re.finditer(r':doc:`([^`]+)`', text):
        ref = m.group(1).lstrip('/')
        # Strip any display text (format: display <path>)
        if ' <' in ref:
            ref = re.search(r'<([^>]+)>', ref).group(1)
        base = DOCS / Path(ref.replace('/', '/'))
        candidates = [base.with_suffix('.rst'), base.with_suffix('.md'),
                      base / 'index.rst', base / 'index.md']
        if not any(c.exists() for c in candidates):
            line = text[:m.start()].count('\n') + 1
            issues.append(f"LINK   {rel}:{line}  broken :doc:`{ref}`")

# Check navbar in conf.py
conf = Path("docs/conf.py").read_text()
for bad_nav in ['"API"', '"API Keys"', '"Contribution"']:
    if bad_nav in conf:
        issues.append(f"NAV    conf.py  navbar contains {bad_nav}")
if "aiscientist.tools" not in conf:
    issues.append("NAV    conf.py  navbar missing aiscientist.tools link")

# Check per-platform page line counts
platform_dir = DOCS / "guide" / "building_ai_scientists"
for rst in platform_dir.glob("*.rst"):
    if rst.name in ("index.rst", "mcp_support.rst", "compact_mode.rst",
                    "mcpb_introduction.rst", "mcp_name_shortening.rst"):
        continue
    lines = rst.read_text().splitlines()
    if len(lines) > 15:
        issues.append(f"SIZE   guide/building_ai_scientists/{rst.name}  {len(lines)} lines (max 15)")

if issues:
    for i in sorted(issues):
        print(i)
    sys.exit(1)
else:
    print("Phase E clean")
EOF

阶段 E 报告问题时,按照上文的“少即是多”决策树处理。继续阶段 C 之前,先修复所有问题。


阶段 C:工具调用检查

python scripts/check_skill_tool_calls.py

从 src/tooluniverse/data/*.json 读取工具架构,并检查技能 Markdown 中每个 tools.X(...)、tu run X '{...}' 和 {"name": "X", "arguments": {...}} 调用:工具未声明的参数(ToolUniverse 会拒绝这些参数,或者在它们与有效参数混用时静默丢弃)、缺少的必需参数,以及不存在的工具名。此检查是静态的:不会实例化任何对象,而且必须始终如此,因为某些工具类在构造时会加载数 GB 的数据集(对大量工具进行实时运行时,数据量曾超过 100 GB)。在有意展示错误用法的示例中,在调用所在行或其正上方标注 WRONG、❌ 或 # noqa: skill-call。只有在即将记录返回结构时,才逐个实时运行工具进行确认。

常见故障:

错误原因修复方法
KeyError: 'parameters'tool_specification() 未使用 format="openai"添加 format="openai"
OpenTargets 结果出现 KeyError: slice(...)对字典进行切片使用 result['data']['disease']['associatedTargets']['rows'][:N]
TypeError: load_tools() got unexpected kwarguse_cache 或 cache_dir移除该关键字参数

阶段 A:自动验证

python scripts/validate_documentation.py
# scripts/validate_documentation.py checks:
DEPRECATED_PATTERNS = [
    (r"python -m tooluniverse\.server", "tooluniverse-server"),
    (r"600\+?\s+tools", "1000+ tools"),
    (r"750\+?\s+tools", "1000+ tools"),
]

该检查会扫描 docs/、README.md 和 skills/(跳过 docs/dev_docs/ 和本技能,因为其中的示例会有意引用已弃用文本),打印每处匹配的 file:line,若存在任何匹配项则以状态码 1 退出。在同一行添加 noqa: docs-validate,以标记有意提及的内容。


阶段 B:ToolUniverse 专项审核

循环导航——手动追踪 index.rst → quickstart → getting_started;不允许出现循环。

工具数量——所有位置统一使用“1000+ tools”。运行:rg "[0-9]+\+?\s+(tools|integrations)" docs/ --no-filename | sort -u

自动生成的标题——docs/tools/*_tools.rst 和 docs/api/*.rst 必须以 .. AUTO-GENERATED 开头。

CLI 文档——pyproject.toml 中 [project.scripts] 下的每个条目都必须出现在 docs/reference/cli_tools.rst 中。

环境变量——src/ 中的每个 os.getenv("TOOLUNIVERSE_*") 都必须出现在 docs/reference/environment_variables.rst 中。


RST 代码块提取器(重要)

对于 RST 代码块,始终使用 re.MULTILINE(不要使用 re.DOTALL),以免合并相邻的代码块:

# correct
re.findall(r"\.\. code-block:: python\n((?:[ \t]+[^\n]*\n|[ \t]*\n)*)", text, re.MULTILINE)

# wrong — merges adjacent blocks
re.findall(r"\.\..*?code-block.*?python\n((?:[ \t]+.*\n|\n)*)", text, re.DOTALL)

验证清单

审计完成前,所有项目都必须通过。部分通过不可接受。

技术正确性:

  • Phase D 扫描以 0 状态码退出——没有无效的方法调用
  • python scripts/check_skill_tool_calls.py 以 0 状态码退出——技能代码块中没有未知工具或参数
  • 没有未设置 format="openai" 的 spec['parameters']
  • 自动验证通过(HIGH 级问题为 0)
  • 已记录 pyproject.toml 中的所有 CLI
  • 没有循环导航

少即是多(Apple 风格):

  • Phase E 扫描以 0 状态码退出——没有表情符号、错误的数量、失效链接或超大页面
  • 全站统一使用“1000+ tools”(不得使用 600+、750+、10000+ 等其他表述)
  • 每个平台的设置页面仅包含安装链接、MCP 指南链接和设置提示词(≤15 行)
  • 网站各处没有重复的设置说明
  • 导航栏:没有“API”“API Keys”或“Contribution”;包含 aiscientist.tools 链接
  • 首页“Explore More”网格:卡片 ≤4 个
  • 同一概念或章节不会在多个位置逐字重复出现
  • “See Also”/“Related”章节的链接不超过 4 个

如果任何项目未通过:停止,修复问题,重新运行相关阶段,并确认通过。当前项目未通过前,不要继续处理下一项。


参考文件

  • API_REFERENCE.md — 有效的方法签名、错误方法表和正确模式
  • DOCS_STRUCTURE.md — 所有文档文件的逐文件审计状态
  • scripts/check_skill_tool_calls.py — Phase C 静态工具调用检查器

评论

支持 Markdown