bot-forge 1.0.2

Rust CLI for installing agent skills and developer tools from configurable forms.
Documentation
#!/usr/bin/env python3
import pathlib
import re
import urllib.parse

root = pathlib.Path(__file__).resolve().parent.parent
manual_dir = root / "docs" / "manual"
documents = [root / "README.md", root / "SECURITY.md", *sorted(manual_dir.glob("*.md"))]
link_pattern = re.compile(r"(?<!!)\[[^]]*\]\(([^)]+)\)")
failures = []
stale_phrases = {
    "docs/README.md": "stale flat manual path",
    "05-planning-and-concurrency": "stale combined planning/concurrency chapter",
    "1.0 发布候选配置": "stale pre-release configuration wording",
    "1.0 正处于发布候选阶段": "stale pre-release status wording",
}


def anchors_for(text):
    anchors = set(re.findall(r'<a\s+id=["\']([^"\']+)["\']\s*></a>', text, re.IGNORECASE))
    counts = {}
    for heading in re.findall(r"^#{1,6}\s+(.+?)\s*$", text, re.MULTILINE):
        heading = re.sub(r"<[^>]+>", "", heading).strip().lower()
        slug = "".join(
            character
            for character in heading
            if character.isalnum() or character in " _-"
        ).replace(" ", "-")
        count = counts.get(slug, 0)
        counts[slug] = count + 1
        anchors.add(slug if count == 0 else f"{slug}-{count}")
    return anchors


def prose_lines(text):
    """Yield source lines outside fenced code blocks."""
    fenced = False
    for number, line in enumerate(text.splitlines(), 1):
        if line.lstrip().startswith("```"):
            fenced = not fenced
            continue
        if not fenced:
            yield number, line

for document in documents:
    text = document.read_text(encoding="utf-8")
    for phrase, message in stale_phrases.items():
        if phrase in text:
            failures.append(f"{document.relative_to(root)}: {message}: {phrase}")
    for raw_target in link_pattern.findall(text):
        target = raw_target.strip().split(maxsplit=1)[0].strip("<>")
        if not target or target.startswith(("http://", "https://", "mailto:")):
            continue
        path_text, _, fragment = target.partition("#")
        resolved = document if not path_text else (document.parent / urllib.parse.unquote(path_text)).resolve()
        try:
            resolved.relative_to(root)
        except ValueError:
            failures.append(f"{document.relative_to(root)}: link escapes repository: {target}")
            continue
        if not resolved.exists():
            failures.append(f"{document.relative_to(root)}: missing link target: {target}")
            continue
        if fragment and resolved.suffix == ".md":
            destination = resolved.read_text(encoding="utf-8")
            decoded_fragment = urllib.parse.unquote(fragment).lower()
            if decoded_fragment not in anchors_for(destination):
                failures.append(f"{document.relative_to(root)}: missing link anchor: {target}")

if failures:
    raise SystemExit("\n".join(failures))

expected_docs = {"README.md", "00-preface.md", *(f"{index:02d}-{name}.md" for index, name in [
    (1, "overview"),
    (2, "getting-started"),
    (3, "commands"),
    (4, "configuration"),
    (5, "planning-and-dag"),
    (6, "efficiency-and-concurrency"),
    (7, "artifacts-and-recovery"),
    (8, "output-and-interaction"),
    (9, "security"),
    (10, "architecture"),
    (11, "quality-and-release"),
    (12, "engineering-reference"),
])}
actual_docs = {path.name for path in manual_dir.iterdir() if path.is_file()}
expected_directories = {"manual", "web"}
actual_directories = {path.name for path in (root / "docs").iterdir() if path.is_dir()}
unexpected_files = sorted(path.name for path in (root / "docs").iterdir() if path.is_file())
if actual_docs != expected_docs or actual_directories != expected_directories or unexpected_files:
    raise SystemExit(
        "docs layout differs: "
        f"expected manual files {sorted(expected_docs)} and directories {sorted(expected_directories)}, "
        f"got manual files {sorted(actual_docs)}, directories {sorted(actual_directories)}, "
        f"root files {unexpected_files}"
    )

for document in sorted(manual_dir.glob("[0-9][0-9]-*.md")):
    chapter = int(document.name[:2])
    text = document.read_text(encoding="utf-8")
    for section in re.findall(r"^##\s+(\d+)\.", text, re.MULTILINE):
        if int(section) != chapter:
            failures.append(
                f"{document.relative_to(root)}: section {section} does not match chapter {chapter}"
            )

for document in sorted(manual_dir.glob("*.md")):
    text = document.read_text(encoding="utf-8")
    relative = document.relative_to(root)
    if text.count("```") % 2:
        failures.append(f"{relative}: unbalanced fenced code block")

    headings = []
    for number, line in prose_lines(text):
        match = re.match(r"^(#{1,6})\s+(.+?)\s*$", line)
        if match:
            headings.append((number, len(match.group(1)), match.group(2)))
        if len(line) > 180 and not line.startswith("|"):
            failures.append(
                f"{relative}:{number}: prose source line exceeds 180 characters"
            )

    top_level = [heading for heading in headings if heading[1] == 1]
    if len(top_level) != 1:
        failures.append(f"{relative}: expected exactly one H1, got {len(top_level)}")
    for previous, current in zip(headings, headings[1:]):
        if current[1] > previous[1] + 1:
            failures.append(
                f"{relative}:{current[0]}: heading level jumps from H{previous[1]} "
                f"to H{current[1]}"
            )

for document in sorted(manual_dir.glob("*.md")):
    text = document.read_text(encoding="utf-8")
    if not re.search(r"[\u3400-\u4dbf\u4e00-\u9fff]", text):
        failures.append(f"{document.relative_to(root)}: manual document must contain Chinese prose")

if failures:
    raise SystemExit("\n".join(failures))

print("Documentation links and docs/manual + docs/web layout are consistent.")