fix(wiki): surface merge conflicts as textual-diff warnings (closes #104)
Textual-diff conflict detection only (semantic contradiction detection via brain-gardener Judge deferred, confirmed by Mathias, comment 12594). Includes canonicalBlock whitespace-run normalization fix for the disagreement comparison. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Mathias <mthbqv@gmail.com>
This commit was merged in pull request #129.
This commit is contained in:
@@ -117,10 +117,18 @@ func buildAndWrite(rawPages []RawPage, sourceSlug, date, brainDir, source string
|
||||
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
|
||||
return Result{}, fmt.Errorf("mkdir for %s: %w", page.Path, err)
|
||||
}
|
||||
content, err := reviseOrCreate(dest, page)
|
||||
content, conflicts, err := reviseOrCreate(dest, page)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
for _, heading := range conflicts {
|
||||
// Reported, not resolved. The on-disk version stands; whether
|
||||
// the disagreement is a genuine contradiction is the
|
||||
// Judge-gated enrich pass's call, not the write path's.
|
||||
warnings = append(warnings, fmt.Sprintf(
|
||||
"%s: source now disagrees on section %q — on-disk version kept",
|
||||
page.Path, heading))
|
||||
}
|
||||
if err := os.WriteFile(dest, []byte(content), 0o644); err != nil {
|
||||
return Result{}, fmt.Errorf("write %s: %w", page.Path, err)
|
||||
}
|
||||
@@ -198,15 +206,16 @@ func storedSourceHash(brainDir, sourceSlug string) string {
|
||||
// its first-ingest date on a revised page. Left deliberately — the alternative
|
||||
// is this function editing frontmatter it does not own, and no lint reads that
|
||||
// field today.
|
||||
func reviseOrCreate(dest string, page wiki.Page) (string, error) {
|
||||
func reviseOrCreate(dest string, page wiki.Page) (string, []string, error) {
|
||||
existing, err := os.ReadFile(dest)
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
return page.Content, nil
|
||||
return page.Content, nil, nil
|
||||
case err != nil:
|
||||
return "", fmt.Errorf("read existing %s: %w", page.Path, err)
|
||||
return "", nil, fmt.Errorf("read existing %s: %w", page.Path, err)
|
||||
}
|
||||
return wiki.Merge(wiki.Page{Path: page.Path, Content: string(existing)}, page).Content, nil
|
||||
merged, conflicts := wiki.MergeReport(wiki.Page{Path: page.Path, Content: string(existing)}, page)
|
||||
return merged.Content, conflicts, nil
|
||||
}
|
||||
|
||||
// mergeAll deduplicates pages by path, merging content from later occurrences.
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -293,3 +294,34 @@ func TestRun_FrontmatterOnlyChangeIsNotDrift(t *testing.T) {
|
||||
|
||||
assert.Equal(t, 1, calls, "only the body counts as the source")
|
||||
}
|
||||
|
||||
func TestRunRaw_ReportsWhenTheSourceDisagreesWithWhatIsOnDisk(t *testing.T) {
|
||||
// The on-disk version still wins — that is what makes unattended ingest
|
||||
// safe. But the run must say that the source now claims something else,
|
||||
// or a changed source is indistinguishable from an unchanged one.
|
||||
dir := t.TempDir()
|
||||
page := filepath.Join(dir, "wiki", "sources", "shape-up.md")
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(page), 0o755))
|
||||
require.NoError(t, os.WriteFile(page, []byte(`---
|
||||
title: 'Shape Up'
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Appetite is fixed, scope is variable.
|
||||
`), 0o644))
|
||||
|
||||
res, err := RunRaw(dir, "shape-up", []RawPage{{
|
||||
Title: "Shape Up", Type: "source", Subtype: "book", Domain: "product",
|
||||
Content: "## Summary\n\nAppetite is negotiable and scope is fixed.\n",
|
||||
}}, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := os.ReadFile(page)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(got), "Appetite is fixed", "on-disk version wins")
|
||||
|
||||
joined := strings.Join(res.Warnings, " ")
|
||||
assert.Contains(t, joined, "Summary", "the disagreeing section must be named")
|
||||
assert.Contains(t, joined, "shape-up", "and the page it happened on")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user