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.
- package/README.md +7 -7
- package/README.zh-CN.md +7 -7
- package/agent-forge.yaml +2 -2
- package/mcp-server/writing_master/tools.py +1 -0
- package/metaskills/academic-army-architect/envolve.sh +1 -1
- package/metaskills/academic-army-coding-plan/ENVOLVETASK.md +1 -1
- package/metaskills/academic-army-coding-plan/envolve.sh +1 -1
- package/metaskills/academic-army-experiment-plan/ENVOLVETASK.md +1 -1
- package/metaskills/academic-army-experiment-plan/envolve.sh +1 -1
- package/package.json +2 -2
- package/runs/develop-skill.sh +5 -5
- package/runs/develop.sh +5 -5
- package/skills/academic-army-coding-plan/SKILL.md +6 -6
- package/skills/academic-army-coding-plan/agents/openai.yaml +1 -1
- package/skills/academic-army-coding-style/SKILL.md +676 -103
|
@@ -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.
|
|
67
|
-
|
|
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.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
452
|
-
old path as a thin re-export shim
|
|
453
|
-
import site at once. The shim
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
and `
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
A
|
|
614
|
-
|
|
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
|
|
627
|
-
the shim. If `<pkg>/__init__.py` does `from .<shim> import (...)`, the
|
|
628
|
-
is a **blocking gate** — the shim cannot be deleted until `__init__.py`
|
|
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**:
|
|
708
|
-
`
|
|
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
|
-
**
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
|
796
|
-
|
|
797
|
-
|
|
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
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
-
|
|
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
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
moved and its old location becomes a shim, any package
|
|
826
|
-
eagerly imports through the shim
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
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
|
-
|
|
843
|
-
|
|
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
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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
|
-
-
|
|
1721
|
-
|
|
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,
|