academic-army 0.3.9 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,12 +59,37 @@ This is the most important guardrail in the skill. A task brief may be written
59
59
  against stale memory, an earlier snapshot, or a plan that was since executed.
60
60
  Trust the worktree; memory files and task narratives are secondary.
61
61
 
62
+ **Green-suite verification must use a clean worktree.** Before claiming a test
63
+ suite is fully green, stash every uncommitted change (`git stash --include-untracked`),
64
+ rerun the suite, and then restore the stash. An uncommitted patch that fixes or
65
+ masks a failure produces a false-green signal — the suite appears to pass but a
66
+ clean checkout would fail. This is the most common trap in contract-contradiction
67
+ bugs: a work-in-progress patch elsewhere in the tree makes the suite look green,
68
+ hiding the fact that the committed HEAD has a test failure. Always verify
69
+ greenness on a stash-cleaned HEAD. If stashing is impossible because uncommitted
70
+ work must be preserved, run `git stash list` to confirm what is present, run the
71
+ suite anyway, and explicitly report "verified on dirty worktree with <N> stashed
72
+ patches — greenness not guaranteed on clean HEAD."
73
+
62
74
  #### Already-Complete-on-Disk Sequence
63
75
 
64
76
  When the worktree already satisfies the task's stated objective, the task is an
65
77
  **already-complete-on-disk** case — it was executed in a prior session but the
66
- memory/trajectory layer was never advanced. The correct sequence is
67
- verification, not re-implementation:
78
+ memory/trajectory layer was never advanced. Before acting, distinguish two
79
+ sub-cases:
80
+
81
+ - **Already-committed**: the work is on HEAD and the working tree is clean
82
+ relative to it. The task is a verification pass — confirm the on-disk state,
83
+ re-run validation, update memory files.
84
+ - **Uncommitted fix**: the work is in the working tree but not on HEAD (dirty
85
+ working tree, uncommitted changes). The task is a **commit pass** — stage and
86
+ commit the fix, then verify HEAD is green and the tree is clean. Memory-file
87
+ updates are required only when the task, workflow, or surrounding trajectory
88
+ files explicitly track this fix as a deferred item that must be marked
89
+ complete.
90
+
91
+ For already-committed cases, the correct sequence is verification, not
92
+ re-implementation:
68
93
 
69
94
  1. **Inspect** target files, directories, and shims to confirm on-disk state
70
95
  matches the task objective.
@@ -75,13 +100,78 @@ verification, not re-implementation:
75
100
  3. **Update all lagging memory/trajectory files** to record the verified
76
101
  state — flip phase status, record exact test counts, shim identities,
77
102
  import findings, and advance the stale selection pointer. This is the
78
- primary deliverable in already-complete-on-disk cases. When memory-file
79
- paths referenced by the task do not exist on disk, create the directory
80
- chain and the files an absent directory is a missing scaffold, not a
81
- blocker.
103
+ primary deliverable in already-complete-on-disk cases.
104
+
105
+ **Full update, not header-only bump.** When a memory file tracks a
106
+ counter with an inline enumeration (deleted-shim list, completed-slice
107
+ roster, tier categorization), extending the counter MUST be accompanied
108
+ by extending the inline list. After editing, read back the file and
109
+ confirm: does the header number equal the count of items in every inline
110
+ list, tier list, and roser that backs it? A header that says "20
111
+ deleted" but an inline list that only names 17 items is a stale
112
+ surface — the header was bumped but the enumeration was not. Missing
113
+ items cause the next agent to miscount or to believe those items were
114
+ never completed.
115
+
116
+ **Missing memory directory is a scaffold gap, not a reason to skip.**
117
+ When memory-file paths referenced by the task do not exist on disk, create
118
+ the directory chain and the files. An absent directory is a missing
119
+ scaffold — create it, then write the slice entry. Skipping memory updates
120
+ because the directory doesn't exist causes the next agent to re-execute
121
+ already-completed work.
82
122
  4. Do not re-execute structural work just because memory says it was never
83
123
  done. Memory is the stale surface; the worktree is the source of truth.
84
124
 
125
+ For uncommitted-fix cases, the correct sequence is commit-and-verify:
126
+
127
+ 1. **Inspect** the working-tree diff (`git diff`, `git diff --stat`) to
128
+ confirm the changes match the task description in both files touched and
129
+ content.
130
+ 2. **Pre-commit verification**: run the requested test suite on the working
131
+ tree (pre-commit) to confirm the fix is green before committing. Record
132
+ the pass/fail count.
133
+ 3. **Stage only the named files**: `git add -- <exact paths>`. Never use
134
+ `git add -A` or `git add .` — they sweep in unrelated dirty or untracked
135
+ files.
136
+ 4. **Pre-commit contamination check**: `git diff --cached --name-only`.
137
+ Compare the listed files against the task's allowed-file set. Any file
138
+ outside the allowed set is staging contamination — unstage it before
139
+ committing.
140
+ 5. **Commit** with a conventional-commit message matching the project's
141
+ history. Include any required trailers (e.g. `Co-Authored-By`).
142
+
143
+ **Commit-message accuracy audit.** Before committing, read the actual
144
+ diff and verify every function name, type name, module name, config key,
145
+ parameter name, and behavior claim in the message matches code that
146
+ exists in the committed files. A message that claims the diff "adds
147
+ `build_X` function" when the diff only adds a parameter to an existing
148
+ builder is a phantom claim — it misleads `git log --grep` and future
149
+ bisections. Describe what the diff actually changes: a new parameter, a
150
+ new field, a narrowed guard, a dead-code removal. Do not invent wrapper
151
+ functions, entrypoints, or types that the code does not contain. Phrase
152
+ the message from the diff outward, not from the task brief or developer
153
+ intention inward.
154
+
155
+ **Python-version dead code.** `isinstance(x, (str, bytes))` before
156
+ `isinstance(x, Mapping)` is dead code in Python 3 — `str` and `bytes`
157
+ are not `Mapping` instances, so the `Mapping` check alone rejects them.
158
+ This guard is Python 2 cruft. When you see it, delete it. The general
159
+ principle: guards that protect against type-system guarantees the
160
+ language itself already enforces are dead code — remove them.
161
+ 6. **Post-commit verification**: re-run the exact same test command on HEAD.
162
+ The pass/fail count must match the pre-commit count. A mismatch signals
163
+ that the commit was incomplete or that the working tree had uncommitted
164
+ patches masking a failure.
165
+ 7. **Cleanliness check**: `git status --porcelain` must be empty. Any
166
+ remaining dirty or untracked file signals either incomplete staging or an
167
+ out-of-scope change.
168
+ 8. **Scope audit**: `git diff --name-status HEAD~1` must show exactly the
169
+ files the task authorized. More files = contamination.
170
+ 9. **Memory-file update** only when the task, workflow, or trajectory files
171
+ explicitly track this fix. If the task's boundaries say "no README
172
+ changes" and name no memory files, skip memory updates — the commit
173
+ itself is the deliverable.
174
+
85
175
  #### Multi-File Memory Quorum
86
176
 
87
177
  When a project tracks the same counter or phase in multiple independent memory
@@ -104,6 +194,20 @@ anchor**, not the task brief alone. The pattern:
104
194
  completed-slice list, and what the next real gap is. Any file that still
105
195
  lists a completed subject as "remaining" will cause the next agent to
106
196
  re-execute already-done work.
197
+
198
+ **Agreement means full intra-file consistency, not just cross-file agreement.**
199
+ Within each individual memory file, every surface that conveys the same fact
200
+ must match: the header counter, every inline enumeration of deleted/completed
201
+ items, every tier categorization list, and every "remaining"/"next" roster. A
202
+ file whose header says "20 deleted" but whose inline list only names 17 items
203
+ is **self-inconsistent** — updating the counter without extending the inline
204
+ list leaves a stale enumeration that the next agent will trust as authoritative.
205
+ After every counter or phase update, read back the inline list, tier list, and
206
+ roser in the same file and verify each name count matches the header. The
207
+ common failure mode: bumping the header number without appending the new
208
+ subjects to the inline enumeration. A grep for the new subject name across
209
+ every memory file catches this — if the header counter advanced but the name
210
+ never appears in any list, the file is still split.
107
211
 
108
212
  #### Bytecode and Order-Dependent Checks
109
213
 
@@ -150,7 +254,14 @@ Before editing, establish a small task-relevant inventory:
150
254
  every import form that touches it — `from .<module> import`,
151
255
  `from <package>.<module> import`, `from <package> import <module>`, and any
152
256
  indirect imports through package `__init__.py` — so the full consumer set is
153
- known before the first edit;
257
+ known before the first edit. **Classify every consumer** into two piles:
258
+ **source-internal** (`.py` files inside the same package, excluding tests)
259
+ and **test** (files under the repository's test directory or named with test
260
+ conventions). The source-internal count determines whether a relocation needs
261
+ a shim: zero source-internal consumers means the root file can be deleted
262
+ directly and only the test imports need redirection — no shim, no staged
263
+ migration, no later shim-deletion slice (see **No-Shim Direct Relocation**
264
+ below);
154
265
  - **monkeypatch surface**: when a module will be moved and replaced by a shim,
155
266
  search all test files for `monkeypatch.setattr(<module>, "name", ...)` and
156
267
  `from <module> import _<name>` — any underscore-prefixed name that a test
@@ -166,7 +277,13 @@ Before editing, establish a small task-relevant inventory:
166
277
  every `from .X import` / `from ..X import` line in the file being moved and
167
278
  adjust each dot-count to reach the same target from the new location. A file
168
279
  with only stdlib imports needs zero changes. A file with sibling imports to
169
- other root-level modules needs one extra dot per subpackage level;
280
+ other root-level modules needs one extra dot per subpackage level.
281
+ **Shorter-dot case**: when the moved file imports from modules inside the
282
+ target subpackage (e.g. `from .subpkg.foo import` from a root module moving
283
+ into `subpkg/`), the dotted path shrinks — `.subpkg.foo` becomes `.foo`
284
+ (sibling import). `git mv` preserves content byte-identically, so this
285
+ adjustment happens after the move. Audit every dot-path by resolving it
286
+ against the new package location, not by assuming dot counts only increase;
170
287
  - accepted constructor fields, identity fields, validation owner, provenance
171
288
  fields, and export surfaces for record-backed helpers;
172
289
  - accepted callable signatures, default values, aggregation or identity keys,
@@ -249,13 +366,27 @@ Classify the task before editing:
249
366
 
250
367
  - **Feature or implementation**: add the smallest clear code path that satisfies
251
368
  the requested behavior.
369
+ - **Commit existing uncommitted work**: the code changes exist in the working
370
+ tree (dirty, uncommitted) and the task is to stage and commit them so HEAD
371
+ becomes green. Do not rewrite or expand the fix — it is already correct and
372
+ verified. Follow the uncommitted-fix sequence in
373
+ **Already-Complete-on-Disk Sequence**: inspect the diff, pre-commit verify,
374
+ stage only the named files, run `git diff --cached --name-only` to confirm
375
+ no contamination, commit, post-commit verify, confirm clean `git status`.
376
+ This task type produces no new code — the commit is the deliverable.
377
+ Memory-file updates are required only when the task explicitly tracks
378
+ this fix as a deferred item.
252
379
  - **Stabilization or acceptance**: compare the current draft against the
253
380
  accepted contract before deciding no edits are needed. Passing tests alone is
254
381
  not enough; verify signatures, defaults, key derivation, boundary behavior,
255
382
  non-mutation/provenance requirements, docs wording, and the tests that prove
256
383
  those behaviors.
257
384
  - **Refactor or cleanup**: move, split, merge, rename, or delete code only to
258
- improve locality, readability, or testability for the current change.
385
+ improve locality, readability, or testability for the current change. For
386
+ pure relocation slices (move module → canonical package, update consumers),
387
+ the no-new-behavior rule is absolute: see **Pure Relocation Slices** in the
388
+ Change Locality section. Do not add logic, defaults, fields, or helpers
389
+ during a relocation — those are scope contamination.
259
390
  - **Shim deletion (cleanup slice)**: migrate every consumer of a re-export shim
260
391
  to the canonical path, then delete the shim file from disk and git.
261
392
  This is the closing phase of staged package migration — the shim was a
@@ -310,6 +441,53 @@ Classify the task before editing:
310
441
  ``python_executable`` — is a config-only correction, not a command
311
442
  widening.
312
443
 
444
+ Place auto-detection and environment-discovery logic at the config or
445
+ runner layer, never in low-level validators or path-sanitization helpers.
446
+ A ``validate_runtime_pythonpath`` function should validate its input and
447
+ return a clean result; it should not probe the filesystem, search for
448
+ project roots, or apply heuristic fallbacks. Those validators are called
449
+ from tests that expect ``()`` for empty input — adding auto-detection
450
+ there silently changes the test contract. When a subprocess needs a
451
+ ``PYTHONPATH`` entry that the user might not configure, add a fallback in
452
+ the config dataclass ``__post_init__`` or in the runner before plan
453
+ dispatch, where the execution context (repo root, CWD, known directories)
454
+ is available. Fallback logic at the config/runner layer can search for
455
+ directories, resolve relative paths against the repo root, and supply
456
+ defaults without changing the contract of lower-level validators. This
457
+ keeps validators narrow, testable, and free of side-channel filesystem
458
+ dependencies.
459
+
460
+ **Split-coerce-validate pattern.** When a data-class field validator enforces
461
+ a semantic rule (non-empty, non-zero, positive, within-range) that a
462
+ downstream consumer already handles gracefully, the semantic rule belongs at
463
+ the builder/work layer, not in the data container. Split the original
464
+ validator into two functions:
465
+
466
+ - ``_coerce_X`` — type and element-type checks only. Accepts empty, zero, or
467
+ boundary values that the consumer knows how to handle. Use this in
468
+ ``__post_init__`` so the container preserves type safety without rejecting
469
+ semantically valid edge cases.
470
+ - ``_validate_X`` — calls ``_coerce_X``, then adds the semantic check
471
+ (non-empty, non-zero, etc.). Use this at builder and work-function sites
472
+ where the semantic rule is actually needed.
473
+
474
+ The semantic invariant becomes **stronger** after the split, not weaker,
475
+ because each builder site that enforces it should have a focused regression
476
+ test — the invariant is now explicitly test-covered rather than an implicit
477
+ container guard. The container stays permissive, matching the downstream
478
+ consumer's contract.
479
+
480
+ When splitting, verify that no other consumer was relying on the container
481
+ to reject the edge case (grep every construction site for the relevant
482
+ operation type). The coercer must preserve every type-rejection branch of
483
+ the original validator verbatim — only the semantic branches (empty, zero,
484
+ boundary) are removed from the container path. Do not touch independent
485
+ validators that serve a different construction path.
486
+
487
+ This is the concrete application of the pipeline-disagreement principle
488
+ (see Change Locality): the downstream consumer has more context about why
489
+ the edge case is valid, so relax the upstream stage to match.
490
+
313
491
  When transitioning a config from smoke/dry-run to real execution, audit
314
492
  which run spec each runtime plan is attached to. Smoke configs sometimes
315
493
  attach plans to a convenience spec (e.g. the first spec, or the one
@@ -338,6 +516,36 @@ Classify the task before editing:
338
516
  reference objects, frame mapping gap, missing CSV) rather than accepting
339
517
  a green run at face value.
340
518
 
519
+ When subprocess-driven pipeline outputs already exist on disk (from a prior
520
+ manual run, a different session, or a pre-built artifact set), the runner may
521
+ detect completed outputs and short-circuit to ``executed=True`` rather than
522
+ re-executing. Add a small output-existence check after the dry-run gate: for
523
+ each plan operation, define the expected output paths (files or directories
524
+ that the subprocess produces on success). If all expected outputs exist, skip
525
+ the subprocess call and return the plan as executed. This caching gate:
526
+ - sits **after the dry-run gate and after any preflight-error raise**,
527
+ immediately before the execution branches. Do not place it before
528
+ preflight — invalid runtime environments must still fail fast. Preflight
529
+ runs before the cache check; GPU/subprocess work is skipped only when
530
+ outputs already exist. This ordering is an invariant: moving the gate
531
+ before preflight would skip catching broken environments;
532
+ - uses the same expected-output definitions that an "all outputs exist"
533
+ check would use, requiring no new data structures;
534
+ - records ``executed=True`` because the underlying operation did succeed
535
+ (the outputs prove it), even though the current process did not invoke the
536
+ subprocess. **This is correctness-critical, not cosmetic.** Downstream
537
+ quality bridges frequently gate on ``result.executed`` (e.g. a quality-score
538
+ reader that does ``if not result.executed: continue``). Returning
539
+ ``executed=False`` on a cache hit silently drops quality scores from the
540
+ evidence chain and breaks any measured-quality loop that depends on them.
541
+ ``executed=True`` + ``missing_outputs=()`` correctly signals "outputs
542
+ available" and keeps downstream metrics meaningful;
543
+ - does not affect the dry-run path — dry-run plans still record
544
+ ``executed=False`` regardless of disk state.
545
+ The gate is a small inline conditional (typically 4–6 lines), not a new
546
+ helper or abstraction. Do not add caching as a framework feature; it is a
547
+ local optimization inside each adapter's plan-execution function.
548
+
341
549
  When replay steps (controlled by trace length parameters such as
342
550
  ``camera_limit``, ``bandwidth_limit``) and media-object frame ranges
343
551
  (controlled by ``frame_start``/``frame_end`` in options) are configured
@@ -446,15 +654,70 @@ Reduce global state, hidden path assumptions, implicit side effects, long call
446
654
  chains, repeated registration points, and heavy configuration for simple
447
655
  experiments.
448
656
 
657
+ ### Relocation: Which Protocol?
658
+
659
+ When a task relocates a module, choose the protocol before editing:
660
+
661
+ 1. **Inventory source-internal consumers** (search `src/` for all import forms of
662
+ the module, excluding tests).
663
+ 2. **Zero source-internal consumers** → use the **No-Shim Direct Relocation**
664
+ protocol below. The module moves to its canonical location, test imports are
665
+ redirected, the old file is deleted. No shim, no later deletion slice.
666
+ 3. **One or more source-internal consumers** → use the **Staged Package
667
+ Migration (Re-Export Shim)** protocol below. Leave a thin re-export shim at
668
+ the old path; source consumers resolve through it until later migration
669
+ phases.
670
+
671
+ The decision gate is a single count. Do not read the shim protocol for a
672
+ no-shim relocation, and do not skip the shim protocol when source-internal
673
+ consumers exist.
674
+
675
+ ### No-Shim Direct Relocation
676
+
677
+ When the moved module has **zero source-internal consumers** (only test files
678
+ import from it), skip the shim entirely:
679
+
680
+ 1. Move the file to its canonical location inside the target subpackage (`git mv`
681
+ or equivalent, keeping content byte-identical).
682
+ 2. Adjust relative-import depth inside the moved file (one extra dot for each
683
+ subpackage level below the package root; dots can also shrink — see relative-
684
+ import depth in Pre-Edit Inventory).
685
+ 3. Redirect every test consumer's import to the new canonical path.
686
+ 4. Delete the old root file from disk and git — no shim, no re-exports, no later
687
+ shim-deletion slice.
688
+ 5. Clear `__pycache__` under the source tree.
689
+ 6. Verify: import smoke at new canonical path, full test suite (count must match
690
+ pre-move baseline), grep for leftover references to old path, whitespace
691
+ check, scope-fence audit (`git diff --name-status` must show only the moved
692
+ file and consumer redirects).
693
+
694
+ **Empty `__init__.py` convention.** When importers use the full dotted
695
+ submodule path instead of relying on `__init__.py` re-exports, keep `__init__.py`
696
+ empty. Do not add re-exports during relocation — the empty init is a stable
697
+ convention, not a gap.
698
+
699
+ **Default-value flips are a code smell.** A boolean config field whose default
700
+ changes during a relocation campaign signals that someone patched code instead of
701
+ fixing their config file. Safe defaults go in code; unsafe defaults go in config
702
+ files. Reversing a default mid-campaign is never a companion fix — it is a
703
+ behavior change that belongs to its own scoped task.
704
+
705
+ **Feature creep during relocation.** A relocation slice that adds a new
706
+ dataclass field, expands an evidence record, introduces a new experiment spec
707
+ type, adds auto-detection logic, or inserts a helper function is no longer a
708
+ relocation slice. Before committing, run `git diff --name-only` and compare
709
+ against the task's scope. Revert every file that is not the relocation target,
710
+ its consumers, or an explicitly scoped companion fix.
711
+
449
712
  ### Staged Package Migration (Re-Export Shim)
450
713
 
451
- When a phased refactor moves a module to a new canonical location, leave the
452
- old path as a thin re-export shim rather than hunting down and rewriting every
453
- import site at once. The shim keeps the old module file but replaces its entire
454
- body with a single import that re-exports the public surface from the new
455
- location. Every existing `from <old> import X` or `from .<old> import X`
456
- continues to resolve without change; consumers migrate to the new path in later
457
- phases at their own pace.
714
+ When a phased refactor moves a module to a new canonical location **and
715
+ source-internal consumers exist**, leave the old path as a thin re-export shim
716
+ rather than hunting down and rewriting every import site at once. The shim
717
+ keeps the old module file but replaces its entire body with a single import
718
+ that re-exports the public surface from the new location. Every existing
719
+ `from <old> import X` or `from .<old> import X` continues to resolve without
720
+ change; consumers migrate to the new path in later phases at their own pace.
458
721
 
459
722
  A proper shim:
460
723
 
@@ -492,6 +755,14 @@ Count from the new file's package position up to the common ancestor, then down
492
755
  to the target. A smoke test that imports every moved module immediately after
493
756
  creation catches depth errors before the test suite runs.
494
757
 
758
+ **git-mv trap**: `git mv` preserves file content byte-identically, so relative
759
+ imports inside the moved file keep their original dot counts but now resolve
760
+ from a different package depth. A `from .subpkg.foo import` line that was
761
+ correct at the root level will resolve to `<pkg>.subpkg.subpkg.foo` after the
762
+ file moves into `subpkg/`. The fix is to shorten the path: `.subpkg.foo` →
763
+ `.foo` (now a sibling import). Audit every dot-path in the moved file by
764
+ resolving it from the new location; dot counts can shrink, not only grow.
765
+
495
766
  Also verify **monkeypatch continuity** when the moved code calls a function that
496
767
  tests monkeypatch through the old module's namespace. After the split, the new
497
768
  canonical module has its own import binding for that function — independent of
@@ -587,31 +858,74 @@ When all consumers of a re-export shim have been migrated (or the task is to
587
858
  migrate them as part of the deletion), the shim can be removed. This closes the
588
859
  staged-package-migration lifecycle.
589
860
 
590
- **Complexity assessment.** Before editing, inventory:
861
+ **Complexity assessment.** Before editing, inventory shim-specific surfaces. The
862
+ general import-surface, monkeypatch-surface, and relative-import-depth search
863
+ techniques are covered in **Pre-Edit Inventory** above; apply them here with the
864
+ shim's module path as the search target. Add these shim-specific checks:
591
865
 
592
866
  - **Re-exported names**: what the shim exports. Note the distinction between
593
867
  names the shim re-exports (often the full canonical surface — dozens of names)
594
868
  and names consumers actually import (typically a small subset). Only the
595
869
  consumer-imported names matter for migration; the canonical location already
596
870
  owns the full surface and remains available at the canonical path.
871
+ **Double-re-export shims**: when a shim re-exports from two or more unrelated
872
+ canonical packages (e.g. `from .evidence.summarizers.X import *` plus
873
+ `from .config.Y import A, B, C`), inventory each source package separately.
874
+ Consumers of the summarizer names migrate to the summarizer package; consumers
875
+ of the config names migrate to the config package. These are independent
876
+ migration sets — the summarizer consumers do not care about the config
877
+ canonical path and vice versa. Monkeypatch bindings tied to config names are
878
+ config-module monkeypatches, not shim monkeypatches — they survive deletion
879
+ without any shim-level binding. Treat this as a complexity signal but not a
880
+ blocker: the shim still deletes after both consumer groups are migrated.
597
881
  - **Consumer set**: every import site that references the shim's module path, in
598
882
  both source and test trees. Search all import forms: `from <shim_path> import`
599
883
  (absolute), `from .<shim> import` (1 dot), `from ..<shim> import` (2 dots),
600
- `from ...<shim> import` (3 dots), and beyond for deeper nesting. Also check
601
- indirect imports through package `__init__.py`. Count **sites, not files**
602
- a single consumer file may have multiple import sites (e.g. a top-level import
603
- plus a deferred import inside a function body), and every site must be
604
- migrated. A consumer nested 3+ levels deep needs the corresponding dot count
605
- stopping at 2 dots misses it. Note which specific names each consumer imports
606
- this is the verification target, not the full shim re-export list.
607
- - **Monkeypatch bindings**: search tests for `monkeypatch.setattr(<shim_module>, ...)`
608
- and `setattr(<shim_module>, ...)`. Zero hits = simplest case; no shim-module
609
- binding needed after deletion. Positive hits = complex case; the monkeypatch
610
- surface requires a shim-module binding to survive. **Memory tier labels are
611
- claims, not facts** re-verify monkeypatch counts against the worktree every
612
- slice, even when memory files classify a shim as "easy-tier no monkeypatch."
613
- A memory file written in a prior session may predate the discovery of
614
- monkeypatch bindings; the worktree grep is the truth.
884
+ `from ...<shim> import` (3 dots), and beyond for deeper nesting. Also search
885
+ `from <pkg> import <shim_module>` (bare module binding, no `as` downstream
886
+ code accesses `<shim_module>.<attr>`). Also check indirect imports through
887
+ package `__init__.py`. Count **sites, not files** a single consumer file may
888
+ have multiple import sites, and every site must be migrated. The most common
889
+ multi-site pattern is a bare module binding plus a named import in the same
890
+ test file (e.g. `from <pkg> import <shim>` on one line and `from <pkg>.<shim>
891
+ import (...)` on the next). Also cover deferred imports inside function bodies
892
+ and **dynamic `importlib.import_module` calls** search for
893
+ `importlib.import_module("<shim_path_or_prefix>")` string literals in both
894
+ source and test trees. These bypass static import analysis and are a common
895
+ trap in test files that dynamically reload a module under test. Every such
896
+ string literal must be retargeted to the canonical path alongside the static
897
+ imports. A consumer nested 3+ levels deep needs the corresponding dot count
898
+ stopping at 2 dots misses it. Note which specific names each consumer
899
+ imports — this is the verification target, not the full shim re-export list.
900
+
901
+ **Brief-stated consumer count is a claim, not a fact.** Always run your own
902
+ grep across `src/` and `tests/` independently. A task brief's "N consumers"
903
+ number may miss files (e.g. a harness test that imports the shim was
904
+ overlooked during brief authoring). Reconcile the count before migrating:
905
+ report the discrepancy, migrate the extra consumer, and record the correct
906
+ count.
907
+ - **Monkeypatch bindings**: use the monkeypatch-surface search from
908
+ **Pre-Edit Inventory** with the shim module as target. Zero hits = simplest
909
+ case; no shim-module binding needed after deletion. Positive hits = complex
910
+ case; the monkeypatch surface requires a shim-module binding to survive.
911
+ **Memory tier labels are claims, not facts** — re-verify monkeypatch counts
912
+ against the worktree every slice, even when memory files classify a shim as
913
+ "easy-tier no monkeypatch." A memory file written in a prior session may
914
+ predate the discovery of monkeypatch bindings; the worktree grep is the
915
+ truth.
916
+
917
+ **Distinguish shim-object patches from canonical-module patches.** When the
918
+ monkeypatch target is `<shim_module>.<name>` and `<name>` is re-exported from
919
+ a canonical module, read the test to determine which module object receives
920
+ the patch. If the test does `monkeypatch.setattr(shim_module, "name", fake)`,
921
+ the patch is on the shim's namespace — this needs continuity handling. If the
922
+ test does `monkeypatch.setattr(config_module, "_private_name", fake)` where
923
+ `config_module` is the canonical module itself (not the shim), the patch was
924
+ never on the shim and survives deletion untouched. A double-re-export shim
925
+ that re-exports config names often has this pattern: the monkeypatches are on
926
+ the config module, not the harness shim. Audit each monkeypatch hit to
927
+ confirm which module object is actually being patched before deciding whether
928
+ continuity handling is needed.
615
929
  - **Deferred-shim consumers**: when a consumer of the current shim is itself a
616
930
  deferred root shim (its own deletion belongs to a future slice), its import
617
931
  line must still be redirected to prevent `ImportError` when the current shim
@@ -623,13 +937,21 @@ staged-package-migration lifecycle.
623
937
  path transitively — the root shim may import from `<intermediate_module>` which
624
938
  itself imports from the target shim. If the root shim's import chain survives
625
939
  after migration (root → intermediate → canonical), the root shim needs no edit.
626
- - **`__init__.py` gate**: check whether any package `__init__.py` imports from
627
- the shim. If `<pkg>/__init__.py` does `from .<shim> import (...)`, the redirect
628
- is a **blocking gate** — the shim cannot be deleted until `__init__.py` is
629
- retargeted to the canonical path. This redirect is order-sensitive:
940
+ - **`__init__.py` gate**: check whether any package `__init__.py` imports directly
941
+ from the shim. If `<pkg>/__init__.py` does `from .<shim> import (...)`, the
942
+ redirect is a **blocking gate** — the shim cannot be deleted until `__init__.py`
943
+ is retargeted to the canonical path. This redirect is order-sensitive:
630
944
  `__init__.py` runs first in any `import <pkg>`, so it must resolve first.
631
945
  Verify `import <pkg>` succeeds before deleting the shim. After the redirect,
632
946
  `__init__.py` must re-export the same names verbatim from the canonical path.
947
+
948
+ **Not a gate**: when an `__init__.py` re-exports names from a module that itself
949
+ imports from the shim (e.g. `__init__` → `.batch` → shim), the `__init__.py`
950
+ has no direct dependency on the shim. After the intermediary's import is
951
+ redirected to canonical, the `__init__.py` chain resolves automatically — no
952
+ `__init__.py` edit is needed. A task brief that says "Do not change:
953
+ `<pkg>/__init__.py`" for such transitive re-exports is correct; verify by
954
+ tracing the import chain rather than mechanically editing `__init__.py`.
633
955
  - **`_`-prefixed private names**: the shim may re-export underscore-prefixed
634
956
  private names that the canonical module's own public consumers don't use.
635
957
  Before deletion, grep test files for imports of any `_`-prefixed name from
@@ -675,6 +997,14 @@ staged-package-migration lifecycle.
675
997
  the same module object after migration. This pattern often co-occurs with
676
998
  `monkeypatch.setattr(<alias>, ...)` — the alias name is the continuity
677
999
  binding.
1000
+ - **`from <pkg> import <module>` (bare module binding, no `as`)**: when a
1001
+ consumer imports the shim module as a bare namespace object
1002
+ (`from <pkg> import <shim>`), the redirect is `from <canonical_pkg> import
1003
+ <canonical>`. Downstream code accesses `<shim>.<attr>` — the name must stay
1004
+ identical. This is different from `import <shim> as <alias>` because the
1005
+ consumer uses the module's real name, not an alias. When the canonical
1006
+ module lives in a subpackage, the redirect reaches into that subpackage
1007
+ (e.g. `from <pkg>.adapters.<sub> import <canonical>`).
678
1008
  2. Edit every consumer's import line to resolve to the canonical path. Migrate
679
1009
  source consumers first (if any), then test consumers. A shim with zero source
680
1010
  consumers and only test consumers is valid — proceed directly to the test
@@ -699,13 +1029,24 @@ staged-package-migration lifecycle.
699
1029
  (handled first, step 0) and `import … as <alias>` lines (continuity
700
1030
  bindings requiring hand-edit). After the sweep, verify every rewritten
701
1031
  import line resolves to the correct depth and preserves the same names.
1032
+
1033
+ **Small consumer count**: when a shim has ≤2 consumers, direct import-line
1034
+ edits are simpler and less error-prone than a regex sweep. The sweep
1035
+ machinery pays off at ~5+ consumers; for trivially small consumer sets,
1036
+ spend the inventory effort on depth calculation and name preservation, not
1037
+ regex construction.
702
1038
  3. After all consumers are migrated, delete the shim: `git rm <path_to_shim>`.
703
1039
  Leave no comment-only stub, empty placeholder, or `.bak` rename.
704
1040
 
705
1041
  **Verification checklist.** After deletion, run in order:
706
1042
 
707
- 1. **Shim-gone check**: `python -c "import <shim_path>"` must fail with
708
- `ModuleNotFoundError`. Confirm the file does not exist on disk.
1043
+ 1. **Shim-gone check**: clear the language's bytecode cache under the source
1044
+ tree first (e.g. `find src/ -type d -name __pycache__ -exec rm -rf {} +`),
1045
+ then run `python -c "import <shim_path>"`. It must fail with
1046
+ `ModuleNotFoundError`. Confirm the file does not exist on disk. Stale
1047
+ `.pyc` files for a deleted `.py` module are ignored by the import system
1048
+ but can confuse later searches or give a false impression of a surviving
1049
+ module — sweep them even though they are functionally harmless.
709
1050
  2. **Canonical-import check**: `python -c "from <canonical_path> import <exported_names>"` must succeed for every name that any consumer imports. Verify only the consumer-imported subset — the canonical location owns the full surface; checking every name the shim re-exports is unnecessary when the canonical location was not modified. **Byte-identity**: confirm the canonical file was not modified by the migration — `git diff -- <canonical_subpackage>/` must be empty. (For a single-file canonical target, `git diff -- <path_to_canonical_file>` is sufficient.) The slice only rewrites consumer import lines; the canonical source is the immutable source of truth.
710
1051
  3. **Scoped test suite**: run the test files that were edited plus any test files
711
1052
  that exercise the deleted shim's exports. Expected count should match
@@ -749,16 +1090,73 @@ staged-package-migration lifecycle.
749
1090
  8. **Whitespace check**: `git diff --check` from the repository root. Report
750
1091
  findings in files the task did not touch as pre-existing; note them but do not
751
1092
  fix them.
1093
+ 9. **Memory-file consistency**: after the commit, read every memory/trajectory
1094
+ file that the task or active workflow names (status, progress, module-relationship,
1095
+ known-gap files). After editing each file, read it back from disk and verify:
1096
+ does every header counter match every inline enumeration that backs it? Are the
1097
+ deleted shim names and completed slice numbers present in every inline list,
1098
+ tier list, and "remaining" roster? Is every completed subject removed from every
1099
+ "next"/"remaining"/tier list? If a file says "N deleted" in the header, count
1100
+ the names in its deleted-shim inline list — the counts must be equal. A header
1101
+ counter that advanced without appending the new subject name to the inline
1102
+ enumeration is a self-inconsistent file; grep for the new subject name across
1103
+ every memory file to catch this. After the commit, no memory file should list
1104
+ the just-deleted shim or just-closed slice as remaining work.
752
1105
 
753
1106
  Treat a new test failure, a broken import smoke, or a leftover import of the
754
1107
  deleted shim path as a blocking defect. A pre-existing test failure that is
755
1108
  unchanged from baseline is not a defect.
756
1109
 
757
- **Stale bytecode.** After deleting the shim, clear the language's bytecode
758
- cache under the source tree (e.g. `find <src_root> -type d -name __pycache__ -exec rm -rf {} +`).
759
- A `.pyc` file from a prior session can make `import <shim_path>` silently
760
- resolve against the deleted source, masking a real resolution failure. Run
761
- the shim-gone check after clearing, not before.
1110
+ **Step triage by complexity.** Not all 9 steps carry equal risk in every slice.
1111
+ Distinguish essential from confirmatory based on the complexity assessment:
1112
+
1113
+ - **Always essential** (steps 1, 2, 4, 6, 8, 9): shim-gone, canonical-import, full
1114
+ suite, absence grep, whitespace, memory-file consistency. These catch path-resolution
1115
+ defects, accidental canonical edits, regressions, leftover imports, formatting
1116
+ issues, and stale memory surfaces regardless of shim complexity.
1117
+ - **Conditionally essential**: step 3 (scoped test suite) is essential when
1118
+ monkeypatch bindings exist — it is the continuity smoke. Without monkeypatch
1119
+ bindings, step 4 (full suite) already covers the same consumer tests; step 3
1120
+ is confirmatory.
1121
+ - **Confirmatory for simple shims**: step 5 (entrypoint-order smoke) and step 7
1122
+ (root-shim transitives) are confirmatory when the complexity assessment found
1123
+ no `__init__.py` gate, no root-shim transitive chain, and no eager package
1124
+ initialization dependency through the deleted shim. A passing full suite
1125
+ (step 4) already exercises the same import paths. These steps remain essential
1126
+ when any of those conditions exist.
1127
+ - **Confirmatory**: byte-identity check within step 2 is confirmatory when the
1128
+ canonical file was not edited in this slice — `git diff -- <canonical>` will
1129
+ be empty by construction. It is essential only when the task modified or moved
1130
+ the canonical file.
1131
+
1132
+ Do not skip an essential step because it would "probably" pass. Skip a
1133
+ confirmatory step only when the complexity assessment explicitly rules out the
1134
+ condition it guards against, and note the skip in the verification report.
1135
+
1136
+ **Slices and git commits.** Commit each successful shim-deletion slice before
1137
+ starting the next one. An uncommitted slice stack (two or more deletions + their
1138
+ redirects accumulating in the worktree) makes it impossible to `git bisect` a
1139
+ regression and harder to revert a single slice. One commit per slice keeps the
1140
+ history traceable and the diff reviewable. When a task lands on an already-green
1141
+ slice that is uncommitted from a prior session, commit it first or verify the
1142
+ worktree matches the task brief, then proceed.
1143
+
1144
+ **Staging hygiene.** Stage files by explicit path (`git add -- <paths>`) rather
1145
+ than `git add -A` — the latter can sweep in unrelated untracked files or stray
1146
+ changes. When deleting files for multiple slices (e.g. `git rm` for slices N
1147
+ and N+1), the second `git rm` stages alongside the first. Commit or
1148
+ `git reset HEAD -- <path>` the second deletion before the first commit to keep
1149
+ per-slice boundaries clean. After committing, `git status --porcelain` must be
1150
+ empty — any remaining dirty or untracked file signals either an incomplete
1151
+ staging or an out-of-scope change.
1152
+
1153
+ **Committing with pre-existing failures.** During simplification or cleanup
1154
+ campaigns, a commit may land on a tree with known pre-existing test failures
1155
+ that predate the campaign and are explicitly out of scope. This is acceptable
1156
+ when: the failures are documented in memory/trajectory files, the commit
1157
+ message does not claim "all green," and the commit itself introduces no new
1158
+ failures. Run the full test suite immediately after committing to confirm no
1159
+ regression — the pass/fail count must match the pre-commit baseline exactly.
762
1160
 
763
1161
  **Multi-file canonical targets**: when the shim re-exports names from many
764
1162
  separate files (e.g. one class per file), the byte-identity check (step 2)
@@ -790,63 +1188,46 @@ deleting, focus inventory and verification on the consumer-imported subset.
790
1188
  The full canonical surface remains available at the canonical path for any
791
1189
  future consumer that needs it.
792
1190
 
793
- ### Move-Only Refactor Verification Checklist
1191
+ ### Move-Only Refactor Verification Checklist (Shimmed)
794
1192
 
795
- When a task is a pure move-only refactor (files relocated, shims left behind,
796
- no logic changes), the verification surface is specific and mechanical. After
797
- creating the canonical files and root shims, run this checklist in order:
1193
+ When a move leaves a shim behind (source-internal consumers exist), run this
1194
+ checklist after creating the canonical files and shims. For no-shim relocations,
1195
+ use the verification steps in **No-Shim Direct Relocation** instead.
798
1196
 
799
- 1. **Import smoke**: import every moved module through both the shim path and
800
- the canonical path in a single smoke command. For Python, use
801
- `python -B -c "import <shim_path>, <shim_path2>, <canonical_path>, <canonical_path2>"`.
802
- An `ImportError` or `ModuleNotFoundError` at this stage catches depth errors
803
- before the test suite runs.
1197
+ The common checks full test suite, scope-fence audit (`git diff --name-status`),
1198
+ whitespace (`git diff --check`), and memory update are identical to the
1199
+ no-shim relocation protocol above. Run them here as well. The shim-specific
1200
+ checks are:
804
1201
 
805
- 2. **Scoped test suite**: run the exact test files the task brief names. Record
806
- the pass/fail count. The pre-existing failure count (if any) should match the
807
- task brief's documented baseline no new failures introduced.
1202
+ 1. **Import smoke (both paths)**: import every moved module through both the
1203
+ shim path and the canonical path: `python -B -c "import <shim_path>,
1204
+ <canonical_path>"`. Catches depth errors before the test suite runs.
808
1205
 
809
- 3. **Byte-identical verification**: diff each moved canonical file against the
810
- git-HEAD original. The only permitted difference is the import-depth line(s)
811
- that changed because the file moved one or more package levels deeper. No
812
- logic, signature, docstring, or whitespace changes. If the file has no
813
- relative repo-internal imports, it must be byte-identical.
1206
+ 2. **Byte-identical verification**: read every relative import line in the moved
1207
+ file and verify each resolves correctly from the new location. Watch for the
1208
+ shorter-dot case: `from .subpkg.foo import` at the old root level must become
1209
+ `from .foo import` after moving into `subpkg/`. Diff each moved canonical file
1210
+ against the git-HEAD original the only permitted difference is the
1211
+ import-depth line(s). No logic, signature, docstring, or whitespace changes.
814
1212
 
815
- 4. **Whitespace check**: run `git diff --check` from the repo root. Whitespace
816
- findings in files the task did not touch are pre-existing and should be noted
817
- as such, not fixed.
818
-
819
- 5. **Import direction scan**: verify the new canonical module does not import
1213
+ 3. **Import direction scan**: verify the new canonical module does not import
820
1214
  back through the root shim. Grep the canonical file for any import that
821
- resolves to the old shim path. For Python, search for `from <shim_relative_path> import`
822
- patterns that would create a circular chain.
823
-
824
- 6. **Downstream `__init__.py` circular-import check**: when a flat module is
825
- moved and its old location becomes a shim, any package `__init__.py` that
826
- eagerly imports through the shim can create a circular chain that did not
827
- exist when the old location was a flat file. For each package `__init__.py`
828
- in the repository, trace whether an eager module-level import reaches the
829
- shim and whether the shim's canonical target imports back into that package.
830
- If found, classify as pre-existing (the `__init__.py` re-export and the
831
- shim both predate this slice) or task-induced (the current move created the
832
- chain). Task-induced circular imports are blocking defects. Pre-existing ones
833
- are recording targets — but if they block test collection in the scoped
834
- suite, narrow the test command to the collectable subset, record the excluded
835
- file and the circular chain in the verification report, and file the
836
- pre-existing violation as a deferred decoupling gap in memory. Do not
837
- silently omit uncollectable tests from the command without noting them.
838
-
839
- 7. **Root-init check**: confirm `__init__.py` of the package root has zero diff
1215
+ resolves to the old shim path a `from <shim_relative_path> import` pattern
1216
+ would create a circular chain.
1217
+
1218
+ 4. **Downstream `__init__.py` circular-import check**: when a flat module is
1219
+ moved and its old location becomes a shim, trace whether any package
1220
+ `__init__.py` eagerly imports through the shim and whether the shim's
1221
+ canonical target imports back into that package. Task-induced circular imports
1222
+ are blocking defects; pre-existing ones are recording targets (document in
1223
+ memory, narrow the test command to the collectable subset if they block
1224
+ collection).
1225
+
1226
+ 5. **Root-init check**: confirm `__init__.py` of the package root has zero diff
840
1227
  (not widened). Only the new subpackage's own `__init__.py` is new.
841
1228
 
842
- 8. **Memory update**: after all checks pass, update the memory/trajectory files
843
- the task brief names with the verified state, exact test counts, shim
844
- identities, and the fact that the phase is now complete. Create the memory
845
- file directory if it does not yet exist.
846
-
847
- This checklist is the minimum acceptance gate for every move-only refactor
848
- slice. Skipping any step risks silent import failures, circular dependencies,
849
- or stale memory that causes future rounds to re-execute completed work.
1229
+ Skipping shim-specific checks risks silent import failures through the shim
1230
+ path, circular dependencies, or test-collection failures on the next slice.
850
1231
 
851
1232
  ### Domain-Local Extraction vs. Cross-Module Dedup
852
1233
 
@@ -888,14 +1269,10 @@ where is it stored, where is it used, and what is the CWD at each stage.
888
1269
 
889
1270
  When code uses both in-process import checks and out-of-process subprocess
890
1271
  calls, verify that they resolve to the same Python interpreter and package
891
- set. A preflight that passes in-process (import succeeds with the current
892
- ``sys.path`` and venv packages) does not prove that a subprocess
893
- ``["python", "-m", "some.module"]`` will succeed ``"python"`` may be a
894
- different interpreter on ``PATH``. Test the exact subprocess command (with
895
- the same env vars) before concluding the runtime logic is correct. When the
896
- subprocess command is built by an adapter from configurable fields, prefer
897
- to make the python executable configurable and default to the venv Python
898
- when the task's environment context makes that the correct choice.
1272
+ set (see the diagnostic in **Bounded runtime adapter stabilization** above
1273
+ for the full procedure: audit the actual subprocess command's Python, test
1274
+ it manually before concluding logic is wrong, and prefer a configurable
1275
+ python-executable that defaults to the venv Python).
899
1276
 
900
1277
  When an interface forces every caller to pass excessive parameters, consider a
901
1278
  small explicit context or config object. Do not turn that into a framework when
@@ -909,6 +1286,23 @@ finite. If an existing shared validator has intentionally weaker legacy
909
1286
  behavior, leave it unchanged unless the task scopes that contract change, and
910
1287
  add a local validator for the stricter new config.
911
1288
 
1289
+ ### Pure Relocation: No-Behavior-Change Constraint
1290
+
1291
+ Every relocation slice (no-shim or shimmed) carries an absolute boundary:
1292
+ **no new logic, no new defaults, no new fields, no new helper functions, no
1293
+ new config values, no new class members, no new type annotations, and no new
1294
+ test assertions.** The only permitted changes are the file move, relative-import
1295
+ depth adjustments, consumer-import redirections, and stale bytecode sweep.
1296
+
1297
+ If a relocation requires a companion fix (a broken import from a prior slice,
1298
+ a missing re-export), scope it to the exact broken surface — one import line,
1299
+ one re-export name — and report it as a companion fix, not silently folded into
1300
+ "relocation."
1301
+
1302
+ For the protocol, use **No-Shim Direct Relocation** (above) when zero
1303
+ source-internal consumers exist, or **Staged Package Migration** (above) when
1304
+ they do. Both are covered earlier in this section with their full procedures.
1305
+
912
1306
  ## Change Locality
913
1307
 
914
1308
  Before writing code, identify the natural owner of the change:
@@ -940,6 +1334,38 @@ Keep code that changes together close. Keep unrelated reasons to change in
940
1334
  separate modules. Public/shared layers should contain only stable capabilities
941
1335
  needed by multiple users; special cases should stay near their use sites.
942
1336
 
1337
+ **Config-reconstruction functions are a known footgun.** When a helper
1338
+ reconstructs a config dataclass from an existing instance (e.g. a
1339
+ ``_config_with_run_specs`` that slices ``run_specs`` for an evidence
1340
+ summarizer), it must propagate every source field to the new instance. A field
1341
+ present in the source but omitted from the reconstruction is silently dropped —
1342
+ the downstream consumer never sees it and the evidence chain breaks with no
1343
+ error. After adding any new field to a config dataclass, grep for every
1344
+ construction site that builds the dataclass from another instance and verify
1345
+ the new field is propagated. This is the most common cause of "the field is in
1346
+ the config but the feature doesn't fire" bugs.
1347
+
1348
+ **Layered plumbing requires full-chain propagation.** When a boolean flag or
1349
+ config field threads from a top-level config dataclass through intermediate
1350
+ runners to a low-level adapter function, every layer must accept and forward
1351
+ it. The invariant is: config dataclass field → ``_RESERVED_RUNNER_OPTION_KEYS``
1352
+ entry → ``_from_mapping`` parse → ``run_config`` forward → batch→runner→adapter
1353
+ kwarg chain. Missing one layer silently drops the flag at that boundary. Default
1354
+ the flag to ``False`` at every layer so existing callers (positional or keyword)
1355
+ are unaffected. After plumbing, run a cache-hit test and a cache-miss test:
1356
+ the hit test asserts the expensive path was NOT invoked; the miss test asserts
1357
+ it WAS. A single "defaults off" test that only checks existing behavior is
1358
+ insufficient — it proves the flag doesn't break the baseline but doesn't prove
1359
+ the flag works when enabled.
1360
+
1361
+ When a builder, factory, or construction function already receives the data
1362
+ needed to set a downstream field, set the field inside the builder rather than
1363
+ requiring every caller to post-hoc patch the output via `dataclasses.replace()`
1364
+ or equivalent. Caller-side patching when the builder could propagate the value
1365
+ internally is a data-flow ownership defect: it distributes one concept's
1366
+ construction across two unrelated sites and guarantees drift when a new caller
1367
+ forgets the patch.
1368
+
943
1369
  When a fix patches a latent gap at one call site and sibling call sites share
944
1370
  the same pattern, they share one change reason and belong in the same change.
945
1371
  Audit siblings before closing the fix: if a second entry point builds and runs
@@ -950,6 +1376,26 @@ divergence explicitly — which site, which gap, why deferred — instead of lea
950
1376
  the inconsistency implicit. Change locality is not a reason to fix one site and
951
1377
  leave an identical latent gap unflagged at a co-located site.
952
1378
 
1379
+ When two pipeline stages disagree about an edge-case convention (e.g. stage A
1380
+ rejects empty input while stage B gracefully handles it and returns ``None``),
1381
+ prefer making the upstream stage more permissive to match the downstream
1382
+ stage's existing behavior. The downstream consumer typically has more context
1383
+ about why the edge case is valid — it knows what empty means, what downstream
1384
+ consumers expect, and how the result feeds into later processing. Tightening
1385
+ the downstream stage forces every upstream caller to coordinate on the stricter
1386
+ convention; relaxing the upstream validation keeps both stages aligned on the
1387
+ convention that already works. Tie-break by asking: which side has more context
1388
+ about why the edge case matters?
1389
+
1390
+ When the upstream stage is a data-class ``__post_init__`` validator and the
1391
+ downstream stage is a builder or consumer that already handles the edge case,
1392
+ apply the **split-coerce-validate pattern** (see Implementation Style): split
1393
+ the validator into a coercion function (type checks only, used in the
1394
+ container) and a validation function (coercion + semantic check, used at
1395
+ builder/work sites). This is the most common concrete form of the pipeline
1396
+ disagreement: a data container enforcing a business rule that belongs at the
1397
+ construction layer.
1398
+
953
1399
  ## Harness And Test Discipline
954
1400
 
955
1401
  Harnesses serve paper goals, performance comparison, method screening, module
@@ -963,6 +1409,17 @@ Keep harness and test responsibilities separate:
963
1409
  raw artifacts, seeds, splits, config snapshots, and parseable outputs;
964
1410
  - tests should use small fixtures, toy inputs, and clear pass/fail assertions;
965
1411
  - each test should have one named behavioral responsibility;
1412
+ - **do not test state variations that control flow excludes.** When code has an
1413
+ early-return guard (``if dry_run: return``, ``if not enabled: return``), the
1414
+ guarded block is unreachable under that condition. A test that sets the
1415
+ guard-active condition and then varies state that only affects the unreachable
1416
+ block is redundant — it exercises a path that every existing guard test already
1417
+ proves is skipped. Specifically: a function with ``if dry_run: return early``
1418
+ followed later by ``if skip_cached and outputs_exist: return executed=True``
1419
+ needs two cache-behavior tests (hit → skip, miss → execute) but does NOT need
1420
+ a "defaults off + dry_run + outputs exist" test — dry_run returns before
1421
+ reaching the cache gate regardless of disk state. The guard already proves the
1422
+ cache gate is irrelevant when dry_run is True;
966
1423
  - formula, threshold, ordering, percentile, or ranking tests should use
967
1424
  discriminating fixtures where a neighboring formula, adjacent threshold,
968
1425
  reversed ordering, or copied existing helper would fail;
@@ -1037,6 +1494,13 @@ Keep harness and test responsibilities separate:
1037
1494
  command and gives no evidence under it. Place the assertion where the suite
1038
1495
  runs it, or explicitly label it as an unexecuted extra guard; do not count an
1039
1496
  unrun doctest toward the green-suite total;
1497
+ - when a builder, factory, spec-construction function, or public entrypoint is
1498
+ the change surface, test it through its public API with representative input
1499
+ combinations, not only through internal validators or helpers in isolation. An
1500
+ internal validator passing its own unit test does not prove the builder wires
1501
+ it correctly into the full call chain — a `.get()` default-value gap, a missing
1502
+ parameter propagation, or a wrong fallback branch can survive when every
1503
+ isolated helper test is green;
1040
1504
  - harness code should not become functional test code;
1041
1505
  - test code should not become paper-performance evaluation.
1042
1506
 
@@ -1200,6 +1664,26 @@ pass/fail counts, pre-existing failures documented separately from new
1200
1664
  failures, and cache cleanup findings. A green validation run confirms current
1201
1665
  contracts; it does not create new feature, docs, or experiment work.
1202
1666
 
1667
+ **Record pre-existing failure specifics.** When a test failure predates the
1668
+ current task, record the exact test name, source file, line number, and error
1669
+ message in memory files. A bare count ("1 pre-existing failure") is enough for
1670
+ the commit message; memory files need the identity so future rounds can
1671
+ distinguish that failure from a new regression with the same file name. The
1672
+ minimum entry: test function name, file path, line, exception type, and
1673
+ exception message. If the failure is investigated, add the root cause.
1674
+
1675
+ **Post-slice memory audit.** After committing a simplification or cleanup
1676
+ slice, apply the intra-file and cross-file consistency rules from
1677
+ **Multi-File Memory Quorum** (above) to every memory/trajectory file the
1678
+ project maintains. The essential checks: (1) every header counter matches
1679
+ every inline enumeration within the same file; (2) every completed subject
1680
+ is removed from all "remaining," "next," and tier lists; (3) the slice entry
1681
+ records exact re-verified counts from this session's rerun; (4) pre-existing
1682
+ failure entries include test name, file, line, exception type, and message.
1683
+ Do not skip this audit when the project uses multiple memory files — a stale
1684
+ inline list or leftover tier entry causes the next agent to miscount progress
1685
+ or re-execute completed work.
1686
+
1203
1687
  ## Naming, State, And References
1204
1688
 
1205
1689
  Names must reflect real meaning and data shape. Do not keep historical,
@@ -1517,7 +2001,9 @@ For bounded helpers, verify that the implementation:
1517
2001
  - keeps any reused private helper name semantically true for all current
1518
2002
  callers;
1519
2003
  - avoids adjacent runtime surfaces such as loaders, registries, exporters,
1520
- harnesses, CLI, experiments, or paper outputs unless explicitly in scope.
2004
+ harnesses, CLI, experiments, or paper outputs unless explicitly in scope;
2005
+ - contains no Python-version dead code (see canonical definition under
2006
+ **Task Classification** — Python-version dead code);
1521
2007
 
1522
2008
  When reviewing a scoped change with an allowed-file list, compare the actual
1523
2009
  changed-file list against that list before reviewing behavior. Flag any package
@@ -1526,6 +2012,23 @@ TODO/memory note, stash/backup directory, generated file, or adjacent module as
1526
2012
  blocking when the request excluded it. Do not accept in-repository stash folders
1527
2013
  as cleanup; out-of-scope work must be removed from the task's worktree state or
1528
2014
  explicitly separated outside the repository by user-approved workflow.
2015
+
2016
+ **Slice-scope contamination pattern.** When a relocation slice's diff touches
2017
+ more files than the relocation target plus its consumers, treat the excess as
2018
+ contamination regardless of test greenness. `git diff --name-status` should show
2019
+ at most: the renamed file (R), consumer files (M) whose imports were redirected,
2020
+ and at most one companion fix for a prior-slice import breakage. More than 3
2021
+ non-rename files in a relocation slice = redirect to fix. A developer report
2022
+ that omits contaminated files is two defects: the contamination itself plus the
2023
+ reporting omission. The reviewer should flag both.
2024
+
2025
+ For any commit-review task, also audit the commit message against the actual
2026
+ diff. A message that names a function (`build_X`), type (`XSweepBuild`), or
2027
+ entrypoint that does not appear in the diff is a phantom claim — flag it as
2028
+ blocking regardless of test greenness. Phantom function names in git history
2029
+ corrupt `git log --grep` results and mislead future bisections. The fix is a
2030
+ commit-message amend, not a code change.
2031
+
1529
2032
  For cleanup reviews, compare deletions against the preservation map and the
1530
2033
  requested validation command. Deleting a validation target, accepted feature, or
1531
2034
  unrelated subsystem is blocking even when the stale excluded search becomes
@@ -1717,14 +2220,84 @@ After edits, audit:
1717
2220
  - excluded capabilities are absent from source files, tests, docs, package
1718
2221
  metadata, parser or handler branches, module entrypoints, and untracked files,
1719
2222
  with no explanatory stubs or placeholders left behind;
1720
- - no generated cache/build/test/output/result artifacts were left behind unless
1721
- explicitly requested.
2223
+ - stale `__pycache__` directories and `.pyc` files for deleted or moved modules
2224
+ were swept from the source tree; no generated cache/build/test/output/result
2225
+ artifacts were left behind unless explicitly requested;
2226
+ - **dict `.get()` calls that branch on the result** supply an explicit default
2227
+ when the key may legitimately be absent; `None` is not assumed to be the safe
2228
+ fallback unless `None` is a valid sentinel for every downstream use. When the
2229
+ code intends "use this uniform spec when no per-key override exists," the
2230
+ default argument, not the implicit `None`, carries that intent;
2231
+ - **Python-version dead code** was not left in — guards that protect against
2232
+ type-system guarantees the current language version already enforces are dead
2233
+ code; see the canonical definition and examples under **Task Classification**
2234
+ (Python-version dead code);
2235
+ - updated memory/trajectory files are intra-file consistent: every header counter
2236
+ matches every inline enumeration, tier list, and deleted-item roster within the
2237
+ same file; no completed subject remains in "remaining," "next," or tier lists;
2238
+ pre-existing failure entries include test name, file, line, exception type, and
2239
+ message.
1722
2240
 
1723
2241
  For skill edits, also perform a project leakage audit. Remove or generalize any
1724
2242
  real project path, symbol, dataset, method, metric, harness, test, artifact
1725
2243
  field, historical output, or one-off debug lesson that does not hold across
1726
2244
  repositories.
1727
2245
 
2246
+ ## Developer Report Requirements
2247
+
2248
+ A developer report must list every file the task changed, created, moved, or
2249
+ deleted. The report is the primary scope-audit surface — a reviewer reads it
2250
+ first to determine whether the slice stayed inside its fence.
2251
+
2252
+ **Honest inventory.** List every changed file, not only the ones the task
2253
+ intended to change. A report that says "only one module moved" when the diff
2254
+ touches 5+ files is a reporting defect even when every test passes. Run
2255
+ `git diff --name-status` (or equivalent) after the work is done and include
2256
+ every path in the report. Categorize each file: in-scope relocation, in-scope
2257
+ consumer redirect, companion fix (with the prior slice that caused it), or
2258
+ out-of-scope contamination (with an admission and remediation plan).
2259
+
2260
+ **No silent contamination.** If the diff includes files outside the task
2261
+ scope — config defaults flipped, runtime logic added, experiment specs
2262
+ expanded, new fields on evidence records, helper functions inserted — those
2263
+ files are scope contamination. Report them explicitly, do not omit them from
2264
+ the report, and do not claim "no behavior change, pure relocation" when
2265
+ behavior-changing code landed. The correct response to discovering self-inflicted
2266
+ contamination is to revert the out-of-scope edits and re-verify, not to omit
2267
+ them from the report.
2268
+
2269
+ **Test count reporting.** Report the exact pass/fail count from the current
2270
+ session's rerun. Do not copy test counts from a prior slice entry, a task
2271
+ narrative, or a memory file. If the suite was run twice (scoped then full),
2272
+ report both counts separately and label which is which. A test count that is
2273
+ higher than the pre-slice baseline may indicate that unauthorized feature code
2274
+ added new tests — investigate before reporting success.
2275
+
2276
+ **No invented behavior claims.** Do not claim the code performs optimization X,
2277
+ has a caching gate Y, skips execution Z, or implements feature W unless you have
2278
+ verified by reading the exact code path end to end. A function or field name
2279
+ that *suggests* a behavior (e.g. `_missing_outputs` implying a pre-execution
2280
+ gate) does not prove the behavior exists — the name may be aspirational, or the
2281
+ check may run at a different phase than the name implies. The only evidence that
2282
+ a gate, cache, or optimization exists is the `if` statement, early-return, or
2283
+ short-circuit that you read in the code. When a report claims a code property
2284
+ that the reviewer can disprove by reading one function, the claim damages the
2285
+ report's credibility even when every other claim is accurate.
2286
+
2287
+ This rule applies equally to commit messages. A commit message that claims the
2288
+ diff "adds `build_X` function" when the code only adds a parameter to an
2289
+ existing builder is a phantom claim — it introduces fake function names into
2290
+ `git log --grep` results and misleads future bisections. Write commit messages
2291
+ from the diff outward: name only symbols, parameters, fields, and behavior that
2292
+ the diff actually contains. Do not repeat the task brief's aspirational
2293
+ description as the commit message when the implemented diff is narrower.
2294
+
2295
+ **Pre-existing failure documentation.** When failures predate the current
2296
+ task, list each one with its test name, source file, line number, and exception
2297
+ type. A bare count of pre-existing failures is enough for a commit message; the
2298
+ developer report needs the identities so reviewers can distinguish pre-existing
2299
+ failures from new regressions.
2300
+
1728
2301
  ## Final Response
1729
2302
 
1730
2303
  Keep the final response concise: changed paths, behavior or contract covered,