chore(docs): widen docs-consistency test, archive stale concept docs

The TestDocsConsistency walk only iterated [".", "guides", "reference",
"analysis"] — concepts/ was silently invisible, which is why
amazon-music-oauth.md slipped into the tree without a SUMMARY entry.

Refactored to walk the entire docs/ tree, with a small dirsToSkip
allow-list (_includes, archive, diagrams, images) for asset trees.
New top-level narrative directories are picked up automatically;
only asset dirs need an explicit entry.

The wider walk surfaced six previously-hidden concepts/* files. Five
older planning artefacts ("Enhanced State Management System",
"Upstream Bose Service Simulation") moved into docs/archive/ where
the dirsToSkip already excludes them; concepts/README.md renamed to
upstream-service-simulation-overview.md since "README.md" inside
archive/ would be misleading. Spotify Overview and Amazon Music
OAuth are user-facing narrative docs and are now linked under
Concepts in SUMMARY.md.

Note: concepts/streborn-patterns.md is internal review notes (its
own opening line says so) and is currently unlinked from SUMMARY.md;
will be handled separately by the maintainer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-05-23 00:34:36 +02:00
co-authored by Claude Sonnet 4.6
parent 56ace2f960
commit 1421ad5ce1
7 changed files with 78 additions and 43 deletions
+2
View File
@@ -53,8 +53,10 @@
## Concepts
* [Request Recording](REQUEST_RECORDING_CONCEPT.md)
* [Spotify Overview](concepts/spotify-overview.md)
* [Spotify Priming Strategy](concepts/spotify-priming-strategy.md)
* [Spotify OAuth](concepts/spotify-oauth.md)
* [Amazon Music OAuth](concepts/amazon-music-oauth.md)
* [Encrypted Export](concepts/ENCRYPTED-EXPORT.md)
* [Diagnostic Export (Maintainer Setup)](DIAGNOSTIC-EXPORT.md)
* [soundtouch-web Roadmap](soundtouch-web-roadmap.md)
+76 -43
View File
@@ -9,6 +9,27 @@ import (
"testing"
)
// dirsToSkip names docs/ subdirectories whose contents are not meant
// to appear in SUMMARY.md. These are asset / partial / archive trees,
// not narrative documentation:
//
// - _includes : HTML partials consumed by the docs site renderer
// - archive : superseded plans / status reports kept for the
// record but deliberately unlinked
// - diagrams : Mermaid sources for embedded diagrams
// - images : binary assets + a directory README that explains them
//
// Adding a new top-level dir under docs/ does NOT require touching
// this list — only add the dir name here when its contents should
// stay out of SUMMARY.md by design. Individual file exclusions live
// in .docsignore instead.
var dirsToSkip = map[string]bool{
"_includes": true,
"archive": true,
"diagrams": true,
"images": true,
}
func TestDocsConsistency(t *testing.T) {
// Root of the project relative to this test file
// The test runs in the directory of the package
@@ -22,61 +43,73 @@ func TestDocsConsistency(t *testing.T) {
}
summaryText := string(summaryContent)
docsIgnore := readDocsIgnore(t, filepath.Join(projectRoot, ".docsignore"))
// List of directories to check
dirsToCheck := []string{".", "guides", "reference", "analysis"}
// Walk the entire docs tree. Directory-level exclusions live in
// dirsToSkip above (asset / archive trees); file-level exclusions
// live in .docsignore (individual narrative docs that are
// intentionally unlinked). New subdirectories are picked up
// automatically — this is the behaviour amazon-music-oauth.md
// surprised us by lacking.
err = filepath.WalkDir(docsDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
for _, dir := range dirsToCheck {
dirPath := filepath.Join(docsDir, dir)
err := filepath.WalkDir(dirPath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
// Don't recurse into subdirectories if we are checking the root,
// as they are handled separately or ignored (like archive)
if dir == "." && path != dirPath {
return filepath.SkipDir
}
return nil
}
if !strings.HasSuffix(d.Name(), ".md") {
if d.IsDir() {
if path == docsDir {
return nil
}
// Skip SUMMARY.md itself
if d.Name() == "SUMMARY.md" {
return nil
rel, relErr := filepath.Rel(docsDir, path)
if relErr != nil {
return relErr
}
// Skip files listed in .docsignore at the project root
for _, skip := range docsIgnore {
if strings.HasSuffix(path, filepath.FromSlash(skip)) {
return nil
}
}
// Get relative path from docs/
relPath, err := filepath.Rel(docsDir, path)
if err != nil {
return err
}
// Check if this file is linked in SUMMARY.md
// We look for [Label](relPath)
linkPattern := "(" + relPath + ")"
if !strings.Contains(summaryText, linkPattern) {
t.Errorf("Documentation file %s is not linked in docs/SUMMARY.md", relPath)
// Skip only top-level asset / archive directories. Nested
// directories inside narrative trees (e.g. docs/guides/foo/)
// would still be walked.
if !strings.ContainsRune(rel, filepath.Separator) && dirsToSkip[d.Name()] {
return filepath.SkipDir
}
return nil
})
if err != nil {
t.Errorf("Error walking directory %s: %v", dir, err)
}
if !strings.HasSuffix(d.Name(), ".md") {
return nil
}
// Skip SUMMARY.md itself
if d.Name() == "SUMMARY.md" {
return nil
}
// Skip files listed in .docsignore at the project root
for _, skip := range docsIgnore {
if strings.HasSuffix(path, filepath.FromSlash(skip)) {
return nil
}
}
// Get relative path from docs/
relPath, err := filepath.Rel(docsDir, path)
if err != nil {
return err
}
// Check if this file is linked in SUMMARY.md
// We look for [Label](relPath)
linkPattern := "(" + relPath + ")"
if !strings.Contains(summaryText, linkPattern) {
t.Errorf("Documentation file %s is not linked in docs/SUMMARY.md", relPath)
}
return nil
})
if err != nil {
t.Errorf("Error walking docs directory: %v", err)
}
}