fix: harden docs checker heading lines, definitions and docs symlinks
ci / check (push) Successful in 59s
ci / check (pull_request) Successful in 58s

- N1: never call Lines() on inline nodes; locate empty ATX headings after the
  end of all preceding blocks (no panic, correct line after `***`)
- N2: take reference definition lines from ast.LinkReferenceDefinition and
  drop refCtx/defRe
- N3: reject docs/adr and docs/plans that resolve outside the repository

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
endandClaude Sonnet 5.5 committed 2026-10-23 15:03:26 -07:00
1 parent 9eec1d1016
commit 694c43051e
6 files changed
+59 -62

No files matched your search

+14
View File
@@ -337,6 +337,10 @@ func TestCheck(t *testing.T) {
{name: "plan missing section", files: map[string]string{pPath: sub(planBase, "## Steps\n\n1. Do it.\n\n", "")}, want: []string{pPath + ":1: missing required section \"Steps\""}},
{name: "plan empty H1", files: map[string]string{pPath: sub(planBase, "# Adopt Queue", "#")}, want: []string{pPath + ":8: H1"}},
{name: "empty ADR H1 is on its own line", files: map[string]string{aPath: sub(adrBase, "# Use Queue", "#")}, want: []string{aPath + ":16: must equal title"}},
{name: "empty H2 after a paragraph", files: map[string]string{aPath: adrBase + "\nText.\n\n##\n"}},
{name: "H1 after an empty H2 after a paragraph", files: map[string]string{aPath: adrBase + "\nText.\n\n##\n#\n"}, want: []string{aPath + ":38: more than one H1"}},
{name: "empty H1 after a thematic break", files: map[string]string{aPath: adrBase + "\n***\n#\n"}, want: []string{aPath + ":36: more than one H1"}},
{name: "two empty H1s", files: map[string]string{aPath: adrBase + "\n#\n#\n"}, want: []string{aPath + ":35: more than one H1", aPath + ":36: more than one H1"}},
{name: "headings inside blockquotes and lists do not count", files: map[string]string{aPath: adrBase + "\n> ## Consequences\n\n* ## Decision Outcome\n"}},
{name: "H4 does not end the Decision Outcome prose", files: map[string]string{aPath: sub(adrBase, "Chosen option: Queue.", "#### Detail\n\nChosen.")}},
{name: "plan second H1", files: map[string]string{pPath: planBase + "\n# Again\n"}, want: []string{pPath + ":22: more than one H1"}},
@@ -367,6 +371,16 @@ func TestCheckSymlinkedDocsDir(t *testing.T) {
expect(t, res.Problems, []string{"docs/plans:1: real directory"})
}
func TestCheckSymlinkedDocsParent(t *testing.T) {
root := tree(t, nil, map[string]string{"docs": "@outside"})
out, err := os.Readlink(filepath.Join(root, "docs"))
must(t, err)
must(t, os.MkdirAll(filepath.Join(out, "adr"), 0o755))
res, err := Check(root)
must(t, err)
expect(t, res.Problems, []string{"docs/adr:1: inside the repository"})
}
func TestCheckEmptyRoot(t *testing.T) {
res, err := Check(t.TempDir())
must(t, err)