refactor(memory): keep index read fix within lint budgets

This commit is contained in:
xiexian committed 2026-09-26 15:43:22 +08:00
1 parent 2eb878989e
commit 79acaf6e99
5 files changed
+140 -127

No files matched your search

-30
View File
@@ -381,36 +381,6 @@ func repairOwnerWrite(root *os.Root, path string, dir bool) {
// MEMORY.md.
var indexLineRe = regexp.MustCompile(`(?m)^\s*-\s\[.+?\]\(([^)]+)\.md\)\s*—\s.*$`)
// readIndexIn treats a missing index as empty, but does not hide other read errors.
func readIndexIn(dir string) ([]byte, error) {
existing, err := fileencoding.ReadFileUTF8(filepath.Join(dir, indexFile))
if os.IsNotExist(err) {
return nil, nil
}
return existing, err
}
// indexLinesExceptIn returns the managed MEMORY.md lines keyed by filename stem,
// dropping the entry for name and reporting whether it was present.
func indexLinesExceptIn(dir, name string) (map[string]string, bool, error) {
existing, err := readIndexIn(dir)
if err != nil {
return nil, false, err
}
keep := map[string]string{}
contains := false
for line := range strings.SplitSeq(string(existing), "\n") {
if mt := indexLineRe.FindStringSubmatch(line); mt != nil {
if mt[1] == name {
contains = true
} else {
keep[mt[1]] = strings.TrimRight(line, "\r")
}
}
}
return keep, contains, nil
}
// flushIndexIn rewrites MEMORY.md in the given directory from the managed lines,
// preserving hand-written content. Managed lines are updated or removed, and
// new managed entries are appended in sorted order.
+51
View File
@@ -0,0 +1,51 @@
package memory
import (
"os"
"path/filepath"
"strings"
fileencoding "reasonix/internal/fileutil/encoding"
)
// readIndexIn treats a missing index as empty, but does not hide other read errors.
func readIndexIn(dir string) ([]byte, error) {
existing, err := fileencoding.ReadFileUTF8(filepath.Join(dir, indexFile))
if os.IsNotExist(err) {
return nil, nil
}
return existing, err
}
// indexLinesExceptIn returns the managed MEMORY.md lines keyed by filename stem,
// dropping the entry for name and reporting whether it was present.
func indexLinesExceptIn(dir, name string) (map[string]string, bool, error) {
existing, err := readIndexIn(dir)
if err != nil {
return nil, false, err
}
keep := map[string]string{}
contains := false
for line := range strings.SplitSeq(string(existing), "\n") {
if mt := indexLineRe.FindStringSubmatch(line); mt != nil {
if mt[1] == name {
contains = true
} else {
keep[mt[1]] = strings.TrimRight(line, "\r")
}
}
}
return keep, contains, nil
}
func removeMemoryFromDir(path, name string) error {
dir := filepath.Dir(path)
lines, _, err := indexLinesExceptIn(dir, name)
if err != nil {
return err
}
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
return err
}
return flushIndexIn(dir, lines)
}
+87
View File
@@ -0,0 +1,87 @@
package memory
import (
"os"
"path/filepath"
"testing"
)
func TestStoreSavePreservesUnreadableIndex(t *testing.T) {
s := Store{Dir: t.TempDir()}
if _, err := s.Save(Memory{Name: "alpha", Description: "keep", Type: TypeProject, Body: "body"}); err != nil {
t.Fatal(err)
}
indexPath := filepath.Join(s.Dir, indexFile)
before := mustReadString(t, indexPath)
if err := os.Chmod(indexPath, 0); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.Chmod(indexPath, 0o644) })
if _, err := os.ReadFile(indexPath); err == nil {
t.Skip("this user can read files without read permission")
}
if _, err := s.Save(Memory{Name: "beta", Description: "new", Type: TypeProject, Body: "body"}); err == nil {
t.Fatal("Save should report the index read error")
}
if err := os.Chmod(indexPath, 0o644); err != nil {
t.Fatal(err)
}
if got := mustReadString(t, indexPath); got != before {
t.Fatalf("Save overwrote an unreadable index:\n%s", got)
}
}
func TestStoreArchivePreservesUnreadableIndex(t *testing.T) {
s := Store{Dir: t.TempDir()}
for _, name := range []string{"alpha", "beta"} {
if _, err := s.Save(Memory{Name: name, Description: "keep", Type: TypeProject, Body: "body"}); err != nil {
t.Fatal(err)
}
}
indexPath := filepath.Join(s.Dir, indexFile)
before := mustReadString(t, indexPath)
if err := os.Chmod(indexPath, 0); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.Chmod(indexPath, 0o644) })
if _, err := os.ReadFile(indexPath); err == nil {
t.Skip("this user can read files without read permission")
}
if _, err := s.Archive("alpha"); err == nil {
t.Fatal("Archive should report the index read error")
}
if err := os.Chmod(indexPath, 0o644); err != nil {
t.Fatal(err)
}
if got := mustReadString(t, indexPath); got != before {
t.Fatalf("Archive overwrote an unreadable index:\n%s", got)
}
if _, err := os.Stat(filepath.Join(s.Dir, "alpha.md")); err != nil {
t.Fatalf("Archive moved the fact before checking the index: %v", err)
}
}
func TestFlushIndexPreservesUnreadableFile(t *testing.T) {
dir := t.TempDir()
indexPath := filepath.Join(dir, indexFile)
before := "# Memory\n\nHand-written note.\n"
if err := os.WriteFile(indexPath, []byte(before), 0o644); err != nil {
t.Fatal(err)
}
if err := os.Chmod(indexPath, 0); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.Chmod(indexPath, 0o644) })
if _, err := os.ReadFile(indexPath); err == nil {
t.Skip("this user can read files without read permission")
}
if err := flushIndexIn(dir, map[string]string{"beta": "- [beta](beta.md) — new"}); err == nil {
t.Fatal("flushIndexIn should report the index read error")
}
if err := os.Chmod(indexPath, 0o644); err != nil {
t.Fatal(err)
}
if got := mustReadString(t, indexPath); got != before {
t.Fatalf("flushIndexIn overwrote hand-written content:\n%s", got)
}
}
-80
View File
@@ -412,86 +412,6 @@ func TestStoreArchiveFlushesStaleIndexWithoutFile(t *testing.T) {
}
}
func TestStoreSavePreservesUnreadableIndex(t *testing.T) {
s := Store{Dir: t.TempDir()}
if _, err := s.Save(Memory{Name: "alpha", Description: "keep", Type: TypeProject, Body: "body"}); err != nil {
t.Fatal(err)
}
indexPath := filepath.Join(s.Dir, indexFile)
before := mustReadString(t, indexPath)
if err := os.Chmod(indexPath, 0); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.Chmod(indexPath, 0o644) })
if _, err := os.ReadFile(indexPath); err == nil {
t.Skip("this user can read files without read permission")
}
if _, err := s.Save(Memory{Name: "beta", Description: "new", Type: TypeProject, Body: "body"}); err == nil {
t.Fatal("Save should report the index read error")
}
if err := os.Chmod(indexPath, 0o644); err != nil {
t.Fatal(err)
}
if got := mustReadString(t, indexPath); got != before {
t.Fatalf("Save overwrote an unreadable index:\n%s", got)
}
}
func TestStoreArchivePreservesUnreadableIndex(t *testing.T) {
s := Store{Dir: t.TempDir()}
for _, name := range []string{"alpha", "beta"} {
if _, err := s.Save(Memory{Name: name, Description: "keep", Type: TypeProject, Body: "body"}); err != nil {
t.Fatal(err)
}
}
indexPath := filepath.Join(s.Dir, indexFile)
before := mustReadString(t, indexPath)
if err := os.Chmod(indexPath, 0); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.Chmod(indexPath, 0o644) })
if _, err := os.ReadFile(indexPath); err == nil {
t.Skip("this user can read files without read permission")
}
if _, err := s.Archive("alpha"); err == nil {
t.Fatal("Archive should report the index read error")
}
if err := os.Chmod(indexPath, 0o644); err != nil {
t.Fatal(err)
}
if got := mustReadString(t, indexPath); got != before {
t.Fatalf("Archive overwrote an unreadable index:\n%s", got)
}
if _, err := os.Stat(filepath.Join(s.Dir, "alpha.md")); err != nil {
t.Fatalf("Archive moved the fact before checking the index: %v", err)
}
}
func TestFlushIndexPreservesUnreadableFile(t *testing.T) {
dir := t.TempDir()
indexPath := filepath.Join(dir, indexFile)
before := "# Memory\n\nHand-written note.\n"
if err := os.WriteFile(indexPath, []byte(before), 0o644); err != nil {
t.Fatal(err)
}
if err := os.Chmod(indexPath, 0); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.Chmod(indexPath, 0o644) })
if _, err := os.ReadFile(indexPath); err == nil {
t.Skip("this user can read files without read permission")
}
if err := flushIndexIn(dir, map[string]string{"beta": "- [beta](beta.md) — new"}); err == nil {
t.Fatal("flushIndexIn should report the index read error")
}
if err := os.Chmod(indexPath, 0o644); err != nil {
t.Fatal(err)
}
if got := mustReadString(t, indexPath); got != before {
t.Fatalf("flushIndexIn overwrote hand-written content:\n%s", got)
}
}
func mustReadString(t *testing.T, path string) string {
t.Helper()
b, err := os.ReadFile(path)
+2 -17
View File
@@ -254,15 +254,7 @@ func (s Store) SaveWithOptions(m Memory, opts SaveOptions) (SaveResult, error) {
return SaveResult{}, err
}
if exists && cleanMemoryPath(existingPath) != cleanMemoryPath(path) {
oldDir := filepath.Dir(existingPath)
lines, _, err := indexLinesExceptIn(oldDir, existing.Name)
if err != nil {
return SaveResult{}, err
}
if err := os.Remove(existingPath); err != nil && !os.IsNotExist(err) {
return SaveResult{}, err
}
if err := flushIndexIn(oldDir, lines); err != nil {
if err := removeMemoryFromDir(existingPath, existing.Name); err != nil {
return SaveResult{}, err
}
}
@@ -278,14 +270,7 @@ func (s Store) SaveWithOptions(m Memory, opts SaveOptions) (SaveResult, error) {
continue
}
if duplicate, _, ok := s.findActiveInDir(otherDir, m.Name); ok && duplicate.ID != m.ID {
lines, _, err := indexLinesExceptIn(otherDir, duplicate.Name)
if err != nil {
return SaveResult{}, err
}
if _, err := archiveInDir(otherDir, duplicate.Name); err != nil {
return SaveResult{}, err
}
if err := flushIndexIn(otherDir, lines); err != nil {
if _, err := archiveMemoryInDir(otherDir, duplicate.Name); err != nil {
return SaveResult{}, err
}
}