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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions