@@ -4,6 +4,12 @@ import path from "node:path";
44import test from "node:test" ;
55import { fileURLToPath } from "node:url" ;
66
7+ import {
8+ isExternalTarget ,
9+ markdownLinkTargets ,
10+ withoutFencedCode ,
11+ } from "../scripts/markdown-documentation.js" ;
12+
713const repository = fileURLToPath ( new URL ( ".." , import . meta. url ) ) ;
814const markdownFiles = [
915 ...[ "AGENTS.md" , "README.md" , "RELEASING.md" , "SECURITY.md" ] . map ( ( file ) =>
@@ -59,6 +65,61 @@ test("documentation routes commands, recovery, and release knowledge", () => {
5965 ) ;
6066} ) ;
6167
68+ test ( "documentation entrypoints stay lean and route every public topic" , ( ) => {
69+ const entrypointBudgets = new Map ( [
70+ [ path . join ( repository , "AGENTS.md" ) , 2_048 ] ,
71+ [ path . join ( repository , "README.md" ) , 6_144 ] ,
72+ [ path . join ( repository , "docs/README.md" ) , 4_096 ] ,
73+ ] ) ;
74+
75+ for ( const [ file , budget ] of entrypointBudgets ) {
76+ const source = sources . get ( file ) ;
77+ assert ( source ) ;
78+ assert . ok (
79+ Buffer . byteLength ( source ) <= budget ,
80+ `${ path . relative ( repository , file ) } exceeds its ${ budget } -byte retrieval budget` ,
81+ ) ;
82+ }
83+
84+ const publicTopics = new Set (
85+ markdownFiles . filter ( ( file ) => file !== path . join ( repository , "AGENTS.md" ) ) ,
86+ ) ;
87+ const pending = [
88+ path . join ( repository , "README.md" ) ,
89+ path . join ( repository , "docs/README.md" ) ,
90+ ] ;
91+ const reachable = new Set ( ) ;
92+
93+ while ( pending . length > 0 ) {
94+ const sourceFile = pending . pop ( ) ;
95+ assert ( sourceFile ) ;
96+ if ( reachable . has ( sourceFile ) ) continue ;
97+ reachable . add ( sourceFile ) ;
98+
99+ const source = sources . get ( sourceFile ) ;
100+ assert ( source ) ;
101+ for ( const target of markdownLinkTargets ( source ) ) {
102+ if ( isExternalTarget ( target ) ) continue ;
103+
104+ const [ rawPath ] = target . split ( "#" , 1 ) ;
105+ if ( rawPath === undefined || rawPath === "" ) continue ;
106+ const targetFile = path . resolve (
107+ path . dirname ( sourceFile ) ,
108+ decodeURIComponent ( rawPath ) ,
109+ ) ;
110+ if ( publicTopics . has ( targetFile ) ) pending . push ( targetFile ) ;
111+ }
112+ }
113+
114+ for ( const file of publicTopics ) {
115+ assert . equal (
116+ reachable . has ( file ) ,
117+ true ,
118+ `${ path . relative ( repository , file ) } is not reachable from a documentation entrypoint` ,
119+ ) ;
120+ }
121+ } ) ;
122+
62123test ( "local documentation links and fragments resolve" , ( ) => {
63124 for ( const [ sourceFile , source ] of sources ) {
64125 for ( const target of markdownLinkTargets ( source ) ) {
@@ -101,25 +162,6 @@ function findMarkdownFiles(directory) {
101162 . sort ( ) ;
102163}
103164
104- /** @param {string } source @returns {string[] } */
105- function markdownLinkTargets ( source ) {
106- const targets = [ ] ;
107- const linkPattern = / (?< ! ! ) \[ [ ^ \] ] + \] \( ( [ ^ \s ) ] + ) (?: \s + " [ ^ " ] * " ) ? \) / g;
108-
109- for ( const match of withoutFencedCode ( source ) . matchAll ( linkPattern ) ) {
110- const target = match [ 1 ] ;
111- assert ( target ) ;
112- targets . push ( target . replace ( / ^ < | > $ / g, "" ) ) ;
113- }
114-
115- return targets ;
116- }
117-
118- /** @param {string } target */
119- function isExternalTarget ( target ) {
120- return / ^ [ a - z ] [ a - z 0 - 9 + . - ] * : / i. test ( target ) || target . startsWith ( "//" ) ;
121- }
122-
123165/** @param {string } source @returns {Set<string> } */
124166function markdownHeadingFragments ( source ) {
125167 const fragments = new Set ( ) ;
@@ -145,30 +187,3 @@ function markdownHeadingFragments(source) {
145187
146188 return fragments ;
147189}
148-
149- /** @param {string } source */
150- function withoutFencedCode ( source ) {
151- /** @type {string | undefined } */
152- let fence ;
153-
154- return source
155- . split ( "\n" )
156- . filter ( ( line ) => {
157- const match = / ^ { 0 , 3 } ( ` { 3 , } | ~ { 3 , } ) / . exec ( line ) ;
158- if ( match ) {
159- const marker = match [ 1 ] ;
160- assert ( marker ) ;
161-
162- if ( fence === undefined ) {
163- fence = marker [ 0 ] ;
164- } else if ( marker [ 0 ] === fence ) {
165- fence = undefined ;
166- }
167-
168- return false ;
169- }
170-
171- return fence === undefined ;
172- } )
173- . join ( "\n" ) ;
174- }
0 commit comments