split-docs — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited split-docs (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A reference doc that has grown past a few hundred lines is read whole every time any part of it is relevant, which wastes context and buries the section that matters. The fix is to split it into one file per top-level section under a sibling directory, and replace the original with a curated index that routes a reader to the one section their task touches.
The hard constraint is that this is a move, not a rewrite. Section prose must survive byte-for-byte; only the index is new writing. Three failure modes recur and all are silent:
thedoc.md#some-heading), left dangling because that anchor no longer exists in the doc - it moved to a section file.This skill makes all three verifiable instead of hoped-for. The third is the easiest to miss and the most damaging, because the dangling links sit in other files, often ones an agent never reopens during the split.
A frequent variant: the doc travels with a hand-written digest (*-digest.md, *-summary.md) that has drifted out of date. The digest's job - route a reader to the right part - is exactly what the new index does, so the digest is deleted after the split, not carried forward.
This document uses one example doc to keep the commands concrete, but nothing here is specific to it: it applies equally to an architecture spec, a PRD, or any long reference. Set the two variables below once and the rest is doc-agnostic.
Run it when a single Markdown reference doc is too large to read whole and the request is to break it up. It generalizes across doc types. If the repo holds several monolith-plus-digest pairs (for example an architecture spec and a PRD, each with its own digest), each is a separate application of this same procedure.
It does not apply to authoring new prose (make-docs), to picking a Diataxis quadrant, or to splitting code. If the task involves rewriting or improving the prose while moving it, stop - that breaks the byte-faithful contract and needs a different approach the user should approve first.
Set the source and target as variables so every later command is doc-agnostic, then copy the source to a scratch path. Every later check diffs against this snapshot, so it must be taken before the first edit.
SRC=docs/the-doc.md # the oversized doc to split (e.g. docs/architecture.md, docs/PRD.md)
DIR=docs/the-doc # sibling directory that will hold one file per section
cp "$SRC" /tmp/_split_original.md
wc -l "$SRC" # record the line countList the headers at the split level (usually ##) with their line numbers. These are the cut points; the count is how many section files you will produce.
grep -nE '^## ' "$SRC"Decide the per-file naming up front: zero-padded ordinal plus a slug from the heading (01-<slug>.md, 02-<slug>.md) keeps the directory sorted in document order. Create the target directory (mkdir -p "$DIR").
Extract each section as the byte range from its header line up to (but not including) the next header line. Drive it from the boundary line numbers in step 2 - do not hand-retype prose. Run sed -n 'START,ENDp' "$SRC" for each range, writing to the matching section file. The first section starts at the doc's first ##; anything above it (title, preamble) belongs in the index, not a section file. For the last section, end at $ so a missing trailing newline does not truncate it.
Extract every section before editing either the snapshot or the live file, so the line numbers stay valid.
This is the gate that makes the split trustworthy. Concatenate the section files in order and diff against the snapshot. The only legitimate difference is the title and preamble lifted into the index (the lines above the first ##).
diff <(sed -n '<first-##-line>,$p' /tmp/_split_original.md) <(cat $(ls "$DIR"/*.md | sort))Investigate every diff line. A diff hunk in the middle of a section means dropped or duplicated content - fix the tiling and re-run. Do not proceed until the only diff is the lifted preamble (or run the diff against the original from the first section line, as above, for a clean empty result).
Section files now live one directory deeper than the original, so every relative link that pointed outside the doc's own directory needs one extra ../. A link to decisions/0008.md from the old $SRC becomes ../decisions/0008.md from $DIR/05-*.md. Links within the new tree (section to section, including same-file #anchor links) do not change.
Do not trust a hand-built list of links to fix - that is exactly where one gets missed (a single doc can hold more escaping links than you expect). After repairing, sweep all relative links across the new files and confirm each resolves:
grep -rnoE '\]\(([^)]+)\)' "$DIR" | grep -v '://' # every non-http linkFor each hit, check the target exists from the section file's location (test -e against the resolved path, or open it). A link that does not resolve is either an unrepaired depth (../ missing) or over-repaired (one ../ too many). The grep sweep, not the edit plan, is the source of truth for which links exist.
Replace the original file with an index page. Derive each section's summary by reading that section file as it now stands - do not port descriptions from the old digest or an outdated table of contents, which may describe content that has since moved or changed. Open each section, write its row from what is actually there.
Match the repo's existing index house-style: study a sibling index (for example docs/README.md or a decisions/README.md) and reuse its table shape and column semantics. A routing index typically carries a short preamble, an optional orientation paragraph, and a table whose columns name each section, what it covers, and when a reader should open it. State, near the top, that the section file wins when the index summary and the section disagree - the index is a map, not a second source of truth. Follow the project's writing rules (sentence-case headings, punctuation conventions, banned words, em-dash bans) the same as any other doc edit.
If a *-digest.md / *-summary.md accompanied the original, the new index supersedes it. Before deleting, diff its content against the section files and the new index to confirm it carries no information that exists nowhere else. If it holds a unique line, fold that into the right section or the index first; then delete. Removing it stops a second stale artifact from accumulating drift.
This is the step that is most often under-done. Other files point at the doc you just split: agent context files, other docs, ADRs, reference pages, even source comments. Search the whole repo, not just *.md, and not just the doc's own directory.
DOCNAME=$(basename "$SRC") # e.g. architecture.md
SECTIONS=$(basename "$DIR") # e.g. architecture
EXC="--exclude-dir=node_modules --exclude-dir=.next --exclude-dir=$SECTIONS"
grep -rn "$DOCNAME" . $EXC # by file name
grep -rnE "${SECTIONS}[[:space:]]+(section[[:space:]]+)?§?[0-9]" . $EXC # by short name + sectionThe second grep matters because a reference like architecture §11C.1 or architecture section 20 names the doc by its short name with no .md, so the first grep never sees it. Adjust ${SECTIONS} to whatever short name prose actually uses for this doc.
Classify each hit; the forms need different handling. The split changes what the doc file contains, so any reference that resolves through a section - whether by anchor or by section number - is now stale, even when nothing about it is a clickable link:
](...${DOCNAME}#some-section)): broken. The anchor moved to a section file. Repoint the path to the section file that now owns that heading, keeping the anchor slug unchanged (the slug is derived from the heading text, which the move preserved): ](../the-doc.md#92-aws-s3) becomes ](../the-doc/09-storage.md#92-aws-s3).](...${DOCNAME})): still resolves to the new index. Leave it.architecture §5.3.10 becomes [architecture §5.3.10](../the-doc/05-workflow.md#5310-dispatch-object). Do not assume the section number equals the file ordinal: §20 may live in 25-webhook-support.md because numbering and file order diverged. Resolve the owning file from the heading, the same way step 5 verifies, never from the number alone.§N - that is a same-doc reference, still valid, leave it. Treat a bare §N as a reference into the split doc only when the surrounding text or the file's role makes the split doc the referent. When genuinely unsure which doc a bare number points at, ask rather than rewrite a self-reference into a wrong cross-doc link.Two traps here, both learned the hard way:
${DOCNAME}# pattern will false-match the section files you just created if any section slug ends in the doc's base name. Splitting architecture.md produces files like 09-storage-architecture.md, and 09-storage-architecture.md#91-postgresql contains the substring architecture.md#91-postgresql. Anchor the search on the boundary - require a / immediately before the name (/${DOCNAME}#) or exclude "$SECTIONS" - and treat "every rewritten link resolves to a real heading" as the pass condition, never "the raw grep count is zero". The same holds for the short-name + section grep: it can match a section file's own heading or the new index, so exclude "$SECTIONS" and judge by resolution, not count.Distinguish active references (context files, ADRs, current reference docs - fix them) from historical ones (implementation plans, past reviews, dated task logs - point-in-time records that should keep citing the state they were written against; leave them, and say you did).
Verify each repaired inbound link resolves the same way as in step 5: target file exists, and a heading in it slugifies to the anchor.
The split is correct when all of these hold:
cat of section files diffs clean against the pre-split snapshot, save for the preamble lines lifted into the index.../, none with one too many.§N reference of ambiguous source has been judged a same-doc self-reference (left) or a reference into the split doc (repointed) on evidence, not rewritten blind.*-digest.md survives carrying information absent from the sections or index.§N that the surrounding doc means as its own section stays as-is. Conflating the two ships stale cross-references.*-${DOCNAME} false-match it. Resolve the links; do not count strings.../; only links escaping the directory do. A global substitution over-repairs the internal ones.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.