From 05ed553c538092904e5e1d61dc9753ac5ea911e4 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 30 Jun 2026 15:16:18 -0500 Subject: [PATCH 1/3] fix(journey): satisfy desktop eslint sort rules in star map The merged #55859 left the star-map NodeContextMenu import and the canvas onContextMenu prop out of perfectionist's required order, failing `npm run lint` in the desktop workspace. Reorder both. --- apps/desktop/src/app/starmap/star-map.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/app/starmap/star-map.tsx b/apps/desktop/src/app/starmap/star-map.tsx index d4427c0ea..a1a5b652c 100644 --- a/apps/desktop/src/app/starmap/star-map.tsx +++ b/apps/desktop/src/app/starmap/star-map.tsx @@ -10,13 +10,13 @@ import type { StarmapGraph } from '@/types/hermes' import { computePalette, memoryInkFor, resolveRgb, rgba } from './color' import { RING_OUTER, TILT, ZOOM_MAX, ZOOM_MIN } from './constants' import { clamp, distToSegmentSq, fitScale, fitViewport, nodeRadius } from './geometry' +import { NodeContextMenu, type NodeMenuTarget } from './node-context-menu' import { drawScene, drawScramble } from './render' import { decodeShareCode, encodeShareCode, ShareCodeError } from './share-code' import { ShareControls } from './share-controls' import { buildSimulation } from './simulation' import { formatDate } from './text' import { buildTimeAxis, dateAtReveal, type TimeAxis } from './time-axis' -import { NodeContextMenu, type NodeMenuTarget } from './node-context-menu' import { Timeline } from './timeline' import type { FadeBuckets, MemoryCard, Palette, Ring, RingLabelRect, SimLink, SimNode, Viewport } from './types' @@ -918,10 +918,10 @@ export function StarMap({
Date: Tue, 30 Jun 2026 15:16:21 -0500 Subject: [PATCH 2/3] refactor(journey): route memory mutations through MemoryStore atomic I/O MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit learning_mutations re-implemented the §-delimited read/write that tools/memory_tool already owns, and its writer used a plain write_text (truncate-then-write) — reintroducing exactly the partial-file race that MemoryStore._write_file engineered away with atomic temp-file + rename. Reuse MemoryStore._read_file/_write_file so the format is single-sourced, the write is atomic against concurrent readers, and journey indices stay aligned with the graph. --- agent/learning_mutations.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/agent/learning_mutations.py b/agent/learning_mutations.py index 7566fa216..c723b6153 100644 --- a/agent/learning_mutations.py +++ b/agent/learning_mutations.py @@ -20,7 +20,6 @@ from __future__ import annotations from pathlib import Path from typing import Any -_MEMORY_DELIM = "\n§\n" _MEMORY_FILES = {"memory": "MEMORY.md", "profile": "USER.md"} @@ -63,20 +62,18 @@ def _memory_local_index(source: str, global_index: int) -> int: return global_index - sum(1 for c in cards if c.get("source") == "memory") -def _read_chunks(path: Path) -> list[str]: - """Raw ``§``-delimited chunks, preserving formatting; empties dropped to - match ``_memory_cards`` indexing.""" - text = path.read_text(encoding="utf-8") - - return [c for c in text.split(_MEMORY_DELIM) if c.strip()] - - def _locate_memory(source: str, gidx: int) -> tuple[Path, list[str], int]: - """Resolve a memory card to its file, all chunks, and local index.""" + """Resolve a memory card to its file, all §-delimited entries, and local index. + + Entries come from ``MemoryStore._read_file`` — the same parser the memory + tool uses — so journey indices stay aligned with what the graph renders. + """ + from tools.memory_tool import MemoryStore + path = _memories_dir() / _MEMORY_FILES[source] if not path.exists(): raise ValueError(f"{path.name} not found") - chunks = _read_chunks(path) + chunks = MemoryStore._read_file(path) local = _memory_local_index(source, gidx) if not 0 <= local < len(chunks): raise ValueError("memory node id is stale — refresh the graph") @@ -149,7 +146,7 @@ def _delete_memory(node_id: str) -> dict[str, Any]: path, chunks, local = _locate_memory(source, gidx) del chunks[local] - _write_chunks(path, chunks) + _write_memory(path, chunks) return {"ok": True, "message": f"deleted memory from {path.name}"} @@ -184,7 +181,7 @@ def _edit_memory(node_id: str, content: str) -> dict[str, Any]: path, chunks, local = _locate_memory(source, gidx) chunks[local] = body - _write_chunks(path, chunks) + _write_memory(path, chunks) return {"ok": True, "message": f"updated memory in {path.name}"} @@ -192,9 +189,12 @@ def _edit_memory(node_id: str, content: str) -> dict[str, Any]: # ── Helpers ───────────────────────────────────────────────────────────────── -def _write_chunks(path: Path, chunks: list[str]) -> None: - body = _MEMORY_DELIM.join(c.strip() for c in chunks) - path.write_text(f"{body}\n" if body else "", encoding="utf-8") +def _write_memory(path: Path, chunks: list[str]) -> None: + """Atomic temp-file + rename via the memory tool, so a concurrent reader + never sees a half-written file (and the §-join stays single-sourced).""" + from tools.memory_tool import MemoryStore + + MemoryStore._write_file(path, [c.strip() for c in chunks if c.strip()]) def _clear_skill_cache() -> None: From 6241cc54e3ecae97b98b10e1bb1c1b1c4363759b Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 30 Jun 2026 15:16:25 -0500 Subject: [PATCH 3/3] test(journey): lock memory write format-parity with the memory tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assert a journey edit leaves MEMORY.md byte-identical to MemoryStore's own §-join (no trailing-newline drift) and round-trips through MemoryStore._read_file, so the two surfaces can never diverge on format. --- tests/agent/test_learning_mutations.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/agent/test_learning_mutations.py b/tests/agent/test_learning_mutations.py index 79afa4d13..dc2f562d0 100644 --- a/tests/agent/test_learning_mutations.py +++ b/tests/agent/test_learning_mutations.py @@ -112,3 +112,17 @@ def test_edit_skill_rewrites_and_validates(home): def test_missing_skill_detail(home): assert not lm.node_detail("nonexistent-skill")["ok"] + + +def test_memory_writes_match_memory_tool_format(home): + """A journey mutation must leave the file byte-identical to what the memory + tool itself writes — same §-join, no trailing-newline drift — so the two + surfaces never fight over format and indices stay aligned.""" + from tools.memory_tool import ENTRY_DELIMITER, MemoryStore + + assert lm.edit_node("memory:memory:0", "alpha rewritten")["ok"] + path = home / "memories" / "MEMORY.md" + entries = MemoryStore._read_file(path) + + assert entries == ["alpha rewritten", "beta note"] + assert path.read_text(encoding="utf-8") == ENTRY_DELIMITER.join(entries)