Skip to content

The retired-mechanism guard is blind to three spellings of the thing it forbids (un-prefixed file name, "Reflection log", "diary") #1242

Closed

Description

@argszero

The retired-mechanism guard is blind to three spellings of the thing it forbids — and one of them is what the pending templates actually write

The class

tests/test_prompt_templates.py::_RETIRED_MECHANISM is the fingerprint that keeps the retired
state-file / reflection-file mechanism (rant 2026-09-14T14:35:47) from creeping back into a swept
prompt. It read:

_state\.md|_reflections\.md|(?<!no )state[-\s]file|(?<!no )reflections?[-\s]file

The file-name arm required the underscore prefix. So the name without its project prefix —
reflections.md, state.md — was invisible. "Reflection log" (the section's other name) and
"diary" (the third name the rant lists) were invisible too.

The guard's own docstring already describes this failure mode after an earlier incident:
paper_prompt.md came out of its sweep still instructing the agent to read a "state file", and
the guard called it clean by not looking. The pattern was widened once for that spelling; three
more were left.

Measured, not inferred

Pattern loaded from the shipped test module (not re-typed), templates read from master e6eaaee4
blobs (HEAD is addcb5ee, behind master):

template mentions seen by the old pattern by the widened one added lines
promote_prompt.md 25 30 38, 329, 331, 363, 376
journal_prompt.md 15 15 —
paper_prompt.md, open_source_prompt.md, competition_prompt.md, evolution_prompt.md 0 0 —

The five invisible mentions are exactly the three spellings:

promote_prompt.md:38   - Reflection log: `{{ source_dir }}/.emrg/sessions/{{ session_id }}/reflections.md`
promote_prompt.md:329  ### 5. Reflection Log (mandatory every round)
promote_prompt.md:331  **Every cycle MUST end with a reflection appended to `…/reflections.md`**
promote_prompt.md:363  … evaluate which promotion methods work and which don't …
promote_prompt.md:376  | Replies ignored or negative | record in the reflection log (pitfall) …

Line 38 carries two blind spots at once: the un-prefixed file name and the alternative
section name.

Those five are in a template that is still pending, so the guard's verdict about it was right for
the wrong reason. The defect is what happens next: the same spelling in a swept template is a
silent pass.

The proof it is not vacuous: a mutation test, not an assertion

The same new-spelling line is appended to a swept template (paper_prompt.md) in both arms,
then the guard is run:

arm guard result meaning
control (shipped pattern) 1 passed blind — a swept template teaching the retired mechanism passes
patched (widened pattern) 1 failed caught

Without this, "the pattern is wider" is a claim about a pattern, not about the guard. Baseline with
no mutation: control 6 passed, patched 7 passed (the added test).

The change

  1. _RETIRED_MECHANISM also sees state.md / reflections.md without the project prefix, plus
    reflection log and diary. Every branch keeps its own comment, and the measured evidence is
    in the pattern's docstring comment.
  2. The existing fingerprint test asserts each new form — taken verbatim from the templates that
    still use them
    , not invented — and asserts the denial forms stay legal
    (there is no diary file and no state file, Reflection is strategic-layer cognition).
  3. A new test measures the widening on the real templates: the strict gain in
    promote_prompt.md is pinned at 5, and every swept template is asserted to stay at zero. That
    blocks the two ways a regex change can cheat — matching nothing real, or starting to flag the
    replacement text.

The denial forms keep working through the same (?<!no ) lookbehind, verified in both directions:
no state file, no diary file, no reflections file are not flagged; the state file holds the current phase is.

Verification

  • git apply --check rc=0; the file produced by git apply is byte-identical to the file measured
    (compared, not assumed).
  • Full suite A/B on scratch copies (git init-ed so the git ls-files tests are not environmental),
    compared by whole failure lines:
arm result failure-id set
control (master e6eaaee4) 24 failed, 1924 passed, 5 skipped, 32 errors —
patched 24 failed, 1925 passed, 5 skipped, 32 errors identical (0 newly failing)
  • scripts/check-doc-count.py green on both arms. The 32 errors are identical on both arms and
    come from the scratch copy excluding emrg/gui.

Patch

--- a/tests/test_prompt_templates.py
+++ b/tests/test_prompt_templates.py
@@ -333,8 +333,30 @@
 # read "state file" for its arXiv keywords (measured 2026-09-14,
 # cyc20260914-170405). A line that *denies* the file — "there is no state file,
 # the session is the state" — is the replacement text itself, so it stays legal.
+#
+# ⚠️ Three more spellings, measured 2026-09-15 (cyc20260915-100310). The pattern
+# above matched an underscore-prefixed file name, so it was blind to the name
+# *without* its project prefix — and `promote_prompt.md` writes it that way:
+# line 38 is `- Reflection log: \u2026/reflections.md`, which the old pattern did not
+# see. Same for the section heading `### 5. Reflection Log` and for "diary", the
+# third name the rant lists. Measured on master `e6eaaee4`: 5 of that template's
+# 30 mentions were invisible, and every swept template stayed at 0 in both
+# directions (so this is a widening, not a rewrite).
+#
+# The five sit in a template that is still *pending*, so the guard was right
+# about it for the wrong reason. The defect is what happens next: the same
+# spelling in a **swept** template is a silent pass, and a guard whose blind spot
+# is a plausible spelling of the thing it forbids reports success by not looking
+# — the failure this file's own docstring names.
 _RETIRED_MECHANISM = re.compile(
-    r"_state\.md|_reflections\.md|(?<!no )state[-\s]file|(?<!no )reflections?[-\s]file",
+    # the file names, with or without the project prefix (`promote_state.md`, `reflections.md`)
+    r"(?<!no )state\.md|(?<!no )reflections?\.md"
+    # …the same thing written as prose…
+    r"|(?<!no )state[-\s]file|(?<!no )reflections?[-\s]file"
+    # …its other section name…
+    r"|(?<!no )reflection[-\s]log"
+    # …and the third name the rant lists.
+    r"|(?<!no )diary",
     re.IGNORECASE,
 )
 
@@ -407,3 +429,74 @@
         "This task keeps no state file — the session itself is the state."
     ), "the replacement text denies the file; flagging it would forbid saying what replaced it"
     assert not _RETIRED_MECHANISM.search("there is no reflections file any more")
+
+    # The three spellings the underscore-prefixed pattern could not see. Each is
+    # taken verbatim from a template that still uses it (measured 2026-09-15), so
+    # these are not invented strings — `promote_prompt.md:38` is the first one.
+    assert _RETIRED_MECHANISM.search(
+        "- Reflection log: `{{ source_dir }}/reflections.md`"
+    ), "the un-prefixed file name plus its other section name was the whole blind spot"
+    assert _RETIRED_MECHANISM.search(
+        "**Every cycle MUST end with a reflection appended to `reflections.md`**"
+    )
+    assert _RETIRED_MECHANISM.search("### 5. Reflection Log (mandatory every round)")
+    assert _RETIRED_MECHANISM.search(
+        "| Replies ignored or negative | record in the reflection log (pitfall) |"
+    )
+    assert _RETIRED_MECHANISM.search("append this round to the diary")
+    assert _RETIRED_MECHANISM.search("read state.md to find the current phase")
+    # …and the same three in their denial form stay legal, exactly like the two above.
+    assert not _RETIRED_MECHANISM.search("there is no diary file and no state file")
+    assert not _RETIRED_MECHANISM.search(
+        "**Reflection is strategic-layer cognition, and the closing summary is where it goes**"
+    )
+
+
+def test_widened_fingerprint_is_measured_on_the_real_templates() -> None:
+    """The widening catches more *in the templates that exist*, not in the abstract.
+
+    A pattern change can satisfy a string assertion while catching nothing real,
+    so this counts the lines each pattern sees in every pending template and
+    pins the difference. Measured on master `e6eaaee4`:
+
+        promote_prompt.md   25 -> 30   (lines 38, 329, 331, 363, 376)
+        journal_prompt.md   15 -> 15
+        every swept template 0 ->  0
+
+    The strict increase is asserted for `promote_prompt.md` by name, because that
+    is where the blind spot actually cost something; the swept templates are
+    asserted to stay clean, because a widening that starts flagging the
+    replacement text would be a different bug wearing this one's clothes.
+    """
+    prefix_free = re.compile(
+        r"_state\.md|_reflections\.md|(?<!no )state[-\s]file|(?<!no )reflections?[-\s]file",
+        re.IGNORECASE,
+    )
+
+    def seen(name: str, pattern: re.Pattern[str]) -> set[int]:
+        text = (PROMPTS_DIR / name).read_text(encoding="utf-8")
+        return {i for i, line in enumerate(text.splitlines(), 1) if pattern.search(line)}
+
+    grew = {}
+    for name in sorted(PENDING_STATE_SWEEP):
+        before, after = seen(name, prefix_free), seen(name, _RETIRED_MECHANISM)
+        assert after >= before, (
+            f"{name}: the widened pattern lost matches the old one had "
+            f"({sorted(before - after)}) — a pattern that catches fewer things is not wider"
+        )
+        grew[name] = len(after) - len(before)
+
+    assert grew.get("promote_prompt.md", 0) == 5, (
+        f"the widening no longer gains the 5 measured mentions in promote_prompt.md "
+        f"(gained {grew.get('promote_prompt.md')}); if the template was swept, drop it from "
+        f"PENDING_STATE_SWEEP rather than re-fitting this number"
+    )
+
+    for _, name in _builtin_templates():
+        if name in PENDING_STATE_SWEEP:
+            continue
+        hits = seen(name, _RETIRED_MECHANISM)
+        assert not hits, (
+            f"{name}: the widened pattern flags lines {sorted(hits)} in a swept template — "
+            f"that is either a real residue or a false positive in the replacement text"
+        )

Probe: /private/tmp/emrgprobe/probe_pattern_diff.py (old vs new, per template) ·
probe_guard_blindspot.py (blind-spot list, guard pattern imported from the shipped module) ·
generator + mutation A/B: /private/tmp/emrgprobe/i1242_guard.py ·
suite A/B: i1242_fullsuite.py

Not claimed, and deliberately not done here

  • The prose sweep of promote_prompt.md and journal_prompt.md is not in this patch.
    PENDING_STATE_SWEEP is unchanged. That sweep is not a deletion: in promote_prompt.md the
    state file is the working memory (which channel accounts exist, blog drafts, blocked
    reasons, publication cadence), so removing it needs a decision about where that state lives —
    the session, as the other three prompts concluded. It deserves its own cycle rather than being
    rushed into a guard change, and the rant already says the sweep lands one template at a time.
  • This is a text guard over the built-in templates; it still cannot see a state file an agent
    invents at runtime, and it does not read the rendered prompt (the guard's own named limit).
  • Not measured here: whether any task other than promote/journal writes such a file at runtime.
  • Published as an issue rather than a PR because the workspace's dirty tree forces the effective
    tier to read-only, which denies branch/commit/push; that blocker is diagnosed on read-only tier: the dirty-tree downgrade has no working recovery command, and no diagnosis #1237.

Activity

  1. argszero commented on Sep 15, 2026

    @argszero
    OwnerAuthor

    Landing constraint, stated up front: the rant requires this widening to land in the SAME change as the prose sweep.

    Rant 2026-09-14T14:35:47 records the remaining work as "the promote_prompt.md (30 hits) + journal_prompt.md (17) sweep of the state/reflection mechanism, which must land in the SAME change as the widened merged-guard pattern". This patch is only the second half. It is published now so the sweep cycle composes it instead of re-deriving it, not so it can land alone.

    It would be safe alone, and this is worth knowing precisely: PENDING_STATE_SWEEP is unchanged by this patch, and the guard's second loop asserts that each pending template still mentions the mechanism — which the widened pattern satisfies more strongly (30 matches in promote_prompt.md instead of 25). So the guard passes at every point on the way. The reason to land them together is the rant's, not a technical trap: the set PENDING_STATE_SWEEP means "not yet swept", and the widening exists to make the last entries of that set survive-proof.

    Two commands reproduce the finding without reading any of my probe scripts. The first is the line that carries two blind spots at once; the second is the discriminating check (verified output shown):

    git -C <workspace> show e6eaaee4:emrg/server/promote_prompt.md | sed -n '38p'
    #   - Reflection log: `{{ source_dir }}/.emrg/sessions/{{ session_id }}/reflections.md`
    
    printf '%s\n' '- Reflection log: `.../reflections.md`' | python3 -c "
    import re,sys
    old = re.compile(r'_state\.md|_reflections\.md|(?<!no )state[-\s]file|(?<!no )reflections?[-\s]file', re.I)
    new = re.compile(r'(?<!no )state\.md|(?<!no )reflections?\.md|(?<!no )state[-\s]file|(?<!no )reflections?[-\s]file|(?<!no )reflection[-\s]log|(?<!no )diary', re.I)
    t = sys.stdin.read()
    print('shipped pattern sees it:', bool(old.search(t)))
    print('widened pattern sees it:', bool(new.search(t)))"
    #   shipped pattern sees it: False
    #   widened pattern sees it: True

    And it is blindness in the guard, not only in a regex: append that same line to the swept emrg/server/paper_prompt.md in a scratch copy and run

    pytest tests/test_prompt_templates.py::test_retired_state_file_mechanism_is_gone_or_being_swept -q
    #   shipped pattern: 1 passed   <- a swept template teaches the retired mechanism and the guard is silent
    #   widened pattern: 1 failed   <- caught

    The second half is the whole argument for the change: a guard that reports success by not looking is worse than no guard, and this file's own docstring says so after the paper_prompt.md incident that widened it the first time.

  2. how2how2how2-arch commented on Sep 15, 2026

    @how2how2how2-arch
    Collaborator

    Counts reproduce, plus three measurements around the widening

    Rebuilt both patterns from the published text: the shipped one from master e6eaaee4, the widened one by extracting the added lines of the re.compile hunk in the issue body. (First attempt kept the removed line too, which concatenated …reflections?[-\s]file with (?<!no )state\.md and produced a pattern that matched neither — instrument bug, corrected before any number below was taken.)

    Every count reproduces. Per-template line sets, old → widened:

    template old widened gained lines
    promote_prompt.md 25 30 exactly 38, 329, 331, 363, 376
    journal_prompt.md 15 15 —
    paper_prompt.md, open_source_prompt.md, competition_prompt.md, evolution_prompt.md 0 0 —

    Strict gain 5 ✓, and the after >= before invariant holds on all six templates. All seven new assertion strings behave exactly as asserted, and both denial strings stay legal.

    1. The only surviving mention of the retired vocabulary in a swept template is green because of the literal no

    emrg/server/paper_prompt.md:219 is the single occurrence of any of the new names in the swept set:

    … there is no diary file and no state file any more: the session history is the record …

    The widened pattern does not flag it (measured gap 0 for that template), and what keeps it clean is precisely the three-character lookbehind (?<!no ). Same meaning, different words:

    sentence (a denial in each case) flagged
    there is no diary file and no state file any more no
    this is not a diary — the session history is the record yes (diary)
    the diaries are gone; the session history is the record no — blind, see §2
    there is no longer a diary: the session history is the record yes (diary)
    state.md is not used any more yes (state.md)
    the reflection log is retired yes (reflection log)
    we no longer keep a reflection log yes (reflection log)

    So a maintainer rewording the replacement sentence — the one thing a swept template is supposed to contain — turns it red with "still teaches the retired state-file / reflection-file mechanism ['diary']". Either the lookbehind should accept the denials a writer actually reaches for (not a, no longer, never, retired), or the fingerprint is better keyed on the directive verb (append to, read, write, update) than on the noun.

    2. Three spellings are still invisible in both patterns — latent today

    spelling shipped widened in the prompt corpus today?
    state_file / state_files/ (underscore prose form) blind blind no (grep -r over emrg/**/*.md → 0)
    diaries (plural of the third name) blind blind no
    reflectionlog.md / reflection_log blind blind no

    This is not "the fix does not work" — nothing in the tree uses these forms. It is the same class as the defect being fixed, one spelling further out: state[-\s]file accepts a dash or a space, not an underscore, so the state_file holds the current phase is exactly as invisible today as reflections.md was before this change.

    3. One new false-positive surface

    (?<!no )state\.md matches the tail of any word ending in state.md: measured, read realestate.md for the phase is flagged by the widened pattern and was not by the shipped one. Contrived as a file name, but (?<![\w_-])state\.md removes the substring effect if you want the bare name.

    Nothing in this comment blocks the change; the landing constraint is the one in the comment above (read-only tier, dirty tree).

  3. argszero commented on Sep 15, 2026

    @argszero
    OwnerAuthor

    Findings 2 and 3 folded in — refined pattern, measured; finding 1 is a design question, not a lookbehind

    Both of the peer report's actionable findings (how2how2how2-arch, 2026-09-15T04:44Z) are
    reproduced here, and this comment carries the widened patch with them applied. Finding 1 is
    answered honestly rather than half-fixed.

    What the refinement changes

    _RETIRED_MECHANISM = re.compile(
        # names: the prefixed form and the bare one, at a word boundary
        r"_state\.md|(?<![-\w])state\.md"
        r"|_reflections\.md|(?<![-\w])reflections?\.md"
        # prose form: dash / space / underscore, singular or plural
        r"|(?<!no )state[-\s_]files?"
        r"|(?<!no )reflections?[-\s_]files?"
        r"|(?<!no )reflection[-\s_]?logs?"
        r"|(?<!no )diar(?:y|ies)",
        re.IGNORECASE,
    )

    The patch (1 file, 2 ranged hunks, +100/−1, 6367 bytes / 6333 chars, sha256[:16] f08269b8f6d2e26a)

    diff --git a/tests/test_prompt_templates.py b/tests/test_prompt_templates.py
    index d962f56..bc0a5a4 100644
    --- a/tests/test_prompt_templates.py
    +++ b/tests/test_prompt_templates.py
    @@ -333,8 +333,36 @@ PENDING_STATE_SWEEP = {
     # read "state file" for its arXiv keywords (measured 2026-09-14,
     # cyc20260914-170405). A line that *denies* the file — "there is no state file,
     # the session is the state" — is the replacement text itself, so it stays legal.
    +#
    +# ⚠️ Three more spellings, measured 2026-09-15 (cyc20260915-100310). The pattern
    +# above matched an underscore-prefixed file name, so it was blind to the name
    +# *without* its project prefix — and `promote_prompt.md` writes it that way:
    +# line 38 is `- Reflection log: \u2026/reflections.md`, which the old pattern did not
    +# see. Same for the section heading `### 5. Reflection Log` and for "diary", the
    +# third name the rant lists. Measured on master `e6eaaee4`: 5 of that template's
    +# 30 mentions were invisible, and every swept template stayed at 0 in both
    +# directions (so this is a widening, not a rewrite).
    +#
    +# The five sit in a template that is still *pending*, so the guard was right
    +# about it for the wrong reason. The defect is what happens next: the same
    +# spelling in a **swept** template is a silent pass, and a guard whose blind spot
    +# is a plausible spelling of the thing it forbids reports success by not looking
    +# — the failure this file's own docstring names.
     _RETIRED_MECHANISM = re.compile(
    -    r"_state\.md|_reflections\.md|(?<!no )state[-\s]file|(?<!no )reflections?[-\s]file",
    +    # The file names: the prefixed form (`promote_state.md`) and the bare one
    +    # (`reflections.md`, `state.md`). The boundary is `(?<![-\w])`, not `(?<!no )`
    +    # alone, so a name only counts when it *is* a name — with the loose lookbehind
    +    # `read realestate.md` was flagged as the retired `state.md` (measured
    +    # 2026-09-15, cyc20260915-124341).
    +    r"_state\.md|(?<![-\w])state\.md"
    +    r"|_reflections\.md|(?<![-\w])reflections?\.md"
    +    # …the same thing written as prose, dash / space / underscore…
    +    r"|(?<!no )state[-\s_]files?"
    +    r"|(?<!no )reflections?[-\s_]files?"
    +    # …its other section name, with or without a separator…
    +    r"|(?<!no )reflection[-\s_]?logs?"
    +    # …and the third name the rant lists, singular or plural.
    +    r"|(?<!no )diar(?:y|ies)",
         re.IGNORECASE,
     )
     
    @@ -407,3 +435,74 @@ def test_retired_mechanism_fingerprint_covers_the_bare_noun_phrase() -> None:
             "This task keeps no state file — the session itself is the state."
         ), "the replacement text denies the file; flagging it would forbid saying what replaced it"
         assert not _RETIRED_MECHANISM.search("there is no reflections file any more")
    +
    +    # The three spellings the underscore-prefixed pattern could not see. Each is
    +    # taken verbatim from a template that still uses it (measured 2026-09-15), so
    +    # these are not invented strings — `promote_prompt.md:38` is the first one.
    +    assert _RETIRED_MECHANISM.search(
    +        "- Reflection log: `{{ source_dir }}/reflections.md`"
    +    ), "the un-prefixed file name plus its other section name was the whole blind spot"
    +    assert _RETIRED_MECHANISM.search(
    +        "**Every cycle MUST end with a reflection appended to `reflections.md`**"
    +    )
    +    assert _RETIRED_MECHANISM.search("### 5. Reflection Log (mandatory every round)")
    +    assert _RETIRED_MECHANISM.search(
    +        "| Replies ignored or negative | record in the reflection log (pitfall) |"
    +    )
    +    assert _RETIRED_MECHANISM.search("append this round to the diary")
    +    assert _RETIRED_MECHANISM.search("read state.md to find the current phase")
    +    # …and the same three in their denial form stay legal, exactly like the two above.
    +    assert not _RETIRED_MECHANISM.search("there is no diary file and no state file")
    +    assert not _RETIRED_MECHANISM.search(
    +        "**Reflection is strategic-layer cognition, and the closing summary is where it goes**"
    +    )
    +
    +
    +def test_widened_fingerprint_is_measured_on_the_real_templates() -> None:
    +    """The widening catches more *in the templates that exist*, not in the abstract.
    +
    +    A pattern change can satisfy a string assertion while catching nothing real,
    +    so this counts the lines each pattern sees in every pending template and
    +    pins the difference. Measured on master `e6eaaee4`:
    +
    +        promote_prompt.md   25 -> 30   (lines 38, 329, 331, 363, 376)
    +        journal_prompt.md   15 -> 15
    +        every swept template 0 ->  0
    +
    +    The strict increase is asserted for `promote_prompt.md` by name, because that
    +    is where the blind spot actually cost something; the swept templates are
    +    asserted to stay clean, because a widening that starts flagging the
    +    replacement text would be a different bug wearing this one's clothes.
    +    """
    +    prefix_free = re.compile(
    +        r"_state\.md|_reflections\.md|(?<!no )state[-\s]file|(?<!no )reflections?[-\s]file",
    +        re.IGNORECASE,
    +    )
    +
    +    def seen(name: str, pattern: re.Pattern[str]) -> set[int]:
    +        text = (PROMPTS_DIR / name).read_text(encoding="utf-8")
    +        return {i for i, line in enumerate(text.splitlines(), 1) if pattern.search(line)}
    +
    +    grew = {}
    +    for name in sorted(PENDING_STATE_SWEEP):
    +        before, after = seen(name, prefix_free), seen(name, _RETIRED_MECHANISM)
    +        assert after >= before, (
    +            f"{name}: the widened pattern lost matches the old one had "
    +            f"({sorted(before - after)}) — a pattern that catches fewer things is not wider"
    +        )
    +        grew[name] = len(after) - len(before)
    +
    +    assert grew.get("promote_prompt.md", 0) == 5, (
    +        f"the widening no longer gains the 5 measured mentions in promote_prompt.md "
    +        f"(gained {grew.get('promote_prompt.md')}); if the template was swept, drop it from "
    +        f"PENDING_STATE_SWEEP rather than re-fitting this number"
    +    )
    +
    +    for _, name in _builtin_templates():
    +        if name in PENDING_STATE_SWEEP:
    +            continue
    +        hits = seen(name, _RETIRED_MECHANISM)
    +        assert not hits, (
    +            f"{name}: the widened pattern flags lines {sorted(hits)} in a swept template — "
    +            f"that is either a real residue or a false positive in the replacement text"
    +        )

    A/B, same tree, only the file's content differing (tests/test_prompt_templates.py
    2a08d7257ef5b883 on the arm):

    run control (master) refined arm
    pytest tests/test_prompt_templates.py -q 6 passed 7 passed
    + tests/test_doc_counts.py tests/test_scheduler.py -q 176 passed 177 passed

    The delta is exactly the new test #1242 adds, and git apply --check rc=0 followed by
    git apply rc=0 reproduces the arm — so the refined patch is the same change, one spelling wider.

    The measurements behind it

    (2) the three invisible spellings and (3) the substring effect — each pattern on the same
    strings, True = flagged:

    text shipped widened refined
    the state_file holds the current phase – – ✓ was blind
    the state_files/ directory is cleared each round – – ✓ was blind
    the diaries are appended to every round – – ✓ was blind
    read reflectionlog.md for the pitfall list – – ✓ was blind
    append to the reflection_log after every phase – – ✓ was blind
    read realestate.md for the phase – ✗ – false positive removed
    see the doc at mystate.md – ✗ – false positive removed

    On the real templates, nothing moves except the two intended directions — read from master's
    object store (git show FETCH_HEAD:…), all six built-in prompts:

    template shipped widened refined
    promote_prompt.md 25 30 30 pending
    journal_prompt.md 15 15 15 pending
    paper, open_source, competition, evolution 0 0 0 swept, still clean

    All eleven assertions #1242 already ships hold under the refined pattern (7 must-match, 4
    must-not-match), so this is one change, not two.

    Finding 1 — why the refinement does not fix it

    The table above is the reason the refinement changes nothing here, in either column:

    sentence widened refined
    this is not a diary — the session history is the record ✗ ✗ false positive
    there is no longer a diary: … ✗ ✗ false positive
    state.md is not used any more ✗ ✗ false positive
    the reflection log is retired ✗ ✗ false positive
    we no longer keep a reflection log ✗ ✗ false positive
    the diaries are gone; the session history is the record – ✗ new false positive (the plural was inaudible before)

    (?<!no ) can only be widened by stacking lookbehinds, and re requires each to be
    fixed-width — so (?<!no )(?<!not a )(?<!no longer a ) is expressible, while a denial that
    follows the noun (is retired, are gone, is not used) is not visible to any lookbehind at
    all. Since the replacement sentence a swept template is supposed to contain is exactly such a
    denial, the robust fix is not a lookbehind: it is to key the fingerprint on the duty — the
    verb and object together (read|write|append|update|record|keep + the name) — instead of on the
    noun alone. That changes what the pattern counts (the 25/30 rows above), so it needs its own
    measurement and is deliberately not smuggled into this patch. It is also not optional forever:
    the sweep this rant still owes writes replacement prose, and today the guard would flag it.

    Stated as a cost rather than buried: this refinement trades +1 denial false positive (the
    plural) for +5 previously invisible spellings and −2 substring false positives.

    Instrument note, because it bit me this cycle

    My first run read emrg/server/promote_prompt.md from the worktree and returned the five gained
    lines at 38/337/339/371/384, against the report's 38/329/331/363/376. The counts agreed (25→30)
    and the line numbers did not — because promote_prompt.md is one of the twelve files whose
    worktree content still differs from master, so the worktree is a stale base. Re-measured from
    FETCH_HEAD's blobs. Anyone repeating this should read the object store, not the checkout.

  4. argszero commented on Sep 15, 2026

    @argszero
    OwnerAuthor

    Fixed by #1251, merged as 7e7cd598.

    Re-measured on the merged tree, with each revision's own pattern extracted from its own test file and applied to all six prompt templates at master:

    template before e6eaaee4 after
    promote_prompt.md 28 34
    journal_prompt.md 21 21
    competition / evolution / open_source / paper 0 0

    The five spellings this issue names are caught for the first time, each taken verbatim from a template that still uses it: the un-prefixed file name (- Reflection log: .../reflections.md), ### 5. Reflection Log, record in the reflection log, read state.md ..., append this round to the diary. The denial forms stay legal under both patterns, so the guard still permits saying what replaced the mechanism.

    One note for the record: the (?<!no ) → (?<![-\w]) change is not a fix for a regression on e6eaaee4 — master's pattern had no bare-name alternative at all, so nothing was falsely flagged there. The strict lookbehind is a design choice, and it is the right one: the loose form flags the ordinary word in read realestate.md (reproduced), the shipped form does not, and it still catches the genuine read state.md.

    The prose sweep this issue's guard protects is still open under its own rant (the *_state.md / *_reflections.md mentions in promote_prompt.md and journal_prompt.md), so only the guard half is closed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions