From d8f2dca917e847e2f13d1be46b202e15be965f90 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Tue, 10 Nov 2026 17:21:52 +0800 Subject: [PATCH] emrg: a change to a name's meaning carries the readers it does not update --- emrg/server/open_source_prompt.md | 1 + tests/test_prompt_templates.py | 65 +++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/emrg/server/open_source_prompt.md b/emrg/server/open_source_prompt.md index da295194..7414ac03 100644 --- a/emrg/server/open_source_prompt.md +++ b/emrg/server/open_source_prompt.md @@ -253,6 +253,7 @@ cd "$DEV" && git checkout -b "upstream/$DEF - **A claim you write into the project's documents says who decided it.** Before a line goes into a decisions file, a design document, an issue reply or a pull request body as the project's rule or as the maintainer's, name the message it rests on — its instant and its text. A rule you inferred, or proposed yourself, is written as a proposal, in wording that does not borrow a decision's authority: once your own proposal sits in your own context, the marker "I wrote this" is gone from it, and a premise you have forgotten you invented is the one you defend as a requirement. Memory is not the boundary; the project's documents are, because they are what the next reader acts on. - **The documents a change invalidates move with it.** A behaviour change dates documents the change never opened: a README's example output, an install guide's step, a protocol document's field list, a limitation someone wrote down while it was still true. Before the change is committed, search the tree for the old behaviour's own spelling — its name, its path, its count, its stated limitation — because remembering to look is not a method and searching is. What the search finds is moved in this same change: a document left describing what the project no longer does is a defect the change introduced, not a follow-up for the next contributor, and a reviewer who finds one cannot tell whether the code or the document is wrong. Where a document states a count a guard can measure, it names the guard instead — a number written in prose is a number nothing keeps true. - **A document with a translated twin is edited in one change.** Where the file you are changing has a counterpart in another language, both are changed in the same change and read against each other before it is committed: a twin left behind states the old rule to the next reader, and the reader who opens that side first is told the opposite of what the project now does. Where the project states which copy governs a disagreement, that statement sets the order — the other copy follows it, never the reverse. +- **A change that alters what a name means carries its readers with it.** Making an argument required, widening a return shape, moving a function out of its file, deleting an export — each changes the answer for code that already called it, and that code is rarely the file the change opened. Find those readers before the commit by searching the name across the tree the change will **land on**, not the checkout alone: a merge brings in callers no local search has seen, and a suite stays green for a caller that no test path drives. A reader the change leaves broken is part of the change — repairing it is what finishes the edit — and one that genuinely belongs to a different contribution is named in the pull request rather than left for the suite to discover. ##### 3.1.6 Test diff --git a/tests/test_prompt_templates.py b/tests/test_prompt_templates.py index 2e8205fc..e2d0f8b4 100644 --- a/tests/test_prompt_templates.py +++ b/tests/test_prompt_templates.py @@ -2291,6 +2291,71 @@ def test_the_translated_twin_pin_reads_position_and_absence() -> None: ) +# ── §3.1.5 told a round to read the code it touches and to carry the documents it dates, and +# said nothing about the code that reads a name the change alters. A caller the change does not +# open goes on being wrong while the diff reads coherent and the suite stays green — a caller +# no test path drives is the exact case a suite cannot answer. Pinned by position for the same +# reason the twin rule above is: §3.1.6 is about running tests, and the same sentence there +# would be read as a testing rule rather than as an edit rule. +_OPEN_SOURCE_READERS_PHRASE = "A change that alters what a name means carries its readers with it" +_OPEN_SOURCE_READERS_SECTION = "##### 3.1.5 Implement" +_OPEN_SOURCE_READERS_NEXT = "##### 3.1.6 Test" + + +def test_the_open_source_prompt_carries_the_readers_of_a_changed_name() -> None: + """An edit rule that stops at the file it opened leaves every other caller behind. + + Not decoration: §3.1.3 covers reading a project before contributing and §3.1.5's documents + rule covers prose the change dates, so a round that followed both to the letter could make + an argument required and submit a pull request whose own diff is coherent while each caller + it did not open is broken. The suite is no substitute — a caller that no test path drives + reports nothing. + """ + text = (PROMPTS_DIR / "open_source_prompt.md").read_text(encoding="utf-8") + + assert text.count(_OPEN_SOURCE_READERS_PHRASE) == 1, ( + f"{_OPEN_SOURCE_READERS_PHRASE!r} appears {text.count(_OPEN_SOURCE_READERS_PHRASE)} " + "times — a rule written twice is two answers that will disagree" + ) + for heading in (_OPEN_SOURCE_READERS_SECTION, _OPEN_SOURCE_READERS_NEXT): + assert f"\n{heading}" in "\n" + text, ( + f"the section this pin reads position against is gone: {heading!r} — a positional " + "reading of a missing bound compares against nothing" + ) + assert _fifth_level_section_of(text, _OPEN_SOURCE_READERS_PHRASE) == _OPEN_SOURCE_READERS_SECTION, ( + "the readers-of-a-changed-name rule is no longer under " + f"{_OPEN_SOURCE_READERS_SECTION!r} (found: " + f"{_fifth_level_section_of(text, _OPEN_SOURCE_READERS_PHRASE)!r}) — a rule in a section " + "that does not own its subject is one a round reads as something else" + ) + assert "land on" in text.split(_OPEN_SOURCE_READERS_PHRASE, 1)[1].split(_OPEN_SOURCE_READERS_NEXT, 1)[0], ( + "the rule no longer says to search the tree the change will land on — a search of the " + "checkout alone misses every caller a merge brings in, which is the case it exists for" + ) + + +def test_the_readers_pin_reads_position_and_absence() -> None: + """The controls: deleting the rule and moving it must each redden, and they are not the same + arm — deletion is what a rewrite does, relocation is what a busy edit does.""" + text = (PROMPTS_DIR / "open_source_prompt.md").read_text(encoding="utf-8") + + deleted = text.replace(_OPEN_SOURCE_READERS_PHRASE, "") + assert deleted != text, ( + f"{_OPEN_SOURCE_READERS_PHRASE!r} is pinned but absent, so this arm measured nothing" + ) + assert _fifth_level_section_of(deleted, _OPEN_SOURCE_READERS_PHRASE) is None, ( + "a deleted rule still reads as present, so the pin would not notice its own rule going" + ) + + moved = deleted.rstrip() + f"\n\n{_OPEN_SOURCE_READERS_PHRASE}\n" + found = _fifth_level_section_of(moved, _OPEN_SOURCE_READERS_PHRASE) + assert found is not None, "the relocation arm lost the phrase it moved, not the position" + assert found != _OPEN_SOURCE_READERS_SECTION, ( + f"moving the phrase to the end of the file left it reading as {found!r} — the pin is " + "reading presence, not position" + ) + + # --------------------------------------------------------------------------- the inbound half # # A round of the promotion task is written as four outward steps: find a topic, join it,