Skip to content

Commit e7af1dd

Browse files
committed
Treat title mismatch as warning in validate
1 parent 57fb715 commit e7af1dd

File tree

2 files changed

+61
-24
lines changed

2 files changed

+61
-24
lines changed

cmd/validate.go

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ func init() {
5656

5757
// validateResult represents a single validation result.
5858
type 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

519537
func validateChapterSequence(chapters []config.ChapterDef) []string {
@@ -624,6 +642,12 @@ type validationReport struct {
624642

625643
func 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

671707
func 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
}

cmd/validate_run_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ func TestValidateChapterContentAndSequence_ValidProject(t *testing.T) {
268268
t.Fatalf("config.Load failed: %v", err)
269269
}
270270

271-
issues, err := validateChapterContentAndSequence(cfg)
271+
issues, _, err := validateChapterContentAndSequence(cfg)
272272
if err != nil {
273273
t.Errorf("should not error for valid project: %v", err)
274274
}
@@ -289,7 +289,7 @@ func TestValidateChapterContentAndSequence_MissingFile(t *testing.T) {
289289
{Title: "Chapter 1", File: "missing.md"},
290290
}
291291

292-
_, err := validateChapterContentAndSequence(cfg)
292+
_, _, err := validateChapterContentAndSequence(cfg)
293293
if err == nil {
294294
t.Error("should error when chapter file is missing")
295295
}
@@ -302,7 +302,7 @@ func TestValidateChapterContentAndSequence_EmptyChapters(t *testing.T) {
302302
cfg.Book.Title = "Test"
303303
cfg.Chapters = []config.ChapterDef{}
304304

305-
issues, err := validateChapterContentAndSequence(cfg)
305+
issues, _, err := validateChapterContentAndSequence(cfg)
306306
if err != nil {
307307
t.Errorf("empty chapters should not error: %v", err)
308308
}

0 commit comments

Comments
 (0)