@@ -1309,18 +1309,39 @@ def _est_tokens(text: str) -> int:
13091309 return max (1 , len (text ) // _CHARS_PER_TOKEN )
13101310
13111311
1312- def _chunk_topic_file (rule : dict , level : int = 2 ) -> dict :
1313- """Return {relative_path: content} for one oversized topic rule:
1314- `<topic>.md` index + `<topic>/<section-slug>.md` chunks."""
1315- topic = rule ["topic" ]
1316- preamble , sections = _split_h2_sections (rule ["body" ], level )
1317- # A split below H2 leaves the wrapping heading dangling at the end of
1318- # the preamble — drop trailing heading-only lines.
1319- pre_lines = preamble .splitlines ()
1320- while pre_lines and (not pre_lines [- 1 ].strip () or pre_lines [- 1 ].startswith ("#" )):
1321- pre_lines .pop ()
1322- preamble = "\n " .join (pre_lines ).strip ()
1312+ # An oversized section chunk recurses one heading level deeper (topic →
1313+ # section → entry), so e.g. an 85 KB Models section becomes per-model files
1314+ # behind a sub-index. Depth is capped: entries below H4 don't split further.
1315+ _MAX_CHUNK_DEPTH = 2
1316+
1317+
1318+ def _strip_dangling_headings (preamble : str ) -> str :
1319+ """A split below the top level leaves the wrapping heading dangling at
1320+ the end of the preamble — drop trailing heading-only/blank lines."""
1321+ lines = preamble .splitlines ()
1322+ while lines and (not lines [- 1 ].strip () or lines [- 1 ].startswith ("#" )):
1323+ lines .pop ()
1324+ return "\n " .join (lines ).strip ()
1325+
13231326
1327+ def _chunk_level (rule : dict , title : str , index_title : str , body : str ,
1328+ rel_dir : str , intro : str , level : int , depth : int ) -> dict :
1329+ """Chunk `body` at `level` headings into files under `rel_dir`/ and
1330+ return {rel_path: content} including `rel_dir`.md as the routing index.
1331+
1332+ Recurses one level deeper for sections that are still oversized and have
1333+ enough subsections, turning the section file into a sub-index.
1334+ """
1335+ preamble = ""
1336+ sections : list [tuple [str , str ]] = []
1337+ for lv in (level , level + 1 ):
1338+ preamble , sections = _split_h2_sections (body , lv )
1339+ if len (sections ) >= 2 :
1340+ level = lv
1341+ break
1342+ preamble = _strip_dangling_headings (preamble )
1343+
1344+ dirname = rel_dir .rsplit ("/" , 1 )[- 1 ]
13241345 out : dict [str , str ] = {}
13251346 rows : list [str ] = []
13261347 seen : dict [str , int ] = {}
@@ -1331,33 +1352,64 @@ def _chunk_topic_file(rule: dict, level: int = 2) -> dict:
13311352 slug = f"{ slug } -{ seen [slug ]} "
13321353 else :
13331354 seen [slug ] = 1
1334- chunk_body = f"# { topic .replace ('-' , ' ' ).title ()} : { heading } \n \n { text } \n "
1335- out [f"{ topic } /{ slug } .md" ] = _render_claude ({** rule , "body" : chunk_body })
1355+ chunk_title = f"{ title } : { heading } "
1356+ chunk_body = f"# { chunk_title } \n \n { text } \n "
1357+ rel_path = f"{ rel_dir } /{ slug } .md"
1358+ rendered = _render_claude ({** rule , "body" : chunk_body })
1359+ _ , subsections = _split_h2_sections (text , level + 1 )
1360+ if (depth < _MAX_CHUNK_DEPTH
1361+ and len (rendered .encode ("utf-8" )) > _CHUNK_THRESHOLD_BYTES
1362+ and len (subsections ) >= 2 ):
1363+ out .update (_chunk_level (
1364+ rule , chunk_title , chunk_title , text , f"{ rel_dir } /{ slug } " ,
1365+ f"This section is chunked. Load only the entry file(s) under "
1366+ f"`{ slug } /` relevant to your task — this index is the routing table." ,
1367+ level + 1 , depth + 1 ,
1368+ ))
1369+ else :
1370+ out [rel_path ] = rendered
13361371 summary = _section_summary (text )
13371372 rows .append (
1338- f"| { _escape_table_cell (heading )} | [`{ topic } /{ slug } .md`]({ topic } /{ slug } .md) "
1373+ f"| { _escape_table_cell (heading )} | [`{ dirname } /{ slug } .md`]({ dirname } /{ slug } .md) "
13391374 f"| ~{ _est_tokens (chunk_body )} | { _escape_table_cell (summary )} |"
13401375 )
13411376
13421377 index_lines = [
1343- f"# { rule . get ( 'description' ) or topic } " ,
1378+ f"# { index_title } " ,
13441379 "" ,
1345- f"This topic is chunked. Load only the section file(s) under "
1346- f"`.claude/rules/{ topic } /` relevant to your task — this index is the "
1347- f"routing table." ,
1380+ intro ,
13481381 "" ,
13491382 "| Section | File | ~Tokens | Contains |" ,
13501383 "|---------|------|---------|----------|" ,
13511384 * rows ,
13521385 ]
13531386 if preamble :
13541387 index_lines += ["" , preamble ]
1355- out [f"{ topic } .md" ] = _render_claude (
1388+ out [f"{ rel_dir } .md" ] = _render_claude (
13561389 {** rule , "body" : "\n " .join (index_lines ).rstrip () + "\n " }
13571390 )
13581391 return out
13591392
13601393
1394+ def _chunk_topic_file (rule : dict , level : int = 2 ) -> dict :
1395+ """Return {relative_path: content} for one oversized topic rule:
1396+ `<topic>.md` index + `<topic>/<section-slug>.md` chunks (recursing into
1397+ `<topic>/<section>/<entry>.md` when a section is itself oversized)."""
1398+ topic = rule ["topic" ]
1399+ return _chunk_level (
1400+ rule ,
1401+ topic .replace ("-" , " " ).title (),
1402+ rule .get ("description" ) or topic ,
1403+ rule ["body" ],
1404+ topic ,
1405+ f"This topic is chunked. Load only the section file(s) under "
1406+ f"`.claude/rules/{ topic } /` relevant to your task — this index is the "
1407+ f"routing table." ,
1408+ level ,
1409+ 1 ,
1410+ )
1411+
1412+
13611413def _render_topic_files (rule : dict ) -> dict :
13621414 """Render one topic rule into its output file(s), chunking when the
13631415 rendered body crosses the size threshold and has enough H2 sections."""
@@ -2127,6 +2179,10 @@ def _rm(p: Path):
21272179 rel = str (md .relative_to (project_root ))
21282180 if rel not in files :
21292181 _rm (md )
2182+ # Prune empty dirs bottom-up (nested entry dirs first, then the topic dir).
2183+ for sub in sorted ((d for d in chunk_dir .rglob ("*" ) if d .is_dir ()), reverse = True ):
2184+ if not any (sub .iterdir ()):
2185+ sub .rmdir ()
21302186 if not any (chunk_dir .iterdir ()):
21312187 chunk_dir .rmdir ()
21322188 # Stale enforcement by-topic files (topic disappeared from rules.json).
0 commit comments