mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
fix(memory): preserve index when reads fail
This commit is contained in:
1 parent
7aff0b79ab
commit
2eb878989e
3 files changed
+131
-25
No files matched your search
+40
-22
@@ -221,12 +221,16 @@ func (s Store) archiveLocked(name string) (string, error) {
|
||||
if dir == "" {
|
||||
continue
|
||||
}
|
||||
lines, contains, err := indexLinesExceptIn(dir, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
p, err := archiveInDir(dir, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if p != "" || indexContainsIn(dir, name) {
|
||||
if err := flushIndexIn(dir, indexLinesExceptIn(dir, name)); err != nil {
|
||||
if p != "" || contains {
|
||||
if err := flushIndexIn(dir, lines); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
@@ -238,12 +242,16 @@ func (s Store) archiveLocked(name string) (string, error) {
|
||||
}
|
||||
|
||||
func archiveMemoryInDir(dir, name string) (string, error) {
|
||||
lines, contains, err := indexLinesExceptIn(dir, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
path, err := archiveInDir(dir, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if path != "" || indexContainsIn(dir, name) {
|
||||
if err := flushIndexIn(dir, indexLinesExceptIn(dir, name)); err != nil {
|
||||
if path != "" || contains {
|
||||
if err := flushIndexIn(dir, lines); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
@@ -373,30 +381,34 @@ func repairOwnerWrite(root *os.Root, path string, dir bool) {
|
||||
// MEMORY.md.
|
||||
var indexLineRe = regexp.MustCompile(`(?m)^\s*-\s\[.+?\]\(([^)]+)\.md\)\s*—\s.*$`)
|
||||
|
||||
// indexLinesExceptIn returns the managed MEMORY.md lines keyed by filename stem
|
||||
// in the given directory, dropping the entry for name (a missing index → empty map).
|
||||
func indexLinesExceptIn(dir, name string) map[string]string {
|
||||
existing, _ := fileencoding.ReadFileUTF8(filepath.Join(dir, indexFile))
|
||||
keep := map[string]string{}
|
||||
for line := range strings.SplitSeq(string(existing), "\n") {
|
||||
if mt := indexLineRe.FindStringSubmatch(line); mt != nil && mt[1] != name {
|
||||
keep[mt[1]] = strings.TrimRight(line, "\r")
|
||||
}
|
||||
// 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 keep
|
||||
return existing, err
|
||||
}
|
||||
|
||||
func indexContainsIn(dir, name string) bool {
|
||||
existing, err := fileencoding.ReadFileUTF8(filepath.Join(dir, indexFile))
|
||||
// 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 false
|
||||
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 && mt[1] == name {
|
||||
return true
|
||||
if mt := indexLineRe.FindStringSubmatch(line); mt != nil {
|
||||
if mt[1] == name {
|
||||
contains = true
|
||||
} else {
|
||||
keep[mt[1]] = strings.TrimRight(line, "\r")
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return keep, contains, nil
|
||||
}
|
||||
|
||||
// flushIndexIn rewrites MEMORY.md in the given directory from the managed lines,
|
||||
@@ -404,7 +416,10 @@ func indexContainsIn(dir, name string) bool {
|
||||
// new managed entries are appended in sorted order.
|
||||
func flushIndexIn(dir string, lines map[string]string) error {
|
||||
path := filepath.Join(dir, indexFile)
|
||||
existing, _ := fileencoding.ReadFileUTF8(path)
|
||||
existing, err := readIndexIn(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
processed := map[string]bool{}
|
||||
var preserved strings.Builder
|
||||
preservedEmpty := true
|
||||
@@ -457,7 +472,10 @@ func flushIndexIn(dir string, lines map[string]string) error {
|
||||
// reindexIn rewrites the MEMORY.md line for name in the given directory,
|
||||
// preserving every other managed line.
|
||||
func reindexIn(dir, name string, m Memory) error {
|
||||
lines := indexLinesExceptIn(dir, name)
|
||||
lines, _, err := indexLinesExceptIn(dir, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines[name] = renderIndexLine(name, m)
|
||||
return flushIndexIn(dir, lines)
|
||||
}
|
||||
|
||||
@@ -412,6 +412,86 @@ 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)
|
||||
|
||||
@@ -254,11 +254,15 @@ 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
|
||||
}
|
||||
oldDir := filepath.Dir(existingPath)
|
||||
if err := flushIndexIn(oldDir, indexLinesExceptIn(oldDir, existing.Name)); err != nil {
|
||||
if err := flushIndexIn(oldDir, lines); err != nil {
|
||||
return SaveResult{}, err
|
||||
}
|
||||
}
|
||||
@@ -274,10 +278,14 @@ 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, indexLinesExceptIn(otherDir, duplicate.Name)); err != nil {
|
||||
if err := flushIndexIn(otherDir, lines); err != nil {
|
||||
return SaveResult{}, err
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user