Skip to content

Broken in-doc cross-references: 34 links across 14 doc files (sibling to #58) #62

@initializ-mk

Description

@initializ-mk

Summary

Follow-up to #58. While the main README.md (now fixed in #61) was the most visible broken-link surface, the same canonical-name mismatch and relative-path errors exist inside the doc tree itself. A reader on docs/core-concepts/runtime-engine.md who clicks "Memory" or "Tools" gets a GitHub 404, and the docs/security/overview.md "Security Hardening Checklist" links to a half-dozen siblings that all 404.

Concretely: 34 broken in-doc links across 14 of the ~35 doc files, mapping to 16 unique missing targets. The CI workflow added in #61 only checks README.md; this issue is about expanding the same gate to the rest of the doc tree, plus a one-pass cleanup of the existing breakage.

Audit (run from repo root on main)

python3 - <<'PY'
import re, os
broken = []
for root, _, files in os.walk("docs"):
    for f in files:
        if not f.endswith(".md"): continue
        path = os.path.join(root, f)
        for m in re.finditer(r"\[([^\]]+)\]\(([^)]+\.md)(#[^)]*)?\)", open(path).read()):
            target = m.group(2)
            if target.startswith("http"): continue
            resolved = os.path.normpath(os.path.join(os.path.dirname(path), target))
            if not os.path.exists(resolved):
                broken.append((path, target))
print(f"{len(broken)} broken")
PY

Result on main after #61 merges: 34 broken.

Categorized breakdown

The 34 occurrences fall into three causes:

Cause 1: Missing ../ — link assumes "relative to docs/ root" but Markdown resolves relative to the file's directory

Most of the broken security/guardrails.md / security/egress.md / tools.md / memory.md / runtime.md / dashboard.md references are this. The author wrote the path as if docs/ were the project root.

File Broken link Should be
docs/core-concepts/channels.md runtime.md runtime-engine.md (same directory)
docs/core-concepts/hooks.md security/guardrails.md (×2) ../security/guardrails.md
docs/core-concepts/how-forge-works.md dashboard.md ../reference/web-dashboard.md
docs/core-concepts/how-forge-works.md security/egress.md ../security/egress-control.md
docs/core-concepts/runtime-engine.md tools.md tools-and-builtins.md
docs/core-concepts/runtime-engine.md memory.md memory-system.md
docs/core-concepts/runtime-engine.md security/guardrails.md ../security/guardrails.md
docs/core-concepts/skill-md-format.md security/egress.md ../security/egress-control.md
docs/core-concepts/tools-and-builtins.md security/guardrails.md (×3) ../security/guardrails.md
docs/core-concepts/tools-and-builtins.md runtime.md runtime-engine.md
docs/core-concepts/tools-and-builtins.md memory.md memory-system.md
docs/skills/embedded-skills.md security/guardrails.md ../security/guardrails.md
docs/skills/embedded-skills.md tools.md ../core-concepts/tools-and-builtins.md
docs/skills/skills-cli.md dashboard.md ../reference/web-dashboard.md
docs/skills/writing-custom-skills.md security/egress.md ../security/egress-control.md
docs/skills/writing-custom-skills.md security/guardrails.md ../security/guardrails.md
docs/reference/cli-reference.md dashboard.md web-dashboard.md (same directory)
docs/reference/web-dashboard.md skills.md ../skills/writing-custom-skills.md or ../core-concepts/skill-md-format.md (choose)
docs/deployment/production-checklist.md command-integration.md ../reference/command-integration.md
docs/getting-started/contributing.md command-integration.md ../reference/command-integration.md
docs/getting-started/contributing.md ../README.md ../../README.md (two levels up to repo root)

Cause 2: Canonical-name mismatch — same pattern as #58 inside docs/security/overview.md

This single file accounts for 10 of the 34 broken links. It was written before the docs were renamed to the *-control, *-management, *-signing pattern and never updated.

Broken link Should be
egress.md (×2) egress-control.md
secrets.md (×2) secret-management.md
signing.md (×2) build-signing.md
../architecture.md ../core-concepts/how-forge-works.md
../tools.md ../core-concepts/tools-and-builtins.md
../skills.md ../skills/writing-custom-skills.md
../commands.md ../reference/cli-reference.md

The first six are sibling-renames within docs/security/; the last four are the same canonical-name mismatch issue that #58 cleaned up in the README.

Per-file blast radius

File Broken links
docs/security/overview.md 10
docs/core-concepts/tools-and-builtins.md 5
docs/core-concepts/runtime-engine.md 3
docs/core-concepts/hooks.md 2
docs/core-concepts/how-forge-works.md 2
docs/getting-started/contributing.md 2
docs/skills/embedded-skills.md 2
docs/skills/writing-custom-skills.md 2
6 other files 1 each

Proposed fix shape

One PR, two halves.

Half 1: Apply the remap

Walk the table above. 33 of 34 are mechanical string substitutions per the remap. The one judgment call is docs/reference/web-dashboard.md → "[SKILL.md format]" — should it point at core-concepts/skill-md-format.md (the actual format reference) or skills/writing-custom-skills.md (the broader skills doc the README links to)? Issue: pick one — recommendation is core-concepts/skill-md-format.md since the link's anchor text is literally "SKILL.md format".

Half 2: Extend the CI link check to the whole doc tree

.github/workflows/docs-links.yaml (added in #61) currently checks only README.md. Extend the script to walk docs/**/*.md too, with the same fail-on-broken behavior. Path filter is already correct (docs/**).

- name: Verify all relative .md links resolve
  run: |
    python3 - <<'PY'
    import re, os, sys
    bad = []
    targets = ["README.md"] + [os.path.join(r, f) for r, _, fs in os.walk("docs") for f in fs if f.endswith(".md")]
    for path in targets:
        for m in re.finditer(r"\[([^\]]+)\]\(([^)]+\.md)(#[^)]*)?\)", open(path).read()):
            t = m.group(2)
            if t.startswith("http"): continue
            r = os.path.normpath(os.path.join(os.path.dirname(path), t))
            if not os.path.exists(r):
                bad.append(f"{path}  ->  {t}")
    if bad:
        print("Broken doc links:")
        for line in bad: print(f"  {line}")
        sys.exit(1)
    print(f"All {len(targets)} doc files: relative .md links resolve")
    PY

Acceptance criteria

Out of scope

  • Anchor-fragment validation (e.g. #some-heading). Could be a separate enhancement to the same workflow if it ever becomes worth checking; trivially harder because it requires reading each linked file's headings.
  • External (https://...) link rot — different problem, different tooling.

Related

Surfaced during the sync-docs runs for v0.10.0 and the #58 audit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions