diff --git a/.gitignore b/.gitignore index 7784a49..7da01be 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ dist/* !dist/openclaw/ build/ +# Internal dev docs +docs/ + # IDE .vscode/ .idea/ diff --git a/docs/superpowers/plans/2026-03-30-anti-ai-diagnose.md b/docs/superpowers/plans/2026-03-30-anti-ai-diagnose.md deleted file mode 100644 index 6e70c87..0000000 --- a/docs/superpowers/plans/2026-03-30-anti-ai-diagnose.md +++ /dev/null @@ -1,500 +0,0 @@ -# Anti-AI Diagnostic Command Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add a `scripts/diagnose.py` diagnostic command and SKILL.md auxiliary function so users can check which anti-AI measures are active. - -**Architecture:** A standalone Python script (`scripts/diagnose.py`) performs 5 groups of programmatic checks and outputs text or JSON. SKILL.md gets a new auxiliary trigger that calls the script and layers on LLM cross-analysis. - -**Tech Stack:** Python 3.11+, PyYAML (already a dependency), argparse, json, importlib - -**Spec:** `docs/superpowers/specs/2026-03-30-anti-ai-diagnose-design.md` - ---- - -## File Map - -| File | Action | Responsibility | -|------|--------|---------------| -| `scripts/diagnose.py` | Create | All 5 check groups, scoring, text/JSON output | -| `SKILL.md` | Modify | Add auxiliary function entry + Step 8c row | -| `README.md` | Modify | Add diagnose command to CLI usage section | - ---- - -### Task 1: Create `scripts/diagnose.py` — check infrastructure and data model - -**Files:** -- Create: `scripts/diagnose.py` - -- [ ] **Step 1: Create the script with argument parsing and constants** - -```python -#!/usr/bin/env python3 -""" -Diagnose which anti-AI measures are active in this WeWrite installation. - -Checks: Python deps, config.yaml, style.yaml, enhancement files, dimension variance. -Outputs a human-readable report or structured JSON. - -Usage: - python3 scripts/diagnose.py # text report - python3 scripts/diagnose.py --json # JSON for agent consumption -""" - -import argparse -import importlib -import json -import sys -from pathlib import Path - -import yaml - -SKILL_ROOT = Path(__file__).resolve().parent.parent - -# Modules to check (import_name, package_name_for_pip) -REQUIRED_MODULES = [ - ("markdown", "markdown"), - ("bs4", "beautifulsoup4"), - ("cssutils", "cssutils"), - ("requests", "requests"), - ("yaml", "pyyaml"), - ("pygments", "Pygments"), - ("PIL", "Pillow"), -] - -# Anti-AI weight per check (0 = no anti-AI impact, higher = more important) -WEIGHTS = { - "style_file": 3, - "writing_persona": 3, - "persona_file": 2, - "writing_config": 1, - "playbook": 2, - "history_articles": 1, - "dimension_variance": 1, - # These have 0 weight (no anti-AI impact) - "python_packages": 0, - "config_file": 0, - "wechat_credentials": 0, - "image_api_key": 0, -} - -MAX_ANTI_AI_SCORE = sum(v for v in WEIGHTS.values() if v > 0) # 13 - - -def make_check(group, name, status, detail=None, impact=None): - """Create a check result dict.""" - c = {"group": group, "name": name, "status": status} - if detail: - c["detail"] = detail - if impact: - c["impact"] = impact - return c -``` - -- [ ] **Step 2: Implement Group 1 — dependency checks** - -Add below `make_check`: - -```python -def check_dependencies(): - """Group 1: Check Python package imports.""" - missing = [] - for mod_name, pip_name in REQUIRED_MODULES: - try: - importlib.import_module(mod_name) - except ImportError: - missing.append(pip_name) - - if not missing: - return [make_check("dependencies", "python_packages", "pass", "all installed")] - return [make_check( - "dependencies", "python_packages", "fail", - f"missing: {', '.join(missing)}. Run: pip install {' '.join(missing)}", - )] -``` - -- [ ] **Step 3: Implement Group 2 — config.yaml checks** - -```python -def check_config(): - """Group 2: Check config.yaml and its fields.""" - checks = [] - config_path = SKILL_ROOT / "config.yaml" - - if not config_path.exists(): - checks.append(make_check( - "config", "config_file", "warn", - "not found → publish and image generation disabled", - impact="skip_publish,skip_image_gen", - )) - # Can't check fields if file missing - checks.append(make_check("config", "wechat_credentials", "warn", "no config.yaml", impact="skip_publish")) - checks.append(make_check("config", "image_api_key", "warn", "no config.yaml", impact="skip_image_gen")) - return checks - - checks.append(make_check("config", "config_file", "pass", "found")) - - with open(config_path, "r", encoding="utf-8") as f: - cfg = yaml.safe_load(f) or {} - - # WeChat credentials - wechat = cfg.get("wechat", {}) - if wechat.get("appid") and wechat.get("secret"): - checks.append(make_check("config", "wechat_credentials", "pass", "configured")) - else: - checks.append(make_check("config", "wechat_credentials", "warn", "missing appid/secret", impact="skip_publish")) - - # Image API key - image = cfg.get("image", {}) - if image.get("api_key"): - checks.append(make_check("config", "image_api_key", "pass", "configured")) - else: - checks.append(make_check("config", "image_api_key", "warn", "missing → image generation will be skipped", impact="skip_image_gen")) - - return checks -``` - -- [ ] **Step 4: Implement Group 3 — style.yaml checks** - -```python -def check_style(): - """Group 3: Check style.yaml and persona configuration.""" - checks = [] - style_path = SKILL_ROOT / "style.yaml" - - if not style_path.exists(): - checks.append(make_check("style", "style_file", "fail", "not found → run onboard first")) - return checks - - checks.append(make_check("style", "style_file", "pass", "found")) - - with open(style_path, "r", encoding="utf-8") as f: - style = yaml.safe_load(f) or {} - - # writing_persona field - persona_name = style.get("writing_persona") - if persona_name: - checks.append(make_check("style", "writing_persona", "pass", persona_name)) - else: - persona_name = "midnight-friend" - checks.append(make_check("style", "writing_persona", "warn", "not set → defaults to midnight-friend")) - - # Persona file exists - persona_path = SKILL_ROOT / "personas" / f"{persona_name}.yaml" - if persona_path.exists(): - checks.append(make_check("style", "persona_file", "pass", str(persona_path.relative_to(SKILL_ROOT)))) - else: - checks.append(make_check("style", "persona_file", "fail", f"{persona_name}.yaml not found in personas/")) - - return checks -``` - -- [ ] **Step 5: Implement Group 4 — enhancement files** - -```python -def check_enhancements(): - """Group 4: Check writing-config, playbook, history.""" - checks = [] - - # writing-config.yaml - if (SKILL_ROOT / "writing-config.yaml").exists(): - checks.append(make_check("enhancement", "writing_config", "pass", "found")) - else: - checks.append(make_check( - "enhancement", "writing_config", "warn", - "not found → using defaults (run optimize_loop.py to tune)", - )) - - # playbook.md - if (SKILL_ROOT / "playbook.md").exists(): - checks.append(make_check("enhancement", "playbook", "pass", "found")) - else: - checks.append(make_check( - "enhancement", "playbook", "warn", - 'not found → no learned style (say "学习我的修改" after editing)', - )) - - # history.yaml - history_path = SKILL_ROOT / "history.yaml" - if history_path.exists(): - with open(history_path, "r", encoding="utf-8") as f: - data = yaml.safe_load(f) - articles = data if isinstance(data, list) else (data or {}).get("articles", []) - if articles: - checks.append(make_check("enhancement", "history_articles", "pass", f"{len(articles)} articles")) - else: - checks.append(make_check("enhancement", "history_articles", "warn", "file exists but empty")) - else: - checks.append(make_check("enhancement", "history_articles", "warn", "not found → no dedup, no dimension tracking")) - - return checks -``` - -- [ ] **Step 6: Implement Group 5 — dimension variance** - -```python -def check_dimensions(): - """Group 5: Check dimension diversity across recent articles.""" - history_path = SKILL_ROOT / "history.yaml" - if not history_path.exists(): - return [make_check("dimensions", "dimension_variance", "skip", "no history.yaml")] - - with open(history_path, "r", encoding="utf-8") as f: - data = yaml.safe_load(f) - - articles = data if isinstance(data, list) else (data or {}).get("articles", []) - # Get last 3 articles that have dimensions - recent = [a for a in articles if a.get("dimensions")][-3:] - - if len(recent) < 3: - return [make_check("dimensions", "dimension_variance", "skip", f"only {len(recent)} articles with dimensions (need 3)")] - - # Compare dimension sets — stringify and check uniqueness - dim_sets = [tuple(sorted(a["dimensions"])) for a in recent] - if len(set(dim_sets)) == len(dim_sets): - return [make_check("dimensions", "dimension_variance", "pass", "last 3 articles have distinct dimensions")] - - return [make_check("dimensions", "dimension_variance", "warn", "dimension overlap in recent articles → cross-article fingerprint risk")] -``` - -- [ ] **Step 7: Implement scoring, recommendations, and output formatting** - -```python -def compute_summary(checks): - """Compute pass/warn/fail counts, anti-AI score, and recommendations.""" - passed = sum(1 for c in checks if c["status"] == "pass") - warnings = sum(1 for c in checks if c["status"] == "warn") - failures = sum(1 for c in checks if c["status"] == "fail") - - score = sum(WEIGHTS.get(c["name"], 0) for c in checks if c["status"] == "pass") - pct = score / MAX_ANTI_AI_SCORE if MAX_ANTI_AI_SCORE else 0 - if pct >= 0.76: - level = "HIGH" - elif pct >= 0.41: - level = "MODERATE" - else: - level = "LOW" - - # Build recommendations ordered by weight (highest first) - recs = [] - non_pass = [c for c in checks if c["status"] in ("warn", "fail") and WEIGHTS.get(c["name"], 0) > 0] - non_pass.sort(key=lambda c: WEIGHTS.get(c["name"], 0), reverse=True) - for c in non_pass: - name = c["name"] - if name == "style_file": - recs.append('Run the skill once to trigger onboard, or copy style.example.yaml to style.yaml') - elif name == "writing_persona": - recs.append('Add writing_persona: "midnight-friend" to style.yaml (best anti-AI detection rate)') - elif name == "persona_file": - recs.append(f'Persona file missing — check personas/ directory') - elif name == "playbook": - recs.append('Edit a generated article, then say "学习我的修改" to build playbook.md') - elif name == "writing_config": - recs.append('Run: python3 scripts/optimize_loop.py --topic "your topic" --iterations 10') - elif name == "history_articles": - recs.append("Generate your first article to start building history") - elif name == "dimension_variance": - recs.append("Recent articles reuse same dimensions — the pipeline will auto-fix on next run") - - return { - "passed": passed, - "warnings": warnings, - "failures": failures, - "anti_ai_score": score, - "anti_ai_max": MAX_ANTI_AI_SCORE, - "anti_ai_level": level, - }, recs - - -def file_status_map(checks): - """Build a quick file-existence map for agent use.""" - style_path = SKILL_ROOT / "style.yaml" - persona_name = "midnight-friend" - if style_path.exists(): - with open(style_path, "r", encoding="utf-8") as f: - s = yaml.safe_load(f) or {} - persona_name = s.get("writing_persona", "midnight-friend") - - return { - "config_yaml": (SKILL_ROOT / "config.yaml").exists(), - "style_yaml": style_path.exists(), - "writing_config_yaml": (SKILL_ROOT / "writing-config.yaml").exists(), - "playbook_md": (SKILL_ROOT / "playbook.md").exists(), - "history_yaml": (SKILL_ROOT / "history.yaml").exists(), - "persona_file": f"personas/{persona_name}.yaml", - } - - -def format_text(checks, summary, recs): - """Format human-readable text report.""" - lines = ["WeWrite Anti-AI Diagnostic", "=" * 26, ""] - - current_group = None - group_labels = { - "dependencies": "Dependencies", - "config": "Config", - "style": "Style", - "enhancement": "Enhancement", - "dimensions": "Dimension Variance", - } - for c in checks: - if c["group"] != current_group: - current_group = c["group"] - lines.append(group_labels.get(current_group, current_group)) - tag = c["status"].upper() - label = c["name"].replace("_", " ").title() - detail = f": {c['detail']}" if c.get("detail") else "" - lines.append(f" [{tag:4s}] {label}{detail}") - lines.append("") - - p, w, f_ = summary["passed"], summary["warnings"], summary["failures"] - lines.append(f"Summary: {p} passed, {w} warnings, {f_} failures") - - score = summary["anti_ai_score"] - mx = summary["anti_ai_max"] - filled = round(score / mx * 12) if mx else 0 - bar = "\u2588" * filled + "\u2591" * (12 - filled) - lines.append(f"Anti-AI level: {bar} {summary['anti_ai_level']} ({score}/{mx})") - - if recs: - lines.append("") - lines.append("Top recommendations:") - for i, r in enumerate(recs, 1): - lines.append(f" {i}. {r}") - - return "\n".join(lines) - - -def format_json(checks, summary, recs): - """Format JSON output.""" - return json.dumps({ - "checks": checks, - "summary": summary, - "recommendations": recs, - "files": file_status_map(checks), - }, ensure_ascii=False, indent=2) -``` - -- [ ] **Step 8: Implement main() and wire everything together** - -```python -def run_all_checks(): - """Run all check groups and return combined list.""" - checks = [] - checks.extend(check_dependencies()) - checks.extend(check_config()) - checks.extend(check_style()) - checks.extend(check_enhancements()) - checks.extend(check_dimensions()) - return checks - - -def main(): - parser = argparse.ArgumentParser( - description="Diagnose which anti-AI measures are active in this WeWrite installation.", - ) - parser.add_argument("--json", action="store_true", help="Output structured JSON") - args = parser.parse_args() - - checks = run_all_checks() - summary, recs = compute_summary(checks) - - if args.json: - print(format_json(checks, summary, recs)) - else: - print(format_text(checks, summary, recs)) - - # Exit code: 1 if any failures, 0 otherwise - sys.exit(1 if summary["failures"] > 0 else 0) - - -if __name__ == "__main__": - main() -``` - -- [ ] **Step 9: Smoke test the script** - -Run: `python3 scripts/diagnose.py` - -Expected: text report with check results (likely some warns for missing user files, which is correct). - -Run: `python3 scripts/diagnose.py --json` - -Expected: valid JSON output with `checks`, `summary`, `recommendations`, `files` keys. - -- [ ] **Step 10: Commit** - -```bash -git add scripts/diagnose.py -git commit -m "feat: add anti-AI diagnostic command (scripts/diagnose.py)" -``` - ---- - -### Task 2: Update SKILL.md — add diagnostic auxiliary function - -**Files:** -- Modify: `SKILL.md:44-48` (辅助功能 section) -- Modify: `SKILL.md:281-288` (Step 8c 后续操作 table) - -- [ ] **Step 1: Add auxiliary function entry** - -In the "辅助功能" section (around line 46), after the existing entries, add: - -```markdown -- 用户说"诊断配置"/"检查反AI"/"为什么AI检测没过" → 执行以下流程: - 1. `python3 {skill_dir}/scripts/diagnose.py --json` - 2. 如果有 fail 项 → 直接报告,建议修复 - 3. 如果全 pass 或仅 warn → 继续 LLM 深度分析: - - 读取 `style.yaml` 的 tone/voice 与 writing_persona,判断是否矛盾 - - 读取 `writing-config.yaml`(如存在),检查是否有 AI 特征参数(emotional_arc: flat、paragraph_rhythm: structured、closing_style: summary) - - 读取 `history.yaml` 最近 5 篇,检查 persona 使用和 WebSearch 降级情况 - 4. 综合输出自然语言报告 + 按优先级排序的改进建议 -``` - -- [ ] **Step 2: Add Step 8c table row** - -In the Step 8c "后续操作" table (around line 288), add a new row: - -```markdown -| 诊断配置 / 检查反AI / 为什么AI检测没过 | `python3 {skill_dir}/scripts/diagnose.py --json` + LLM 交叉分析 | -``` - -- [ ] **Step 3: Commit** - -```bash -git add SKILL.md -git commit -m "feat: add diagnose auxiliary function to SKILL.md" -``` - ---- - -### Task 3: Update README.md — document the diagnose command - -**Files:** -- Modify: `README.md:249-269` (Toolkit 独立使用 section) - -- [ ] **Step 1: Add diagnose command to the CLI usage block** - -In the "Toolkit 独立使用" section, add after the existing commands: - -```bash -# 诊断反 AI 配置 -python3 scripts/diagnose.py -``` - -- [ ] **Step 2: Add trigger phrase to 快速开始 section** - -In the "快速开始" section (around line 149), add: - -``` -你:检查一下反 AI 配置 → 诊断报告 -``` - -- [ ] **Step 3: Commit** - -```bash -git add README.md -git commit -m "docs: add diagnose command to README" -``` diff --git a/docs/superpowers/specs/2026-03-30-anti-ai-diagnose-design.md b/docs/superpowers/specs/2026-03-30-anti-ai-diagnose-design.md deleted file mode 100644 index b74b234..0000000 --- a/docs/superpowers/specs/2026-03-30-anti-ai-diagnose-design.md +++ /dev/null @@ -1,190 +0,0 @@ -# Anti-AI Diagnostic Command Design - -## Problem - -Users (e.g., issue #2) configure `writing_persona: "midnight-friend"` but still fail AI detection. They have no way to know which anti-AI measures are actually in effect and which silently degraded. A one-command diagnostic tells them exactly what's working, what's missing, and what to fix first. - -## Solution - -Two entry points, one data flow: - -1. **`scripts/diagnose.py`** — standalone Python script, programmatic checks, text/JSON output -2. **SKILL.md auxiliary function** — calls the script, adds LLM-powered cross-analysis - -### Part 1: `scripts/diagnose.py` - -**Location**: `scripts/diagnose.py` (same level as fetch_hotspots.py — it checks the whole skill, not just the toolkit) - -**Invocation**: -```bash -python3 scripts/diagnose.py # human-readable text -python3 scripts/diagnose.py --json # structured JSON for agent consumption -``` - -**Path resolution**: The script resolves the skill root as its own parent directory (`Path(__file__).parent.parent`), same convention as other scripts. - -**Check groups** (5 groups, each item yields `pass` / `warn` / `fail`): - -#### Group 1: Dependencies -- Import each module from requirements.txt (`markdown`, `bs4`, `cssutils`, `requests`, `yaml`, `pygments`, `PIL`) -- Missing module = `fail` with install hint - -#### Group 2: Config (`config.yaml`) -- File exists → `pass`; missing → `warn` (skip_publish + skip_image_gen) -- `wechat.appid` + `wechat.secret` present → `pass`; missing → `warn` (skip_publish) -- `image.api_key` present → `pass`; missing → `warn` (skip_image_gen) - -#### Group 3: Style (`style.yaml`) -- File exists → check fields; missing → `fail` -- `writing_persona` field present → `pass`; missing → `warn` (defaults to midnight-friend) -- Corresponding persona file in `personas/` exists → `pass`; missing → `fail` - -#### Group 4: Enhancement files -- `writing-config.yaml` exists → `pass`; missing → `warn` (using defaults, suggest optimize_loop.py) -- `playbook.md` exists → `pass`; missing → `warn` (no learned style, suggest "学习我的修改") -- `history.yaml` exists and has articles → `pass`; missing/empty → `warn` (no dedup, no dimension tracking) - -#### Group 5: Dimension variance -- Read `history.yaml`, extract `dimensions` from last 3 articles -- All 3 have distinct dimension sets → `pass`; duplicates → `warn` -- Fewer than 3 articles → `skip` (not enough data) - -**Anti-AI level scoring**: - -Each check has a weight reflecting its impact on AI detection: - -| Check | Weight | Rationale | -|-------|--------|-----------| -| style.yaml exists | 3 | No style = no persona, no tone control | -| writing_persona configured | 3 | Persona is the primary anti-AI lever | -| persona file exists | 2 | Without it, persona degrades to default | -| writing-config.yaml exists | 1 | Fine-tuning parameters, moderate impact | -| playbook.md exists | 2 | Learned style significantly improves human-ness | -| history.yaml has articles | 1 | Enables dimension dedup | -| dimension variance OK | 1 | Cross-article fingerprint diversity | -| config.yaml with wechat creds | 0 | Publish capability, no anti-AI impact | -| config.yaml with image key | 0 | Image gen, no anti-AI impact | -| Python dependencies | 0 | Prerequisite, not anti-AI specific | - -Sum of weights for `pass` items / total possible (13) → percentage → level: -- 0-40% → `LOW` -- 41-75% → `MODERATE` -- 76-100% → `HIGH` - -**Text output format**: -``` -WeWrite Anti-AI Diagnostic -========================== - -Dependencies - [PASS] Python packages: all installed - -Config - [PASS] config.yaml: found - [PASS] WeChat credentials: configured - [WARN] Image API key: missing → image generation will be skipped - -Style - [PASS] style.yaml: found - [PASS] writing_persona: midnight-friend - [PASS] personas/midnight-friend.yaml: exists - -Enhancement - [WARN] writing-config.yaml: not found → using defaults (run optimize_loop.py to tune) - [WARN] playbook.md: not found → no learned style (say "学习我的修改" after editing) - [PASS] history.yaml: 12 articles - -Dimension Variance - [PASS] Last 3 articles have distinct dimensions - -Summary: 7 passed, 3 warnings, 0 failures -Anti-AI level: ██████████░░ MODERATE (8/13) - -Top recommendations: - 1. Run optimize_loop.py to generate writing-config.yaml - 2. Edit a generated article, then say "学习我的修改" to build playbook.md -``` - -**JSON output** (`--json`): -```json -{ - "checks": [ - {"group": "dependencies", "name": "python_packages", "status": "pass", "detail": "all installed"}, - {"group": "config", "name": "config_file", "status": "pass", "detail": "found"}, - {"group": "config", "name": "wechat_credentials", "status": "pass"}, - {"group": "config", "name": "image_api_key", "status": "warn", "detail": "missing", "impact": "skip_image_gen"}, - ... - ], - "summary": { - "passed": 7, - "warnings": 3, - "failures": 0, - "anti_ai_score": 8, - "anti_ai_max": 13, - "anti_ai_level": "MODERATE" - }, - "recommendations": [ - "Run optimize_loop.py to generate writing-config.yaml", - "Edit a generated article, then say \"学习我的修改\" to build playbook.md" - ], - "files": { - "config_yaml": true, - "style_yaml": true, - "writing_config_yaml": false, - "playbook_md": false, - "history_yaml": true, - "persona_file": "personas/midnight-friend.yaml" - } -} -``` - -The `recommendations` list is ordered by impact (highest weight missing items first). The `files` map gives the agent quick access to which files exist without re-checking. - -### Part 2: SKILL.md Auxiliary Function - -**Trigger**: User says "诊断反 AI 配置" / "检查配置" / "为什么 AI 检测没过" - -**Agent flow**: - -1. Run `python3 {skill_dir}/scripts/diagnose.py --json` -2. If any `fail` items → report them, suggest fixes, stop here -3. If all `pass` or only `warn` → proceed to LLM deep analysis: - - Read `style.yaml`: extract `tone`, `voice`, `writing_persona` - - Read the active persona YAML file - - Read `writing-config.yaml` (if exists) - - Read `history.yaml` last 5 entries (if exists) -4. LLM cross-analysis checks: - -| Check | What to look for | Example issue | -|-------|-----------------|---------------| -| tone ↔ persona consistency | tone/voice keywords vs persona's voice_density, emotional_arc, avoid list | tone="严谨客观" with midnight-friend (极度口语化) | -| writing-config danger params | Values that produce AI-like output | `emotional_arc: flat`, `paragraph_rhythm: structured`, `closing_style: summary` | -| history persona usage | Whether persona is actually being used in recent articles | history entries with no `writing_persona` field | -| WebSearch degradation | Recent articles' `topic_source` showing LLM fallback | All recent articles lack real material anchoring | - -5. Output natural language report with prioritized action items - -**What it does NOT do**: -- Does not run humanness_score.py (requires an existing article) -- Does not modify any config files (diagnose + recommend only) -- Does not re-run the full pipeline - -### SKILL.md Changes - -Add to the "辅助功能" section after existing entries: -``` -- 用户说"诊断配置"/"检查反AI" → 运行 diagnose.py --json,结合 LLM 分析输出报告 -``` - -Add to Step 8c "后续操作" table: -``` -| 诊断配置 / 检查反AI | 运行 diagnose.py + LLM 交叉分析 | -``` - -## Files Changed - -| File | Change | -|------|--------| -| `scripts/diagnose.py` | New file — diagnostic script | -| `SKILL.md` | Add auxiliary function entry + Step 8c row | -| `README.md` | Add diagnose command to "Toolkit 独立使用" section |