@@ -56,8 +56,9 @@ func init() {
5656
5757// validateResult represents a single validation result.
5858type validateResult struct {
59- OK bool `json:"ok"` // Whether the check passed.
60- Message string `json:"message"` // Human-readable description.
59+ OK bool `json:"ok"` // Whether the check passed.
60+ Warning bool `json:"warning,omitempty"` // True for non-fatal warnings (OK is also true).
61+ Message string `json:"message"` // Human-readable description.
6162}
6263
6364// executeValidate runs the full validation flow.
@@ -237,26 +238,41 @@ func executeValidate(ctx context.Context, targetDir string) error {
237238 }
238239
239240 // ========== 8. Check chapter numbering and Mermaid diagnostics ==========
240- contentIssues , contentErr := validateChapterContentAndSequence (cfg )
241+ contentIssues , contentWarnings , contentErr := validateChapterContentAndSequence (cfg )
241242 if contentErr != nil {
242243 results = append (results , validateResult {
243244 OK : false ,
244245 Message : fmt .Sprintf ("Content validation failed: %v" , contentErr ),
245246 })
246247 hasError = true
247- } else if len (contentIssues ) == 0 {
248+ } else if len (contentIssues ) == 0 && len ( contentWarnings ) == 0 {
248249 results = append (results , validateResult {
249250 OK : true ,
250- Message : "Chapter numbering and Mermaid checks passed" ,
251+ Message : "Chapter numbering, title, and Mermaid checks passed" ,
251252 })
252253 } else {
254+ if len (contentIssues ) == 0 {
255+ results = append (results , validateResult {
256+ OK : true ,
257+ Message : "Chapter numbering and Mermaid checks passed" ,
258+ })
259+ }
253260 for _ , issue := range contentIssues {
254261 results = append (results , validateResult {
255262 OK : false ,
256263 Message : issue ,
257264 })
258265 }
259- hasError = true
266+ for _ , warn := range contentWarnings {
267+ results = append (results , validateResult {
268+ OK : true ,
269+ Warning : true ,
270+ Message : warn ,
271+ })
272+ }
273+ if len (contentIssues ) > 0 {
274+ hasError = true
275+ }
260276 }
261277
262278 // ========== 9. Check Markdown chapter links ==========
@@ -317,7 +333,9 @@ func printResults(results []validateResult, skipFirst ...int) {
317333 if i < skip {
318334 continue
319335 }
320- if r .OK {
336+ if r .Warning {
337+ utils .Warning ("%s" , r .Message )
338+ } else if r .OK {
321339 utils .Success ("%s" , r .Message )
322340 } else {
323341 utils .Error ("%s" , r .Message )
@@ -471,32 +489,32 @@ func normalizeMarkdownLinkTarget(raw string) string {
471489 return target
472490}
473491
474- func validateChapterContentAndSequence (cfg * config.BookConfig ) ([]string , error ) {
475- issues : = validateChapterSequence (cfg .Chapters )
492+ func validateChapterContentAndSequence (cfg * config.BookConfig ) (issues []string , warnings [] string , err error ) {
493+ issues = validateChapterSequence (cfg .Chapters )
476494 parser := markdown .NewParser ()
477495
478496 for _ , flat := range flattenChaptersWithDepth (cfg .Chapters ) {
479497 filePath := cfg .ResolvePath (flat .Def .File )
480- content , err := utils .ReadFile (filePath )
481- if err != nil {
482- return issues , fmt .Errorf ("failed to read chapter file %s: %w" , flat .Def .File , err )
498+ content , readErr := utils .ReadFile (filePath )
499+ if readErr != nil {
500+ return issues , warnings , fmt .Errorf ("failed to read chapter file %s: %w" , flat .Def .File , readErr )
483501 }
484502
485- htmlContent , headings , diagnostics , err := parser .ParseWithDiagnostics (content )
486- if err != nil {
487- return issues , fmt .Errorf ("failed to parse chapter %s: %w" , flat .Def .File , err )
503+ htmlContent , headings , diagnostics , parseErr := parser .ParseWithDiagnostics (content )
504+ if parseErr != nil {
505+ return issues , warnings , fmt .Errorf ("failed to parse chapter %s: %w" , flat .Def .File , parseErr )
488506 }
489507
490508 if diag := validateChapterTitleSequence (flat .Def .Title , headings ); diag != nil {
491509 issues = append (issues , fmt .Sprintf ("Chapter title numbering mismatch: %s (rule=%s)" , flat .Def .File , diag .Rule ))
492510 }
493511
494- // Check SUMMARY title vs file heading mismatch.
512+ // Check SUMMARY title vs file heading mismatch (warning, not error) .
495513 if flat .Def .Title != "" && len (headings ) > 0 && headings [0 ].Text != "" {
496514 summaryNorm := normalizeChapterTitle (flat .Def .Title )
497515 headingNorm := normalizeChapterTitle (headings [0 ].Text )
498516 if summaryNorm != "" && headingNorm != "" && summaryNorm != headingNorm {
499- issues = append (issues , fmt .Sprintf ("Title mismatch in %s: SUMMARY %q vs file heading %q (SUMMARY title takes precedence)" ,
517+ warnings = append (warnings , fmt .Sprintf ("Title mismatch in %s: SUMMARY %q vs file heading %q (SUMMARY title takes precedence)" ,
500518 flat .Def .File , flat .Def .Title , headings [0 ].Text ))
501519 }
502520 }
@@ -513,7 +531,7 @@ func validateChapterContentAndSequence(cfg *config.BookConfig) ([]string, error)
513531 }
514532 }
515533
516- return issues , nil
534+ return issues , warnings , nil
517535}
518536
519537func validateChapterSequence (chapters []config.ChapterDef ) []string {
@@ -624,6 +642,12 @@ type validationReport struct {
624642
625643func finalizeValidate (results []validateResult , hasError bool , alreadyPrinted ... int ) error {
626644 passed , failed := summarizeValidationResults (results )
645+ warned := 0
646+ for _ , r := range results {
647+ if r .Warning {
648+ warned ++
649+ }
650+ }
627651
628652 if validateReportPath != "" {
629653 if err := writeValidationReport (validateReportPath , validationReport {
@@ -645,12 +669,24 @@ func finalizeValidate(results []validateResult, hasError bool, alreadyPrinted ..
645669
646670 if ! quiet {
647671 fmt .Println ()
672+ warnSuffix := ""
673+ if warned > 0 {
674+ warnSuffix = fmt .Sprintf (", %s warnings" , utils .Yellow (fmt .Sprintf ("%d" , warned )))
675+ }
648676 if hasError {
649- fmt .Printf (" %s %d checks total, %s passed, %s failed\n " ,
677+ fmt .Printf (" %s %d checks total, %s passed, %s failed%s \n " ,
650678 utils .Red ("Result:" ),
651679 len (results ),
652680 utils .Green (fmt .Sprintf ("%d" , passed )),
653681 utils .Red (fmt .Sprintf ("%d" , failed )),
682+ warnSuffix ,
683+ )
684+ fmt .Println ()
685+ } else if warned > 0 {
686+ fmt .Printf (" %s %d checks passed, %s warnings ✓\n " ,
687+ utils .Green ("Result:" ),
688+ passed ,
689+ utils .Yellow (fmt .Sprintf ("%d" , warned )),
654690 )
655691 fmt .Println ()
656692 } else {
@@ -670,11 +706,12 @@ func finalizeValidate(results []validateResult, hasError bool, alreadyPrinted ..
670706
671707func summarizeValidationResults (results []validateResult ) (passed int , failed int ) {
672708 for _ , r := range results {
673- if r .OK {
709+ if r .OK && ! r . Warning {
674710 passed ++
675- } else {
711+ } else if ! r . OK {
676712 failed ++
677713 }
714+ // Warnings are counted separately (not in passed or failed).
678715 }
679716 return passed , failed
680717}
0 commit comments