@@ -192,6 +192,117 @@ fn stale_render_version_disables_reuse() {
192192 ) ;
193193}
194194
195+ #[ test]
196+ fn per_category_render_version_isolates_invalidation ( ) {
197+ let ( project, input) = reuse_project ( ) ;
198+ let out_dir = project. path ( ) . join ( "codewiki" ) ;
199+
200+ let mut first_generator = |_prompt : & str , system : & str , _tier : PromptTier | {
201+ if system == prompts:: CURATED_NAVIGATION_SYSTEM {
202+ Some ( test_curated_navigation_json ( ) )
203+ } else if system == prompts:: CONCEPT_PAGE_SYSTEM {
204+ Some ( test_concept_handbook_body ( ) )
205+ } else if system == prompts:: NARRATIVE_PAGE_SYSTEM {
206+ Some ( test_narrative_handbook_body ( ) )
207+ } else {
208+ Some ( "Generated prose." . to_string ( ) )
209+ }
210+ } ;
211+ let mut progress = CodewikiProgress :: silent ( ) ;
212+ let first = generate_hierarchical_docs_with_progress (
213+ & input,
214+ Some ( & mut first_generator) ,
215+ AiDepth :: Symbols ,
216+ & mut progress,
217+ ) ;
218+ write_incremental_doc_set_with_snapshot (
219+ project. path ( ) ,
220+ & out_dir,
221+ & first,
222+ None ,
223+ "symbols" ,
224+ DocPruneScope :: unscoped ( ) ,
225+ )
226+ . expect ( "first write" ) ;
227+
228+ // Stale only the architecture page's render version. Every other category
229+ // keeps version 20 and must reuse; only code/_architecture.md regenerates.
230+ let meta_path = out_dir. join ( "_meta/codewiki.json" ) ;
231+ let raw_meta = std:: fs:: read_to_string ( & meta_path) . expect ( "read meta" ) ;
232+ let mut meta: serde_json:: Value = serde_json:: from_str ( & raw_meta) . expect ( "parse meta" ) ;
233+ if let Some ( arch) = meta[ "docs" ]
234+ . as_object_mut ( )
235+ . expect ( "docs object" )
236+ . get_mut ( "code/_architecture.md" )
237+ {
238+ arch[ "render_version" ] = serde_json:: json!( 1 ) ;
239+ }
240+ std:: fs:: write (
241+ & meta_path,
242+ format ! (
243+ "{}\n " ,
244+ serde_json:: to_string_pretty( & meta) . expect( "serialize meta" )
245+ ) ,
246+ )
247+ . expect ( "write stale meta" ) ;
248+
249+ let mut regenerated_paths = Vec :: new ( ) ;
250+ let mut second_generator = |_prompt : & str , system : & str , _tier : PromptTier | {
251+ if system == prompts:: CURATED_NAVIGATION_SYSTEM {
252+ Some ( test_curated_navigation_json ( ) )
253+ } else if system == prompts:: CONCEPT_PAGE_SYSTEM {
254+ Some ( test_concept_handbook_body ( ) )
255+ } else if system == prompts:: NARRATIVE_PAGE_SYSTEM {
256+ Some ( test_narrative_handbook_body ( ) )
257+ } else {
258+ Some ( "Regenerated prose." . to_string ( ) )
259+ }
260+ } ;
261+ let mut plan = ReusePlan :: load ( project. path ( ) , & out_dir, "symbols" ) . expect ( "reuse plan loads" ) ;
262+ let mut reuse = Some ( & mut plan) ;
263+ let mut progress = CodewikiProgress :: silent ( ) ;
264+ let second = generate_hierarchical_docs_with_reuse (
265+ & input,
266+ Some ( & mut second_generator) ,
267+ AiDepth :: Symbols ,
268+ & mut reuse,
269+ & mut progress,
270+ ) ;
271+
272+ // Collect paths whose content changed (regenerated, not reused).
273+ for doc in & second {
274+ let prev = first. iter ( ) . find ( |d| d. path == doc. path ) ;
275+ if prev. is_none_or ( |p| p. content != doc. content ) {
276+ regenerated_paths. push ( doc. path . as_str ( ) ) ;
277+ }
278+ }
279+
280+ // Architecture must regenerate.
281+ assert ! (
282+ regenerated_paths. contains( & "code/_architecture.md" ) ,
283+ "architecture page must regenerate when its render version is stale, got: {regenerated_paths:?}"
284+ ) ;
285+
286+ // File docs and module docs must NOT regenerate — their render versions are
287+ // still valid.
288+ let file_or_module_regen = regenerated_paths
289+ . iter ( )
290+ . any ( |p| p. starts_with ( "code/files/" ) || p. starts_with ( "code/modules/" ) ) ;
291+ assert ! (
292+ !file_or_module_regen,
293+ "file/module pages must reuse when only architecture render version is stale, regenerated: {regenerated_paths:?}"
294+ ) ;
295+
296+ // Curated pages must NOT regenerate.
297+ let curated_regen = regenerated_paths
298+ . iter ( )
299+ . any ( |p| p. starts_with ( "code/concepts/" ) || p. starts_with ( "code/narrative/" ) ) ;
300+ assert ! (
301+ !curated_regen,
302+ "curated pages must reuse when only architecture render version is stale, regenerated: {regenerated_paths:?}"
303+ ) ;
304+ }
305+
195306#[ test]
196307fn reused_docs_feed_recorded_summaries_into_parent_prompts ( ) {
197308 let ( project, input) = reuse_project ( ) ;
0 commit comments