docs(scheduler-sim-step-verbosity): add approved spec (requirements/design/tasks)

Follow-on to scheduler-sim-repl-verbosity: collapses the REPL's
per-event "[sim] t=... session=..." preamble to print once per
distinct rendered (timestamp, session) run of consecutive echoed
events, instead of once per event, in both step mode and continue
mode's structural-event echo. Requirement 1.3 deliberately keys the
dedup on the rendered, second-granularity timestamp rather than the
raw nanosecond clock value, which ticks on every event and would
otherwise never repeat -- confirmed against a real user-supplied
transcript before writing requirements (see research.md). Requirement
2 preserves event selection, ordering, content, every other REPL
command, and all scheduling decisions/state exactly as
scheduler-sim-repl-verbosity's Requirement 5 already established.

Generated via kiro-spec-quick (single-pass, no approval gates) with
an inline requirements review gate, an inline design review gate, and
an independent task-graph sanity review (one NEEDS_FIXES round,
repaired: added file-size watch bullets to tasks 1.1/1.2 per
design.md's own housekeeping directives).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YAWMhfRJFnmmnG8h97yUDd
This commit is contained in:
Rongsong Shen 2026-09-03 03:53:24 +00:00
parent a51790dcbb
commit 1374540954
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
5 changed files with 260 additions and 0 deletions

View file

@ -0,0 +1,146 @@
# Design Document
## Overview
`repl_print_event_summary()` (`repl_break.c`) prints a `"[sim] t=<ns> (<ISO8601>) session=<N>"` preamble line unconditionally before every echoed event's own content line — this is its only call site, `xce_repl_on_event()`, which already gates WHICH events reach it per `scheduler-sim-repl-verbosity`'s Event Echo Gate (structural events always; routine events only in `step` mode). Because the preamble is a fixed prefix printed on every call regardless of what the previous call printed, an operator watching several consecutive events that land in the same displayed second and session sees that context restated once per event, diluting the actual event content it's meant to introduce.
This feature makes `repl_print_event_summary()` preamble-aware of its own immediately-preceding output: it prints the preamble only when the rendered (second-granularity) timestamp or the displayed session number has changed since the last time a preamble was actually printed; otherwise it prints only the event's content line. No other REPL behavior changes.
**Users**: Operators running `step` or `continue` (structural-event) transcripts through `xce_scheduler --sim <file> --interactive`.
**Impact**: `repl_print_event_summary()` gains a short-circuit at its top; `xce_sched_repl_state_s` gains two small cache fields recording what was last actually printed.
### Goals
- Print the timestamp/session preamble once per distinct (rendered timestamp, session) run of consecutive echoed events, not once per event.
- Base "distinct" on the rendered, second-granularity timestamp and displayed session number — not the underlying raw nanosecond clock value, which changes on every event and would otherwise defeat deduplication entirely.
- Leave every echoed event's own content line, its ordering, and whether it is echoed at all completely unchanged.
### Non-Goals
- Changing the Event Echo Gate's routine/structural classification (`repl_event_is_structural()`) — which events reach `repl_print_event_summary()` at all is unaffected.
- Changing the Round Rollup / collapsing behavior `continue` mode already uses for routine events and per-round decision summaries.
- Changing decision output (`sim_print_decision`/`sim_print_decision_gate`), which has no preamble of its own and is untouched.
- Reformatting the preamble's own content (timestamp precision, session-number convention) — only whether it is printed, not what it looks like when it is.
## Boundary Commitments
### This Spec Owns
- The preamble-print decision inside `repl_print_event_summary()`'s single call site.
- Two new cache fields on `xce_sched_repl_state_s` (`repl_internal.h`) recording the last preamble actually printed.
### Out of Boundary
- The Event Echo Gate's classification logic (`repl_event_is_structural()`, `repl_break.c`) — unmodified, this spec only changes what happens after an event has already been selected for echoing.
- The Round Rollup / collapsing logic (`repl_rollup.c`) and `sim_print_decision_gate()` (`sim_loop.c`) — both unmodified.
- Every REPL command's parsing, dispatch, or output other than this one preamble line.
### Allowed Dependencies
- `xce_sched_repl_state_s` (`repl_internal.h`, existing) — the two new cache fields live here, alongside this file's other REPL-session-scoped state.
- `repl_format_ts_human()` (`repl_fmt.c`, existing, from `scheduler-sim-repl-verbosity`) — reused as-is to render the human-readable timestamp string used for the dedup comparison; not modified.
### Revalidation Triggers
- If `repl_format_ts_human()`'s rendered granularity or format ever changes, re-verify the dedup key still reflects what an operator actually sees as "the same displayed context."
- If `repl_print_event_summary()` ever gains a second call site, revisit whether a single "last printed" cache still correctly models "immediately preceding echoed event" across both sites.
## Architecture
### Existing Architecture Analysis
`repl_print_event_summary()` is `static` and has exactly one caller, `xce_repl_on_event()` (`repl_break.c`), itself reached only when `sim->interactive` and `sim->repl_state != NULL` — both already guaranteed by the time this function runs, so no new NULL-handling is introduced. The function currently: (1) unconditionally builds and prints the preamble, then (2) switches on `ev->type` to print the event's own content. This design touches only step (1); step (2) and the surrounding gate in `xce_repl_on_event()` are unchanged.
`xce_sched_repl_state_s` already carries several REPL-session-scoped caches added by prior tasks (the Round Rollup's collapse-state fields, `units_mode`) — the two new fields here follow that same established pattern: session-lifetime state, zero-initialized by `repl_state_ensure()`'s existing `calloc`, with no additional lifecycle code required (an empty string and a `0` session number can never equal a real echoed event's rendered values, so the "no preceding echoed event" case, AC 1.2, is satisfied by zero-initialization alone — the same convention every field in this struct except `units_mode` already relies on).
**Architecture Integration**:
- Selected pattern: extend one existing function's own early logic; no new component, no new file.
- Domain/feature boundaries: strictly inside the REPL Output domain (`repl_break.c`/`repl_internal.h`) `scheduler-sim-repl-verbosity` already established; no other domain (Round Rollup, Resource Formatter, Help/Breakpoint) is touched.
- Existing patterns preserved: `xce_sched_repl_state_s` as the single home for REPL-session-scoped display state; `repl_format_ts_human()` as the sole human-timestamp renderer.
- New components rationale: none — this is a localized behavior change to one existing function plus two state fields, not a new component.
- Steering compliance: `src/scheduler/CLAUDE.md`'s 800-line-per-file cap applies to `repl_break.c`, which is already at 949 lines (an accepted residual from `scheduler-sim-repl-verbosity` task 9.2). This spec's addition is small (a handful of lines inside one existing function), but implementation must re-check the file's line count after landing it and, per this file family's established practice, empirically test extracting `repl_print_event_summary()` (and its Event Echo Gate neighbors) into a new file if the total meaningfully exceeds the residual already accepted — not a decision to make speculatively before the actual diff size is known.
## File Structure Plan
### Modified Files
- `src/scheduler/src/repl_break.c` — `repl_print_event_summary()` gains a preamble-print decision at its top: render the candidate human timestamp and displayed session number, compare against `rs->last_echo_ts_human`/`rs->last_echo_session`; print the preamble and update both cache fields only when either differs (or the cache is still at its zero-initialized default), otherwise skip straight to the event's own content switch.
- `src/scheduler/src/repl_internal.h` — `xce_sched_repl_state_s` gains `last_echo_ts_human` (fixed-size char buffer, same size as the existing `repl_format_ts_human()` output buffers elsewhere in this file family) and `last_echo_session` (`uint64_t`), both doc-commented as this feature's cache, zero-initialized by the existing `repl_state_ensure()` allocation path with no additional lifecycle code.
### New/Modified Test Files
- `src/scheduler/testsuite/test_repl_event_echo.c` (extend, existing home for Event Echo Gate behavior this feature builds directly on top of) — the new preamble-dedup tests (see Testing Strategy). This file is at 705 lines going into this spec; re-check its line count after landing these tests and split into a new `test_repl_step_verbosity.c` (following this file family's established "duplicate fixture helpers, don't share" convention — see `scheduler-sim-repl-verbosity` design.md's own Corrections for the precedent) if it approaches this project's ~1000-line split trigger, rather than growing past it.
## Requirements Traceability
| Requirement | Summary | Components | Interfaces | Flows |
|-------------|---------|------------|------------|-------|
| 1.1 | Omit preamble when rendered timestamp+session unchanged | Event Echo Preamble Deduplicator | `repl_print_event_summary()` | n/a |
| 1.2 | Print preamble when rendered timestamp+session changed, or no prior echo | Event Echo Preamble Deduplicator | `repl_print_event_summary()` | n/a |
| 1.3 | Dedup key is rendered (second-granularity) timestamp + session, not raw ns | Event Echo Preamble Deduplicator | `repl_print_event_summary()` | n/a |
| 1.4 | Event content/ordering/echo-selection unaffected | Event Echo Preamble Deduplicator | `repl_print_event_summary()` | n/a |
| 2.1 | No event newly suppressed or added | Out of Boundary (Event Echo Gate unmodified) | n/a | n/a |
| 2.2 | Ordering preserved | Event Echo Preamble Deduplicator | `repl_print_event_summary()` | n/a |
| 2.3 | Every other command's behavior unchanged | Out of Boundary | n/a | n/a |
| 2.4 | No change to scheduling decisions/state | Out of Boundary (display-only change) | n/a | n/a |
## Components and Interfaces
| Component | Domain/Layer | Intent | Req Coverage | Key Dependencies (P0/P1) | Contracts |
|-----------|--------------|--------|---------------|---------------------------|-----------|
| Event Echo Preamble Deduplicator | REPL / repl_break.c | Print the timestamp/session preamble once per distinct rendered context instead of once per event | 1.1-1.4, 2.2 | `repl_format_ts_human()` (P0), `xce_sched_repl_state_s` (P0) | State |
### REPL Output (repl_break.c)
#### Event Echo Preamble Deduplicator
| Field | Detail |
|-------|--------|
| Intent | Suppress the timestamp/session preamble on an echoed event when it would exactly restate the immediately preceding echoed event's rendered context |
| Requirements | 1.1, 1.2, 1.3, 1.4, 2.2 |
**Responsibilities & Constraints**
- Computes the candidate human-readable timestamp via the existing `repl_format_ts_human(xce_core_now(&sim->core), ...)` call already present in `repl_print_event_summary()`, and the candidate displayed session number (`rs->session_count + 1`, the existing expression already used) — no new value sources.
- Compares the candidate pair against `rs->last_echo_ts_human`/`rs->last_echo_session`. Prints the preamble and updates both cache fields when they differ, or when the cache is still at its zero-initialized "never printed" default (empty string / session `0`, values no real echoed event can legitimately produce). Skips the preamble entirely, with no cache update, when they match (Requirement 1.1).
- The comparison is on the RENDERED values only — the underlying raw nanosecond `xce_core_now()` return value is never part of the dedup key, since it changes on every event and two events sharing the same displayed second still have different raw values (Requirement 1.3).
- The event's own content line (the existing `switch (ev->type)` block) is printed unconditionally regardless of the preamble decision — this component only ever suppresses the preamble, never the content (Requirement 1.4, 2.2).
**Dependencies**
- Inbound: `repl_print_event_summary()` (repl_break.c, existing) (P0)
- Outbound: `repl_format_ts_human()` (repl_fmt.c, existing) (P0)
**Contracts**: Service [ ] / API [ ] / Event [ ] / Batch [ ] / State [x]
##### State Management
- State model: two new fields on `xce_sched_repl_state_s` — `last_echo_ts_human` (the last rendered human timestamp actually printed in a preamble) and `last_echo_session` (the last displayed session number actually printed).
- Persistence & consistency: in-memory, scoped to one REPL session, written only by this component, read only by this component. Zero-initialized by `repl_state_ensure()`'s existing `calloc` — no dedicated default-assignment step is needed (unlike `units_mode`'s documented exception in `scheduler-sim-repl-verbosity`), because the zero defaults (empty string, session `0`) can never collide with a real echoed event's rendered values.
- Concurrency strategy: none required — single-threaded sim driver, same as every other REPL state field.
**Implementation Notes**
- Integration: no change to `repl_print_event_summary()`'s call site or signature — purely internal to the function body.
- Validation: covered entirely by the child-process stdout-capture technique `test_repl_event_echo.c` already establishes for Requirement 1 (Event Echo Gate) — no new test harness needed.
- Risks: `repl_break.c`'s existing 949-line accepted residual (see Architecture Integration above) — watch, don't pre-split.
## Data Models
### Domain Model
This feature adds no new domain concept — it extends the existing "REPL session-scoped display state" already modeled on `xce_sched_repl_state_s` (`repl_internal.h`), the same struct the Round Rollup and Resource Value Formatter components added fields to in `scheduler-sim-repl-verbosity`.
### Logical Data Model
| Field | Type | Purpose |
|-------|------|---------|
| `last_echo_ts_human` | `char[32]` | The human-readable timestamp last actually printed in an event's preamble (same buffer size `repl_format_ts_human()`'s other call sites already use) |
| `last_echo_session` | `uint64_t` | The displayed session number last actually printed in an event's preamble |
**Consistency & Integrity**: both fields reset together only at REPL session start, via `repl_state_ensure()`'s existing `calloc` zero-initialization — no explicit reset code path exists or is needed, since no other event in this struct's lifecycle should cause "what was last displayed" to become stale mid-session.
## Error Handling
### Error Strategy
No new failure modes: this feature performs only in-memory string/integer comparisons and a `fprintf` that already exists at this call site — no allocation, no I/O beyond the pre-existing `stdout` write.
### Monitoring
No new logging — the existing `[sim] ...` stdout convention is the only output surface, and this feature only changes how often one of its existing lines appears.
## Testing Strategy
### Integration Tests (extend `test_repl_event_echo.c`, child-process output capture — see File Structure Plan for the split-if-needed note)
- Two consecutive echoed events with identical rendered timestamp and session: the second event's preamble line is absent, its content line still appears (1.1, 1.4).
- Two consecutive echoed events whose session differs (a session boundary occurs between them): both events print their own full preamble (1.2).
- Two consecutive echoed events whose raw high-resolution timestamps differ but whose rendered (rounded) timestamp and session are identical: the second event's preamble is still omitted — the critical case distinguishing this feature's actual dedup key from a naive raw-timestamp comparison (1.3).
- The very first echoed event in a fresh REPL session prints its preamble (no prior echo exists to compare against) (1.2).
- A structural event echoed under `continue` mode after a stretch of suppressed routine events: its preamble prints or is suppressed based on the same rendered-context rule, exercising the dedup logic outside `step` mode too (1.1, 1.2).
- `step` mode's full event/content ordering and count are unchanged versus this feature's baseline — regression check alongside this file's existing Event Echo Gate assertions (2.1, 2.2).
### Regression (existing suites, re-run, no new tests)
- `test_repl_execution_control.c` and `test_repl_equivalence.c`: confirm no other REPL command's output changed and the REPL/batch decision-equivalence guarantee still holds (2.3, 2.4) — the same spot-check discipline `scheduler-sim-repl-verbosity`'s task 10.1/10.2 already established for this exact concern.

View file

@ -0,0 +1,29 @@
# Requirements Document
## Introduction
xce_scheduler's `--interactive` simulation REPL echoes a timestamp/session preamble line immediately before every event it prints — in `step` mode for every echoed event, and in `continue` mode for every structural event the already-shipped `scheduler-sim-repl-verbosity` spec still echoes. In practice, many consecutive echoed events share the identical rendered (second-granularity) timestamp and session number, so the operator sees the same context line repeated over and over, burying the actual event content between near-duplicate preambles. This feature collapses that repetition: the preamble prints once per distinct (timestamp, session) context, not once per event, while leaving which events are echoed, their content, their ordering, and every other REPL behavior exactly as `scheduler-sim-repl-verbosity`'s Requirement 5 already established.
## Boundary Context
- **In scope**: the timestamp/session preamble line printed immediately before an echoed event's own content line, in every mode/context where that echoing currently occurs (step mode's per-event echo, and continue mode's structural-event echo already shipped by `scheduler-sim-repl-verbosity`).
- **Out of scope**: which events are echoed, an event's own content line, event ordering, decision output, the Round Rollup/collapsing behavior already shipped for continue mode's routine-event suppression, and any REPL command's behavior other than this one preamble's rendering.
- **Adjacent expectations**: this spec builds directly on `scheduler-sim-repl-verbosity`'s Requirement 5 ("preserve step mode's output outside this feature's explicit exceptions") and Requirement 1 (Event Echo Gate). It narrows that preservation guarantee to allow exactly this one, previously out-of-scope redundancy to change — it does not reopen or relax any other part of that guarantee.
## Requirements
### Requirement 1: Preamble Deduplication Across Consecutive Same-Context Events
**Objective:** As an operator reading a step-by-step or continue-mode structural-event transcript, I want the timestamp/session preamble shown once per distinct context instead of before every single event, so repeated identical context doesn't bury the actual event content.
#### Acceptance Criteria
1. When the REPL echoes an event whose rendered timestamp and session number are identical to the immediately preceding echoed event's rendered timestamp and session number, the REPL shall omit the timestamp/session preamble line for that event.
2. When the REPL echoes an event whose rendered timestamp or session number differs from the immediately preceding echoed event's, or when there is no preceding echoed event, the REPL shall print the full timestamp/session preamble line before that event.
3. The REPL shall determine whether two consecutive echoed events share the same context using each event's rendered, second-granularity human-readable timestamp and its displayed session number — not the underlying raw high-resolution timestamp value, which may differ between two events that share the same displayed second and session.
4. The REPL shall not alter an event's own content line, its position in the output, or whether it is echoed at all — deduplication under this requirement applies only to the preamble line.
### Requirement 2: Preservation of Existing Detail, Ordering, and Behavior
**Objective:** As an operator relying on step mode's full per-event/per-decision detail and continue mode's structural-event echo, I want this feature to change only the repeated preamble, so my existing workflows and expectations about output completeness remain reliable.
#### Acceptance Criteria
1. The REPL shall continue to echo every event that step mode currently echoes, and every structural event that continue mode currently echoes, with no event newly suppressed or newly added as a result of this feature.
2. The REPL shall preserve the exact ordering of echoed events and their content lines.
3. The REPL shall preserve every other REPL command's existing behavior and output unchanged.
4. The REPL shall not alter any scheduling decision or the simulation's underlying state — this feature affects console display only.

View file

@ -0,0 +1,37 @@
# Research & Design Decisions
## Summary
- **Feature**: `scheduler-sim-step-verbosity`
- **Discovery Scope**: Extension (existing REPL Output domain, `scheduler-sim-repl-verbosity`)
- **Key Findings**:
- The redundant preamble is printed unconditionally by `repl_print_event_summary()` (`repl_break.c`), which has exactly one call site (`xce_repl_on_event()`) — no cross-cutting fan-out to design around.
- The raw nanosecond timestamp (`xce_core_now()`) is effectively unique per event (the virtual clock ticks per decoded event via `sim_advance_virtual_clock()`), so any dedup key based on the raw value would almost never actually trigger — this was confirmed directly against a real user-supplied transcript before writing requirements, and materially changed Requirement 1's acceptance criteria (see Decision below).
- `xce_sched_repl_state_s` already has an established, low-risk pattern for adding session-scoped display-only cache fields (the Round Rollup's collapse state, `units_mode`) that this feature reuses without new lifecycle code.
## Research Log
### What actually repeats between consecutive echoed events?
- **Context**: The initial feature description assumed collapsing on "(t, session)" where `t` was the raw timestamp value shown in the preamble.
- **Sources Consulted**: `repl_print_event_summary()` (`repl_break.c:672-682`); a real interactive-session transcript supplied by the user (`--sim`/`--interactive`, session 47) showing several consecutive events.
- **Findings**: In the real transcript, the raw `t=<ns>` value differed on every single line (e.g. `t=1788169162308792367` then `t=1788169162308787761`), while the parenthesized human-readable ISO8601 timestamp and the `session=` number were identical across many consecutive lines.
- **Implications**: A literal "(t, session)" grouping key as originally worded would essentially never collapse anything, since raw `t` never repeats. Requirement 1's acceptance criteria were corrected to key on the rendered, second-granularity human timestamp and the displayed session number — confirmed with the user before writing requirements.md (see Design Decision below).
## Design Decisions
### Decision: Dedup key is the rendered (human timestamp, session) pair, not the raw nanosecond value
- **Context**: Requirement 1.3 needed to state precisely what "same context" means for two consecutive echoed events.
- **Alternatives Considered**:
1. Raw nanosecond timestamp + session — rejected: virtually never repeats between events, so it would satisfy the letter of "dedup" while delivering none of the actual verbosity reduction the feature exists for.
2. Session number alone (ignore timestamp for grouping) — considered but not selected: presented to the user as an option; they chose option 3 below instead.
3. Rendered second-granularity human timestamp + session number — **selected**. Matches what an operator actually perceives as "the same displayed context," and matches what genuinely repeats in real output.
- **Selected Approach**: Cache the last-printed rendered timestamp string and displayed session number on `xce_sched_repl_state_s`; compare before printing a new preamble; print and update the cache only on a change (or on the very first echoed event, naturally covered by the cache's zero-initialized default).
- **Rationale**: Directly addresses the literal, evidence-based redundancy pattern observed in real output, without introducing a new formatting concept.
- **Trade-offs**: None material — this is a strict readability improvement with no behavioral cost; the cache adds 40 bytes to a per-session struct.
- **Follow-up**: None — the design's Testing Strategy already covers the "raw ns differs, rendered value doesn't" case explicitly (the case that motivated this decision).
## Risks & Mitigations
- `repl_break.c` is already at an accepted 949-line residual from `scheduler-sim-repl-verbosity` task 9.2; this feature's addition is small but non-zero — mitigated by design.md's explicit "watch, don't pre-split" note and the established empirical-test-before-splitting practice this file family already follows.
- `test_repl_event_echo.c` (705 lines) may approach the ~1000-line split trigger once this feature's tests land — mitigated by design.md's File Structure Plan flagging a proactive split into `test_repl_step_verbosity.c` if needed, following the exact precedent already set for sibling test files in `scheduler-sim-repl-verbosity`.
## References
- `.kiro/specs/scheduler-sim-repl-verbosity/design.md` — the sibling spec this one extends; source of the file-size-cap precedent and the `xce_sched_repl_state_s` state-field convention reused here.

View file

@ -0,0 +1,22 @@
{
"feature_name": "scheduler-sim-step-verbosity",
"created_at": "2026-09-03T03:00:41Z",
"updated_at": "2026-09-03T03:35:00Z",
"language": "en",
"phase": "tasks-generated",
"approvals": {
"requirements": {
"generated": true,
"approved": true
},
"design": {
"generated": true,
"approved": true
},
"tasks": {
"generated": true,
"approved": true
}
},
"ready_for_implementation": true
}

View file

@ -0,0 +1,26 @@
# Implementation Plan
- [ ] 1. Core: event echo preamble deduplication
- [ ] 1.1 Write failing tests for preamble deduplication across consecutive echoed events
- Cover: two consecutive echoed events with identical rendered timestamp and session omit the second's preamble; two consecutive events with a different session both print their own preamble; two consecutive events whose raw high-resolution timestamps differ but whose rendered timestamp and session are identical still dedupe; the first echoed event in a fresh REPL session always prints its preamble; a structural event echoed under continue mode after a stretch of suppressed routine events follows the same rule; every event's own content line and ordering remain present and unaffected regardless of the preamble decision
- Observable: the new test cases fail against today's REPL, which prints the timestamp/session preamble unconditionally before every echoed event
- After adding the new test cases, check `test_repl_event_echo.c`'s resulting line count against this project's ~1000-line split trigger (design.md's File Structure Plan); if approached or exceeded, split the preamble-dedup tests into a new `test_repl_step_verbosity.c` (duplicating shared fixture helpers per this file family's established convention) and register the new file's test target in `testsuite/meson.build`
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.2_
- _Boundary: Event Echo Preamble Deduplicator_
- [ ] 1.2 Implement the preamble deduplication cache and print-skip logic
- Add the last-printed rendered timestamp and session cache to the REPL session state, zero-initialized alongside the state's existing fields; update the event echo path to compare the candidate rendered timestamp and session against the cache, printing and updating it only when either has changed (or no preamble has been printed yet this session), while always still printing the event's own content line unconditionally
- Observable: all tests from 1.1 pass; a captured transcript shows exactly one preamble line per distinct rendered (timestamp, session) run of consecutive echoed events, with every event's own content line still present
- After landing the implementation change, re-check `repl_break.c`'s line count against its already-accepted 949-line residual (design.md's Architecture Integration); if it meaningfully exceeds that residual, evaluate extracting `repl_print_event_summary()` and its Event Echo Gate neighbors into a new file rather than pre-splitting speculatively
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.2_
- _Boundary: Event Echo Preamble Deduplicator_
- [ ] 2. Validation: regression and real-transcript verification
- [ ] 2.1 Verify every other REPL command and mode's existing behavior remain unchanged outside the preamble's own deduplication
- Confirm which events are echoed, event ordering, event content, and every other REPL command's behavior and output match their pre-feature baseline exactly except for the preamble line's now-conditional printing; confirm no scheduling decision or simulation state changed as a result of this feature
- Observable: the existing REPL test suite's pre-existing assertions all still pass, updated only where a specific assertion pinned the old always-print-preamble shape for two events that legitimately share rendered context (each such update documented with which acceptance criterion justifies it), and a step-mode transcript comparison against the pre-feature baseline shows no textual difference outside the preamble line's presence or absence
- _Requirements: 2.1, 2.3, 2.4_
- [ ] 2.2 Run the full scheduler test suite and re-verify against a realistic multi-event transcript
- Observable: the full test suite passes with no regressions, and a step/continue pass over a transcript containing several consecutive same-context events shows the preamble collapsing to one line per distinct context while every event's own content line still appears in order
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.1, 2.2, 2.3, 2.4_