devtu-docs-quality について
ToolUniverse の保守担当者向けに、技術的な正確さと文書の簡素化を点検する QA スキルです。段階的な静的チェック、スキル内のツール呼び出し検証、監査チェックリストを備えています。
主な機能
- 01技術的正確性を優先
- 02重複や古い内容を削除
- 03無効なメソッド呼び出しを検出
- 04スキル内のツール呼び出しを検証
- 05リンク、分量、ナビゲーションを確認
活用シーン
- 01ToolUniverse の文書変更をレビュー
- 02スキル例の無効なツール呼び出しを発見
- 03重複または古いセットアップ説明を整理
- 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.mdDOCS_STRUCTURE.mdEXAMPLES.mdSUMMARY.md詳細
ドキュメントの品質保証
2 つの同等に重要な優先事項
監査を行う前に、以下の両方を満たす必要があります。どちらか一方がもう一方に優先することはありません。
1. 技術的な正確さ — 誤ったコードは、コードがないことよりも悪く、ユーザーのワークフローを実際に壊します。
2. 少ないほどよい — 冗長なコンテンツは、コンテンツがないことよりも悪く、混乱を招き、信頼を損ない、メンテナンスを難しくします。
技術的な正確さに疑問がある場合:ソースを確認してください(
src/tooluniverse/execute_function.py)。 コンテンツが必要かどうかに疑問がある場合:削除してください。
少ないほどよい — 基本理念
ToolUniverse は本格的な研究ツールです。ドキュメントにも、量ではなく抑制と正確さを反映させる必要があります。
実践における「少ないほどよい」の意味
追加するのではなく、削除します。 編集のたびに、語数、ページ数、リンク数を減らしてください。分野の変化は速く、古い手順はユーザーに実害を及ぼします。どちらの方法も有効なら、片方だけ残してください。
各コンセプトは一度だけ記載します。 セットアップ手順が 3 か所に記載されていると、いずれか 1 か所を読んだユーザーはほかにも記載があることに気づけず、メンテナーは変更のたびに 3 か所すべてを更新しなければなりません。正式な記載場所を 1 か所に決め、ほかの場所からはすべて相互リンクしてください。
絵文字は使いません。 見出し、ナビゲーション項目、カードタイトル、箇条書きの絵文字は「AI 生成」を連想させ、本格的な科学ツールへの信頼を損ないます。強調目的の例外もなく、必ず削除してください。
要約するのではなく、短くします。 1000 行のページを TL;DR ボックスで要約するより、1000 行のページを 30 行の案内に短縮するほうが望ましいです。思い切って整理してください。
サイズの上限(厳守):
| ページの種類 | 最大行数 |
|---|---|
プラットフォーム別セットアップページ(claude_desktop.rst、cursor.rst など) | 15 |
| ホームページのグリッドセクション | グリッドごとに最大 4 枚のカード |
| 「関連項目」/「関連情報」のリンクリスト | 最大 4 件のリンク |
| Python ガイドと重複するチュートリアルページ | 0 — 削除するか、案内のみにする |
「少ないほどよい」の判断フロー
コンテンツを見直す際は、次の点を確認してください。
- このコンテンツはサイト内の別の場所にもありますか? → 削除し、正式な記載場所への相互リンクを追加します。
- このコンテンツは古くなっている、または最新状態の維持が難しいですか? → 削除し、上流の情報源(例:
aiscientist.tools/setup.md)にリンクします。 - ユーザーはホームページから 2 回のクリックでこの情報にたどり着けますか? → たどり着けて情報がすでに掲載されている場合、このコピーを削除します。
- これは絵文字、装飾的な見出し、または埋め草の文章ですか? → 必ず削除します。
- サイドバーですでにアクセスできる場所への「詳細を見る」カードですか? → 削除します。
Apple スタイルの簡素化ルール
監査中に編集するすべてのファイルに、これらのルールを適用してください。
見出し、箇条書き、カードタイトル、toctree のキャプション、ナビゲーションバーの項目、ボタンのテキストに絵文字を使わないでください。
セットアップの入口は 1 か所にします。 正式なインストール手順は次のとおりです。
Read https://aiscientist.tools/setup.md and set up ToolUniverse for me.
プラットフォーム別のページに記載するのは、次の 3 点だけです。(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 ファイルに解決される必要があります。壊れたリンクは決して許容されません。すぐに削除するか、置き換えてください。
5 フェーズの戦略
フェーズは順番に実行します。まず D(即時)、次に E(簡略化スキャン)、その後に C/A/B を実行します。
| フェーズ | 検出する内容 | 所要時間 |
|---|---|---|
| D 静的メソッドスキャン | メソッド名の誤り(tu.run_batch、tu.call_tool など) | 約 2 秒 |
| E 簡略化スキャン | 絵文字、件数の誤り、リンク切れ、サイズ違反 | 約 10 秒 |
| C ライブコード実行 | 実行時エラー(キーの誤り、戻り値の型の誤り) | 3~5 分 |
| A 自動検証 | 非推奨コマンド、用語の不統一 | 15 分 |
| B ToolUniverse の監査 | ナビゲーションの循環、MCP 設定の重複、ツール数 | 20 分 |
フェーズ 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 のいずれかを付けます。記載しようとしている戻り値の形式を確認する場合に限り、個々のツールを一度に 1 つずつライブ実行します。
よくある失敗:
| エラー | 原因 | 修正 |
|---|---|---|
KeyError: 'parameters' | format="openai" を指定せずに tool_specification() を呼び出している | format="openai" を追加する |
OpenTargets の結果で KeyError: slice(...) | dict をスライスしている | result['data']['disease']['associatedTargets']['rows'][:N] を使用する |
TypeError: load_tools() got unexpected kwarg | use_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.DOTALL ではなく re.MULTILINE を使用します。
# 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 以下
- 概念やセクションが、複数の場所に同じ文言で掲載されていない
- 4 件を超えるリンクを含む「See Also」/「Related」セクションがない
いずれかの項目に不合格の場合は、作業を中断して修正し、該当するフェーズを再実行して合格を確認してください。現在の項目に問題がなくなるまで、次の項目に進まないでください。
参照ファイル
- API_REFERENCE.md — 有効なメソッドシグネチャ、誤ったメソッドの一覧、正しいパターン
- DOCS_STRUCTURE.md — すべてのドキュメントファイルのファイルごとの監査状況
scripts/check_skill_tool_calls.py— Phase C の静的ツール呼び出しチェッカー