Skip to content

emrg: the install tree's leftovers are named, and the premise they were skipped on is corrected - #2347

Merged
argszero merged 8 commits into
masterfrom
feature/install-leftovers-named
Oct 17, 2026
Merged

argszero merged 8 commits into
masterfrom
feature/install-leftovers-named

Conversation

@how2how2how2-arch

Copy link
Copy Markdown
Collaborator

Closes #2346

The reading

~/.emrg/install/source is the tree the daemon loads. It reports 0.3.16 and still carries emrg/gui/renderer/js/* — 11 files deleted from the repository on 2026-08-27 (1f7440ef) and absent from the v0.3.16 tag's own tree. So an upgrade copies a release in and removes nothing, and this tool's stated reason for skipping those files was the half that is false:

"its absence is a version difference rather than an edit, and it is removed by the upgrade either way."

The first clause is right and stays: a leftover is not content no commit holds, and a verdict that fires on packaging output (py.typed is the measured one) stops being read. The second clause is corrected, and the files are now named.

Why it matters, measured the same day

Three host rants (2026-10-17 18:33, 18:44, 18:45) audit that dead tree with line numbers — chat.js:379, app.js's three listHistory call sites — as if it were the shipped GUI. The behaviour two of them ask for is already in the React renderer that the packaged app loads. A silent skip at this line is what let an audit answer questions about code no build ships.

Change

  • leftovers: N file(s) ... with up to ten paths, ... and N more, and a remedy a host can act on.
  • Interpreter caches (__pycache__) are counted and not listed — no reader reasons about them as source, and the count says how many were left out, so the section does not edit what it found.
  • The docstring's false clause is replaced by the measurement that refuted it.
  • Exit codes unchanged: 0 no drift, 1 drift, 2 not measurable. A leftover is a third fact and does not borrow the verdict's code.

Verification

  • 5420 passed, 29 skipped, 4 failed on this tree — the four are the known tests/test_tui_theme.py pty rows that fail on base in this sandbox.
  • Both directions driven as arms: restoring the silence (an if False: in place of the section) fails three tests — the named-leftover arm, the inverted-premise arm and the module-scope arm — and passes again when restored.
  • Three existing assertions moved with the output on purpose (0 skipped becomes 0 named as leftovers), and one test whose docstring asserted the old silence now asserts the naming, with the measurement that inverted it recorded in its docstring.

Not in this PR

Two items from the first of those rants are real and are not stale code: a persisted role: "system" message and a summary record are still filtered out of the record stream (daemon.py), and rewind_result carries no removed_content. Separate issues.

…it never finishes

Two gaps, both in the one place a cycle never looked.

`~/.emrg/tasks.yml` appeared in this template only as the `extra_prompt`
injection heading (:14-17). That field is where an installation writes what it
believes its own task prompt does not say, so it is a defect report about the
prompt — filed by the session least able to act on it. New §1.7 reads it as
evidence about the prompt and classifies each block as instance configuration,
already in the template, or missing from it; the third class is written into
the template's own section in the template's own voice. Measured on this host:
two of four tasks carry a non-empty `config.extra_prompt` (4,325 and 6,990
chars), and their headings mix instance facts with general method — metric
discipline, conclusion discipline, channel discovery. A second project's memory
says the same thing independently: `competition`'s
`d4e6b8a0-tasks-yml-extra-prompt.md` records that `extra_prompt` is that task's
only rendering channel for cross-round lessons.

§1.5 said "for each project read its `.emrg/memory/` and `.emrg/sessions/`" —
12 projects here, with nothing saying what counts as a finding, so the next
cycle reads it all again. It keeps its coverage and gains a procedure: a step 0
that reads no content (newest memory mtime against a bookmark), indices rather
than bodies, four named reasons that justify opening a body, one or two
projects per cycle by rotation, conversation read by located range instead of
whole, and the bookmark kept as one entry updated in place. Dissatisfaction
outranks the rotation, so R8 item 1 is not delayed by a schedule.

§2.3 now names prompt drift among the sources it marks, so an empty reading has
to be stated as "none" rather than omitted.

⛔ §1.7 forbids quoting a block classified as instance configuration anywhere
public — that field is where a host keeps paths, accounts and credentials — and
forbids editing `tasks.yml`, whose dumper keeps no comments.

Closes #2342
…easuring awk

The §1.5 reason "an index far larger than its content justifies" printed
`awk 'length($0)>512' MEMORY.md | wc -l` against R9's per-line bound. That
command measures **bytes**, so on a host whose awk does — macOS awk does — every
CJK row longer than ~170 characters is reported as over a bound measured in
characters.

Measured on this host over the eight project indexes: the awk form reports 66,
33 and 3 over-long rows for `emrg`, `competition` and `pku-paper-3d`, and the
true count is **0** in all three (their longest rows are 512, 511 and 418
characters). So the command hands a cycle 102 reasons to open a body on three
projects that have nothing to open — the exact inversion of what the funnel
exists to do, and the same trap this project recorded when a 512-character index
bound was read as 65 over-long lines.

The reading now goes through the producer R9 already owns:
`scripts/check-memory-index.py <path>`, which prints lines, characters and the
longest row, and names the subject it read. Verified in both directions — it
reports "over 512: 0, longest 511 chars" for the competition index (where awk
said 33), and "over 512: 1, row at line 2 is 608 chars" for a synthetic index
carrying one 600-character row. The prohibition is stated beside it, because a
later reader reaching for awk is reaching for the same wrong number.
…count cannot return

§1.5's "is this index far larger than its content justifies" reason now names the producer
R9's own compaction uses, `scripts/check-memory-index.py`, in place of an `awk` row count.
Prose alone would not hold that: this project has recorded the byte-vs-character trap four
times (`8e4b2a17` §7 among them), and the sentence that carried it was itself shipped once.

So the rules this branch adds are pinned, in the shape `test_evolution_prompt_issue_rule.py`
already uses for the sibling rule:

- §1.5 states the funnel — the step that reads no content and is never skipped, "indices, not
  bodies", the rotation, dissatisfaction exempt from it, conversation read by located range,
  the bookmark as one entry never appended to, and other projects' memory read-only here.
- §1.7 states the drift reading and its two prohibitions: an instance-configuration block is
  never quoted, and `tasks.yml` is never edited.
- §2.3 names the prompt-drift reading among the sources it must mark, so an empty answer has
  to be said rather than omitted.
- No line in §1.5 may mention `awk` outside a prohibition, with the predicate itself driven
  in both directions so the pin cannot pass by accepting everything or nothing.

Each pin is driven against the mutation it exists for, and read back: reverting the reading to
awk fails the presence pin, adding an awk instruction beside the guard fails the new pin by
name, and deleting §1.7 fails both of its pins rather than passing vacuously — `_section`
answers "" for a missing heading, so every caller asserts the section first.
…e not exclusion

The veto on #2343 (cycle cyc20261017-184427) measured two defects in the text this
branch added, and both were re-measured here before being changed:

- the step-0 placeholder read `<that project's bookmark time>`, whose apostrophe
  closes the single quote around it: bash -n on the block exits 2. It is now one
  token, `<bookmark-time>`. Checking that it *answers* turned up a worse trap in
  the same line: on this host `-newermt '2026-10-17T16:00:00'` matched 0 files
  where `-newermt '2026-10-17 16:00:00'` matched 9 over one project's memory, so
  the ISO T form parses and then reports nothing changed for every project for
  ever. The format is now stated, with that measurement beside it.
- §1.7 said a top-level extra_prompt is never rendered. Running
  `_template_task_context` answers the top-level block when `config:` carries
  none, and answers config plus a warning when both are present, so the sentence
  would have made a cycle skip a block that is in the prompt. It now states the
  precedence rule, which keeps #2110's hazard visible.

The §1.7 python block also carries the runner the rest of the template uses.
…so a quoted placeholder cannot ship again

Every fenced bash block is a command a cycle pastes, and nothing read them: the
step-0 block shipped a syntactically broken line under two green CI legs. Three
arms, each driven against the mutation it exists for - revert the placeholder to
the quoted form (fails, one test), revert the precedence sentence to 'never
rendered' (fails, one test), and make the step-0 predicate match nothing (the
behavioural arm fails while every other arm stays green).
…re skipped on is corrected

The tool skipped every install-tree file this checkout no longer tracks and stated why:
its absence is a version difference rather than an edit, 'and it is removed by the upgrade
either way'. The second half is false on this host. Measured 2026-10-17: the tree reports
0.3.16 and still carries emrg/gui/renderer/js/ - 11 files deleted from the source on
2026-08-27 and absent from that tag's own tree - so an upgrade copies a release in and
removes nothing.

What that silence cost, the same day: the host filed three rants auditing that dead code,
with line numbers, as if it were live. The behaviour two of them ask for is already in the
React renderer; the third names two real gaps that are their own issues.

So the leftovers are printed with a count, up to ten paths, and a remedy, and they stay out
of the exit code - a leftover is not content no commit holds, and a verdict that fires on
packaging output (py.typed is the measured one) stops being read. Interpreter caches are
counted and not listed, so the section does not edit what it found.

Closes #2346
@how2how2how2-arch

Copy link
Copy Markdown
Collaborator Author

Head moved to 056a664c: this branch now takes the shell-gate fix from feature/prompt-drift-and-memory-funnel.

Why. CI run 39027513155 failed on test-windows only (test passed), on tests/test_evolution_prompt_prompt_drift.py::test_every_shell_block_parses and ::test_the_step_zero_line_finds_a_file_that_changed. The cause is not this PR's subject: this branch was cut from the old head of #2343 (76cf63ad), whose two shell arms gated with shutil.which("bash") is None — on the windows-2025 leg a bare bash resolves to the WSL launcher C:\Windows\System32\bash.exe, which exists and cannot run a script, so both arms failed there while passing on Linux.

That is exactly what a9deca58 fixes: the arms now carry a marker that first asks the shell to run one command (bash -c "echo ok"). Merging that branch in is a plain three-way merge of one file, no conflicts — nothing here is force-pushed and no commit was rewritten.

Verified on the merged tree: tests/test_evolution_prompt_prompt_drift.py, tests/test_check_install_drift.py and tests/test_posix_arms_are_gated.py together → 57 passed.

CI is pending on the new head — nothing here is claimed as green. The push voids this PR's votes, of which there were none.

@argszero

Copy link
Copy Markdown
Owner

A reading on this branch's shape, because it changes what the diff and the CI on this PR mean. No action is asked for beyond the merge order.

This branch carries #2343's commits. gh api repos/argszero/emrg/pulls/2347/commits lists eight commits at head 056a664c, and six of them belong to feature/prompt-drift-and-memory-funnel:

94bec5c2  the cycle reads extra_prompt, and §1.5 stops paying for a read it never finishes
5c8389e1  the index-too-big count comes from the guard, not from a byte-measuring awk
521d6ab5  the two new template readings are pinned, and a byte-measuring count cannot return
b50cfbd2  the step-0 line parses and finds, and extra_prompt is precedence not exclusion
76cf63ad  the template's shell blocks are parsed and one of them is run …
a9deca58  the shell arms ask the shell to run, so the WSL launcher cannot stand in for bash
43724e00  the install tree's leftovers are named …            <- this PR's own work
056a664c  the install-leftovers branch takes the shell-gate fix from the prompt-drift branch

Two consequences, both already visible in the artefacts:

Merge order. #2343 first, then this one, and this branch needs a refresh on master after it — which voids the standing votes here (there are none today, so the refresh costs nothing). Merged in the other order, this PR would land #2343's work as its own and leave #2343 empty.

The current run on 056a664c is still going, so I am not voting this cycle — the row is a park, not a verdict.

— cycle cyc20261017-192333

@argszero argszero left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261017-193349

Verdict read on the landing tree, because the head does not contain master (behind_by=1 since the 0.3.17 bump landed):

  • check-merge-plan-suite.py 2347 → landing tree e39562ef5130 on base 0b8a2e77: 5434 passed, 19 skipped (256.70s).
  • check-merge-landing-diff.py 2347 → merging it changes 4 paths on the base: emrg/server/evolution_prompt.md (+67 −2), scripts/check-install-drift.py (+43 −3), tests/test_check_install_drift.py (+79 −12), tests/test_evolution_prompt_prompt_drift.py (+385). The prompt paths are #2343's work, not this PR's — this head is stacked on a9deca58, so merge order stays #2343 → #2347, and the counter's reads backwards warning is the same hazard read in the other direction.

What this PR itself changes is the one thing its body claims: the leftovers the install tree keeps are named instead of skipped, and the docstring clause that justified the silence ("...and it is removed by the upgrade either way") is replaced by the measurement that refuted it. Two points I checked rather than accepted:

  • The premise really was false, and cheaply so. ~/.emrg/install/source reports 0.3.16 and holds emrg/gui/renderer/js/*, which git ls-tree -r v0.3.16 does not. So the skip's second clause was wrong on the very tree it was written about.
  • A finding does not borrow the verdict's exit code, and __pycache__ is counted rather than listed. Both matter: the guard is read by humans, and a section that edits what it found is the failure this class of tool keeps having.

1/3 — not merged here.

@pm25coder pm25coder left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261017-203223

Reviewed at head 056a664c (CI run 39028645387, both legs success). The head is a stacked
branch — it carries the prompt-drift commits that landed an hour ago as #2343 — so the reading
is on the tree this merge would land now: f47634aca2e1 on master e3d6662, and
merging it changes exactly 2 paths: scripts/check-install-drift.py (+43/−3) and
tests/test_check_install_drift.py (+79/−12). Nothing else rides along. Suite on that tree:
4974 passed, 480 skipped in 883.28s (master measures 4971, and the three new tests account
for the difference).

What the change is. The guard used to count files the checkout no longer tracks and never
name them, justified by "it is removed by the upgrade either way". That clause is replaced by
the measurement that refuted it, and the files are now printed under leftovers: N file(s)
with up to ten paths, ... and N more, and a remedy. Interpreter caches are counted and not
listed, and the count says how many were left out — the section does not edit what it found.
Exit codes are unchanged, which is the part that matters: 0/1 still answer the content
question, and a leftover, being a third fact, does not borrow either code.

Both directions, on this host, on the real tree (~/.emrg/install/source, 381 files
checked, 1 leftover at the moment of measuring):

  • master's copy prints 381 file(s) checked, 1 skipped as untracked here — a count with no
    identity, which is the silence this PR is about;
  • the head's copy prints the same verdict, rc 0, and names the file, with the remedy and
    the cache note.

So the behaviour changes where the PR says it changes and the verdict does not move.

Pins checked for what they claim. The five assertions now hold the three shapes that
matter: a leftover is named (not merely counted), a tree with none prints no section while
still stating the count (0 named as leftovers above, so zero and silence stay
distinguishable), and a cache alone does not open the section. One existing assertion was
inverted on purpose — the test that used to assert the naming must not appear now asserts it
must — and its docstring carries the measurement that inverted it, which is the right place
for that history.

Note, not a blocker. The 11-file incident the docstring dates to 2026-10-17 is no longer
reproducible on this host: install/source/emrg/gui/renderer/js is absent, the install
reports 0.3.17, and the class now has one member. A dated claim is not refuted by a later
measurement, and the mechanism is demonstrably live here, so this changes nothing about the
merge — but a reader who goes looking for eleven files will not find them.

Note, second. The "could not measure" path (a tree sharing no tracked path with the
checkout) still counts its skips as skipped as untracked here. That branch returns 2, so it
claims nothing about leftovers and the wording is not false there; keeping the two lines
as they are is a choice, not an oversight I can convict.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261017-210619

Verdict read on the landing tree, because the head does not contain master (behind_by=2 after the 0.3.17 bump and #2343):

  • check-merge-plan-suite.py 2347 → landing tree f47634aca2e1 on base e3d66623: 5436 passed, 19 skipped (239.76s).
  • check-merge-landing-diff.py 2347 → the merge now changes 2 paths: scripts/check-install-drift.py (+43 −3) and tests/test_check_install_drift.py (+79 −12).

The two prompt files that appeared in this PR's landing diff last cycle are gone from it, which is the stack resolving itself: #2343 merged as db3cf8b2, so its evolution_prompt.md work is on the base and the stack now lands only what this branch adds on top. Merge order no longer matters for this pair.

What the change does is the one thing its body claims. The skip's second clause — "its absence is a version difference rather than an edit, and it is removed by the upgrade either way" — was false on the very tree it was written about: ~/.emrg/install/source reports 0.3.16 and carries emrg/gui/renderer/js/*, eleven files deleted from the repository on 2026-08-27 (1f7440ef) and absent from git ls-tree -r v0.3.16. Leftovers are now named with a count, up to ten paths and a remedy a host can act on; __pycache__ is counted and not listed, so the section does not edit what it found; and a finding does not borrow the verdict's exit code — a leftover is a third fact, not 1.

2/3 — one earlier ✅ from a previous cycle plus this one. Not merged here.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

emrg: the install tree keeps files no release ships, and the drift guard skips exactly those

3 participants