@@ -186,6 +186,11 @@ impl<'a> DocSink<'a> {
186186 let target = safe_doc_path ( self . out_dir , & doc. path ) ?;
187187 let write_outcome = ai_outcome. for_doc ( doc. degraded ) ;
188188 let content = apply_ai_outcome_to_markdown ( & doc. content , write_outcome) ;
189+ let content = if doc. path . ends_with ( ".md" ) {
190+ strict_markdown:: normalize_codewiki_markdown ( & content)
191+ } else {
192+ content
193+ } ;
189194 let previous_meta = self . previous_docs . get ( & doc. path ) ;
190195 if let ( Some ( since) , Some ( meta) ) = ( self . since . as_ref ( ) , previous_meta)
191196 && doc. invalidation_key . is_none ( )
@@ -204,10 +209,14 @@ impl<'a> DocSink<'a> {
204209 . chain ( meta. neighbor_hashes . keys ( ) )
205210 . all ( |file| !since. contains ( file) )
206211 {
212+ let refreshed = refresh_doc_if_needed ( self . out_dir , & doc. path , & content) ?;
213+ if refreshed {
214+ self . generated_docs . push ( doc. path . clone ( ) ) ;
215+ }
207216 self . next_docs . insert ( doc. path . clone ( ) , meta. clone ( ) ) ;
208217 self . seen . insert ( doc. path . clone ( ) ) ;
209218 self . flush ( ) ?;
210- return Ok ( false ) ;
219+ return Ok ( refreshed ) ;
211220 }
212221
213222 let source_hashes = source_hashes_for_doc ( self . project_root , & content) ?;
@@ -280,7 +289,15 @@ impl<'a> DocSink<'a> {
280289 } ) ;
281290 let unchanged = unchanged || since_unchanged;
282291
292+ let refreshed = if unchanged {
293+ refresh_doc_if_needed ( self . out_dir , & doc. path , & content) ?
294+ } else {
295+ false
296+ } ;
283297 let entry = if unchanged {
298+ if refreshed {
299+ self . generated_docs . push ( doc. path . clone ( ) ) ;
300+ }
284301 // A skip keeps the previous healthy content on disk, so the meta
285302 // entry keeps the previous summary and stays healthy even when
286303 // this run's generation failed — degraded fallback never displaces
@@ -321,7 +338,7 @@ impl<'a> DocSink<'a> {
321338 self . next_docs . insert ( doc. path . clone ( ) , entry) ;
322339 self . seen . insert ( doc. path . clone ( ) ) ;
323340 self . flush ( ) ?;
324- Ok ( !unchanged)
341+ Ok ( !unchanged || refreshed )
325342 }
326343
327344 /// Pages written with a degraded structural fallback this run (#900), in
@@ -627,14 +644,37 @@ pub(crate) fn write_doc(out_dir: &Path, relative_path: &str, content: &str) -> a
627644 . and_then ( |extension| extension. to_str ( ) )
628645 == Some ( "md" )
629646 {
630- gobby_core :: markdown :: normalize_markdown ( content)
647+ strict_markdown :: normalize_codewiki_markdown ( content)
631648 } else {
632649 content. to_string ( )
633650 } ;
634651 std:: fs:: write ( target, content) ?;
635652 Ok ( ( ) )
636653}
637654
655+ fn refresh_doc_if_needed (
656+ out_dir : & Path ,
657+ relative_path : & str ,
658+ content : & str ,
659+ ) -> anyhow:: Result < bool > {
660+ let target = safe_doc_path ( out_dir, relative_path) ?;
661+ let existing = match std:: fs:: read_to_string ( & target) {
662+ Ok ( existing) => existing,
663+ Err ( error) if error. kind ( ) == std:: io:: ErrorKind :: NotFound => return Ok ( false ) ,
664+ Err ( error) => return Err ( error. into ( ) ) ,
665+ } ;
666+ if existing == content {
667+ return Ok ( false ) ;
668+ }
669+ if !relative_path. ends_with ( ".md" )
670+ || strict_markdown:: normalize_codewiki_markdown ( & existing) != content
671+ {
672+ return Ok ( false ) ;
673+ }
674+ write_doc ( out_dir, relative_path, content) ?;
675+ Ok ( true )
676+ }
677+
638678pub ( crate ) fn reject_symlinked_doc_path ( out_dir : & Path , target : & Path ) -> anyhow:: Result < ( ) > {
639679 let relative = target. strip_prefix ( out_dir) ?;
640680 let mut current = out_dir. to_path_buf ( ) ;
0 commit comments