Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 91 additions & 2 deletions scripts/archive-memory-index.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,24 @@
That residual gap is a count taken over a subset, and it is the honest cost of not
refusing files whose readable rows are perfectly manageable.

The other half of the file: what the prompt would not carry
----------------------------------------------------------
`--check` also reports the **embed cap's** reading, because the row rules bound the
file while the cap bounds what a reader of the system prompt sees of it. The daemon
keeps the index's **head** up to `INDEX_SIZE_WARN` characters (cut at a line
boundary), and an index that appends newest-last therefore shows its *oldest* rows:
measured on the host's evolution index, 65 of 166 rows were past the cut, the rows of
the three preceding days among them (issue #1554). The reading says how many rows are
past the cut, which row is the last one kept, and whether the **newest** cycle row is
among the embedded ones - the one line #1554 asks for, and the one a maintainer
cannot get from a count.

It is a reading and never a verdict: which end an index should keep is a product
decision (#1554 records four options, none taken), so the exit code stays the two row
rules, and an index past the cap with sound rows is reported `OK` with the reading
following it. The cut is asked of `EmrgServer._cap_memory_index` rather than
re-implemented here, so the two cannot disagree about what is embedded.

Exit codes
----------
``0`` the index is within its cap (nothing to move), or the move was made and
Expand Down Expand Up @@ -133,7 +151,10 @@
# `python3 scripts/archive-memory-index.py` the root is not `sys.path[0]`, and the
# test suite loads this file by path (`spec_from_file_location`).
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from emrg.memory import INDEX_TITLE_MAX_CHARS # noqa: E402 (needs the path above)
from emrg.memory import ( # noqa: E402 (needs the path above)
INDEX_SIZE_WARN,
INDEX_TITLE_MAX_CHARS,
)

#: One index row: a markdown link whose text is the title and whose target is a
#: detail file. Only the target is load-bearing here, so the link text is free.
Expand Down Expand Up @@ -404,6 +425,67 @@ def check_rules(index_path: Path, cap: int) -> list[str]:
return problems


def embed_cap_reading(index_path: Path, rows: list[Row]) -> list[str]:
"""The rows this index would not reach the prompt with - the embed cap's cut.

The two rules above are about the *file*; this is about the half of it a reader
of the system prompt actually sees. The daemon applies `_cap_memory_index` to
the two indexes a store embeds (the project's and the session's), and that
method keeps the file's **head** up to `INDEX_SIZE_WARN` characters, cutting at
a line boundary. An index that appends newest-last therefore shows the *oldest*
rows: measured on the host's evolution index (issue #1554), **65 of 166** rows
were past the cut - every row of the preceding three days, the cycle's own
included - while the head was filled by the oldest topic rows. Which end an
index should keep is a product decision (#1554 records four options, none
taken), so this is a **reading**: it never fails the run, and `--check`'s exit
code stays the two row rules. A reading allowed to fail would take that decision
by accident.

The cut is asked of the production function rather than re-implemented. A second
copy of a three-line rule is a second answer to "which rows are embedded", free
to drift the moment the cap's line-boundary handling changes - and the copy would
be the one `--check` reports. `EmrgServer` is imported lazily (the move never
pays for it) and called unbound: that method does not read `self`.

What is relied on about its shape: the cap keeps a **prefix** of the file, so the
rows it keeps are the index's first rows in file order - which is what makes
`rows[len(kept_rows):]` the dropped set. The tests pin that against the capped
text itself rather than against this reasoning.
"""
text = index_path.read_text(encoding="utf-8")
size = len(text)
limit = INDEX_SIZE_WARN
if size <= limit:
return [
f"embed cap: {size} char(s), within the {limit}-char cap - the whole "
"index is embedded"
]

from emrg.server.daemon import EmrgServer # lazy: only this reading needs it

kept_rows = parse_rows(EmrgServer._cap_memory_index(None, index_path))
dropped = rows[len(kept_rows) :]

out = [
f"embed cap: {size} char(s), over the {limit}-char cap - the head is kept; "
f"{len(dropped)} of {len(rows)} row(s) are past the cut"
]
if kept_rows:
out.append(f" last row embedded: {kept_rows[-1].text.strip()[:120]}")
cycle_rows = [row for row in rows if row.is_cycle]
if cycle_rows:
# Cycle ids sort as text (`YYYYMMDD-HHMMSS`), so the maximum is the newest.
newest = max(cycle_rows, key=lambda row: row.stamp or "")
out.append(
f" newest cycle row {newest.stamp}: "
f"{'not embedded' if newest in dropped else 'embedded'}"
)
out.append(
" (a reading, not a rule: `--check` fails only on the row rules above)"
)
return out


def atomic_write_text(path: Path, text: str) -> None:
"""Write ``text`` to ``path`` so a reader sees the old file or the new one.

Expand Down Expand Up @@ -573,7 +655,8 @@ def main(argv: list[str] | None = None) -> int:
parser.add_argument(
"--check",
action="store_true",
help="read-only: report row-rule violations, exit 1 if there are any",
help="read-only: report row-rule violations (exit 1 if there are any) and the "
"embed cap's reading (which rows the prompt would not carry; no verdict)",
)
parser.add_argument(
"--dry-run",
Expand Down Expand Up @@ -629,6 +712,12 @@ def main(argv: list[str] | None = None) -> int:
cycle_rows = [row for row in rows if row.is_cycle]
print(f"index: {index_path}")
print(f"{len(cycle_rows)} cycle row(s) of {len(rows)} row(s), cap {args.cap}")
# The cap's reading, printed before the verdict so it is read in the same
# pass as the rules - and printed in both outcomes, because an index past
# the cap with sound rows is the ordinary case, not a fault. It returns no
# verdict: the exit code below is the two row rules and nothing else.
for line in embed_cap_reading(index_path, rows):
print(line)
if problems:
for problem in problems:
print(f"VIOLATION: {problem}")
Expand Down
118 changes: 118 additions & 0 deletions tests/test_archive_memory_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import sys
from collections import Counter
from dataclasses import replace
from datetime import datetime, timedelta
from pathlib import Path

import pytest
Expand Down Expand Up @@ -251,6 +252,123 @@ def test_check_mode_flags_a_long_row_and_a_duplicate(tmp_path, mod, capsys):
assert "duplicate" in capsys.readouterr().out


def _topic_row(i: int) -> str:
"""A readable row that is not a cycle row - bulk for an over-cap index."""
return f"- [topic {i}](topic-{i}.md) - {'x' * 90}\n"


def _capped(index: Path) -> list[str]:
"""The rows the daemon's embed cap would keep - the oracle, not a re-derivation.

Asked of the production function, because the reading's whole claim is that it
reports *that* cut: a test that re-implemented the cap would agree with a second
implementation while both drifted from the daemon.
"""
from emrg.server.daemon import EmrgServer

capped = EmrgServer._cap_memory_index(None, index)
return [line for line in capped.splitlines() if line.startswith("- [")]


def test_the_reading_names_the_rows_the_cap_does_not_embed(tmp_path, mod, capsys):
"""An index past the embed cap: the rows of its tail are not what a reader sees."""
index = tmp_path / "MEMORY.md"
rows = [_row(OLD)] + [_topic_row(i) for i in range(600)] + [_row(NEWEST)]
_write_index(index, rows)
assert len(index.read_text(encoding="utf-8")) > mod.INDEX_SIZE_WARN, (
"the fixture must be past the cap, or this test measures nothing"
)

assert mod.main([str(index), "--cap", "50", "--check"]) == 0, (
"an index past the embed cap is not a row-rule violation"
)
out = capsys.readouterr().out

capped = _capped(index)
dropped = len(rows) - len(capped)
assert dropped > 0, "the oracle must find rows the cap drops"
assert f"{dropped} of {len(rows)} row(s) are past the cut" in out
assert f"last row embedded: {capped[-1][:120]}" in out
assert f"newest cycle row {NEWEST}: not embedded" in out
assert "(a reading, not a rule" in out


def test_the_reading_reports_an_embedded_newest_row_when_it_is_one(tmp_path, mod, capsys):
"""The other direction: a reading that always says 'not embedded' proves nothing."""
index = tmp_path / "MEMORY.md"
rows = [_row(NEWEST)] + [_topic_row(i) for i in range(600)] + [_row(OLD)]
_write_index(index, rows)

assert mod.main([str(index), "--cap", "50", "--check"]) == 0
out = capsys.readouterr().out

capped = _capped(index)
assert len(capped) < len(rows), "the fixture must still drop rows"
assert _row(NEWEST).strip()[:120] in capped, "the oracle must keep the newest row"
assert f"newest cycle row {NEWEST}: embedded" in out


def test_an_index_within_the_cap_says_so(tmp_path, mod, capsys):
index = tmp_path / "MEMORY.md"
_write_index(index, [_row(NEWEST), _row(OLD)])

assert mod.main([str(index), "--cap", "50", "--check"]) == 0
out = capsys.readouterr().out

assert "the whole index is embedded" in out
assert "past the cut" not in out


def test_the_reading_asks_the_cap_rather_than_cutting_the_text_itself(
tmp_path, mod, capsys, monkeypatch
):
"""The reading's claim is that it reports *that* cut, so it has to ask it.

A local copy of the three-line rule would pass every assertion above - the two
agree until the cap's line-boundary handling changes - and then diverge in
silence, with the copy being what `--check` reports. Spying on the production
method is what separates "asked" from "agrees today".
"""
from emrg.server import daemon

index = tmp_path / "MEMORY.md"
_write_index(index, [_topic_row(i) for i in range(600)] + [_row(NEWEST)])

asked: list[Path] = []
real = daemon.EmrgServer._cap_memory_index

def spy(self, path): # noqa: ANN001 - mirrors the method it wraps
asked.append(path)
return real(self, path)

monkeypatch.setattr(daemon.EmrgServer, "_cap_memory_index", spy)

assert mod.main([str(index), "--cap", "50", "--check"]) == 0
capsys.readouterr()
assert asked == [index], (
"the reading must ask the cap what it keeps, not cut the text itself"
)


def test_the_reading_is_printed_under_a_violation_too(tmp_path, mod, capsys):
"""`--check` reports both questions in one run, whichever way each of them goes."""
index = tmp_path / "MEMORY.md"
stamps = [
(datetime(2026, 9, 1) + timedelta(hours=i)).strftime("%Y%m%d-%H%M%S")
for i in range(60)
]
rows = [_row(stamp) for stamp in stamps] + [_topic_row(i) for i in range(600)]
_write_index(index, rows)

assert mod.main([str(index), "--cap", "50", "--check"]) == 1, (
"60 cycle rows are over the 50-row cap"
)
out = capsys.readouterr().out

assert "VIOLATION" in out
assert "embed cap:" in out, "the reading must not be hidden by the violation"


def test_the_verifiers_rules_fire_on_a_bad_plan(tmp_path, mod):
"""The guard's own arms: each rule must fire when the plan breaks it.

Expand Down
Loading