documentation-layout: treat symlinked markdown as pointers (allow module READMEs symlinked into docs/) #152

Closed
opened 2026-06-04 06:52:13 +00:00 by coilysiren · 1 comment
Owner

Problem

check_documentation_layout.py walks *.md via rglob, which yields symlinked markdown files. The location rule (check_markdown_locations) then fires on whichever copy lands in a forbidden dir (anything outside repo root, docs/*.md, a skill path, or examples/). This blocks a useful pattern: a module folder (e.g. warp/) carrying a README.md that points back into the canonical docs/ surface, so GitHub renders the module README while the doc itself stays in docs/ under the size cap.

A symlinked .md is a pointer, not content-at-rest, so the location and size rules should treat it as such instead of flagging the pointer (or double-counting its target's bytes).

Direction (decided)

Canonical markdown stays in an allowed, size-capped location. Module folders carry a symlinked pointer into it. Concretely for the warp module:

  • Canonical: docs/warp.md (real file, linted normally).
  • Pointer: warp/README.md -> ../docs/warp.md (symlink, GitHub follows it to render the module README).

The rejected inverse (real file in warp/, pointer in docs/) does not work and is not pursued: the real warp/README.md violates the location rule on its own, and allowing it would require broadening allowed locations, which erodes the "markdown lives in exactly three places" invariant the validator protects.

Change

In agentic_os/check_documentation_layout.py:

  • check_markdown_locations() - skip rel when (REPO_ROOT / rel).is_symlink(). A symlinked .md is a pointer, not a doc living there.
  • check_markdown_sizes() - skip symlinks too, so the target is not double-counted (it gets size-checked at its real path).

Guardrails so "skip" does not hide bugs:

  • Dangling symlink fails - path.exists() is False on a broken link. Emit a violation (a broken README renders empty on GitHub) instead of silently skipping.
  • Target must stay in-repo - path.resolve() must sit under REPO_ROOT. An escaping symlink means the content never gets linted and loses size-cap coverage, so fail it.

Tests

  • In-repo pointer (warp/README.md -> ../docs/warp.md) passes location + size.
  • Dangling symlink fails.
  • Escaping symlink (target outside REPO_ROOT) fails.

Artifacts to land

  • Validator change + tests.
  • docs/warp.md starter doc + warp/README.md -> ../docs/warp.md symlink.
  • Short paragraph in docs/documentation-layout-exceptions.md documenting the symlink-pointer rule.

This is validator semantics (generalizable), so it lands in agentic-os (the repo that ships the hook), not as a per-repo pyproject.toml opt-out. It dogfoods here.

## Problem `check_documentation_layout.py` walks `*.md` via `rglob`, which yields symlinked markdown files. The location rule (`check_markdown_locations`) then fires on whichever copy lands in a forbidden dir (anything outside repo root, `docs/*.md`, a skill path, or `examples/`). This blocks a useful pattern: a module folder (e.g. `warp/`) carrying a `README.md` that points back into the canonical `docs/` surface, so GitHub renders the module README while the doc itself stays in `docs/` under the size cap. A symlinked `.md` is a pointer, not content-at-rest, so the location and size rules should treat it as such instead of flagging the pointer (or double-counting its target's bytes). ## Direction (decided) Canonical markdown stays in an allowed, size-capped location. Module folders carry a symlinked pointer into it. Concretely for the warp module: - **Canonical:** `docs/warp.md` (real file, linted normally). - **Pointer:** `warp/README.md -> ../docs/warp.md` (symlink, GitHub follows it to render the module README). The rejected inverse (real file in `warp/`, pointer in `docs/`) does not work and is not pursued: the real `warp/README.md` violates the location rule on its own, and allowing it would require broadening allowed locations, which erodes the "markdown lives in exactly three places" invariant the validator protects. ## Change In `agentic_os/check_documentation_layout.py`: - `check_markdown_locations()` - skip `rel` when `(REPO_ROOT / rel).is_symlink()`. A symlinked `.md` is a pointer, not a doc living there. - `check_markdown_sizes()` - skip symlinks too, so the target is not double-counted (it gets size-checked at its real path). Guardrails so "skip" does not hide bugs: - **Dangling symlink fails** - `path.exists()` is False on a broken link. Emit a violation (a broken README renders empty on GitHub) instead of silently skipping. - **Target must stay in-repo** - `path.resolve()` must sit under `REPO_ROOT`. An escaping symlink means the content never gets linted and loses size-cap coverage, so fail it. ## Tests - In-repo pointer (`warp/README.md -> ../docs/warp.md`) passes location + size. - Dangling symlink fails. - Escaping symlink (target outside `REPO_ROOT`) fails. ## Artifacts to land - Validator change + tests. - `docs/warp.md` starter doc + `warp/README.md -> ../docs/warp.md` symlink. - Short paragraph in `docs/documentation-layout-exceptions.md` documenting the symlink-pointer rule. This is validator semantics (generalizable), so it lands in `agentic-os` (the repo that ships the hook), not as a per-repo `pyproject.toml` opt-out. It dogfoods here.
Author
Owner

Backlog burndown 2026-06-17: closing low-priority (P3/P4) to bring the open count to a manageable level. Nothing lost — reopen if this resurfaces. Batch tag: burndown-2026-06.

Backlog burndown 2026-06-17: closing low-priority (P3/P4) to bring the open count to a manageable level. Nothing lost — reopen if this resurfaces. Batch tag: `burndown-2026-06`.
coilysiren 2026-06-17 08:23:10 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/agentic-os#152
No description provided.