| id | 69 |
|---|---|
| title | Include enhancements: link adjustment and heading-level |
| status | 🔲 |
Add two features to the include directive (MDS021):
automatic link-path rewriting and a heading-level
parameter for heading-level adjustment.
Relative links break when a file is included into a
document in a different directory. For example,
docs/guide.md includes DEVELOPMENT.md (a copy or
symlink placed alongside the including file, since
include file: paths may not contain .. traversal
segments). A link like [layout](internal/rules/) in
DEVELOPMENT.md resolves from the repo root, but when
included from docs/guide.md it points to
docs/internal/rules/ instead. The include directive
must rewrite each relative link target so it resolves
from the including file.
Heading levels also need adjustment. DEVELOPMENT.md
uses ## headings. When included under ## Project
in CLAUDE.md those headings appear as siblings, not
children. The heading-level parameter (set to "absolute")
shifts included headings to nest under the parent.
After reading the file and stripping frontmatter,
scan every Markdown link and image. A link looks like
[text](target) and an image like .
For each relative target (not /, #, http://,
or https://):
- Get the included file's directory relative to the
FS root (e.g.
DEVELOPMENT.md→.). - Get the including file's directory relative to the
FS root (e.g.
docs/guide.md→docs). - Rewrite the target:
newTarget = relpath(includingDir, join(includedDir, target)).
Skip the transformation when both files share the same directory.
New optional parameter heading-level (values:
"absolute" or omitted).
When heading-level: "absolute":
- Find the heading level of the section that contains
the
<?include?>marker (the "parent level"). Use 0 when the marker sits at the document root. - Find the minimum heading level in the included content (the "source top level").
- Compute
shift = parentLevel - sourceTopLevel + 1so included top-level headings become children of the parent. Skip when shift is zero. - Add
shiftto every ATX heading (#prefix) and setext heading (underline). Cap at level 6.
Example: include under ## Project (level 2), source
has ## Build (level 2) and ### Sub (level 3).
shift = 2 - 2 + 1 = 1. Result: ### Build (3),
#### Sub (4).
- Add a helper
adjustLinks(content, includedFilePath, includingFilePath)ininternal/rules/include/that rewrites relative link/image targets - Write unit tests for
adjustLinks: same directory (no-op), different directories, anchors and absolute URLs left untouched, query strings preserved - Call
adjustLinksingenerateIncludeContentafter frontmatter stripping, before wrap - Add a helper
adjustHeadings(content, parentLevel)that shifts ATX and setext heading levels - Write unit tests for
adjustHeadings: shift up, shift down, cap at 6, no headings (no-op) - Extend
validateIncludeDirectiveto accept and validate theheading-levelparameter (only"absolute"is valid) - In
generateIncludeContent, detect the parent heading level from the marker position and calladjustHeadingswhenheading-level: "absolute" - Add test for parent-level detection (marker under h2, under h3, at document root)
- Update the rule README at
MDS021-include/README.mdto document both features - Update existing fixtures and tests if link adjustment changes their expected output
- Run
go test ./...,go tool golangci-lint run, andmdsmith check .
- Relative links in included content are rewritten so they resolve from the including file's directory, not the source file's directory
- Absolute URLs, anchor-only links (
#foo), and protocol links (http://,https://) are not modified -
heading-level: "absolute"shifts headings so the included top-level headings appear one level below the enclosing section - When
heading-levelis omitted, heading levels stay unchanged - Heading level never exceeds 6
- Invalid
heading-levelvalues produce a diagnostic - Link adjustment is always applied (no parameter needed)
- All tests pass:
go test ./... -
golangci-lint runreports no issues -
mdsmith check .reports zero diagnostics